Skip to content
Merged
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
135 changes: 135 additions & 0 deletions packages/diff-sequences/src/__tests__/index.property.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/

import fc from 'fast-check';
import diff from '../';

const findCommonItems = (a: Array<string>, b: Array<string>): Array<string> => {
const array: Array<string> = [];
diff(
a.length,
b.length,
(aIndex: number, bIndex: number) => a[aIndex] === b[bIndex],
(nCommon: number, aCommon: number) => {
for (; nCommon !== 0; nCommon -= 1, aCommon += 1) {
array.push(a[aCommon]);
}
},
);
return array;
};

const extractCount = (data: Array<string>): Map<string, number> => {
const countPerChar = new Map<string, number>();
for (const item of data) {
const currentCount = countPerChar.get(item) || 0;
countPerChar.set(item, currentCount + 1);
}
return countPerChar;
};

const flatten = (data: Array<Array<string>>) => {
const array: Array<string> = [];
for (const items of data) {
array.push(...items);
}
return array;
};

const isSubsequenceOf = (
subsequence: Array<string>,
sequence: Array<string>,
): boolean => {
let iSub = 0;
for (let iSeq = 0; iSeq !== sequence.length; iSeq += 1) {
if (iSub !== subsequence.length && subsequence[iSub] === sequence[iSeq]) {
iSub += 1;
}
}

return iSub === subsequence.length;
};

it('should be reflexive', () => {
fc.assert(
fc.property(fc.array(fc.char()), a => {
expect(findCommonItems(a, a)).toEqual(a);
}),
);
});

it('should find the same number of common items when switching the inputs', () => {
// findCommonItems is not symmetric as:
// > findCommonItems(["Z"," "], [" ","Z"]) = [" "]
// > findCommonItems([" ","Z"], ["Z"," "]) = ["Z"]
fc.assert(
fc.property(fc.array(fc.char()), fc.array(fc.char()), (a, b) => {
const commonItems = findCommonItems(a, b);
const symmetricCommonItems = findCommonItems(b, a);
expect(symmetricCommonItems).toHaveLength(commonItems.length);
}),
);
});

it('should have at most the length of its inputs', () => {
fc.assert(
fc.property(fc.array(fc.char()), fc.array(fc.char()), (a, b) => {
const commonItems = findCommonItems(a, b);
expect(commonItems.length).toBeLessThanOrEqual(a.length);
expect(commonItems.length).toBeLessThanOrEqual(b.length);
}),
);
});

it('should have at most the same number of each character as its inputs', () => {
fc.assert(
fc.property(fc.array(fc.char()), fc.array(fc.char()), (a, b) => {
const commonItems = findCommonItems(a, b);
const commonCount = extractCount(commonItems);
const aCount = extractCount(a);
for (const [item, count] of commonCount) {
const countOfItemInA = aCount.get(item) || 0;
expect(countOfItemInA).toBeGreaterThanOrEqual(count);
}
}),
);
});

it('should be a subsequence of its inputs', () => {
fc.assert(
fc.property(fc.array(fc.char()), fc.array(fc.char()), (a, b) => {
const commonItems = findCommonItems(a, b);
expect(isSubsequenceOf(commonItems, a)).toBe(true);
expect(isSubsequenceOf(commonItems, b)).toBe(true);
}),
);
});

it('should be no-op when passing common items', () => {
fc.assert(
fc.property(fc.array(fc.char()), fc.array(fc.char()), (a, b) => {
const commonItems = findCommonItems(a, b);
expect(findCommonItems(a, commonItems)).toEqual(commonItems);
expect(findCommonItems(commonItems, a)).toEqual(commonItems);
}),
);
});

it('should find the exact common items when one array is subarray of the other', () => {
fc.assert(
fc.property(fc.array(fc.array(fc.char())), data => {
const allData = flatten(data); // [...data[0], ...data[1], ...data[2], ...data[3], ...]
const partialData = flatten(data.filter((_, i) => i % 2 === 1)); // [...data[1], ...data[3], ...]
const commonItems = findCommonItems(allData, partialData);
// We have:
// 1. commonItems contains at least all the items of partialData as they are in allData too
// 2. commonItems cannot contain more items than its inputs (partialData for instance)
expect(commonItems.length).toBeGreaterThanOrEqual(partialData.length);
}),
);
});