@cronn/lib-file-snapshots 0.9.0 → 0.11.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 -85
- package/package.json +7 -10
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
|
*
|
|
@@ -120,20 +115,25 @@ interface MatchValidationFileOptions {
|
|
|
120
115
|
}
|
|
121
116
|
type SnapshotNamingStrategy = "file" | "fileSuffix";
|
|
122
117
|
interface ValidationFileMatcherResult {
|
|
118
|
+
isValidationFileMissing: boolean;
|
|
123
119
|
actual: string;
|
|
124
120
|
expected: string;
|
|
125
|
-
|
|
126
|
-
|
|
121
|
+
outputFilePath: string;
|
|
122
|
+
validationFilePath: string;
|
|
127
123
|
message: () => string;
|
|
124
|
+
writeFileSnapshots: () => void;
|
|
128
125
|
}
|
|
129
126
|
|
|
130
127
|
declare class ValidationFileMatcher {
|
|
131
|
-
private readonly
|
|
132
|
-
private readonly
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
128
|
+
private readonly serializer;
|
|
129
|
+
private readonly filePaths;
|
|
130
|
+
private readonly validationFile;
|
|
131
|
+
constructor(config: ValidationFileMatcherConfig);
|
|
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,72 +230,54 @@ 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
|
-
function normalizeFileName(testName) {
|
|
217
|
-
return testName.replaceAll(/[+*%~<>?!$#'"`|\\/()[\]{}]/g, "").replaceAll(/[\s.:,;]+/g, "_").replaceAll(/_{2,}/g, "_");
|
|
218
|
-
}
|
|
219
|
-
function mkdirRecursive(path2) {
|
|
220
|
-
fs.mkdirSync(path2, { recursive: true });
|
|
221
|
-
}
|
|
222
|
-
function readSnapshotFile(path2) {
|
|
223
|
-
return fs.readFileSync(path2, { encoding: "utf8" });
|
|
224
|
-
}
|
|
225
|
-
function writeSnapshotFile(file, data, markAsMissing = false) {
|
|
226
|
-
let finalizedData = addTrailingNewLine(data);
|
|
227
|
-
if (markAsMissing) {
|
|
228
|
-
finalizedData = addMissingFileMarker(finalizedData);
|
|
229
|
-
}
|
|
230
|
-
fs.writeFileSync(file, finalizedData, { encoding: "utf8" });
|
|
231
|
-
}
|
|
232
|
-
function addTrailingNewLine(data) {
|
|
233
|
-
return `${data}${NEW_LINE_SEPARATOR}`;
|
|
234
|
-
}
|
|
235
|
-
function addMissingFileMarker(data) {
|
|
236
|
-
return `===== missing file =====${NEW_LINE_SEPARATOR}${data}`;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// src/matcher/validation-file-matcher.ts
|
|
233
|
+
import * as path2 from "path";
|
|
240
234
|
var ValidationFileMatcher = class {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
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();
|
|
246
242
|
}
|
|
247
|
-
matchFileSnapshot(actual
|
|
248
|
-
|
|
249
|
-
if (!serializer.canSerialize(actual)) {
|
|
243
|
+
matchFileSnapshot(actual) {
|
|
244
|
+
if (!this.serializer.canSerialize(actual)) {
|
|
250
245
|
throw new Error(`Cannot serialize value of type ${typeof actual}`);
|
|
251
246
|
}
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
247
|
+
const serializedActual = this.serializer.serialize(actual);
|
|
248
|
+
if (this.validationFile === void 0) {
|
|
249
|
+
return this.createMatcherResult({
|
|
250
|
+
isValidationFileMissing: true,
|
|
251
|
+
actual: serializedActual,
|
|
252
|
+
expected: addMissingFileMarker(serializedActual)
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
return this.createMatcherResult({
|
|
256
|
+
isValidationFileMissing: false,
|
|
257
|
+
actual: serializedActual,
|
|
258
|
+
expected: this.validationFile
|
|
259
259
|
});
|
|
260
|
-
return this.writeFileSnapshots(
|
|
261
|
-
serializerResult.serializedValue,
|
|
262
|
-
validationFilePath
|
|
263
|
-
);
|
|
264
260
|
}
|
|
265
|
-
|
|
266
|
-
const
|
|
267
|
-
const
|
|
268
|
-
const
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
261
|
+
buildFilePaths(config) {
|
|
262
|
+
const validationDir = config.validationDir ?? "data/test/validation";
|
|
263
|
+
const outputDir = config.outputDir ?? "data/test/output";
|
|
264
|
+
const normalizedTitlePath = config.titlePath.map(normalizeFileName);
|
|
265
|
+
const relativeFilePath = path2.join(config.testPath, ...normalizedTitlePath);
|
|
266
|
+
const relativeFilePathWithName = this.applyNamingStrategy(
|
|
267
|
+
relativeFilePath,
|
|
268
|
+
config.name,
|
|
269
|
+
config.namingStrategy
|
|
273
270
|
);
|
|
274
|
-
|
|
271
|
+
const relativeFilePathWithExtension = `${relativeFilePathWithName}.${this.serializer.fileExtension}`;
|
|
272
|
+
return {
|
|
273
|
+
outputFilePath: path2.join(outputDir, relativeFilePathWithExtension),
|
|
274
|
+
validationFilePath: path2.join(
|
|
275
|
+
validationDir,
|
|
276
|
+
relativeFilePathWithExtension
|
|
277
|
+
)
|
|
278
|
+
};
|
|
275
279
|
}
|
|
276
|
-
applyNamingStrategy(filePath, snapshotName, namingStrategy) {
|
|
280
|
+
applyNamingStrategy(filePath, snapshotName, namingStrategy = "file") {
|
|
277
281
|
if (snapshotName === void 0) {
|
|
278
282
|
return filePath;
|
|
279
283
|
}
|
|
@@ -281,29 +285,37 @@ var ValidationFileMatcher = class {
|
|
|
281
285
|
if (namingStrategy === "fileSuffix") {
|
|
282
286
|
return `${filePath}_${normalizedSnapshotName}`;
|
|
283
287
|
}
|
|
284
|
-
return
|
|
288
|
+
return path2.join(filePath, normalizedSnapshotName);
|
|
285
289
|
}
|
|
286
|
-
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const testValidationDir = `${this.validationDir}/${validationFileDir}`;
|
|
291
|
-
const validationFile = `${this.validationDir}/${validationFilePath}`;
|
|
292
|
-
mkdirRecursive(testOutputDir);
|
|
293
|
-
mkdirRecursive(testValidationDir);
|
|
294
|
-
if (!fs2.existsSync(validationFile)) {
|
|
295
|
-
writeSnapshotFile(validationFile, actual, true);
|
|
290
|
+
readValidationFile() {
|
|
291
|
+
const { validationFilePath } = this.filePaths;
|
|
292
|
+
if (!fs2.existsSync(validationFilePath)) {
|
|
293
|
+
return void 0;
|
|
296
294
|
}
|
|
297
|
-
|
|
295
|
+
return readSnapshotFile(validationFilePath);
|
|
296
|
+
}
|
|
297
|
+
createMatcherResult(params) {
|
|
298
|
+
const { isValidationFileMissing, actual, expected } = params;
|
|
299
|
+
const { outputFilePath, validationFilePath } = this.filePaths;
|
|
298
300
|
return {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
301
|
+
isValidationFileMissing,
|
|
302
|
+
actual,
|
|
303
|
+
expected,
|
|
304
|
+
outputFilePath,
|
|
305
|
+
validationFilePath,
|
|
306
|
+
message: () => isValidationFileMissing ? `Missing validation file '${validationFilePath}'` : `Output file '${outputFilePath}'
|
|
307
|
+
does not match validation file '${validationFilePath}'`,
|
|
308
|
+
writeFileSnapshots: () => this.writeFileSnapshots(params)
|
|
305
309
|
};
|
|
306
310
|
}
|
|
311
|
+
writeFileSnapshots(matcherResult) {
|
|
312
|
+
const { actual, expected } = matcherResult;
|
|
313
|
+
const { outputFilePath, validationFilePath } = this.filePaths;
|
|
314
|
+
writeSnapshotFile(outputFilePath, actual);
|
|
315
|
+
if (matcherResult.isValidationFileMissing) {
|
|
316
|
+
writeSnapshotFile(validationFilePath, expected);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
307
319
|
};
|
|
308
320
|
export {
|
|
309
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.11.0",
|
|
4
4
|
"description": "The library agnostic core for testing file snapshots",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"file snapshots"
|
|
@@ -33,28 +33,25 @@
|
|
|
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.
|
|
41
|
+
"jiti": "2.5.1",
|
|
42
42
|
"prettier": "3.6.2",
|
|
43
43
|
"tsup": "8.5.0",
|
|
44
44
|
"typescript": "5.8.3",
|
|
45
|
-
"typescript-eslint": "8.
|
|
45
|
+
"typescript-eslint": "8.38.0",
|
|
46
46
|
"vitest": "3.2.4",
|
|
47
47
|
"@cronn/shared-configs": "0.0.0"
|
|
48
48
|
},
|
|
49
49
|
"scripts": {
|
|
50
50
|
"lint:check": "eslint src/ --max-warnings=0",
|
|
51
51
|
"lint:fix": "eslint src/ --max-warnings=0 --fix",
|
|
52
|
-
"check": "pnpm run compile && pnpm run lint:check",
|
|
53
|
-
"fix": "pnpm run lint:fix",
|
|
54
52
|
"test": "vitest run",
|
|
55
53
|
"test:coverage": "vitest run --coverage",
|
|
56
54
|
"compile": "tsc",
|
|
57
|
-
"build": "tsup"
|
|
58
|
-
"ci": "pnpm run check && pnpm run test:coverage && pnpm run build"
|
|
55
|
+
"build": "tsup"
|
|
59
56
|
}
|
|
60
57
|
}
|