@cronn/lib-file-snapshots 0.10.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 -86
- 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,73 +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
|
-
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
|
-
matchFileSnapshot(actual
|
|
249
|
-
|
|
250
|
-
if (!serializer.canSerialize(actual)) {
|
|
243
|
+
matchFileSnapshot(actual) {
|
|
244
|
+
if (!this.serializer.canSerialize(actual)) {
|
|
251
245
|
throw new Error(`Cannot serialize value of type ${typeof actual}`);
|
|
252
246
|
}
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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
|
|
260
259
|
});
|
|
261
|
-
return this.writeFileSnapshots(
|
|
262
|
-
serializerResult.serializedValue,
|
|
263
|
-
validationFilePath
|
|
264
|
-
);
|
|
265
260
|
}
|
|
266
|
-
|
|
267
|
-
const
|
|
268
|
-
const
|
|
269
|
-
const
|
|
270
|
-
const
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
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
|
|
274
270
|
);
|
|
275
|
-
|
|
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
|
+
};
|
|
276
279
|
}
|
|
277
|
-
applyNamingStrategy(filePath, snapshotName, namingStrategy) {
|
|
280
|
+
applyNamingStrategy(filePath, snapshotName, namingStrategy = "file") {
|
|
278
281
|
if (snapshotName === void 0) {
|
|
279
282
|
return filePath;
|
|
280
283
|
}
|
|
@@ -282,29 +285,37 @@ var ValidationFileMatcher = class {
|
|
|
282
285
|
if (namingStrategy === "fileSuffix") {
|
|
283
286
|
return `${filePath}_${normalizedSnapshotName}`;
|
|
284
287
|
}
|
|
285
|
-
return
|
|
288
|
+
return path2.join(filePath, normalizedSnapshotName);
|
|
286
289
|
}
|
|
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);
|
|
290
|
+
readValidationFile() {
|
|
291
|
+
const { validationFilePath } = this.filePaths;
|
|
292
|
+
if (!fs2.existsSync(validationFilePath)) {
|
|
293
|
+
return void 0;
|
|
297
294
|
}
|
|
298
|
-
|
|
295
|
+
return readSnapshotFile(validationFilePath);
|
|
296
|
+
}
|
|
297
|
+
createMatcherResult(params) {
|
|
298
|
+
const { isValidationFileMissing, actual, expected } = params;
|
|
299
|
+
const { outputFilePath, validationFilePath } = this.filePaths;
|
|
299
300
|
return {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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)
|
|
306
309
|
};
|
|
307
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
|
+
}
|
|
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.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
|
}
|