Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,5 +78,5 @@
"test:watch": "jest --updateSnapshot --watchAll"
},
"types": "dist/index.d.ts",
"version": "2.0.4"
"version": "2.0.5"
}
63 changes: 40 additions & 23 deletions src/connection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -131,15 +129,14 @@ 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();
} );
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', () => {
Expand All @@ -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();
} );
} );
Expand All @@ -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 );
Expand Down Expand Up @@ -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' } } },
Expand Down Expand Up @@ -295,7 +315,6 @@ describe( 'Connection class', () => {
'tags[6]': source.tags[ 6 ],
'@@GLOBAL': updatedDataEquiv
});
jest.runAllTimers();
jest.useRealTimers();
connection.disconnect();
} );
Expand All @@ -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 ] },
Expand All @@ -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();
Expand All @@ -351,15 +368,15 @@ describe( 'Connection class', () => {
'tags[6]',
'@@GLOBAL'
);
jest.runAllTimers();

const data2 = connection.get(
'friends[1].name.last',
'history.places[2].country',
'history.places[2].year',
'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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
4 changes: 2 additions & 2 deletions src/model/accessor-cache/repository/atom-value/node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import cloneDeep from '@webkrafters/clone-total';
import { GLOBAL_SELECTOR } from '../../../../..';

import {
isAPrefixOfB,
isPlainObject,
isString,
makeReadonly,
Expand Down Expand Up @@ -215,8 +216,7 @@ class AtomNode<T extends Value>{
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;
Expand Down
48 changes: 48 additions & 0 deletions src/utils/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down
11 changes: 11 additions & 0 deletions src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ export function arrangePropertyPaths( propertyPaths : Array<string> ) : Array<st
return Object.keys( superPathTokensMap );
}

export function isAPrefixOfB<T>(
{ length: aLen, ...a } : Array<T>,
b : Array<T>
) {
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 ) }

Expand Down