@argos-ci/vitest 0.5.0 → 0.6.1

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.mjs CHANGED
@@ -1,18 +1,32 @@
1
1
  import { getTestRunKey, nextCaptureIndex } from "@argos-ci/util";
2
2
  //#region src/test-context.ts
3
3
  /**
4
+ * Entry point exposing `getCurrentTest` before Vitest 4.1, and removed
5
+ * altogether in Vitest 5.
6
+ *
7
+ * Held in a variable rather than written inline at the import: Vite's
8
+ * dependency pre-bundler resolves a literal specifier eagerly, and on Vitest 5
9
+ * that fails the whole optimize step over an export that no longer exists —
10
+ * even though the branch importing it cannot run there.
11
+ */
12
+ const LEGACY_SUITE_ENTRY = "vitest/suite";
13
+ /**
4
14
  * Get the current Vitest test task, or `undefined` when not inside a test.
5
15
  *
6
16
  * Vitest >= 4.1 exposes `TestRunner.getCurrentTest()` from the `vitest` entry
7
- * point; the `vitest/suite` export is deprecated. We prefer the new API and
8
- * fall back to `vitest/suite` for older 4.x. Both are imported dynamically so
9
- * importing `@argos-ci/vitest` in a non-Vitest environment does not pull Vitest
10
- * in — only call this once you know Vitest is available.
17
+ * point; the `vitest/suite` export is deprecated there and gone in Vitest 5. We
18
+ * prefer the new API and fall back to `vitest/suite` for older 4.x. Both are
19
+ * imported dynamically so importing `@argos-ci/vitest` in a non-Vitest
20
+ * environment does not pull Vitest in — only call this once you know Vitest is
21
+ * available.
11
22
  */
12
23
  async function getCurrentTest() {
13
24
  const runner = (await import("vitest")).TestRunner;
14
25
  if (runner?.getCurrentTest) return runner.getCurrentTest();
15
- return (await import("vitest/suite")).getCurrentTest();
26
+ return (await import(
27
+ /* @vite-ignore */
28
+ LEGACY_SUITE_ENTRY
29
+ )).getCurrentTest();
16
30
  }
17
31
  //#endregion
18
32
  //#region src/auto-name.ts
@@ -239,7 +253,7 @@ function C({ force: e } = {}) {
239
253
  }
240
254
  var y = C();
241
255
  //#endregion
