Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add property based tests on diff-sequences #9072

Merged
merged 8 commits into from
Oct 29, 2019
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);
}),
);
});