@wordpress/interactivity-router
Version:
Package that exposes state and actions from the `core/router` store, part of the Interactivity API.
368 lines (302 loc) • 11.4 kB
text/typescript
/**
* Internal dependencies
*/
import { shortestCommonSupersequence } from '../scs';
/**
* Checks if the passed array is a contained sequence of the given
* supersequence.
*
* @param arr Array.
* @param superseq Supersequence.
* @param isEqual Optional comparator.
* @return True if the passed array is contained. False otherwise.
*/
function isSubsequence(
arr: unknown[],
superseq: unknown[],
isEqual = ( a: unknown, b: unknown ): boolean => a === b
) {
let i = 0;
let j = 0;
while ( i < arr.length && j < superseq.length ) {
if ( isEqual( arr[ i ], superseq[ j ] ) ) {
i++;
}
j++;
}
return i === arr.length;
}
describe( 'shortestCommonSupersequence', () => {
describe( 'Basic functionality', () => {
it( 'should handle empty arrays', () => {
// Both arrays empty.
expect( shortestCommonSupersequence( [], [] ) ).toEqual( [] );
// X empty, Y with values.
const Y = [ 1, 2, 3 ];
expect( shortestCommonSupersequence( [], Y ) ).toEqual( Y );
// Y empty, X with values.
const X = [ 1, 2, 3 ];
expect( shortestCommonSupersequence( X, [] ) ).toEqual( X );
} );
it( 'should handle identical arrays', () => {
// Same elements, same order.
const X = [ 1, 2, 3 ];
const Y = [ 1, 2, 3 ];
expect( shortestCommonSupersequence( X, Y ) ).toEqual( [
1, 2, 3,
] );
} );
it( 'should handle simple cases with partial overlap', () => {
// Overlap at the beginning.
const result1 = shortestCommonSupersequence(
[ 1, 2, 3 ],
[ 1, 2, 4 ]
);
// Verify it's a valid supersequence.
expect( isSubsequence( [ 1, 2, 3 ], result1 ) ).toBe( true );
expect( isSubsequence( [ 1, 2, 4 ], result1 ) ).toBe( true );
// Verify optimal length.
expect( result1.length ).toBe( 4 );
// Overlap at the end.
const result2 = shortestCommonSupersequence(
[ 1, 2, 3 ],
[ 0, 2, 3 ]
);
// Verify it's a valid supersequence.
expect( isSubsequence( [ 1, 2, 3 ], result2 ) ).toBe( true );
expect( isSubsequence( [ 0, 2, 3 ], result2 ) ).toBe( true );
// Verify optimal length.
expect( result2.length ).toBe( 4 );
// Middle overlap.
const result3 = shortestCommonSupersequence(
[ 1, 2, 3 ],
[ 0, 2, 4 ]
);
// Verify it's a valid supersequence.
expect( isSubsequence( [ 1, 2, 3 ], result3 ) ).toBe( true );
expect( isSubsequence( [ 0, 2, 4 ], result3 ) ).toBe( true );
// Verify optimal length.
expect( result3.length ).toBe( 5 );
} );
it( 'should handle arrays with no overlap', () => {
const X = [ 1, 2, 3 ];
const Y = [ 4, 5, 6 ];
const result = shortestCommonSupersequence( X, Y );
// Test that it's a valid supersequence of both arrays.
expect( isSubsequence( X, result ) ).toBe( true );
expect( isSubsequence( Y, result ) ).toBe( true );
// Result length should be X.length + Y.length.
expect( result.length ).toBe( X.length + Y.length );
} );
it( 'should verify the supersequence length is optimal', () => {
// Example where the shortest supersequence is shorter than just concatenating.
const X = [ 1, 2, 3 ];
const Y = [ 2, 3, 4 ];
const result = shortestCommonSupersequence( X, Y );
expect( result.length ).toBe( 4 );
expect( result ).toEqual( [ 1, 2, 3, 4 ] );
} );
} );
describe( 'Edge cases', () => {
it( 'should handle subsequence cases', () => {
// X is a subsequence of Y.
const X1 = [ 1, 3 ];
const Y1 = [ 1, 2, 3, 4 ];
expect( shortestCommonSupersequence( X1, Y1 ) ).toEqual( Y1 );
// Y is a subsequence of X.
const X2 = [ 1, 2, 3, 4 ];
const Y2 = [ 1, 3 ];
expect( shortestCommonSupersequence( X2, Y2 ) ).toEqual( X2 );
} );
it( 'should handle arrays with duplicate elements', () => {
// Duplicates in X.
const resultX = shortestCommonSupersequence(
[ 1, 2, 2, 3 ],
[ 1, 3, 4 ]
);
expect( isSubsequence( [ 1, 2, 2, 3 ], resultX ) ).toBe( true );
expect( isSubsequence( [ 1, 3, 4 ], resultX ) ).toBe( true );
expect( resultX.length ).toBe( 5 );
// Duplicates in Y.
const resultY = shortestCommonSupersequence(
[ 1, 3, 4 ],
[ 1, 2, 2, 3 ]
);
expect( isSubsequence( [ 1, 3, 4 ], resultY ) ).toBe( true );
expect( isSubsequence( [ 1, 2, 2, 3 ], resultY ) ).toBe( true );
expect( resultY.length ).toBe( 5 );
// Duplicates in both X and Y.
const resultXY = shortestCommonSupersequence(
[ 1, 2, 2, 3 ],
[ 1, 2, 3, 3 ]
);
expect( isSubsequence( [ 1, 2, 2, 3 ], resultXY ) ).toBe( true );
expect( isSubsequence( [ 1, 2, 3, 3 ], resultXY ) ).toBe( true );
expect( resultXY.length ).toBe( 5 );
// Multiple duplicate occurrences.
const result = shortestCommonSupersequence(
[ 1, 2, 1, 2 ],
[ 2, 1, 2, 1 ]
);
expect( isSubsequence( [ 1, 2, 1, 2 ], result ) ).toBe( true );
expect( isSubsequence( [ 2, 1, 2, 1 ], result ) ).toBe( true );
expect( result.length ).toBe( 5 );
} );
it( 'should handle completely duplicate array', () => {
expect(
shortestCommonSupersequence( [ 1, 1, 1 ], [ 1, 1 ] )
).toEqual( [ 1, 1, 1 ] );
} );
it( 'should handle asymmetric cases with different array lengths', () => {
// X much longer than Y.
const X1 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
const Y1 = [ 2, 5 ];
const result1 = shortestCommonSupersequence( X1, Y1 );
// Result should be same as X since Y is a subsequence.
expect( result1 ).toEqual( X1 );
// Y much longer than X.
const X2 = [ 2, 5 ];
const Y2 = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ];
const result2 = shortestCommonSupersequence( X2, Y2 );
// Result should be same as Y since X is a subsequence.
expect( result2 ).toEqual( Y2 );
} );
} );
describe( 'Order preservation', () => {
it( 'should preserve order of elements from input arrays', () => {
const X = [ 1, 3, 5 ];
const Y = [ 5, 3, 1 ];
const result = shortestCommonSupersequence( X, Y );
// Test that it's a valid supersequence of both arrays.
expect( isSubsequence( X, result ) ).toBe( true );
expect( isSubsequence( Y, result ) ).toBe( true );
// Verify optimal length.
expect( result.length ).toBe( 5 );
// Test reverse case.
const result2 = shortestCommonSupersequence( Y, X );
expect( isSubsequence( X, result2 ) ).toBe( true );
expect( isSubsequence( Y, result2 ) ).toBe( true );
// Verify optimal length.
expect( result2.length ).toBe( 5 );
} );
it( 'should handle zigzag patterns correctly', () => {
const X = [ 1, 3, 5, 7, 9 ];
const Y = [ 2, 4, 6, 8, 10 ];
const result = shortestCommonSupersequence( X, Y );
// Verify it's a valid supersequence.
expect( isSubsequence( X, result ) ).toBe( true );
expect( isSubsequence( Y, result ) ).toBe( true );
// Verify optimal length (should be X.length + Y.length because no common elements).
expect( result.length ).toBe( 10 );
} );
} );
describe( 'Advanced cases', () => {
it( 'should handle complex interleavings', () => {
const X = [ 1, 2, 3, 1, 2, 3 ];
const Y = [ 3, 2, 1, 3, 2, 1 ];
const result = shortestCommonSupersequence( X, Y );
// Result should be a valid supersequence.
expect( isSubsequence( X, result ) ).toBe( true );
expect( isSubsequence( Y, result ) ).toBe( true );
// Verify optimal length.
expect( result.length ).toBe( 9 );
} );
it( 'should handle almost identical arrays with one difference', () => {
const X = [ 1, 2, 3, 4, 5 ];
const Y = [ 1, 2, 3, 5, 4 ];
const result = shortestCommonSupersequence( X, Y );
// Result should contain 6 elements (optimal length).
expect( result.length ).toBe( 6 );
// The first 3 elements should be 1,2,3.
expect( result.slice( 0, 3 ) ).toEqual( [ 1, 2, 3 ] );
// Test that it's a valid supersequence of both arrays.
expect( isSubsequence( X, result ) ).toBe( true );
expect( isSubsequence( Y, result ) ).toBe( true );
} );
it( 'should handle multiple matching possibilities optimally', () => {
const X = [ 1, 1, 2, 2 ];
const Y = [ 1, 2, 1, 2 ];
const result = shortestCommonSupersequence( X, Y );
// Result should be a valid supersequence.
expect( isSubsequence( X, result ) ).toBe( true );
expect( isSubsequence( Y, result ) ).toBe( true );
// Verify optimal length.
expect( result.length ).toBe( 5 );
} );
} );
describe( 'Custom equality function', () => {
it( 'should use custom equality function for comparing elements', () => {
const X = [
{ id: 1, name: 'a' },
{ id: 2, name: 'b' },
];
const Y = [
{ id: 1, name: 'c' },
{ id: 2, name: 'd' },
];
// Custom equality function that only compares 'id' property.
const isEqual = ( a, b ) => a.id === b.id;
const result = shortestCommonSupersequence( X, Y, isEqual );
// Should treat objects with same ids as equal.
expect( result.length ).toBe( 2 );
expect( result[ 0 ].id ).toBe( 1 );
expect( result[ 1 ].id ).toBe( 2 );
// Should use references from X.
expect( result[ 0 ] ).toBe( X[ 0 ] );
expect( result[ 1 ] ).toBe( X[ 1 ] );
// Test that it's a valid supersequence of both arrays.
expect( isSubsequence( X, result, isEqual ) ).toBe( true );
expect( isSubsequence( Y, result, isEqual ) ).toBe( true );
} );
it( 'should handle case-insensitive comparison', () => {
const X = [ 'A', 'B', 'C' ];
const Y = [ 'a', 'b', 'D' ];
// Case-insensitive comparison.
const isEqual = ( a, b ) => a.toLowerCase() === b.toLowerCase();
const result = shortestCommonSupersequence( X, Y, isEqual );
// Check references are from X when elements are considered equal.
expect( result[ 0 ] ).toBe( X[ 0 ] ); // 'A' instead of 'a'
expect( result[ 1 ] ).toBe( X[ 1 ] ); // 'B' instead of 'b'
// Verify it contains C and D (which don't match).
expect( result.includes( 'C' ) ).toBe( true );
expect( result.includes( 'D' ) ).toBe( true );
// Test that it's a valid supersequence of both arrays.
expect( isSubsequence( X, result, isEqual ) ).toBe( true );
expect( isSubsequence( Y, result, isEqual ) ).toBe( true );
// Check length is correct (4 instead of 6).
expect( result.length ).toBe( 4 );
} );
} );
describe( 'Null and undefined handling', () => {
it( 'should handle arrays with null values', () => {
const X = [ 1, null, 3 ];
const Y = [ null, 2, 3 ];
const result = shortestCommonSupersequence( X, Y );
// Should treat null values correctly.
expect( result ).toEqual( [ 1, null, 2, 3 ] );
} );
it( 'should handle arrays with undefined', () => {
const X = [ 1, undefined, 3 ];
const Y = [ 2, undefined, 4 ];
const result = shortestCommonSupersequence( X, Y );
// Test that it's a valid supersequence of both arrays.
expect( isSubsequence( X, result ) ).toBe( true );
expect( isSubsequence( Y, result ) ).toBe( true );
// Length should be shorter than concatenating the arrays.
expect( result.length ).toBeLessThan( X.length + Y.length );
} );
} );
describe( 'Mutation checks', () => {
it( 'should not modify the input arrays', () => {
const X = [ 1, 2, 3 ];
const Y = [ 2, 3, 4 ];
// Create copies for comparison.
const originalX = [ ...X ];
const originalY = [ ...Y ];
shortestCommonSupersequence( X, Y );
// Input arrays should remain unchanged.
expect( X ).toEqual( originalX );
expect( Y ).toEqual( originalY );
} );
} );
} );