@cronn/lib-file-snapshots 1.0.0 → 1.1.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.mts CHANGED
@@ -1,15 +1,33 @@
1
- //#region src/types/serializer.d.ts
2
- interface SnapshotSerializer {
1
+ //#region src/models/table.d.ts
2
+ interface TableData<TRow extends TableRow = TableRow> {
3
+ columns: Array<string>;
4
+ rows: Array<TRow>;
5
+ }
6
+ type TableRow = Array<TableCell>;
7
+ type TableCell = string | number | boolean | null | undefined;
8
+ type TableRowRecord = Record<string, TableCell>;
9
+ declare class Table<TRow extends TableRow = TableRow> implements TableData<TRow> {
10
+ readonly columns: Array<string>;
11
+ readonly rows: Array<TRow>;
12
+ private constructor();
3
13
  /**
4
- * The file extension associated with the serialized value
14
+ * Defines a table
5
15
  */
6
- readonly fileExtension: string;
16
+ static define<TRow extends TableRow>(data: TableData<TRow>): Table<TRow>;
7
17
  /**
8
- * Returns true when value can be serialized
18
+ * Defines a table from record-based rows
9
19
  *
10
- * @param value The value to be serialized
20
+ * Columns are derived from keys, rows from values.
11
21
  */
12
- canSerialize(value: unknown): boolean;
22
+ static fromRecords<TRow extends TableRowRecord>(rows: Array<TRow>): Table;
23
+ }
24
+ //#endregion
25
+ //#region src/types/serializer.d.ts
26
+ interface SnapshotSerializer<TValue> {
27
+ /**
28
+ * The file extension associated with the serialized value
29
+ */
30
+ readonly fileExtension: string;
13
31
  /**
14
32
  * Serializes value
15
33
  *
@@ -17,7 +35,7 @@ interface SnapshotSerializer {
17
35
  * @return {string} The serialized value
18
36
  * @throws {Error} Will throw an error if value cannot be serialized.
19
37
  */
20
- serialize(value: unknown): string;
38
+ serialize(value: TValue): string;
21
39
  }
22
40
  //#endregion
23
41
  //#region src/serializers/json-serializer.d.ts
@@ -44,13 +62,12 @@ interface JsonNormalizerContext {
44
62
  key?: string;
45
63
  index?: number;
46
64
  }
47
- declare class JsonSerializer implements SnapshotSerializer {
65
+ declare class JsonSerializer implements SnapshotSerializer<unknown> {
48
66
  readonly fileExtension = "json";
49
67
  private readonly includeUndefinedObjectProperties;
50
68
  private readonly normalizers;
51
69
  private readonly indentSize;
52
70
  constructor(options?: JsonSerializerOptions);
53
- canSerialize(_value: unknown): boolean;
54
71
  serialize(value: unknown): string;
55
72
  private normalizeValueRecursive;
56
73
  private normalizeNumber;
@@ -64,6 +81,23 @@ declare class JsonSerializer implements SnapshotSerializer {
64
81
  private applyCustomNormalizers;
65
82
  }
66
83
  //#endregion
84
+ //#region src/serializers/markdown-table-serializer.d.ts
85
+ interface MarkdownTableSerializerOptions {
86
+ /**
87
+ * Custom normalizers to apply before serialization
88
+ */
89
+ normalizers?: Array<MarkdownTableNormalizer>;
90
+ }
91
+ type MarkdownTableNormalizer = (value: TableCell) => TableCell;
92
+ declare class MarkdownTableSerializer implements SnapshotSerializer<TableData> {
93
+ readonly fileExtension = "md";
94
+ private readonly normalizers;
95
+ constructor(options?: MarkdownTableSerializerOptions);
96
+ serialize(value: TableData): string;
97
+ private serializeCellValue;
98
+ private mapCellValueToString;
99
+ }
100
+ //#endregion
67
101
  //#region src/serializers/text-serializer.d.ts
68
102
  interface TextSerializerOptions {
69
103
  /**
@@ -78,17 +112,16 @@ interface TextSerializerOptions {
78
112
  fileExtension?: string;
79
113
  }
80
114
  type TextNormalizer = (value: string) => string;
81
- declare class TextSerializer implements SnapshotSerializer {
115
+ declare class TextSerializer implements SnapshotSerializer<string> {
82
116
  readonly fileExtension: string;
83
117
  private readonly normalizers;
84
118
  constructor(options?: TextSerializerOptions);
85
- canSerialize(value: unknown): value is string;
86
- serialize(value: unknown): string;
119
+ serialize(value: string): string;
87
120
  private normalizeValue;
88
121
  }
89
122
  //#endregion
90
123
  //#region src/types/matcher.d.ts
91
- interface ValidationFileMatcherConfig {
124
+ interface ValidationFileMatcherConfig<TValue> {
92
125
  /**
93
126
  * Directory in which golden masters are stored
94
127
  */
@@ -106,7 +139,7 @@ interface ValidationFileMatcherConfig {
106
139
  /**
107
140
  * The serializer to use for the snapshot
108
141
  */
109
- serializer: SnapshotSerializer;
142
+ serializer: SnapshotSerializer<TValue>;
110
143
  /**
111
144
  * Whether to update golden masters with the actual result.
112
145
  *
@@ -131,15 +164,15 @@ interface FilePathResolverParams {
131
164
  }
132
165
  //#endregion
133
166
  //#region src/matcher/validation-file-matcher.d.ts
134
- declare class ValidationFileMatcher {
167
+ declare class ValidationFileMatcher<TValue> {
135
168
  private readonly updateSnapshots;
136
169
  private readonly serializer;
137
170
  private readonly filePaths;
138
171
  private validationFile;
139
- constructor(config: ValidationFileMatcherConfig);
172
+ constructor(config: ValidationFileMatcherConfig<TValue>);
140
173
  get isValidationFileMissing(): boolean;
141
174
  get isUpdate(): boolean;
142
- matchFileSnapshot(actual: unknown): ValidationFileMatcherResult;
175
+ matchFileSnapshot(actual: TValue): ValidationFileMatcherResult;
143
176
  private buildFilePaths;
144
177
  private readValidationFile;
145
178
  private createMatcherResult;
@@ -160,4 +193,4 @@ declare function isArray(value: unknown): value is Array<unknown>;
160
193
  type PlainObject = Record<PropertyKey, unknown>;
161
194
  declare function isPlainObject(value: unknown): value is PlainObject;
162
195
  //#endregion
163
- export { type FilePathResolver, type FilePathResolverParams, type JsonNormalizer, type JsonNormalizerContext, JsonSerializer, type PlainObject, type SnapshotSerializer, type TextNormalizer, TextSerializer, type UpdateSnapshotsType, ValidationFileMatcher, type ValidationFileMatcherResult, isArray, isPlainObject, isString, normalizeFileName, resolveNameAsFile, resolveNameAsFileSuffix };
196
+ export { type FilePathResolver, type FilePathResolverParams, type JsonNormalizer, type JsonNormalizerContext, JsonSerializer, type MarkdownTableNormalizer, MarkdownTableSerializer, type PlainObject, type SnapshotSerializer, Table, type TableCell, type TableData, type TableRow, type TextNormalizer, TextSerializer, type UpdateSnapshotsType, ValidationFileMatcher, type ValidationFileMatcherResult, isArray, isPlainObject, isString, normalizeFileName, resolveNameAsFile, resolveNameAsFileSuffix };
package/dist/index.mjs CHANGED
@@ -3,7 +3,35 @@ import fs from "node:fs";
3
3
  import os from "node:os";
4
4
  import * as path$1 from "node:path";
5
5
  import path from "node:path";
6
-
6
+ import { markdownTable } from "markdown-table";
7
+ //#region src/models/table.ts
8
+ var Table = class Table {
9
+ columns;
10
+ rows;
11
+ constructor(data) {
12
+ this.columns = data.columns;
13
+ this.rows = data.rows;
14
+ }
15
+ /**
16
+ * Defines a table
17
+ */
18
+ static define(data) {
19
+ return new Table(data);
20
+ }
21
+ /**
22
+ * Defines a table from record-based rows
23
+ *
24
+ * Columns are derived from keys, rows from values.
25
+ */
26
+ static fromRecords(rows) {
27
+ const [firstRow] = rows;
28
+ return new Table({
29
+ columns: firstRow !== void 0 ? Object.keys(firstRow) : [],
30
+ rows: rows.map((row) => Object.values(row))
31
+ });
32
+ }
33
+ };
34
+ //#endregion
7
35
  //#region src/utils/file.ts
8
36
  const NEW_LINE_SEPARATOR = "\n";
9
37
  const EMOJI_REGEXP = /(?!(\*|#|\d))[\p{Extended_Pictographic}\p{Emoji_Component}]|[\u0030-\u0039]\ufe0f?[\u20e3]|[\u002A\u0023]?\ufe0f?[\u20e3]/gu;
@@ -28,7 +56,6 @@ function addTrailingNewLine(data) {
28
56
  function addMissingFileMarker(data) {
29
57
  return `===== missing file =====${NEW_LINE_SEPARATOR}${data}`;
30
58
  }
31
-
32
59
  //#endregion
33
60
  //#region src/utils/guards.ts
34
61
  function isString(value) {
@@ -45,13 +72,11 @@ function isPlainObject(value) {
45
72
  const proto = Object.getPrototypeOf(value);
46
73
  return proto === null || proto === Object.prototype;
47
74
  }
48
-
49
75
  //#endregion
50
76
  //#region src/utils/validators.ts
51
77
  function isPositiveInteger(value) {
52
78
  return Number.isInteger(value) && value > 0;
53
79
  }
54
-
55
80
  //#endregion
56
81
  //#region src/serializers/json-serializer.ts
57
82
  var JsonSerializer = class {
@@ -65,9 +90,6 @@ var JsonSerializer = class {
65
90
  this.normalizers = options.normalizers ?? [];
66
91
  this.indentSize = options.indentSize ?? 2;
67
92
  }
68
- canSerialize(_value) {
69
- return true;
70
- }
71
93
  serialize(value) {
72
94
  const jsonValue = this.normalizeValueRecursive(value);
73
95
  return addTrailingNewLine(JSON.stringify(jsonValue, void 0, this.indentSize));
@@ -145,7 +167,30 @@ var JsonSerializer = class {
145
167
  return normalizedValue;
146
168
  }
147
169
  };
148
-
170
+ //#endregion
171
+ //#region src/serializers/markdown-table-serializer.ts
172
+ var MarkdownTableSerializer = class {
173
+ fileExtension = "md";
174
+ normalizers;
175
+ constructor(options = {}) {
176
+ this.normalizers = options.normalizers ?? [];
177
+ }
178
+ serialize(value) {
179
+ const { columns, rows } = value;
180
+ if (columns.length === 0) throw new Error("Markdown table must have at least one column.");
181
+ return addTrailingNewLine(markdownTable([columns, ...rows.map((row) => row.map((cellValue) => this.serializeCellValue(cellValue)))]));
182
+ }
183
+ serializeCellValue(value) {
184
+ let normalizedValue = value;
185
+ for (const normalizer of this.normalizers) normalizedValue = normalizer(normalizedValue);
186
+ return this.mapCellValueToString(normalizedValue);
187
+ }
188
+ mapCellValueToString(value) {
189
+ if (value === void 0) return "undefined";
190
+ if (value === null) return "null";
191
+ return value.toString().trim();
192
+ }
193
+ };
149
194
  //#endregion
150
195
  //#region src/serializers/text-serializer.ts
151
196
  var TextSerializer = class {
@@ -155,11 +200,7 @@ var TextSerializer = class {
155
200
  this.normalizers = options.normalizers ?? [];
156
201
  this.fileExtension = options.fileExtension ?? "txt";
157
202
  }
158
- canSerialize(value) {
159
- return isString(value);
160
- }
161
203
  serialize(value) {
162
- if (!this.canSerialize(value)) throw new Error(`Missing text serialization for value of type ${typeof value}.`);
163
204
  return addTrailingNewLine(this.normalizeValue(value));
164
205
  }
165
206
  normalizeValue(value) {
@@ -168,7 +209,6 @@ var TextSerializer = class {
168
209
  return normalizedValue.trim();
169
210
  }
170
211
  };
171
-
172
212
  //#endregion
173
213
  //#region src/matcher/validation-file-matcher.ts
174
214
  var ValidationFileMatcher = class {
@@ -189,7 +229,6 @@ var ValidationFileMatcher = class {
189
229
  return this.updateSnapshots === "all" || this.isValidationFileMissing && this.updateSnapshots === "missing";
190
230
  }
191
231
  matchFileSnapshot(actual) {
192
- if (!this.serializer.canSerialize(actual)) throw new Error(`Cannot serialize value of type ${typeof actual}`);
193
232
  const serializedActual = this.serializer.serialize(actual);
194
233
  const expected = this.resolveExpected(serializedActual);
195
234
  return this.createMatcherResult({
@@ -238,7 +277,6 @@ var ValidationFileMatcher = class {
238
277
  return this.validationFile;
239
278
  }
240
279
  };
241
-
242
280
  //#endregion
243
281
  //#region src/matcher/file-path-resolver.ts
244
282
  function resolveNameAsFile(params) {
@@ -261,6 +299,5 @@ function resolveFilePath(params, resolveNamedFilePath) {
261
299
  normalizedName: normalizeFileName(name)
262
300
  });
263
301
  }
264
-
265
302
  //#endregion
266
- export { JsonSerializer, TextSerializer, ValidationFileMatcher, isArray, isPlainObject, isString, normalizeFileName, resolveNameAsFile, resolveNameAsFileSuffix };
303
+ export { JsonSerializer, MarkdownTableSerializer, Table, TextSerializer, ValidationFileMatcher, isArray, isPlainObject, isString, normalizeFileName, resolveNameAsFile, resolveNameAsFileSuffix };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cronn/lib-file-snapshots",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "The library agnostic core for testing file snapshots",
5
5
  "keywords": [
6
6
  "file snapshots"
@@ -22,12 +22,12 @@
22
22
  "devEngines": {
23
23
  "runtime": {
24
24
  "name": "node",
25
- "version": ">=24.13 <25",
25
+ "version": ">=24.14.1 <25",
26
26
  "onFail": "error"
27
27
  },
28
28
  "packageManager": {
29
29
  "name": "pnpm",
30
- "version": ">=10.28.2 <11",
30
+ "version": ">=10.33.0 <11",
31
31
  "onFail": "error"
32
32
  }
33
33
  },
@@ -45,17 +45,20 @@
45
45
  "devDependencies": {
46
46
  "@arethetypeswrong/core": "0.18.2",
47
47
  "@trivago/prettier-plugin-sort-imports": "6.0.2",
48
- "@typescript/native-preview": "7.0.0-dev.20260218.1",
49
- "@types/node": "24.10.13",
50
- "@vitest/coverage-v8": "4.0.18",
51
- "eslint": "10.0.1",
48
+ "@types/node": "24.12.2",
49
+ "@typescript/native-preview": "7.0.0-dev.20260406.1",
50
+ "@vitest/coverage-v8": "4.1.4",
51
+ "eslint": "10.2.0",
52
52
  "eslint-config-prettier": "10.1.8",
53
+ "eslint-plugin-check-file": "3.3.1",
53
54
  "eslint-plugin-unused-imports": "4.4.1",
55
+ "markdown-table": "3.0.4",
54
56
  "prettier": "3.8.1",
55
- "publint": "0.3.17",
56
- "tsdown": "0.20.3",
57
- "typescript-eslint": "8.56.0",
58
- "vitest": "4.0.18",
57
+ "publint": "0.3.18",
58
+ "rimraf": "6.1.3",
59
+ "tsdown": "0.21.7",
60
+ "typescript-eslint": "8.58.0",
61
+ "vitest": "4.1.4",
59
62
  "@cronn/shared-configs": "0.0.0"
60
63
  },
61
64
  "scripts": {
@@ -64,6 +67,7 @@
64
67
  "test": "vitest run",
65
68
  "test:coverage": "vitest run --coverage",
66
69
  "compile": "tsgo",
67
- "build": "tsdown"
70
+ "build": "tsdown",
71
+ "clean": "rimraf .turbo dist coverage"
68
72
  }
69
73
  }