diff --git a/package.json b/package.json index 7f30991..7c60efc 100644 --- a/package.json +++ b/package.json @@ -78,5 +78,5 @@ "test:watch": "jest --updateSnapshot --watchAll" }, "types": "dist/index.d.ts", - "version": "2.0.4" + "version": "2.0.5" } diff --git a/src/connection.test.ts b/src/connection.test.ts index debd9d6..3971336 100644 --- a/src/connection.test.ts +++ b/src/connection.test.ts @@ -60,8 +60,7 @@ describe( 'Connection class', () => { const propertyPaths = [ 'valid', 'b.message' ]; let d = connection.get( ...propertyPaths ); - jest.runAllTimers(); - + passedNoneFoundTest = Object.keys( d ).length === 2 && d[ 'b.message' ] === undefined @@ -96,8 +95,7 @@ describe( 'Connection class', () => { } const v = connection.get( ...propertyPaths ); - jest.runAllTimers(); - + passedFoundTest = Object.keys( v ).length === 2 && v[ 'b.message' ] as unknown as string === protectedData.b.message @@ -131,7 +129,7 @@ describe( 'Connection class', () => { const gsData = connection.get( GLOBAL_SELECTOR, 'a', 'valid' )[ GLOBAL_SELECTOR ]; - jest.runAllTimers(); + expect( gsData ).toStrictEqual( changes ); expect( gsData ).not.toBe( changes ); jest.useRealTimers(); @@ -139,7 +137,6 @@ describe( 'Connection class', () => { test( 'fetches the GLOBAL_SELECTOR path by default', () => { jest.useFakeTimers(); expect( setup( clonedeep( protectedData ) ).connection.get() ).toEqual({[ GLOBAL_SELECTOR ]: protectedData }); - jest.runAllTimers(); jest.useRealTimers(); } ); test( 'monitors update changes on both global and targeted data retrievals', () => { @@ -148,34 +145,59 @@ describe( 'Connection class', () => { expect( a.connection.get() ).toEqual({ [ GLOBAL_SELECTOR ]: {} }); - jest.runAllTimers(); a.connection.set({ b: 22 }); expect( a.connection.get( 'a' ) ).toEqual({ a: undefined }); - jest.runAllTimers(); - expect( a.connection.get( 'a', 'b' ) ).toEqual({ + expect( a.connection.get( 'a', 'b' ) ).toEqual({ b: 22, a: undefined }); - jest.runAllTimers(); expect( a.connection.get() ).toEqual({ [ GLOBAL_SELECTOR ]: { b: 22 } }); - jest.runAllTimers(); a.connection.set({ a: 1024 }); expect( a.connection.get( 'a' ) ).toEqual({ a: 1024 }); - jest.runAllTimers(); - expect( a.connection.get( 'b', 'a' ) ).toEqual({ + expect( a.connection.get( 'b', 'a' ) ).toEqual({ b: 22, a: 1024 }); - jest.runAllTimers(); expect( a.connection.get() ).toEqual({ [ GLOBAL_SELECTOR ]: { a: 1024, b: 22 } }); - jest.runAllTimers(); + jest.useRealTimers(); + } ); + test( 'updates a request cache slice from a subset of a larger incoming value', () => { + jest.useFakeTimers(); + const sourceData = createSourceData(); + const a = setup( sourceData ); + const propertyPath = 'registered.time.hours'; + expect( a.connection.get( propertyPath ) ) + .toEqual({ [ propertyPath ]: 9 }); + a.connection.set({ + registered: { + month: 7, + time: { + hours: 22, + minutes: 5 + }, + year: 2026 + } as typeof sourceData["registered"] + }); + expect( a.connection.get( propertyPath ) ) + .toEqual({ [ propertyPath ]: 22 }); + a.connection.set({ + registered: { + month: 3, + time: { + hours: 16 + } + } as typeof sourceData["registered"] + }); + expect( a.connection.get( propertyPath ) ) + .toEqual({ [ propertyPath ]: 16 }); + jest.useRealTimers(); } ); } ); @@ -195,7 +217,6 @@ describe( 'Connection class', () => { .mockReturnValue( undefined ); connection.get( expect.any( Array ) as unknown as string ); - jest.runAllTimers(); expect( cacheGetSpy ).toHaveBeenCalledTimes( 1 ); connection.set( {} ); expect( setSpy ).toHaveBeenCalledTimes( 1 ); @@ -267,7 +288,6 @@ describe( 'Connection class', () => { 'tags[6]': source.tags[ 6 ], '@@GLOBAL': source }); - jest.runAllTimers(); connection.set({ isActive: true, friends: { 1: { name: { last: 'NEW LNAME' } } }, @@ -295,7 +315,6 @@ describe( 'Connection class', () => { 'tags[6]': source.tags[ 6 ], '@@GLOBAL': updatedDataEquiv }); - jest.runAllTimers(); jest.useRealTimers(); connection.disconnect(); } ); @@ -307,7 +326,6 @@ describe( 'Connection class', () => { const { connection } = setup( source ); expect( connection.get() ).toEqual({[ GLOBAL_SELECTOR ]: source }); - jest.runAllTimers(); connection.set({ friends: { [ MOVE_TAG ]: [ -1, 1 ] }, @@ -331,8 +349,7 @@ describe( 'Connection class', () => { expectedValue.tags = [ 0, 1, 2, 4, 6 ].map( i => defaultState.tags[ i ] ); expect( connection.get() ).toEqual({[ GLOBAL_SELECTOR ]: expectedValue }); - jest.runAllTimers(); - + connection.disconnect(); jest.useRealTimers(); @@ -351,7 +368,7 @@ describe( 'Connection class', () => { 'tags[6]', '@@GLOBAL' ); - jest.runAllTimers(); + const data2 = connection.get( 'friends[1].name.last', 'history.places[2].country', @@ -359,7 +376,7 @@ describe( 'Connection class', () => { 'company', 'tags[5]', ); - jest.runAllTimers(); + expect( data1 ).toEqual({ 'history.places[2].city': source.history.places[ 2 ].city, 'history.places[2].country': source.history.places[ 2 ].country, diff --git a/src/model/accessor-cache/repository/atom-value/node/index.test.ts b/src/model/accessor-cache/repository/atom-value/node/index.test.ts index 160f638..bc2092e 100644 --- a/src/model/accessor-cache/repository/atom-value/node/index.test.ts +++ b/src/model/accessor-cache/repository/atom-value/node/index.test.ts @@ -126,7 +126,7 @@ describe( 'AtomNode class', () => { node.value = value; expect( isReadonly( node.value ) ).toBe( true ); } ); - test( 'ensures that all atom values of atoms up the are readonly', () => { + test( 'ensures that all atom values of atoms up the tree are readonly', () => { let { root } = createTestAtomArtifact({} as Data); let node = root.findActiveNodeAt([ 'a', 'b', 'c', 'd', 'e' ])!; let value = { message: 'this is the test....' } as unknown as typeof node.value; diff --git a/src/model/accessor-cache/repository/atom-value/node/index.ts b/src/model/accessor-cache/repository/atom-value/node/index.ts index ab79ce0..3a0244c 100644 --- a/src/model/accessor-cache/repository/atom-value/node/index.ts +++ b/src/model/accessor-cache/repository/atom-value/node/index.ts @@ -11,6 +11,7 @@ import cloneDeep from '@webkrafters/clone-total'; import { GLOBAL_SELECTOR } from '../../../../..'; import { + isAPrefixOfB, isPlainObject, isString, makeReadonly, @@ -215,8 +216,7 @@ class AtomNode{ if( !activeNode ) { if( node.isRoot ) { return } for( let dNodes = node._findNearestActiveDescendants(), d = dNodes.length; d--; ) { - // istanbul ignore next - if( !dNodes[ d ].isRootAtom ) { continue } + if( !isAPrefixOfB( fullPath, dNodes[ d ].fullPath ) ) { continue } dNodes[ d ].value = get( value, dNodes[ d ].fullPath.slice( fullPathLen ) )._value as T; } return; diff --git a/src/utils/index.test.ts b/src/utils/index.test.ts index 9805d81..06c7ed6 100644 --- a/src/utils/index.test.ts +++ b/src/utils/index.test.ts @@ -89,6 +89,54 @@ describe( 'utils module', () => { } ); } ); } ); + describe( 'isAPrefixOfB(...)', () => { + test( 'rejects different series of equal lengths', () => { + expect( utils.isAPrefixOfB( + [ 'a', 'b', 'c' ], + [ 'a', 'c', 'v' ] + ) ).toBe( false ); + } ); + test( 'accepts two equal series', () => { + expect( utils.isAPrefixOfB( + [ 'a', 'b', 'c' ], + [ 'a', 'b', 'c' ] + ) ).toBe( true ); + expect( utils.isAPrefixOfB( + [], + [] + ) ).toBe( true ); + } ); + test( 'accepts series A containing the first N shorter subsequence of series B', () => { + expect( utils.isAPrefixOfB( + [], + [ 'a', 'b', 'c', 'd', 'e', 'f' ] + ) ).toBe( true ); + expect( utils.isAPrefixOfB( + [ 'a', 'b', 'c' ], + [ 'a', 'b', 'c', 'd', 'e', 'f' ] + ) ).toBe( true ); + } ); + test( 'rejects series A containing non first N shorter subsequence of series B', () => { + expect( utils.isAPrefixOfB( + [ 'c', 'd', 'e' ], + [ 'a', 'b', 'c', 'd', 'e', 'f' ] + ) ).toBe( false ); + expect( utils.isAPrefixOfB( + [ 'a', 'y', 'c' ], + [ 'a', 'b', 'c', 'd', 'e', 'f' ] + ) ).toBe( false ); + } ); + test( 'rejects series A containing the entire series B and more', () => { + expect( utils.isAPrefixOfB( + [ 'a', 'b', 'c', 'd', 'e', 'f' ], + [] + ) ).toBe( false ); + expect( utils.isAPrefixOfB( + [ 'a', 'b', 'c', 'd', 'e', 'f' ], + [ 'a', 'b', 'c' ] + ) ).toBe( false ); + } ); + } ); describe( 'isDataContainer(...)', () => { test( 'is true for arrays', () => { expect( utils.isDataContainer( [] ) ).toBe( true ); diff --git a/src/utils/index.ts b/src/utils/index.ts index 326d0d3..16abc54 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -45,6 +45,17 @@ export function arrangePropertyPaths( propertyPaths : Array ) : Array( + { length: aLen, ...a } : Array, + b : Array +) { + if( aLen > b.length ) { return false } + for( let i = 0; i < aLen; i++ ) { + if( a[ i ] !== b[ i ] ) { return false } + } + return true; +} + /** Checks if value is either a plain object or an array */ export function isDataContainer( v ) { return isPlainObject( v ) || Array.isArray( v ) }