242
- //#region ../../node_modules/.pnpm/@vitest+pretty-format@4.1.10/node_modules/@vitest/pretty-format/dist/index.js
256
+ //#region ../../node_modules/.pnpm/@vitest+pretty-format@5.0.0/node_modules/@vitest/pretty-format/dist/index.js
243
257
  function _mergeNamespaces(n, m) {
244
258
  m.forEach(function(e) {
245
259
  e && typeof e !== "string" && !Array.isArray(e) && Object.keys(e).forEach(function(k) {
@@ -269,7 +283,7 @@ function getKeysOfEnumerableProperties(object, compareKeys) {
269
283
  * with spacing, indentation, and comma
270
284
  * without surrounding punctuation (for example, braces)
271
285
  */
272
- function printIteratorEntries(iterator, config, indentation, depth, refs, printer, separator = ": ") {
286
+ function printIteratorEntries(iterator, config, indentation, depth, refs, printer, separator = ": ", length) {
273
287
  let result = "";
274
288
  let width = 0;
275
289
  let current = iterator.next();
@@ -279,7 +293,7 @@ function printIteratorEntries(iterator, config, indentation, depth, refs, printe
279
293
  while (!current.done) {
280
294
  result += indentationNext;
281
295
  if (width++ === config.maxWidth) {
282
- result += "…";
296
+ result += typeof length === "number" ? `…(${length - width + 1})` : "…";
283
297
  break;
284
298
  }
285
299
  const name = printer(current.value[0], config, indentationNext, depth, refs);
@@ -298,7 +312,7 @@ function printIteratorEntries(iterator, config, indentation, depth, refs, printe
298
312
  * with spacing, indentation, and comma
299
313
  * without surrounding punctuation (braces or brackets)
300
314
  */
301
- function printIteratorValues(iterator, config, indentation, depth, refs, printer) {
315
+ function printIteratorValues(iterator, config, indentation, depth, refs, printer, length) {
302
316
  let result = "";
303
317
  let width = 0;
304
318
  let current = iterator.next();
@@ -308,7 +322,7 @@ function printIteratorValues(iterator, config, indentation, depth, refs, printer
308
322
  while (!current.done) {
309
323
  result += indentationNext;
310
324
  if (width++ === config.maxWidth) {
311
- result += "…";
325
+ result += typeof length === "number" ? `…(${length - width + 1})` : "…";
312
326
  break;
313
327
  }
314
328
  result += printer(current.value, config, indentationNext, depth, refs);
@@ -336,7 +350,7 @@ function printListItems(list, config, indentation, depth, refs, printer) {
336
350
  for (let i = 0; i < length; i++) {
337
351
  result += indentationNext;
338
352
  if (i === config.maxWidth) {
339
- result += "…";
353
+ result += `…(${length - i})`;
340
354
  break;
341
355
  }
342
356
  if (isDataView(list) || i in list) result += printer(isDataView(list) ? list.getInt8(i) : list[i], config, indentationNext, depth, refs);
@@ -352,17 +366,22 @@ function printListItems(list, config, indentation, depth, refs, printer) {
352
366
  * with spacing, indentation, and comma
353
367
  * without surrounding punctuation (for example, braces)
354
368
  */
355
- function printObjectProperties(val, config, indentation, depth, refs, printer) {
369
+ function printObjectProperties(val, config, indentation, depth, refs, printer, compareKeysOverride = config.compareKeys) {
356
370
  let result = "";
357
- const keys = getKeysOfEnumerableProperties(val, config.compareKeys);
371
+ const keys = getKeysOfEnumerableProperties(val, compareKeysOverride);
358
372
  if (keys.length > 0) {
359
373
  result += config.spacingOuter;
360
374
  const indentationNext = indentation + config.indent;
361
375
  for (let i = 0; i < keys.length; i++) {
376
+ result += indentationNext;
377
+ if (i === config.maxWidth) {
378
+ result += `…(${keys.length - i})`;
379
+ break;
380
+ }
362
381
  const key = keys[i];
363
- const name = printer(key, config, indentationNext, depth, refs);
382
+ const name = !config.quoteKeys && isUnquotableKey(key) ? key : printer(key, config, indentationNext, depth, refs);
364
383
  const value = printer(val[key], config, indentationNext, depth, refs);
365
- result += `${indentationNext + name}: ${value}`;
384
+ result += `${name}: ${value}`;
366
385
  if (i < keys.length - 1) result += `,${config.spacingInner}`;
367
386
  else if (!config.min) result += ",";
368
387
  }
@@ -370,6 +389,10 @@ function printObjectProperties(val, config, indentation, depth, refs, printer) {
370
389
  }
371
390
  return result;
372
391
  }
392
+ const keyStrRegExp = /^[a-z_]\w*$/i;
393
+ function isUnquotableKey(key) {
394
+ return typeof key === "string" && key !== "__proto__" && keyStrRegExp.test(key);
395
+ }
373
396
  const asymmetricMatcher = typeof Symbol === "function" && Symbol.for ? Symbol.for("jest.asymmetricMatcher") : 1267621;
374
397
  const SPACE$2 = " ";
375
398
  const serialize$5 = (val, config, indentation, depth, refs, printer) => {
@@ -428,7 +451,7 @@ function printProps(keys, props, config, indentation, depth, refs, printer) {
428
451
  const colors = config.colors;
429
452
  return keys.map((key) => {
430
453
  const value = props[key];
431
- if (typeof value === "string" && value[0] === "_" && value.startsWith("__vitest_") && value.match(/__vitest_\d+__/)) return "";
454
+ if (typeof value === "string" && value[0] === "_" && value.startsWith("__vitest_") && /__vitest_\d+__/.test(value)) return "";
432
455
  let printed = printer(value, config, indentationNext, depth, refs);
433
456
  if (typeof value !== "string") {
434
457
  if (printed.includes("\n")) printed = config.spacingOuter + indentationNext + printed + config.spacingOuter + indentation;
@@ -905,7 +928,7 @@ function printError(val) {
905
928
  * The first port of call for printing an object, handles most of the
906
929
  * data-types in JS.
907
930
  */
908
- function printBasicValue(val, printFunctionName, escapeRegex, escapeString) {
931
+ function printBasicValue(val, printFunctionName, escapeRegex, escapeString, singleQuote) {
909
932
  if (val === true || val === false) return `${val}`;
910
933
  if (val === void 0) return "undefined";
911
934
  if (val === null) return "null";
@@ -913,8 +936,12 @@ function printBasicValue(val, printFunctionName, escapeRegex, escapeString) {
913
936
  if (typeOf === "number") return printNumber(val);
914
937
  if (typeOf === "bigint") return printBigInt(val);
915
938
  if (typeOf === "string") {
916
- if (escapeString) return `"${val.replaceAll(/"|\\/g, "\\$&")}"`;
917
- return `"${val}"`;
939
+ const q = singleQuote ? "'" : "\"";
940
+ if (escapeString) {
941
+ const escapePattern = singleQuote ? /['\\]/g : /["\\]/g;
942
+ return `${q}${val.replaceAll(escapePattern, "\\$&")}${q}`;
943
+ }
944
+ return `${q}${val}${q}`;
918
945
  }
919
946
  if (typeOf === "function") return printFunction(val, printFunctionName);
920
947
  if (typeOf === "symbol") return printSymbol(val);
@@ -945,10 +972,10 @@ function printComplexValue(val, config, indentation, depth, refs, hasCalledToJSO
945
972
  if (config.callToJSON && !hitMaxDepth && val.toJSON && typeof val.toJSON === "function" && !hasCalledToJSON) return printer(val.toJSON(), config, indentation, depth, refs, true);
946
973
  const toStringed = toString.call(val);
947
974
  if (toStringed === "[object Arguments]") return hitMaxDepth ? "[Arguments]" : `${min ? "" : "Arguments "}[${printListItems(val, config, indentation, depth, refs, printer)}]`;
948
- if (isToStringedArrayType(toStringed)) return hitMaxDepth ? `[${val.constructor.name}]` : `${min ? "" : !config.printBasicPrototype && val.constructor.name === "Array" ? "" : `${val.constructor.name} `}[${printListItems(val, config, indentation, depth, refs, printer)}]`;
949
- if (toStringed === "[object Map]") return hitMaxDepth ? "[Map]" : `Map {${printIteratorEntries(val.entries(), config, indentation, depth, refs, printer, " => ")}}`;
950
- if (toStringed === "[object Set]") return hitMaxDepth ? "[Set]" : `Set {${printIteratorValues(val.values(), config, indentation, depth, refs, printer)}}`;
951
- return hitMaxDepth || isWindow(val) ? `[${getConstructorName(val)}]` : `${min ? "" : !config.printBasicPrototype && getConstructorName(val) === "Object" ? "" : `${getConstructorName(val)} `}{${printObjectProperties(val, config, indentation, depth, refs, printer)}}`;
975
+ if (isToStringedArrayType(toStringed)) return hitMaxDepth ? `[${val.constructor.name}]` : `${!config.printBasicPrototype && val.constructor.name === "Array" ? "" : `${val.constructor.name} `}[${printListItems(val, config, indentation, depth, refs, printer)}]`;
976
+ if (toStringed === "[object Map]") return hitMaxDepth ? "[Map]" : `Map {${printIteratorEntries(val.entries(), config, indentation, depth, refs, printer, " => ", val.size)}}`;
977
+ if (toStringed === "[object Set]") return hitMaxDepth ? "[Set]" : `Set {${printIteratorValues(val.values(), config, indentation, depth, refs, printer, val.size)}}`;
978
+ return hitMaxDepth || isWindow(val) ? `[${getConstructorName(val)}]` : `${!config.printBasicPrototype && getConstructorName(val) === "Object" ? "" : `${getConstructorName(val)} `}{${printObjectProperties(val, config, indentation, depth, refs, printer)}}`;
952
979
  }
953
980
  const ErrorPlugin = {
954
981
  test: (val) => val && val instanceof Error,
@@ -964,7 +991,7 @@ const ErrorPlugin = {
964
991
  ...rest
965
992
  };
966
993
  const name = val.name !== "Error" ? val.name : getConstructorName(val);
967
- return hitMaxDepth ? `[${name}]` : `${name} {${printIteratorEntries(Object.entries(entries).values(), config, indentation, depth, refs, printer)}}`;
994
+ return hitMaxDepth ? `[${name}]` : `${name} {${printObjectProperties(entries, config, indentation, depth, refs, printer, null)}}`;
968
995
  }
969
996
  };
970
997
  function isNewPlugin(plugin) {
@@ -1000,7 +1027,7 @@ function printer(val, config, indentation, depth, refs, hasCalledToJSON) {
1000
1027
  const plugin = findPlugin(config.plugins, val);
1001
1028
  if (plugin !== null) result = printPlugin(plugin, val, config, indentation, depth, refs);
1002
1029
  else {
1003
- const basicResult = printBasicValue(val, config.printFunctionName, config.escapeRegex, config.escapeString);
1030
+ const basicResult = printBasicValue(val, config.printFunctionName, config.escapeRegex, config.escapeString, config.singleQuote);
1004
1031
  if (basicResult !== null) result = basicResult;
1005
1032
  else result = printComplexValue(val, config, indentation, depth, refs, hasCalledToJSON);
1006
1033
  }
@@ -1032,7 +1059,11 @@ const DEFAULT_OPTIONS = {
1032
1059
  printBasicPrototype: true,
1033
1060
  printFunctionName: true,
1034
1061
  printShadowRoot: true,
1035
- theme: DEFAULT_THEME
1062
+ theme: DEFAULT_THEME,
1063
+ singleQuote: false,
1064
+ quoteKeys: true,
1065
+ spacingInner: "\n",
1066
+ spacingOuter: "\n"
1036
1067
  };
1037
1068
  function validateOptions(options) {
1038
1069
  for (const key of Object.keys(options)) if (!Object.hasOwn(DEFAULT_OPTIONS, key)) throw new Error(`pretty-format: Unknown option "${key}".`);
@@ -1077,11 +1108,13 @@ function getConfig(options) {
1077
1108
  maxWidth: options?.maxWidth ?? DEFAULT_OPTIONS.maxWidth,
1078
1109
  min: options?.min ?? DEFAULT_OPTIONS.min,
1079
1110
  plugins: options?.plugins ?? DEFAULT_OPTIONS.plugins,
1080
- printBasicPrototype: options?.printBasicPrototype ?? true,
1111
+ printBasicPrototype: options?.printBasicPrototype ?? !options?.min,
1081
1112
  printFunctionName: getPrintFunctionName(options),
1082
1113
  printShadowRoot: options?.printShadowRoot ?? true,
1083
- spacingInner: options?.min ? " " : "\n",
1084
- spacingOuter: options?.min ? "" : "\n",
1114
+ spacingInner: options?.spacingInner ?? (options?.min ? " " : "\n"),
1115
+ spacingOuter: options?.spacingOuter ?? (options?.min ? "" : "\n"),
1116
+ singleQuote: options?.singleQuote ?? DEFAULT_OPTIONS.singleQuote,
1117
+ quoteKeys: options?.quoteKeys ?? DEFAULT_OPTIONS.quoteKeys,
1085
1118
  maxOutputLength: options?.maxOutputLength ?? DEFAULT_OPTIONS.maxOutputLength,
1086
1119
  _outputLengthPerDepth: []
1087
1120
  };
@@ -1095,16 +1128,13 @@ function createIndent(indent) {
1095
1128
  * @param options Custom settings
1096
1129
  */
1097
1130
  function format(val, options) {
1098
- if (options) {
1099
- validateOptions(options);
1100
- if (options.plugins) {
1101
- const plugin = findPlugin(options.plugins, val);
1102
- if (plugin !== null) return printPlugin(plugin, val, getConfig(options), "", 0, []);
1103
- }
1104
- }
1105
- const basicResult = printBasicValue(val, getPrintFunctionName(options), getEscapeRegex(options), getEscapeString(options));
1131
+ if (options) validateOptions(options);
1132
+ const config = getConfig(options);
1133
+ const plugin = findPlugin(config.plugins, val);
1134
+ if (plugin !== null) return printPlugin(plugin, val, config, "", 0, []);
1135
+ const basicResult = printBasicValue(val, config.printFunctionName, config.escapeRegex, config.escapeString, config.singleQuote);
1106
1136
  if (basicResult !== null) return basicResult;
1107
- return printComplexValue(val, getConfig(options), "", 0, []);
1137
+ return printComplexValue(val, config, "", 0, []);
1108
1138
  }
1109
1139
  const plugins = {
1110
1140
  AsymmetricMatcher: plugin$5,
@@ -7,14 +7,25 @@ import { ViewportSize } from "@argos-ci/browser";
7
7
  */
8
8
  declare const VITEST_IFRAME_SELECTOR = "iframe[data-vitest=\"true\"]";
9
9
  /**
10
- * ID of the Vitest "tester" element that wraps the iframe with a `scale(...)`
11
- * transform.
10
+ * ID of the Vitest "tester" element wrapping the iframe.
12
11
  */
13
12
  declare const VITEST_TESTER_ID = "vitest-tester";
14
13
  /**
15
- * Remove the scale from the Vitest `#vitest-tester` element before taking a
16
- * screenshot to avoid ending up with small screenshots.
17
- * @returns A function to restore the scale after the screenshot.
14
+ * Undo the scale Vitest applies to the `#vitest-tester` element, so the
15
+ * screenshot is captured at full size instead of shrunk.
16
+ *
17
+ * Only some Vitest versions scale the tester. Up to Vitest 4 a viewport larger
18
+ * than the browser window is emulated by sizing the tester to the requested
19
+ * viewport and shrinking it with a CSS `transform: scale(...)`. From Vitest 5
20
+ * the real browser viewport is resized instead, so the tester carries no
21
+ * transform and there is nothing to undo — which is why an unscaled tester is
22
+ * a no-op rather than an error.
23
+ *
24
+ * Detection reads the *computed* transform, so a scale set from a stylesheet
25
+ * counts too, while the override goes on the inline style, which is the only
26
+ * layer guaranteed to win.
27
+ *
28
+ * @returns A function restoring the transform after the screenshot.
18
29
  */
19
30
  declare function resetTesterScale(ctx: BrowserCommandContext): Promise<() => Promise<void>>;
20
31
  /**
package/dist/internal.mjs CHANGED
@@ -7,11 +7,30 @@ import { readVersionFromPackage } from "@argos-ci/util";
7
7
  */
8
8
  const VITEST_IFRAME_SELECTOR = "iframe[data-vitest=\"true\"]";
9
9
  /**
10
- * ID of the Vitest "tester" element that wraps the iframe with a `scale(...)`
11
- * transform.
10
+ * ID of the Vitest "tester" element wrapping the iframe.
12
11
  */
13
12
  const VITEST_TESTER_ID = "vitest-tester";
14
13
  /**
14
+ * Dataset key holding the tester's inline `transform` from before Argos reset
15
+ * it.
16
+ *
17
+ * The presence of the key — not the value it holds — is what marks the
18
+ * transform as backed up: the original inline `transform` is usually an empty
19
+ * string, which is indistinguishable from "nothing was saved yet".
20
+ */
21
+ const TRANSFORM_BACKUP_KEY = "argosBckTransform";
22
+ /**
23
+ * Dataset key counting the screenshots currently holding the tester unscaled.
24
+ *
25
+ * Captures nest: a story calling `argosScreenshot` from its play function runs
26
+ * one inside the automatic screenshot taken after the test. Both share this one
27
+ * element, so without a count the inner restore would hand the scale back while
28
+ * the outer capture is still to come — and the outer screenshot would come out
29
+ * shrunk. Only the first reset saves and overrides the transform, and only the
30
+ * last restore puts it back.
31
+ */
32
+ const SCALE_HOLD_KEY = "argosScaleHold";
33
+ /**
15
34
  * Attribute holding the iframe's inline size from before Argos resized it, as
16
35
  * JSON.
17
36
  *
@@ -21,27 +40,79 @@ const VITEST_TESTER_ID = "vitest-tester";
21
40
  */
22
41
  const SIZE_BACKUP_ATTRIBUTE = "data-argos-size-backup";
23
42
  /**
24
- * Remove the scale from the Vitest `#vitest-tester` element before taking a
25
- * screenshot to avoid ending up with small screenshots.
26
- * @returns A function to restore the scale after the screenshot.
43
+ * Undo the scale Vitest applies to the `#vitest-tester` element, so the
44
+ * screenshot is captured at full size instead of shrunk.
45
+ *
46
+ * Only some Vitest versions scale the tester. Up to Vitest 4 a viewport larger
47
+ * than the browser window is emulated by sizing the tester to the requested
48
+ * viewport and shrinking it with a CSS `transform: scale(...)`. From Vitest 5
49
+ * the real browser viewport is resized instead, so the tester carries no
50
+ * transform and there is nothing to undo — which is why an unscaled tester is
51
+ * a no-op rather than an error.
52
+ *
53
+ * Detection reads the *computed* transform, so a scale set from a stylesheet
54
+ * counts too, while the override goes on the inline style, which is the only
55
+ * layer guaranteed to win.
56
+ *
57
+ * @returns A function restoring the transform after the screenshot.
27
58
  */
28
59
  async function resetTesterScale(ctx) {
29
- await ctx.page.evaluate((testerId) => {
30
- const tester = document.getElementById(testerId);
31
- if (!(tester instanceof HTMLElement)) return;
32
- if (!tester.getAttribute("data-scale")) throw new Error("Vitest iframe data-scale attribute not found");
33
- tester.dataset.bckTransform = tester.style.transform;
34
- tester.style.transform = `scale(1)`;
35
- }, VITEST_TESTER_ID);
60
+ await ctx.page.evaluate(resetTesterScaleInPage, {
61
+ testerId: VITEST_TESTER_ID,
62
+ backupKey: TRANSFORM_BACKUP_KEY,
63
+ holdKey: SCALE_HOLD_KEY
64
+ });
36
65
  return async () => {
37
- await ctx.page.evaluate((testerId) => {
38
- const tester = document.getElementById(testerId);
39
- if (!(tester instanceof HTMLElement)) return;
40
- tester.style.transform = tester.dataset.bckTransform ?? "";
41
- }, VITEST_TESTER_ID);
66
+ await ctx.page.evaluate(restoreTesterScaleInPage, {
67
+ testerId: VITEST_TESTER_ID,
68
+ backupKey: TRANSFORM_BACKUP_KEY,
69
+ holdKey: SCALE_HOLD_KEY
70
+ });
42
71
  };
43
72
  }
44
73
  /**
74
+ * Body of {@link resetTesterScale}, running in the page.
75
+ *
76
+ * Exported for tests only, and self-contained on purpose: it is serialized to
77
+ * the browser by `page.evaluate`, so it can reference nothing but its argument
78
+ * and page globals.
79
+ */
80
+ function resetTesterScaleInPage(args) {
81
+ const { testerId, backupKey, holdKey } = args;
82
+ const tester = document.getElementById(testerId);
83
+ if (!(tester instanceof HTMLElement)) return;
84
+ const held = Number(tester.dataset[holdKey] ?? "0");
85
+ tester.dataset[holdKey] = String(held + 1);
86
+ if (held > 0) return;
87
+ const { transform } = getComputedStyle(tester);
88
+ if (!transform || transform === "none") return;
89
+ const matrix = new DOMMatrixReadOnly(transform);
90
+ if (matrix.a === 1 && matrix.d === 1) return;
91
+ tester.dataset[backupKey] = tester.style.transform;
92
+ tester.style.transform = "scale(1)";
93
+ }
94
+ /**
95
+ * Body of the function {@link resetTesterScale} returns, running in the page.
96
+ *
97
+ * Exported for tests only, and self-contained for the same reason as
98
+ * {@link resetTesterScaleInPage}.
99
+ */
100
+ function restoreTesterScaleInPage(args) {
101
+ const { testerId, backupKey, holdKey } = args;
102
+ const tester = document.getElementById(testerId);
103
+ if (!(tester instanceof HTMLElement)) return;
104
+ const held = Math.max(0, Number(tester.dataset[holdKey] ?? "0") - 1);
105
+ if (held > 0) {
106
+ tester.dataset[holdKey] = String(held);
107
+ return;
108
+ }
109
+ delete tester.dataset[holdKey];
110
+ const backup = tester.dataset[backupKey];
111
+ if (backup === void 0) return;
112
+ tester.style.transform = backup;
113
+ delete tester.dataset[backupKey];
114
+ }
115
+ /**
45
116
  * Resize the Vitest iframe.
46
117
  *
47
118
  * The story/test renders inside an `<iframe data-vitest="true">` on the host
package/dist/plugin.d.mts CHANGED
@@ -3,8 +3,7 @@ import { StabilizationPluginOptions, ViewportOption } from "@argos-ci/browser";
3
3
  import { ScreenshotMetadata } from "@argos-ci/util";
4
4
  import { UploadParameters } from "@argos-ci/core";
5
5
  import { Plugin } from "vitest/config";
6
- import { BrowserCommand, Vitest } from "vitest/node";
7
- import { Reporter } from "vitest/reporters";
6
+ import { BrowserCommand, Reporter, Vitest } from "vitest/node";
8
7
  //#region src/options.d.ts
9
8
  /**
10
9
  * Configuration for the Argos Vitest reporter.
package/dist/plugin.mjs CHANGED
@@ -11,11 +11,30 @@ import { getSnapshotMimeType, readConfig, upload } from "@argos-ci/core";
11
11
  */
12
12
  const VITEST_IFRAME_SELECTOR = "iframe[data-vitest=\"true\"]";
13
13
  /**
14
- * ID of the Vitest "tester" element that wraps the iframe with a `scale(...)`
15
- * transform.
14
+ * ID of the Vitest "tester" element wrapping the iframe.
16
15
  */
17
16
  const VITEST_TESTER_ID = "vitest-tester";
18
17
  /**
18
+ * Dataset key holding the tester's inline `transform` from before Argos reset
19
+ * it.
20
+ *
21
+ * The presence of the key — not the value it holds — is what marks the
22
+ * transform as backed up: the original inline `transform` is usually an empty
23
+ * string, which is indistinguishable from "nothing was saved yet".
24
+ */
25
+ const TRANSFORM_BACKUP_KEY = "argosBckTransform";
26
+ /**
27
+ * Dataset key counting the screenshots currently holding the tester unscaled.
28
+ *
29
+ * Captures nest: a story calling `argosScreenshot` from its play function runs
30
+ * one inside the automatic screenshot taken after the test. Both share this one
31
+ * element, so without a count the inner restore would hand the scale back while
32
+ * the outer capture is still to come — and the outer screenshot would come out
33
+ * shrunk. Only the first reset saves and overrides the transform, and only the
34
+ * last restore puts it back.
35
+ */
36
+ const SCALE_HOLD_KEY = "argosScaleHold";
37
+ /**
19
38
  * Attribute holding the iframe's inline size from before Argos resized it, as
20
39
  * JSON.
21
40
  *
@@ -25,27 +44,79 @@ const VITEST_TESTER_ID = "vitest-tester";
25
44
  */
26
45
  const SIZE_BACKUP_ATTRIBUTE = "data-argos-size-backup";
27
46
  /**
28
- * Remove the scale from the Vitest `#vitest-tester` element before taking a
29
- * screenshot to avoid ending up with small screenshots.
30
- * @returns A function to restore the scale after the screenshot.
47
+ * Undo the scale Vitest applies to the `#vitest-tester` element, so the
48
+ * screenshot is captured at full size instead of shrunk.
49
+ *
50
+ * Only some Vitest versions scale the tester. Up to Vitest 4 a viewport larger
51
+ * than the browser window is emulated by sizing the tester to the requested
52
+ * viewport and shrinking it with a CSS `transform: scale(...)`. From Vitest 5
53
+ * the real browser viewport is resized instead, so the tester carries no
54
+ * transform and there is nothing to undo — which is why an unscaled tester is
55
+ * a no-op rather than an error.
56
+ *
57
+ * Detection reads the *computed* transform, so a scale set from a stylesheet
58
+ * counts too, while the override goes on the inline style, which is the only
59
+ * layer guaranteed to win.
60
+ *
61
+ * @returns A function restoring the transform after the screenshot.
31
62
  */
32
63
  async function resetTesterScale(ctx) {
33
- await ctx.page.evaluate((testerId) => {
34
- const tester = document.getElementById(testerId);
35
- if (!(tester instanceof HTMLElement)) return;
36
- if (!tester.getAttribute("data-scale")) throw new Error("Vitest iframe data-scale attribute not found");
37
- tester.dataset.bckTransform = tester.style.transform;
38
- tester.style.transform = `scale(1)`;
39
- }, VITEST_TESTER_ID);
64
+ await ctx.page.evaluate(resetTesterScaleInPage, {
65
+ testerId: VITEST_TESTER_ID,
66
+ backupKey: TRANSFORM_BACKUP_KEY,
67
+ holdKey: SCALE_HOLD_KEY
68
+ });
40
69
  return async () => {
41
- await ctx.page.evaluate((testerId) => {
42
- const tester = document.getElementById(testerId);
43
- if (!(tester instanceof HTMLElement)) return;
44
- tester.style.transform = tester.dataset.bckTransform ?? "";
45
- }, VITEST_TESTER_ID);
70
+ await ctx.page.evaluate(restoreTesterScaleInPage, {
71
+ testerId: VITEST_TESTER_ID,
72
+ backupKey: TRANSFORM_BACKUP_KEY,
73
+ holdKey: SCALE_HOLD_KEY
74
+ });
46
75
  };
47
76
  }
48
77
  /**
78
+ * Body of {@link resetTesterScale}, running in the page.
79
+ *
80
+ * Exported for tests only, and self-contained on purpose: it is serialized to
81
+ * the browser by `page.evaluate`, so it can reference nothing but its argument
82
+ * and page globals.
83
+ */
84
+ function resetTesterScaleInPage(args) {
85
+ const { testerId, backupKey, holdKey } = args;
86
+ const tester = document.getElementById(testerId);
87
+ if (!(tester instanceof HTMLElement)) return;
88
+ const held = Number(tester.dataset[holdKey] ?? "0");
89
+ tester.dataset[holdKey] = String(held + 1);
90
+ if (held > 0) return;
91
+ const { transform } = getComputedStyle(tester);
92
+ if (!transform || transform === "none") return;
93
+ const matrix = new DOMMatrixReadOnly(transform);
94
+ if (matrix.a === 1 && matrix.d === 1) return;
95
+ tester.dataset[backupKey] = tester.style.transform;
96
+ tester.style.transform = "scale(1)";
97
+ }
98
+ /**
99
+ * Body of the function {@link resetTesterScale} returns, running in the page.
100
+ *
101
+ * Exported for tests only, and self-contained for the same reason as
102
+ * {@link resetTesterScaleInPage}.
103
+ */
104
+ function restoreTesterScaleInPage(args) {
105
+ const { testerId, backupKey, holdKey } = args;
106
+ const tester = document.getElementById(testerId);
107
+ if (!(tester instanceof HTMLElement)) return;
108
+ const held = Math.max(0, Number(tester.dataset[holdKey] ?? "0") - 1);
109
+ if (held > 0) {
110
+ tester.dataset[holdKey] = String(held);
111
+ return;
112
+ }
113
+ delete tester.dataset[holdKey];
114
+ const backup = tester.dataset[backupKey];
115
+ if (backup === void 0) return;
116
+ tester.style.transform = backup;
117
+ delete tester.dataset[backupKey];
118
+ }
119
+ /**
49
120
  * Resize the Vitest iframe.
50
121
  *
51
122
  * The story/test renders inside an `<iframe data-vitest="true">` on the host
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@argos-ci/vitest",
3
3
  "description": "Vitest SDK for visual testing with Argos.",
4
- "version": "0.5.0",
4
+ "version": "0.6.1",
5
5
  "author": "Smooth Code",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -52,8 +52,8 @@
52
52
  },
53
53
  "dependencies": {
54
54
  "@argos-ci/browser": "6.4.5",
55
- "@argos-ci/core": "6.8.3",
56
- "@argos-ci/playwright": "7.5.0",
55
+ "@argos-ci/core": "6.8.4",
56
+ "@argos-ci/playwright": "7.5.1",
57
57
  "@argos-ci/util": "4.2.0"
58
58
  },
59
59
  "peerDependencies": {
@@ -64,9 +64,9 @@
64
64
  },
65
65
  "devDependencies": {
66
66
  "@types/node": "catalog:",
67
- "@vitest/browser": "^4.1.10",
68
- "@vitest/browser-playwright": "^4.1.10",
69
- "@vitest/pretty-format": "^4.1.10",
67
+ "@vitest/browser": "^5.0.0",
68
+ "@vitest/browser-playwright": "^5.0.0",
69
+ "@vitest/pretty-format": "^5.0.0",
70
70
  "playwright": "^1.62.1",
71
71
  "vitest": "catalog:"
72
72
  },
@@ -77,9 +77,10 @@
77
77
  "install-playwright": "playwright install chromium --with-deps",
78
78
  "build-e2e": "pnpm run install-playwright",
79
79
  "e2e": "cross-env BUILD_NAME=\"argos-vitest-e2e-node-$NODE_VERSION-$OS\" UPLOAD_TO_ARGOS=true pnpm run test-e2e",
80
+ "e2e-compat": "cross-env BUILD_NAME=\"argos-vitest4-e2e-node-$NODE_VERSION-$OS\" UPLOAD_TO_ARGOS=true pnpm run test-e2e",
80
81
  "check-types": "tsc",
81
82
  "check-format": "prettier --check --ignore-unknown --ignore-path=./.gitignore --ignore-path=../../.gitignore --ignore-path=../../.prettierignore .",
82
83
  "lint": "eslint ."
83
84
  },
84
- "gitHead": "b2a9a9d84dc14e148a68295f56cfdfec03f887f1"
85
+ "gitHead": "212ed2c37ce532dd32c87ac685b55e2ab6e154c4"
85
86
  }