Skip to content

Commit cd883b3

Browse files
committed
Call the assertions something else, make the unindent explicit
1 parent a359bda commit cd883b3

7 files changed

+259
-160
lines changed

lib/indentString.js

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
function indentString(str, indentationWidth = 2) {
2+
const indent = ' '.repeat(indentationWidth);
3+
return `\n${str.replace(/^.*?$/gm, $0 => {
4+
if ($0) {
5+
return `${indent}${$0}`;
6+
} else {
7+
return '';
8+
}
9+
})}\n`;
10+
}
11+
12+
module.exports = indentString;

lib/isSimpleObjectTree.js

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
function isSimpleObjectTree(obj, expect) {
2+
const seen = new Set();
3+
return (function isSimple(obj) {
4+
if (seen.has(obj)) {
5+
return false; // Circular
6+
} else {
7+
const type = expect.findTypeOf(obj);
8+
if (type.name === 'array') {
9+
seen.add(obj);
10+
const result = obj.every(isSimple);
11+
seen.delete(obj);
12+
return result;
13+
} else if (type.name === 'object') {
14+
seen.add(obj);
15+
const result =
16+
obj.constructor === Object &&
17+
Object.getOwnPropertyNames(obj).every(name => isSimple(obj[name])) &&
18+
Object.getOwnPropertySymbols(obj).length === 0;
19+
seen.delete(obj);
20+
return result;
21+
} else {
22+
return [
23+
'any',
24+
'number',
25+
'NaN',
26+
'boolean',
27+
'regexp',
28+
'null',
29+
'undefined',
30+
'string',
31+
'Buffer'
32+
].includes(type.name);
33+
}
34+
}
35+
})(obj);
36+
}
37+
38+
module.exports = isSimpleObjectTree;

lib/stringSnapshot.js

-24
This file was deleted.

lib/unexpected-snapshot.js

+69-76
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ const { SourceCode } = eslint;
1313
const ruleFixer = require('eslint/lib/util/rule-fixer');
1414
const Traverser = require('eslint/lib/util/traverser');
1515
const SourceCodeFixer = require('eslint/lib/util/source-code-fixer');
16-
const { encode, decode } = require('../lib/stringSnapshot');
16+
const indentString = require('./indentString');
17+
const isSimpleObjectTree = require('./isSimpleObjectTree');
1718

