Skip to content

Commit 8a1acfd

Browse files
committed
Clean up options schemas
Coerce 'true'/'false' into actual booleans (Fixes #223) Better error messages
1 parent 5d635b3 commit 8a1acfd

File tree

10 files changed

+573
-46
lines changed

10 files changed

+573
-46
lines changed

README.md

Lines changed: 11 additions & 11 deletions
Large diffs are not rendered by default.

test/helpers/expect-logs.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { expect, jest } from '@jest/globals';
2+
3+
import logger from '../../transforms/helpers/log-helper';
4+
5+
/**
6+
* Spies on the logger for messages matching those passed in the config.
7+
* If no messages are passed in the config, expects there to be no logs.
8+
*/
9+
export function expectLogs(
10+
callback: () => void,
11+
{
12+
info = [],
13+
warn = [],
14+
error = [],
15+
}: {
16+
/** What */
17+
info?: string[];
18+
warn?: string[];
19+
error?: string[];
20+
} = {}
21+
): void {
22+
const infoConfig = {
23+
level: 'info' as const,
24+
expectedMessages: info,
25+
restoreAllMocks: false,
26+
};
27+
const warnConfig = {
28+
level: 'warn' as const,
29+
expectedMessages: warn,
30+
restoreAllMocks: false,
31+
};
32+
const errorConfig = {
33+
level: 'error' as const,
34+
expectedMessages: error,
35+
restoreAllMocks: true,
36+
};
37+
38+
expectLogLevel(() => {
39+
expectLogLevel(() => {
40+
expectLogLevel(callback, infoConfig);
41+
}, warnConfig);
42+
}, errorConfig);
43+
44+
jest.restoreAllMocks();
45+
}
46+
47+
function expectLogLevel(
48+
callback: () => void,
49+
{
50+
level = 'error',
51+
expectedMessages = [],
52+
restoreAllMocks = true,
53+
}: {
54+
level?: 'info' | 'warn' | 'error';
55+
expectedMessages?: string[];
56+
restoreAllMocks?: boolean;
57+
} = {}
58+
): void {
59+
const spy = jest.spyOn(logger, level);
60+
61+
callback();
62+
63+
if (expectedMessages.length > 0) {
64+
expect(spy).toHaveBeenCalledTimes(expectedMessages.length);
65+
for (const [index, expectedError] of expectedMessages.entries()) {
66+
expect(spy).toHaveBeenNthCalledWith(
67+
index + 1,
68+
expect.stringMatching(expectedError)
69+
);
70+
}
71+
} else {
72+
expect(spy).not.toHaveBeenCalled();
73+
}
74+
75+
if (restoreAllMocks) {
76+
jest.restoreAllMocks();
77+
}
78+
}
79+
80+
/**
81+
* Makes a regexp pattern to match logs. String arguments passed to
82+
* `makeLogMatcher` will be escaped then merged together into a regexp that will
83+
* match partial lines of multi-line logs when paired with Jest
84+
* `expect.stringMatching`.
85+
*
86+
* @example
87+
* ```
88+
* const expected = makeLogMatcher('Line 1', 'Line 2', '3')
89+
* //=> 'Line 1[\S\s]*Line 2[\S\s]*3'
90+
*
91+
* expect('Line 1\nLine 2\nLine 3').toEqual(expect.stringMatching(expected));
92+
* //=> passes
93+
* ```
94+
*/
95+
export function makeLogMatcher(...parts: string[]): string {
96+
return parts.map(escapeRegExp).join('[\\S\\s]*');
97+
}
98+
99+
/**
100+
* @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
101+
*/
102+
function escapeRegExp(string: string): string {
103+
return string.replace(/[$()*+.?[\\\]^{|}]/g, '\\$&'); // $& means the whole matched string
104+
}

test/options.test.ts

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
import { describe, expect, test } from '@jest/globals';
2+
import { DEFAULT_OPTIONS, parseConfig } from '../transforms/helpers/options';
3+
import { expectLogs, makeLogMatcher } from './helpers/expect-logs';
4+
5+
describe('options', () => {
6+
describe('parseConfig', () => {
7+
test('it parses an empty config', () => {
8+
expectLogs(() => {
9+
const config = parseConfig('test', {});
10+
expect(config).toStrictEqual({});
11+
});
12+
});
13+
14+
test('it parses the DEFAULT_OPTIONS', () => {
15+
expectLogs(() => {
16+
const config = parseConfig('test', DEFAULT_OPTIONS);
17+
expect(config).toStrictEqual(DEFAULT_OPTIONS);
18+
});
19+
});
20+
21+
describe('decorators', () => {
22+
test('it parses `{ decorators: true }`', () => {
23+
expectLogs(() => {
24+
const config = parseConfig('test', { decorators: true });
25+
expect(config).toStrictEqual({
26+
decorators: { inObjectLiterals: [] },
27+
});
28+
});
29+
});
30+
31+
test('it parses `{ decorators: "true" }`', () => {
32+
expectLogs(() => {
33+
const config = parseConfig('test', { decorators: 'true' });
34+
expect(config).toStrictEqual({
35+
decorators: { inObjectLiterals: [] },
36+
});
37+
});
38+
});
39+
40+
test('it parses `{ decorators: false }`', () => {
41+
expectLogs(() => {
42+
const config = parseConfig('test', { decorators: false });
43+
expect(config).toStrictEqual({ decorators: false });
44+
});
45+
});
46+
47+
test('it parses `{ decorators: "false" }`', () => {
48+
expectLogs(() => {
49+
const config = parseConfig('test', { decorators: 'false' });
50+
expect(config).toStrictEqual({ decorators: false });
51+
});
52+
});
53+
54+
test('it parses DecoratorOptions.inObjectLiterals with array of strings', () => {
55+
expectLogs(() => {
56+
const config = parseConfig('test', {
57+
decorators: { inObjectLiterals: ['one', 'two', 'three'] },
58+
});
59+
expect(config).toStrictEqual({
60+
decorators: { inObjectLiterals: ['one', 'two', 'three'] },
61+
});
62+
});
63+
});
64+
65+
test('it parses DecoratorOptions.inObjectLiterals with string of strings', () => {
66+
expectLogs(() => {
67+
const config = parseConfig('test', {
68+
decorators: { inObjectLiterals: 'one,two , three' },
69+
});
70+
expect(config).toStrictEqual({
71+
decorators: { inObjectLiterals: ['one', 'two', 'three'] },
72+
});
73+
});
74+
});
75+
76+
test('it logs an error for invalid `decorators` config', () => {
77+
expectLogs(
78+
() => {
79+
const config = parseConfig('test', { decorators: 'oops' });
80+
expect(config).toStrictEqual({});
81+
},
82+
{
83+
error: [
84+
makeLogMatcher(
85+
'[test]: CONFIG ERROR:',
86+
"[decorators] Expected DecoratorOptions object or boolean, received 'oops'"
87+
),
88+
],
89+
}
90+
);
91+
});
92+
});
93+
94+
describe.each(['classFields', 'classicDecorator', 'partialTransforms'])(
95+
'%s (StringBooleanSchema)',
96+
(fieldName) => {
97+
test(`it parses \`{ ${fieldName}: true }\``, () => {
98+
expectLogs(() => {
99+
const config = parseConfig('test', { [fieldName]: true });
100+
expect(config).toStrictEqual({ [fieldName]: true });
101+
});
102+
});
103+
104+
test(`it parses \`{ ${fieldName}: "true" }\``, () => {
105+
expectLogs(() => {
106+
const config = parseConfig('test', { [fieldName]: 'true' });
107+
expect(config).toStrictEqual({ [fieldName]: true });
108+
});
109+
});
110+
111+
test(`it parses \`{ ${fieldName}: false }\``, () => {
112+
expectLogs(() => {
113+
const config = parseConfig('test', { [fieldName]: false });
114+
expect(config).toStrictEqual({ [fieldName]: false });
115+
});
116+
});
117+
118+
test(`it parses \`{ ${fieldName}: "false" }\``, () => {
119+
expectLogs(() => {
120+
const config = parseConfig('test', { [fieldName]: 'false' });
121+
expect(config).toStrictEqual({ [fieldName]: false });
122+
});
123+
});
124+
125+
test(`it logs an error for invalid \`${fieldName}\` config`, () => {
126+
expectLogs(
127+
() => {
128+
const config = parseConfig('test', { [fieldName]: 'oops' });
129+
expect(config).toStrictEqual({});
130+
},
131+
{
132+
error: [
133+
makeLogMatcher(
134+
'[test]: CONFIG ERROR:',
135+
`[${fieldName}] Expected boolean, received string`
136+
),
137+
],
138+
}
139+
);
140+
});
141+
}
142+
);
143+
144+
describe('quote', () => {
145+
test('it parses `{ quote: "single" }`', () => {
146+
expectLogs(() => {
147+
const config = parseConfig('test', { quote: 'single' });
148+
expect(config).toStrictEqual({ quote: 'single' });
149+
});
150+
});
151+
152+
test('it parses `{ quote: "double" }`', () => {
153+
expectLogs(() => {
154+
const config = parseConfig('test', { quote: 'double' });
155+
expect(config).toStrictEqual({ quote: 'double' });
156+
});
157+
});
158+
159+
test('it logs an error for invalid `quote` config', () => {
160+
expectLogs(
161+
() => {
162+
const config = parseConfig('test', { quote: 'oops' });
163+
expect(config).toStrictEqual({});
164+
},
165+
{
166+
error: [
167+
makeLogMatcher(
168+
'[test]: CONFIG ERROR:',
169+
"[quote] Expected 'single' or 'double', received 'oops"
170+
),
171+
],
172+
}
173+
);
174+
});
175+
});
176+
177+
describe('ignoreLeakingState', () => {
178+
test('it parses `ignoreLeakingState` with an empty array', () => {
179+
expectLogs(() => {
180+
const config = parseConfig('test', { ignoreLeakingState: [] });
181+
expect(config).toStrictEqual({ ignoreLeakingState: [] });
182+
});
183+
});
184+
185+
test('it parses `ignoreLeakingState` with array of strings', () => {
186+
expectLogs(() => {
187+
const config = parseConfig('test', {
188+
ignoreLeakingState: ['one', 'two', 'three'],
189+
});
190+
expect(config).toStrictEqual({
191+
ignoreLeakingState: ['one', 'two', 'three'],
192+
});
193+
});
194+
});
195+
196+
test('it parses `ignoreLeakingState` with string of strings', () => {
197+
expectLogs(() => {
198+
const config = parseConfig('test', {
199+
ignoreLeakingState: 'one,two , three',
200+
});
201+
expect(config).toStrictEqual({
202+
ignoreLeakingState: ['one', 'two', 'three'],
203+
});
204+
});
205+
});
206+
207+
test('it logs an error for invalid `ignoreLeakingState` config', () => {
208+
expectLogs(
209+
() => {
210+
const config = parseConfig('test', { ignoreLeakingState: false });
211+
expect(config).toStrictEqual({});
212+
},
213+
{
214+
error: [
215+
makeLogMatcher(
216+
'[test]: CONFIG ERROR:',
217+
'[ignoreLeakingState] Expected array of strings or comma-separated string, received false'
218+
),
219+
],
220+
}
221+
);
222+
});
223+
});
224+
225+
describe('type', () => {
226+
test.each(['services', 'routes', 'components', 'controllers'])(
227+
'it parses `{ type: "%s" }`',
228+
(type) => {
229+
expectLogs(() => {
230+
const config = parseConfig('test', { type });
231+
expect(config).toStrictEqual({ type });
232+
});
233+
}
234+
);
235+
236+
test('it logs an error for invalid `type` config', () => {
237+
expectLogs(
238+
() => {
239+
const config = parseConfig('test', { type: 'oops' });
240+
expect(config).toStrictEqual({});
241+
},
242+
{
243+
error: [
244+
makeLogMatcher(
245+
'[test]: CONFIG ERROR:',
246+
"[type] Expected 'services', 'routes', 'components', or 'controllers', received 'oops"
247+
),
248+
],
249+
}
250+
);
251+
});
252+
});
253+
});
254+
});
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
const Foo1 = EmberObject.extend({});
2+
3+
/**
4+
* Program comments
5+
*/
6+
const Foo2 = Test.extend({
7+
/**
8+
* Property comments
9+
*/
10+
prop: "defaultValue",
11+
boolProp: true,
12+
numProp: 123,
13+
[MY_VAL]: "val",
14+
15+
/**
16+
* Method comments
17+
*/
18+
method() {
19+
// do things
20+
},
21+
22+
otherMethod: function() {},
23+
24+
get accessor() {
25+
return this._value;
26+
},
27+
28+
set accessor(value) {
29+
this._value = value;
30+
},
31+
32+
anotherMethod() {
33+
this._super(...arguments);
34+
}
35+
});

0 commit comments

Comments
 (0)