@cronn/lib-file-snapshots 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +23 -23
- package/dist/index.js +97 -86
- package/package.json +7 -11
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
interface SnapshotSerializer {
|
|
2
|
+
/**
|
|
3
|
+
* The file extension associated with the serialized value
|
|
4
|
+
*/
|
|
5
|
+
readonly fileExtension: string;
|
|
2
6
|
/**
|
|
3
7
|
* Returns true when value can be serialized
|
|
4
8
|
*
|
|
@@ -9,19 +13,10 @@ interface SnapshotSerializer {
|
|
|
9
13
|
* Serializes value
|
|
10
14
|
*
|
|
11
15
|
* @param value The value to be serialized
|
|
16
|
+
* @return {string} The serialized value
|
|
12
17
|
* @throws {Error} Will throw an error if value cannot be serialized.
|
|
13
18
|
*/
|
|
14
|
-
serialize(value: unknown):
|
|
15
|
-
}
|
|
16
|
-
interface SnapshotSerializerResult {
|
|
17
|
-
/**
|
|
18
|
-
* The serialized value
|
|
19
|
-
*/
|
|
20
|
-
serializedValue: string;
|
|
21
|
-
/**
|
|
22
|
-
* The file extension associated with the serialized value
|
|
23
|
-
*/
|
|
24
|
-
fileExtension: string;
|
|
19
|
+
serialize(value: unknown): string;
|
|
25
20
|
}
|
|
26
21
|
|
|
27
22
|
interface JsonSerializerOptions {
|
|
@@ -42,11 +37,12 @@ interface JsonNormalizerContext {
|
|
|
42
37
|
index?: number;
|
|
43
38
|
}
|
|
44
39
|
declare class JsonSerializer implements SnapshotSerializer {
|
|
40
|
+
readonly fileExtension = "json";
|
|
45
41
|
private readonly includeUndefinedObjectProperties;
|
|
46
42
|
private readonly normalizers;
|
|
47
43
|
constructor(options?: JsonSerializerOptions);
|
|
48
44
|
canSerialize(_value: unknown): boolean;
|
|
49
|
-
serialize(value: unknown):
|
|
45
|
+
serialize(value: unknown): string;
|
|
50
46
|
private normalizeValueRecursive;
|
|
51
47
|
private normalizeNumber;
|
|
52
48
|
private normalizeArray;
|
|
@@ -67,10 +63,11 @@ interface TextSerializerOptions {
|
|
|
67
63
|
}
|
|
68
64
|
type TextNormalizer = (value: string) => string;
|
|
69
65
|
declare class TextSerializer implements SnapshotSerializer {
|
|
66
|
+
readonly fileExtension = "txt";
|
|
70
67
|
private readonly normalizers;
|
|
71
68
|
constructor(options?: TextSerializerOptions);
|
|
72
69
|
canSerialize(value: unknown): value is string;
|
|
73
|
-
serialize(value: unknown):
|
|
70
|
+
serialize(value: unknown): string;
|
|
74
71
|
private normalizeValue;
|
|
75
72
|
}
|
|
76
73
|
|
|
@@ -87,8 +84,6 @@ interface ValidationFileMatcherConfig {
|
|
|
87
84
|
* @default "data/test/output"
|
|
88
85
|
*/
|
|
89
86
|
outputDir?: string;
|
|
90
|
-
}
|
|
91
|
-
interface MatchValidationFileOptions {
|
|
92
87
|
/**
|
|
93
88
|
* The full path to the current test file
|
|
94
89
|
*
|
|
@@ -122,18 +117,23 @@ type SnapshotNamingStrategy = "file" | "fileSuffix";
|
|
|
122
117
|
interface ValidationFileMatcherResult {
|
|
123
118
|
actual: string;
|
|
124
119
|
expected: string;
|
|
125
|
-
|
|
126
|
-
|
|
120
|
+
outputFilePath: string;
|
|
121
|
+
validationFilePath: string;
|
|
127
122
|
message: () => string;
|
|
123
|
+
writeFileSnapshots: () => void;
|
|
128
124
|
}
|
|
129
125
|
|
|
130
126
|
declare class ValidationFileMatcher {
|
|
131
|
-
private readonly
|
|
132
|
-
private readonly
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
127
|
+
private readonly serializer;
|
|
128
|
+
private readonly filePaths;
|
|
129
|
+
private readonly validationFile;
|
|
130
|
+
constructor(config: ValidationFileMatcherConfig);
|
|
131
|
+
get isValidationFileMissing(): boolean;
|
|
132
|
+
matchFileSnapshot(actual: unknown): ValidationFileMatcherResult;
|
|
133
|
+
private buildFilePaths;
|
|
136
134
|
private applyNamingStrategy;
|
|
135
|
+
private readValidationFile;
|
|
136
|
+
private createMatcherResult;
|
|
137
137
|
private writeFileSnapshots;
|
|
138
138
|
}
|
|
139
139
|
|
|
@@ -142,4 +142,4 @@ declare function isArray(value: unknown): value is Array<unknown>;
|
|
|
142
142
|
type PlainObject = Record<PropertyKey, unknown>;
|
|
143
143
|
declare function isPlainObject(value: unknown): value is PlainObject;
|
|
144
144
|
|
|
145
|
-
export { type JsonNormalizer, type JsonNormalizerContext, JsonSerializer, type PlainObject, type SnapshotNamingStrategy, type SnapshotSerializer, type TextNormalizer, TextSerializer, ValidationFileMatcher, isArray, isPlainObject, isString };
|
|
145
|
+
export { type JsonNormalizer, type JsonNormalizerContext, JsonSerializer, type PlainObject, type SnapshotNamingStrategy, type SnapshotSerializer, type TextNormalizer, TextSerializer, ValidationFileMatcher, type ValidationFileMatcherResult, isArray, isPlainObject, isString };
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
// src/utils/file.ts
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
var NEW_LINE_SEPARATOR = "\n";
|
|
5
|
+
var EMOJI_REGEXP = /(?!(\*|#|\d))[\p{Extended_Pictographic}\p{Emoji_Component}]|[\u0030-\u0039]\ufe0f?[\u20e3]|[\u002A\u0023]?\ufe0f?[\u20e3]/gu;
|
|
6
|
+
function normalizeFileName(testName) {
|
|
7
|
+
return testName.replaceAll(EMOJI_REGEXP, "").replaceAll(/[+*%~<>?!$#'"`|\\/()[\]{}]/g, "").replaceAll(/[\s.:,;]+/g, "_").replaceAll(/_{2,}/g, "_");
|
|
8
|
+
}
|
|
9
|
+
function readSnapshotFile(path3) {
|
|
10
|
+
return fs.readFileSync(path3, { encoding: "utf8" });
|
|
11
|
+
}
|
|
12
|
+
function writeSnapshotFile(file, data) {
|
|
13
|
+
mkdirRecursive(path.dirname(file));
|
|
14
|
+
fs.writeFileSync(file, data, { encoding: "utf8" });
|
|
15
|
+
}
|
|
16
|
+
function mkdirRecursive(path3) {
|
|
17
|
+
fs.mkdirSync(path3, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
function addTrailingNewLine(data) {
|
|
20
|
+
return `${data}${NEW_LINE_SEPARATOR}`;
|
|
21
|
+
}
|
|
22
|
+
function addMissingFileMarker(data) {
|
|
23
|
+
return `===== missing file =====${NEW_LINE_SEPARATOR}${data}`;
|
|
24
|
+
}
|
|
25
|
+
|
|
1
26
|
// src/utils/guards.ts
|
|
2
27
|
function isString(value) {
|
|
3
28
|
return typeof value === "string";
|
|
@@ -18,6 +43,7 @@ function isPlainObject(value) {
|
|
|
18
43
|
|
|
19
44
|
// src/serializers/json-serializer.ts
|
|
20
45
|
var JsonSerializer = class {
|
|
46
|
+
fileExtension = "json";
|
|
21
47
|
includeUndefinedObjectProperties;
|
|
22
48
|
normalizers;
|
|
23
49
|
constructor(options = {}) {
|
|
@@ -29,11 +55,8 @@ var JsonSerializer = class {
|
|
|
29
55
|
}
|
|
30
56
|
serialize(value) {
|
|
31
57
|
const jsonValue = this.normalizeValueRecursive(value);
|
|
32
|
-
const
|
|
33
|
-
return
|
|
34
|
-
serializedValue,
|
|
35
|
-
fileExtension: "json"
|
|
36
|
-
};
|
|
58
|
+
const jsonString = JSON.stringify(jsonValue, void 0, 2);
|
|
59
|
+
return addTrailingNewLine(jsonString);
|
|
37
60
|
}
|
|
38
61
|
normalizeValueRecursive(value, context) {
|
|
39
62
|
const customValue = this.applyCustomNormalizers(value, context);
|
|
@@ -179,6 +202,7 @@ var JsonSerializer = class {
|
|
|
179
202
|
|
|
180
203
|
// src/serializers/text-serializer.ts
|
|
181
204
|
var TextSerializer = class {
|
|
205
|
+
fileExtension = "txt";
|
|
182
206
|
normalizers;
|
|
183
207
|
constructor(options = {}) {
|
|
184
208
|
this.normalizers = options.normalizers ?? [];
|
|
@@ -192,10 +216,8 @@ var TextSerializer = class {
|
|
|
192
216
|
`Missing text serialization for value of type ${typeof value}.`
|
|
193
217
|
);
|
|
194
218
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
fileExtension: "txt"
|
|
198
|
-
};
|
|
219
|
+
const normalizedValue = this.normalizeValue(value);
|
|
220
|
+
return addTrailingNewLine(normalizedValue);
|
|
199
221
|
}
|
|
200
222
|
normalizeValue(value) {
|
|
201
223
|
let normalizedValue = value;
|
|
@@ -208,73 +230,55 @@ var TextSerializer = class {
|
|
|
208
230
|
|
|
209
231
|
// src/matcher/validation-file-matcher.ts
|
|
210
232
|
import * as fs2 from "fs";
|
|
211
|
-
import * as
|
|
212
|
-
|
|
213
|
-
// src/utils/file.ts
|
|
214
|
-
import fs from "fs";
|
|
215
|
-
var NEW_LINE_SEPARATOR = "\n";
|
|
216
|
-
var EMOJI_REGEXP = /(?!(\*|#|\d))[\p{Extended_Pictographic}\p{Emoji_Component}]|[\u0030-\u0039]\ufe0f?[\u20e3]|[\u002A\u0023]?\ufe0f?[\u20e3]/gu;
|
|
217
|
-
function normalizeFileName(testName) {
|
|
218
|
-
return testName.replaceAll(EMOJI_REGEXP, "").replaceAll(/[+*%~<>?!$#'"`|\\/()[\]{}]/g, "").replaceAll(/[\s.:,;]+/g, "_").replaceAll(/_{2,}/g, "_");
|
|
219
|
-
}
|
|
220
|
-
function mkdirRecursive(path2) {
|
|
221
|
-
fs.mkdirSync(path2, { recursive: true });
|
|
222
|
-
}
|
|
223
|
-
function readSnapshotFile(path2) {
|
|
224
|
-
return fs.readFileSync(path2, { encoding: "utf8" });
|
|
225
|
-
}
|
|
226
|
-
function writeSnapshotFile(file, data, markAsMissing = false) {
|
|
227
|
-
let finalizedData = addTrailingNewLine(data);
|
|
228
|
-
if (markAsMissing) {
|
|
229
|
-
finalizedData = addMissingFileMarker(finalizedData);
|
|
230
|
-
}
|
|
231
|
-
fs.writeFileSync(file, finalizedData, { encoding: "utf8" });
|
|
232
|
-
}
|
|
233
|
-
function addTrailingNewLine(data) {
|
|
234
|
-
return `${data}${NEW_LINE_SEPARATOR}`;
|
|
235
|
-
}
|
|
236
|
-
function addMissingFileMarker(data) {
|
|
237
|
-
return `===== missing file =====${NEW_LINE_SEPARATOR}${data}`;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// src/matcher/validation-file-matcher.ts
|
|
233
|
+
import * as path2 from "path";
|
|
241
234
|
var ValidationFileMatcher = class {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
this.
|
|
235
|
+
serializer;
|
|
236
|
+
filePaths;
|
|
237
|
+
validationFile;
|
|
238
|
+
constructor(config) {
|
|
239
|
+
this.serializer = config.serializer;
|
|
240
|
+
this.filePaths = this.buildFilePaths(config);
|
|
241
|
+
this.validationFile = this.readValidationFile();
|
|
247
242
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
243
|
+
get isValidationFileMissing() {
|
|
244
|
+
return this.validationFile === void 0;
|
|
245
|
+
}
|
|
246
|
+
matchFileSnapshot(actual) {
|
|
247
|
+
if (!this.serializer.canSerialize(actual)) {
|
|
251
248
|
throw new Error(`Cannot serialize value of type ${typeof actual}`);
|
|
252
249
|
}
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
250
|
+
const serializedActual = this.serializer.serialize(actual);
|
|
251
|
+
if (this.validationFile === void 0) {
|
|
252
|
+
return this.createMatcherResult({
|
|
253
|
+
actual: serializedActual,
|
|
254
|
+
expected: addMissingFileMarker(serializedActual)
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return this.createMatcherResult({
|
|
258
|
+
actual: serializedActual,
|
|
259
|
+
expected: this.validationFile
|
|
260
260
|
});
|
|
261
|
-
return this.writeFileSnapshots(
|
|
262
|
-
serializerResult.serializedValue,
|
|
263
|
-
validationFilePath
|
|
264
|
-
);
|
|
265
261
|
}
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
const
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
262
|
+
buildFilePaths(config) {
|
|
263
|
+
const validationDir = config.validationDir ?? "data/test/validation";
|
|
264
|
+
const outputDir = config.outputDir ?? "data/test/output";
|
|
265
|
+
const normalizedTitlePath = config.titlePath.map(normalizeFileName);
|
|
266
|
+
const relativeFilePath = path2.join(config.testPath, ...normalizedTitlePath);
|
|
267
|
+
const relativeFilePathWithName = this.applyNamingStrategy(
|
|
268
|
+
relativeFilePath,
|
|
269
|
+
config.name,
|
|
270
|
+
config.namingStrategy
|
|
274
271
|
);
|
|
275
|
-
|
|
272
|
+
const relativeFilePathWithExtension = `${relativeFilePathWithName}.${this.serializer.fileExtension}`;
|
|
273
|
+
return {
|
|
274
|
+
outputFilePath: path2.join(outputDir, relativeFilePathWithExtension),
|
|
275
|
+
validationFilePath: path2.join(
|
|
276
|
+
validationDir,
|
|
277
|
+
relativeFilePathWithExtension
|
|
278
|
+
)
|
|
279
|
+
};
|
|
276
280
|
}
|
|
277
|
-
applyNamingStrategy(filePath, snapshotName, namingStrategy) {
|
|
281
|
+
applyNamingStrategy(filePath, snapshotName, namingStrategy = "file") {
|
|
278
282
|
if (snapshotName === void 0) {
|
|
279
283
|
return filePath;
|
|
280
284
|
}
|
|
@@ -282,29 +286,36 @@ var ValidationFileMatcher = class {
|
|
|
282
286
|
if (namingStrategy === "fileSuffix") {
|
|
283
287
|
return `${filePath}_${normalizedSnapshotName}`;
|
|
284
288
|
}
|
|
285
|
-
return
|
|
289
|
+
return path2.join(filePath, normalizedSnapshotName);
|
|
286
290
|
}
|
|
287
|
-
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const testValidationDir = `${this.validationDir}/${validationFileDir}`;
|
|
292
|
-
const validationFile = `${this.validationDir}/${validationFilePath}`;
|
|
293
|
-
mkdirRecursive(testOutputDir);
|
|
294
|
-
mkdirRecursive(testValidationDir);
|
|
295
|
-
if (!fs2.existsSync(validationFile)) {
|
|
296
|
-
writeSnapshotFile(validationFile, actual, true);
|
|
291
|
+
readValidationFile() {
|
|
292
|
+
const { validationFilePath } = this.filePaths;
|
|
293
|
+
if (!fs2.existsSync(validationFilePath)) {
|
|
294
|
+
return void 0;
|
|
297
295
|
}
|
|
298
|
-
|
|
296
|
+
return readSnapshotFile(validationFilePath);
|
|
297
|
+
}
|
|
298
|
+
createMatcherResult(params) {
|
|
299
|
+
const { actual, expected } = params;
|
|
300
|
+
const { outputFilePath, validationFilePath } = this.filePaths;
|
|
299
301
|
return {
|
|
300
|
-
actual
|
|
301
|
-
expected
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
message: () => `
|
|
305
|
-
does not match validation file '${
|
|
302
|
+
actual,
|
|
303
|
+
expected,
|
|
304
|
+
outputFilePath,
|
|
305
|
+
validationFilePath,
|
|
306
|
+
message: () => this.isValidationFileMissing ? `Missing validation file '${validationFilePath}'` : `Output file '${outputFilePath}'
|
|
307
|
+
does not match validation file '${validationFilePath}'`,
|
|
308
|
+
writeFileSnapshots: () => this.writeFileSnapshots(params)
|
|
306
309
|
};
|
|
307
310
|
}
|
|
311
|
+
writeFileSnapshots(matcherResult) {
|
|
312
|
+
const { actual, expected } = matcherResult;
|
|
313
|
+
const { outputFilePath, validationFilePath } = this.filePaths;
|
|
314
|
+
writeSnapshotFile(outputFilePath, actual);
|
|
315
|
+
if (this.isValidationFileMissing) {
|
|
316
|
+
writeSnapshotFile(validationFilePath, expected);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
308
319
|
};
|
|
309
320
|
export {
|
|
310
321
|
JsonSerializer,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cronn/lib-file-snapshots",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "The library agnostic core for testing file snapshots",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"file snapshots"
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
},
|
|
18
18
|
"homepage": "https://github.com/cronn/file-snapshots",
|
|
19
19
|
"engines": {
|
|
20
|
-
"node": ">=22",
|
|
20
|
+
"node": ">=22.18",
|
|
21
21
|
"pnpm": ">=10"
|
|
22
22
|
},
|
|
23
23
|
"type": "module",
|
|
@@ -33,28 +33,24 @@
|
|
|
33
33
|
],
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@trivago/prettier-plugin-sort-imports": "5.2.2",
|
|
36
|
-
"@types/node": "22.
|
|
36
|
+
"@types/node": "22.16.5",
|
|
37
37
|
"@vitest/coverage-istanbul": "3.2.4",
|
|
38
|
-
"eslint": "9.
|
|
39
|
-
"eslint-config-prettier": "10.1.
|
|
38
|
+
"eslint": "9.32.0",
|
|
39
|
+
"eslint-config-prettier": "10.1.8",
|
|
40
40
|
"eslint-plugin-unused-imports": "4.1.4",
|
|
41
|
-
"jiti": "2.4.2",
|
|
42
41
|
"prettier": "3.6.2",
|
|
43
42
|
"tsup": "8.5.0",
|
|
44
43
|
"typescript": "5.8.3",
|
|
45
|
-
"typescript-eslint": "8.
|
|
44
|
+
"typescript-eslint": "8.38.0",
|
|
46
45
|
"vitest": "3.2.4",
|
|
47
46
|
"@cronn/shared-configs": "0.0.0"
|
|
48
47
|
},
|
|
49
48
|
"scripts": {
|
|
50
49
|
"lint:check": "eslint src/ --max-warnings=0",
|
|
51
50
|
"lint:fix": "eslint src/ --max-warnings=0 --fix",
|
|
52
|
-
"check": "pnpm run compile && pnpm run lint:check",
|
|
53
|
-
"fix": "pnpm run lint:fix",
|
|
54
51
|
"test": "vitest run",
|
|
55
52
|
"test:coverage": "vitest run --coverage",
|
|
56
53
|
"compile": "tsc",
|
|
57
|
-
"build": "tsup"
|
|
58
|
-
"ci": "pnpm run check && pnpm run test:coverage && pnpm run build"
|
|
54
|
+
"build": "tsup"
|
|
59
55
|
}
|
|
60
56
|
}
|