1819
function stringify(obj, indentationWidth, expectForRendering) {
19-
if (typeof obj === 'string') {
20-
return `\`${encode(obj, indentationWidth)}\``;
20+
if (obj.includes('\n')) {
21+
return `expect.unindent\`${indentString(obj, indentationWidth)}\``;
2122
} else {
2223
expectForRendering.output.indentationWidth = indentationWidth;
2324
return expectForRendering.inspect(obj).toString('text');
@@ -173,7 +174,8 @@ function ensureAfterBlockIsRegistered() {
173174
columnNumber,
174175
status,
175176
subject,
176-
expectForRendering
177+
expectForRendering,
178+
assertionName
177179
} of topLevelFixes) {
178180
const sourceCode = getSourceCode(fileName);
179181
if (!sourceCode) {
@@ -209,7 +211,6 @@ function ensureAfterBlockIsRegistered() {
209211
return '';
210212
}
211213
}
212-
213214
new Traverser().traverse(sourceCode.ast, {
214215
enter(node, parent) {
215216
if (
@@ -230,29 +231,63 @@ function ensureAfterBlockIsRegistered() {
230231
} else {
231232
indent = getNodeIndent(node.arguments[1]);
232233
}
233-
const stringifiedSubject = stringify(
234-
subject,
235-
indentationWidth,
236-
expectForRendering
237-
).replace(/\n^(?=[^\n])/gm, `\n${indent}`);
238-
239-
let fix;
234+
const fixes = [];
235+
let stringifiedSubject;
236+
let newAssertionName = 'to equal snapshot';
237+
if (typeof subject === 'string') {
238+
stringifiedSubject = stringify(
239+
subject,
240+
indentationWidth,
241+
expectForRendering
242+
).replace(/\n^(?=[^\n])/gm, `\n${indent}`);
243+
} else if (isSimpleObjectTree(subject, expectForRendering)) {
244+
expectForRendering.output.indentationWidth = indentationWidth;
245+
stringifiedSubject = expectForRendering
246+
.inspect(subject)
247+
.toString('text');
248+
} else {
249+
newAssertionName = 'to inspect as snapshot';
250+
stringifiedSubject = stringify(
251+
expectForRendering.inspect(subject).toString('text'),
252+
indentationWidth,
253+
expectForRendering
254+
).replace(/\n^(?=[^\n])/gm, `\n${indent}`);
255+
}
240256
if (status === 'missing') {
241-
fix = ruleFixer.insertTextAfter(
242-
node.arguments[node.arguments.length - 1],
243-
`, ${stringifiedSubject}`
244-
);
257+
if (newAssertionName !== assertionName) {
258+
fixes.unshift(
259+
ruleFixer.replaceText(
260+
node.arguments[1],
261+
`'${newAssertionName}', ${stringifiedSubject}`
262+
)
263+
);
264+
} else {
265+
fixes.unshift(
266+
ruleFixer.insertTextAfter(
267+
node.arguments[node.arguments.length - 1],
268+
`, ${stringifiedSubject}`
269+
)
270+
);
271+
}
245272
} else if (status === 'mismatch') {
246-
fix = ruleFixer.replaceText(
247-
node.arguments[node.arguments.length - 1],
248-
stringifiedSubject
273+
if (newAssertionName !== assertionName) {
274+
fixes.unshift(
275+
ruleFixer.replaceText(
276+
node.arguments[1],
277+
`'${newAssertionName}'`
278+
)
279+
);
280+
}
281+
fixes.unshift(
282+
ruleFixer.replaceText(
283+
node.arguments[node.arguments.length - 1],
284+
stringifiedSubject
285+
)
249286
);
250287
}
251-
if (fix) {
288+
if (fixes.length > 0) {
252289
(fixesByFileName[fileName] =
253-
fixesByFileName[fileName] || []).push({
254-
fix
255-
});
290+
fixesByFileName[fileName] || []).push(...fixes);
256291
}
257292
}
258293
}
@@ -263,7 +298,7 @@ function ensureAfterBlockIsRegistered() {
263298
for (const fileName of Object.keys(fixesByFileName)) {
264299
var fixResult = SourceCodeFixer.applyFixes(
265300
getSourceCode(fileName).text,
266-
fixesByFileName[fileName]
301+
fixesByFileName[fileName].map(fix => ({ fix }))
267302
);
268303
if (fixResult.fixed) {
269304
fixedSourceTextByFileName[fileName] = fixResult.output;
@@ -394,52 +429,6 @@ module.exports = {
394429
installInto(expect) {
395430
const expectForRendering = expect.child();
396431

397-
// Copy of https://github.com/unexpectedjs/unexpected/blob/bf9bb959b85459eee43e550f8dfea43bead2e7b1/lib/createTopLevelExpect.js#L274
398-
// so that we get to control the handling of circular references
399-
const defaultDepth = 3;
400-
expectForRendering.inspect = function(obj, depth, outputOrFormat) {
401-
let seen = [];
402-
let printOutput = (obj, currentDepth, output) => {
403-
const objType = this.findTypeOf(obj);
404-
if (
405-
currentDepth <= 0 &&
406-
objType.is('object') &&
407-
!objType.is('expect.it')
408-
) {
409-
return output.text('...');
410-
}
411-
412-
seen = seen || [];
413-
if (seen.indexOf(obj) !== -1) {
414-
throw new Error('Circular references not supported in snapshots');
415-
}
416-
417-
return objType.inspect(obj, currentDepth, output, (v, childDepth) => {
418-
output = output.clone();
419-
seen.push(obj);
420-
if (typeof childDepth === 'undefined') {
421-
childDepth = currentDepth - 1;
422-
}
423-
output = printOutput(v, childDepth, output) || output;
424-
seen.pop();
425-
return output;
426-
});
427-
};
428-
429-
let output =
430-
typeof outputOrFormat === 'string'
431-
? this.createOutput(outputOrFormat)
432-
: outputOrFormat;
433-
output = output || this.createOutput();
434-
return (
435-
printOutput(
436-
obj,
437-
typeof depth === 'number' ? depth : defaultDepth,
438-
output
439-
) || output
440-
);
441-
};
442-
443432
expectForRendering.addType({
444433
name: 'infiniteBuffer',
445434
base: 'Buffer',
@@ -523,21 +512,22 @@ module.exports = {
523512
const symbol = Symbol('unexpectedSnapshot');
524513

525514
expect.addAssertion(
526-
'<any> to equal snapshot <any?>',
515+
'<any> to (equal|inspect as) snapshot <any?>',
527516
(expect, subject, ...args) => {
517+
const assertionName = `to ${expect.alternations[0]} snapshot`;
518+
if (expect.alternations[0] === 'inspect as') {
519+
subject = expectForRendering.inspect(subject).toString('text');
520+
}
528521
if (args.length === 1) {
529-
let value = args[0];
530-
if (typeof value === 'string') {
531-
value = decode(value);
532-
}
533522
return expect.withError(
534523
() => {
535-
expect(subject, 'to equal', value);
524+
expect(subject, 'to equal', args[0]);
536525
},
537526
err => {
538527
topLevelFixes.push({
539528
...expect.context[symbol],
540529
subject,
530+
assertionName,
541531
status: 'mismatch'
542532
});
543533
ensureAfterBlockIsRegistered();
@@ -549,6 +539,7 @@ module.exports = {
549539
topLevelFixes.push({
550540
...expect.context[symbol],
551541
subject,
542+
assertionName,
552543
status: 'missing'
553544
});
554545
ensureAfterBlockIsRegistered();
@@ -562,6 +553,8 @@ module.exports = {
562553
}
563554
);
564555

556+
expect.unindent = require('@gustavnikolaj/string-utils').deindent;
557+
565558
expect.hook(function(next) {
566559
return function unexpectedSnapshot(context, ...rest) {
567560
if (!context[symbol]) {

test/stringSnapshot.js test/indentString.js

+13-26
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,39 @@
11
const expect = require('unexpected').clone();
2-
const { encode, decode } = require('../lib/stringSnapshot');
2+
const indentString = require('../lib/indentString');
3+
const { deindent } = require('@gustavnikolaj/string-utils');
34

45
expect.addAssertion('<string> to roundtrip', (expect, subject) => {
5-
expect(decode(encode(subject)), 'to equal', subject);
6+
expect(deindent(indentString(subject)), 'to equal', subject);
67
});
78

89
describe('stringSnapshot', () => {
9-
describe('encode', () => {
10+
describe('indentString', () => {
1011
it('should indent a single line string', () => {
11-
expect(encode('foo', 2), 'to equal', '\n foo\n');
12+
expect(indentString('foo', 2), 'to equal', '\n foo\n');
1213
});
1314

1415
it('should indent an empty string', () => {
15-
expect(encode('', 2), 'to equal', '\n\n');
16+
expect(indentString('', 2), 'to equal', '\n\n');
1617
});
1718

1819
it('should not indent empty lines', () => {
19-
expect(encode('foo\n\nbar', 2), 'to equal', '\n foo\n\n bar\n');
20+
expect(indentString('foo\n\nbar', 2), 'to equal', '\n foo\n\n bar\n');
2021
});
2122

2223
it('should indent a line containing only spaces', () => {
23-
expect(encode('foo\n \nbar', 2), 'to equal', '\n foo\n \n bar\n');
24-
});
25-
26-
it('should indent a line ending with newline', () => {
27-
expect(encode('foo\n', 2), 'to equal', '\n foo\n\n');
28-
});
29-
});
30-
31-
describe('decode', () => {
32-
it('should throw if the newline at the start is missing', () => {
3324
expect(
34-
() => decode('blah'),
35-
'to throw',
36-
'Missing leading newline in snapshot'
25+
indentString('foo\n \nbar', 2),
26+
'to equal',
27+
'\n foo\n \n bar\n'
3728
);
3829
});
3930

40-
it('should throw if the newline at the end is missing', () => {
41-
expect(
42-
() => decode('\n foo'),
43-
'to throw',
44-
'Missing trailing newline in snapshot'
45-
);
31+
it('should indent a line ending with newline', () => {
32+
expect(indentString('foo\n', 2), 'to equal', '\n foo\n\n');
4633
});
4734
});
4835

49-
describe('encode + decode', () => {
36+
describe('indentString + deindent', () => {
5037
it('should roundtrip a single line string', () => {
5138
expect('foo', 'to roundtrip');
5239
});

0 commit comments

Comments
 (0)