UNPKG

polen

Version:

A framework for delightful GraphQL developer portals

233 lines (197 loc) 7.36 kB
import * as fc from 'fast-check' import { describe, expect, test } from 'vitest' import { objFilter, ObjOmit, ObjPick, objPolicyFilter, spreadShallow } from './kit-temp.js' describe('objPolicyFilter', () => { const testObj = { a: 1, b: 2, c: 3, d: 4 } test('allow mode picks specified keys', () => { expect(objPolicyFilter('allow', testObj, ['a', 'c'])).toEqual({ a: 1, c: 3 }) expect(objPolicyFilter('allow', testObj, [])).toEqual({}) expect(objPolicyFilter('allow', testObj, ['a', 'z'] as any)).toEqual({ a: 1 }) }) test('deny mode omits specified keys', () => { expect(objPolicyFilter('deny', testObj, ['a', 'c'])).toEqual({ b: 2, d: 4 }) expect(objPolicyFilter('deny', testObj, [])).toEqual(testObj) expect(objPolicyFilter('deny', testObj, ['z'] as any)).toEqual(testObj) }) test('preserves undefined values', () => { const obj = { a: 1, b: undefined, c: 3 } expect(objPolicyFilter('allow', obj, ['a', 'b'])).toEqual({ a: 1, b: undefined }) }) }) describe('objFilter', () => { const testObj = { a: 1, b: 2, c: 3, d: 4 } test('filters by predicates', () => { // By value expect(objFilter(testObj, (k, v) => v > 2)).toEqual({ c: 3, d: 4 }) // By key expect(objFilter(testObj, k => k === 'a' || k === 'c')).toEqual({ a: 1, c: 3 }) // By full object context expect(objFilter(testObj, (k, v, obj) => { const avg = Object.values(obj).reduce((a, b) => a + b, 0) / Object.keys(obj).length return v < avg })).toEqual({ a: 1, b: 2 }) }) test('edge cases', () => { expect(objFilter(testObj, () => false)).toEqual({}) expect(objFilter(testObj, () => true)).toEqual(testObj) expect(objFilter({}, () => true)).toEqual({}) }) }) describe('ObjPick and ObjOmit', () => { test('are aliases for objPolicyFilter', () => { const obj = { a: 1, b: 2, c: 3 } const keys = ['a', 'c'] as const expect(ObjPick(obj, keys)).toEqual(objPolicyFilter('allow', obj, keys)) expect(ObjOmit(obj, keys)).toEqual(objPolicyFilter('deny', obj, keys)) }) }) describe('property-based tests', () => { test('objPolicyFilter allow/deny are complementary', () => { fc.assert( fc.property( fc.object(), fc.array(fc.string()), (obj, keys) => { const allowed = objPolicyFilter('allow', obj, keys) const denied = objPolicyFilter('deny', obj, keys) // Every key in obj (own properties only) is either in allowed or denied, never both Object.keys(obj).forEach(key => { const inAllowed = Object.prototype.hasOwnProperty.call(allowed, key) const inDenied = Object.prototype.hasOwnProperty.call(denied, key) expect(inAllowed).toBe(!inDenied) }) // Combined they reconstruct the original object's own properties const reconstructed = { ...allowed, ...denied } // Filter out inherited properties that may have been added from the keys const filteredReconstructed = Object.keys(reconstructed) .filter(key => Object.prototype.hasOwnProperty.call(obj, key)) .reduce<any>((acc, key) => { acc[key] = (reconstructed as any)[key] return acc }, {}) const ownPropsObj = Object.keys(obj).reduce<any>((acc, key) => { acc[key] = obj[key] return acc }, {}) expect(filteredReconstructed).toEqual(ownPropsObj) }, ), ) }) test('objFilter preserves values unchanged', () => { fc.assert( fc.property( fc.object(), (obj) => { const filtered = objFilter(obj, () => true) // Object.keys doesn't include __proto__, so we need to handle it specially const objWithoutProto = Object.keys(obj).reduce<any>((acc, key) => { acc[key] = obj[key] return acc }, {}) expect(filtered).toEqual(objWithoutProto) // Values are the same reference Object.keys(filtered).forEach(key => { expect(filtered[key]).toBe(obj[key]) }) }, ), ) }) test('objPolicyFilter is immutable', () => { fc.assert( fc.property( fc.object(), fc.array(fc.string()), fc.oneof(fc.constant('allow' as const), fc.constant('deny' as const)), (obj, keys, mode) => { const original = { ...obj } objPolicyFilter(mode, obj, keys) expect(obj).toEqual(original) }, ), ) }) test('empty keys behavior', () => { fc.assert( fc.property( fc.object(), (obj) => { expect(objPolicyFilter('allow', obj, [])).toEqual({}) expect(objPolicyFilter('deny', obj, [])).toEqual(obj) }, ), ) }) }) describe('mergeShallow', () => { test('merges objects while omitting undefined values', () => { const base = { a: 1, b: 2, c: 3 } const override = { a: 1, b: undefined, c: 4, d: 5 } expect(spreadShallow<any>(base, override)).toEqual({ a: 1, b: 2, c: 4, d: 5 }) }) test('handles multiple objects', () => { const obj1 = { a: 1, b: 2 } const obj2 = { a: 1, b: undefined, c: 3 } const obj3 = { a: 1, b: 2, c: undefined, d: 4 } expect(spreadShallow<any>(obj1, obj2, obj3)).toEqual({ a: 1, b: 2, c: 3, d: 4 }) }) test('handles empty objects', () => { expect(spreadShallow({}, {})).toEqual({}) expect(spreadShallow({ a: 1 }, {})).toEqual({ a: 1 }) expect(spreadShallow({}, { a: 1 })).toEqual({ a: 1 }) }) test('handles single object', () => { const obj = { a: 1, b: undefined, c: 3 } expect(spreadShallow(obj)).toEqual({ a: 1, c: 3 }) }) test('handles no objects', () => { expect(spreadShallow()).toEqual({}) }) test('handles undefined objects', () => { const obj = { a: 1, b: 2 } expect(spreadShallow(undefined, obj, undefined)).toEqual({ a: 1, b: 2 }) expect(spreadShallow(obj, undefined)).toEqual({ a: 1, b: 2 }) expect(spreadShallow(undefined, undefined)).toEqual({}) }) test('preserves null values', () => { const obj1 = { a: 1, b: null } const obj2 = { a: 1, b: 2, c: null } expect(spreadShallow<any>(obj1, obj2)).toEqual({ a: 1, b: 2, c: null }) }) test('preserves false and 0 values', () => { const obj1 = { a: true, b: 1 } const obj2 = { a: false, b: 0 } expect(spreadShallow(obj1, obj2)).toEqual({ a: false, b: 0 }) }) test('property-based: never includes undefined values', () => { fc.assert( fc.property( fc.array(fc.dictionary(fc.string(), fc.option(fc.anything()))), (objects) => { const result = spreadShallow(...objects) Object.values(result).forEach(value => { expect(value).not.toBe(undefined) }) }, ), ) }) test('property-based: later objects override earlier ones', () => { fc.assert( fc.property( fc.object(), fc.object(), fc.string(), fc.anything().filter(v => v !== undefined), (obj1, obj2, key, value) => { // Set the same key in both objects obj1[key] = 'first' obj2[key] = value const result = spreadShallow(obj1, obj2) expect(result[key]).toBe(value) }, ), ) }) })