Skip to content

Commit 06a01f0

Browse files
committed
add tests
1 parent c9c299d commit 06a01f0

File tree

4 files changed

+297
-1
lines changed

4 files changed

+297
-1
lines changed
Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import {
2+
_interopDefault,
3+
_interopNamespace,
4+
_interopNamespaceDefaultOnly,
5+
_interopRequireDefault,
6+
_interopRequireWildcard,
7+
} from '../../src/buildPolyfills';
8+
import { RequireResult } from '../../src/buildPolyfills/types';
9+
import {
10+
_interopDefault as _interopDefaultOrig,
11+
_interopNamespace as _interopNamespaceOrig,
12+
_interopNamespaceDefaultOnly as _interopNamespaceDefaultOnlyOrig,
13+
_interopRequireDefault as _interopRequireDefaultOrig,
14+
_interopRequireWildcard as _interopRequireWildcardOrig,
15+
} from './originals';
16+
17+
// This file tests five different functions against a range of test cases. Though the inputs are the same for each
18+
// function's test cases, the expected output differs. The testcases for each function are therefore built from separate
19+
// collections of expected inputs and expected outputs. Further, for readability purposes, the tests labels have also
20+
// been split into their own object. It's also worth noting that in real life, there are some test-case/function
21+
// pairings which would never happen, but by testing all combinations, we're guaranteed to have tested the ones which
22+
// show up in the wild.
23+
24+
const dogStr = 'dogs are great!';
25+
const dogFunc = () => dogStr;
26+
const dogAdjectives = { maisey: 'silly', charlie: 'goofy' };
27+
28+
const withESModuleFlag = { __esModule: true, ...dogAdjectives };
29+
const withESModuleFlagAndDefault = { __esModule: true, default: dogFunc, ...dogAdjectives };
30+
const namedExports = { ...dogAdjectives };
31+
const withNonEnumerableProp = { ...dogAdjectives };
32+
// Properties added using `Object.defineProperty` are non-enumerable by default
33+
Object.defineProperty(withNonEnumerableProp, 'hiddenProp', { value: 'shhhhhhhh' });
34+
const withDefaultExport = { default: dogFunc, ...dogAdjectives };
35+
const withOnlyDefaultExport = { default: dogFunc };
36+
const exportsEquals = dogFunc as RequireResult;
37+
const exportsEqualsWithDefault = dogFunc as RequireResult;
38+
exportsEqualsWithDefault.default = exportsEqualsWithDefault;
39+
40+
const mockRequireResults: Record<string, RequireResult> = {
41+
withESModuleFlag,
42+
withESModuleFlagAndDefault,
43+
namedExports,
44+
withNonEnumerableProp,
45+
withDefaultExport,
46+
withOnlyDefaultExport,
47+
exportsEquals: exportsEquals,
48+
exportsEqualsWithDefault: exportsEqualsWithDefault as unknown as RequireResult,
49+
};
50+
51+
const testLabels: Record<string, string> = {
52+
withESModuleFlag: 'module with `__esModule` flag',
53+
withESModuleFlagAndDefault: 'module with `__esModule` flag and default export',
54+
namedExports: 'module with named exports',
55+
withNonEnumerableProp: 'module with named exports and non-enumerable prop',
56+
withDefaultExport: 'module with default export',
57+
withOnlyDefaultExport: 'module with only default export',
58+
exportsEquals: 'module using `exports =`',
59+
exportsEqualsWithDefault: 'module using `exports =` with default export',
60+
};
61+
62+
function makeTestCases(expectedOutputs: Record<string, RequireResult>): Array<[string, RequireResult, RequireResult]> {
63+
return Object.keys(mockRequireResults).map(key => [testLabels[key], mockRequireResults[key], expectedOutputs[key]]);
64+
}
65+
66+
describe('_interopNamespace', () => {
67+
describe('returns the same result as the original', () => {
68+
const expectedOutputs: Record<string, RequireResult> = {
69+
withESModuleFlag: withESModuleFlag,
70+
withESModuleFlagAndDefault: withESModuleFlagAndDefault,
71+
namedExports: { ...namedExports, default: namedExports },
72+
withNonEnumerableProp: {
73+
...withNonEnumerableProp,
74+
default: withNonEnumerableProp,
75+
},
76+
withDefaultExport: { ...withDefaultExport, default: withDefaultExport },
77+
withOnlyDefaultExport: { default: withOnlyDefaultExport },
78+
exportsEquals: { default: exportsEquals },
79+
exportsEqualsWithDefault: { default: exportsEqualsWithDefault },
80+
};
81+
82+
const testCases = makeTestCases(expectedOutputs);
83+
84+
it.each(testCases)('%s', (_, requireResult, expectedOutput) => {
85+
expect(_interopNamespace(requireResult)).toEqual(_interopNamespaceOrig(requireResult));
86+
expect(_interopNamespace(requireResult)).toEqual(expectedOutput);
87+
});
88+
});
89+
});
90+
91+
describe('_interopNamespaceDefaultOnly', () => {
92+
describe('returns the same result as the original', () => {
93+
const expectedOutputs: Record<string, RequireResult> = {
94+
withESModuleFlag: { default: withESModuleFlag },
95+
withESModuleFlagAndDefault: { default: withESModuleFlagAndDefault },
96+
namedExports: { default: namedExports },
97+
withNonEnumerableProp: { default: withNonEnumerableProp },
98+
withDefaultExport: { default: withDefaultExport },
99+
withOnlyDefaultExport: { default: withOnlyDefaultExport },
100+
exportsEquals: { default: exportsEquals },
101+
exportsEqualsWithDefault: { default: exportsEqualsWithDefault },
102+
};
103+
104+
const testCases = makeTestCases(expectedOutputs);
105+
106+
it.each(testCases)('%s', (_, requireResult, expectedOutput) => {
107+
expect(_interopNamespaceDefaultOnly(requireResult)).toEqual(_interopNamespaceDefaultOnlyOrig(requireResult));
108+
expect(_interopNamespaceDefaultOnly(requireResult)).toEqual(expectedOutput);
109+
});
110+
});
111+
});
112+
113+
describe('_interopRequireWildcard', () => {
114+
describe('returns the same result as the original', () => {
115+
const expectedOutputs: Record<string, RequireResult> = {
116+
withESModuleFlag: withESModuleFlag,
117+
withESModuleFlagAndDefault: withESModuleFlagAndDefault,
118+
namedExports: { ...namedExports, default: namedExports },
119+
withNonEnumerableProp: {
120+
...withNonEnumerableProp,
121+
default: withNonEnumerableProp,
122+
},
123+
withDefaultExport: { ...withDefaultExport, default: withDefaultExport },
124+
withOnlyDefaultExport: { default: withOnlyDefaultExport },
125+
exportsEquals: { default: exportsEquals },
126+
exportsEqualsWithDefault: { default: exportsEqualsWithDefault },
127+
};
128+
129+
const testCases = makeTestCases(expectedOutputs);
130+
131+
it.each(testCases)('%s', (_, requireResult, expectedOutput) => {
132+
expect(_interopRequireWildcard(requireResult)).toEqual(_interopRequireWildcardOrig(requireResult));
133+
expect(_interopRequireWildcard(requireResult)).toEqual(expectedOutput);
134+
});
135+
});
136+
});
137+
138+
describe('_interopDefault', () => {
139+
describe('returns the same result as the original', () => {
140+
const expectedOutputs: Record<string, RequireResult> = {
141+
withESModuleFlag: undefined as unknown as RequireResult,
142+
withESModuleFlagAndDefault: withESModuleFlagAndDefault.default as RequireResult,
143+
namedExports: namedExports,
144+
withNonEnumerableProp: withNonEnumerableProp,
145+
withDefaultExport: withDefaultExport,
146+
withOnlyDefaultExport: withOnlyDefaultExport,
147+
exportsEquals: exportsEquals,
148+
exportsEqualsWithDefault: exportsEqualsWithDefault,
149+
};
150+
151+
const testCases = makeTestCases(expectedOutputs);
152+
153+
it.each(testCases)('%s', (_, requireResult, expectedOutput) => {
154+
expect(_interopDefault(requireResult)).toEqual(_interopDefaultOrig(requireResult));
155+
expect(_interopDefault(requireResult)).toEqual(expectedOutput);
156+
});
157+
});
158+
});
159+
160+
describe('_interopRequireDefault', () => {
161+
describe('returns the same result as the original', () => {
162+
const expectedOutputs: Record<string, RequireResult> = {
163+
withESModuleFlag: withESModuleFlag,
164+
withESModuleFlagAndDefault: withESModuleFlagAndDefault,
165+
namedExports: { default: namedExports },
166+
withNonEnumerableProp: { default: withNonEnumerableProp },
167+
withDefaultExport: { default: withDefaultExport },
168+
withOnlyDefaultExport: { default: withOnlyDefaultExport },
169+
exportsEquals: { default: exportsEquals },
170+
exportsEqualsWithDefault: { default: exportsEqualsWithDefault },
171+
};
172+
173+
const testCases = makeTestCases(expectedOutputs);
174+
175+
it.each(testCases)('%s', (_, requireResult, expectedOutput) => {
176+
expect(_interopRequireDefault(requireResult)).toEqual(_interopRequireDefaultOrig(requireResult));
177+
expect(_interopRequireDefault(requireResult)).toEqual(expectedOutput);
178+
});
179+
});
180+
});
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { _nullishCoalesce } from '../../src/buildPolyfills';
2+
import { Value } from '../../src/buildPolyfills/types';
3+
import { _nullishCoalesce as _nullishCoalesceOrig } from './originals';
4+
5+
const dogStr = 'dogs are great!';
6+
const dogFunc = () => dogStr;
7+
const dogAdjectives = { maisey: 'silly', charlie: 'goofy' };
8+
const dogAdjectiveFunc = () => dogAdjectives;
9+
10+
describe('_nullishCoalesce', () => {
11+
describe('returns the same result as the original', () => {
12+
const testCases: Array<[string, Value, () => Value, Value]> = [
13+
['null LHS', null, dogFunc, dogStr],
14+
['undefined LHS', undefined, dogFunc, dogStr],
15+
['false LHS', false, dogFunc, false],
16+
['zero LHS', 0, dogFunc, 0],
17+
['empty string LHS', '', dogFunc, ''],
18+
['true LHS', true, dogFunc, true],
19+
['truthy primitive LHS', 12312012, dogFunc, 12312012],
20+
['truthy object LHS', dogAdjectives, dogFunc, dogAdjectives],
21+
['truthy function LHS', dogAdjectiveFunc, dogFunc, dogAdjectiveFunc],
22+
];
23+
24+
it.each(testCases)('%s', (_, lhs, rhs, expectedValue) => {
25+
expect(_nullishCoalesce(lhs, rhs)).toEqual(_nullishCoalesceOrig(lhs, rhs));
26+
expect(_nullishCoalesce(lhs, rhs)).toEqual(expectedValue);
27+
});
28+
});
29+
});
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { _optionalChain } from '../../src/buildPolyfills';
2+
import { GenericFunction, GenericObject, Value } from '../../src/buildPolyfills/types';
3+
import { _optionalChain as _optionalChainOrig } from './originals';
4+
5+
type OperationType = 'access' | 'call' | 'optionalAccess' | 'optionalCall';
6+
type OperationExecutor =
7+
| ((intermediateValue: GenericObject) => Value)
8+
| ((intermediateValue: GenericFunction) => Value);
9+
type Operation = [OperationType, OperationExecutor];
10+
11+
const truthyObject = { maisey: 'silly', charlie: 'goofy' };
12+
const nullishObject = null;
13+
const truthyFunc = (): GenericObject => truthyObject;
14+
const nullishFunc = undefined;
15+
const truthyReturn = (): GenericObject => truthyObject;
16+
const nullishReturn = (): null => nullishObject;
17+
18+
// The polyfill being tested here works under the assumption that the original code containing the optional chain has
19+
// been transformed into an array of values, labels, and functions. For example, `truthyObject?.charlie` will have been
20+
// transformed into `_optionalChain([truthyObject, 'optionalAccess', _ => _.charlie])`. We are not testing the
21+
// transformation here, only what the polyfill does with the already-transformed inputs.
22+
23+
describe('_optionalChain', () => {
24+
describe('returns the same result as the original', () => {
25+
// In these test cases, the array passed to `_optionalChain` has been broken up into the first entry followed by an
26+
// array of pairs of subsequent elements, because this seemed the easiest way to express the type, which is really
27+
//
28+
// [Value, OperationType, Value => Value, OperationType, Value => Value, OperationType, Value => Value, ...].
29+
//
30+
// (In other words, `[A, B, C, D, E]` has become `A, [[B, C], [D, E]]`, and these are then the second and third
31+
// entries in each test case.) We then undo this wrapping before passing the data to our functions.
32+
const testCases: Array<[string, Value, Operation[], Value]> = [
33+
['truthyObject?.charlie', truthyObject, [['optionalAccess', (_: GenericObject) => _.charlie]], 'goofy'],
34+
['nullishObject?.maisey', nullishObject, [['optionalAccess', (_: GenericObject) => _.maisey]], undefined],
35+
[
36+
'truthyFunc?.().maisey',
37+
truthyFunc,
38+
[
39+
['optionalCall', (_: GenericFunction) => _()],
40+
['access', (_: GenericObject) => _.maisey],
41+
],
42+
'silly',
43+
],
44+
[
45+
'nullishFunc?.().charlie',
46+
nullishFunc,
47+
[
48+
['optionalCall', (_: GenericFunction) => _()],
49+
['access', (_: GenericObject) => _.charlie],
50+
],
51+
undefined,
52+
],
53+
[
54+
'truthyReturn()?.maisey',
55+
truthyReturn,
56+
[
57+
['call', (_: GenericFunction) => _()],
58+
['optionalAccess', (_: GenericObject) => _.maisey],
59+
],
60+
'silly',
61+
],
62+
[
63+
'nullishReturn()?.charlie',
64+
nullishReturn,
65+
[
66+
['call', (_: GenericFunction) => _()],
67+
['optionalAccess', (_: GenericObject) => _.charlie],
68+
],
69+
undefined,
70+
],
71+
];
72+
73+
it.each(testCases)('%s', (_, initialChainComponent, operations, expectedValue) => {
74+
// `operations` is flattened and spread in order to undo the wrapping done in the test cases for TS purposes.
75+
expect(_optionalChain([initialChainComponent, ...operations.flat()])).toEqual(
76+
_optionalChainOrig([initialChainComponent, ...operations.flat()]),
77+
);
78+
expect(_optionalChain([initialChainComponent, ...operations.flat()])).toEqual(expectedValue);
79+
});
80+
});
81+
});

packages/utils/tsconfig.test.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,12 @@
99

1010
// other package-specific, test-specific options
1111
// this is necessary in order to be able to handle the buildPolyfills `originals.js` which is used for testing
12-
"allowJs": true
12+
"allowJs": true,
13+
14+
// `es2020` is the recommended `lib` and `target` for Node 14
15+
// see https://github.com/tsconfig/bases/blob/main/bases/node14.json
16+
"lib": ["dom", "es2020"],
17+
"module": "commonjs",
18+
"target": "es2020",
1319
}
1420
}

0 commit comments

Comments
 (0)