tuffgal 0.2.0-alpha.2 → 0.2.0-alpha.4

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.
Files changed (110) hide show
  1. package/CHANGELOG.md +41 -19
  2. package/README.md +6 -6
  3. package/dist/cli.d.ts +5 -5
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +12 -5
  6. package/dist/cli.js.map +1 -1
  7. package/dist/commands/init.d.ts +1 -1
  8. package/dist/commands/init.js +6 -6
  9. package/dist/commands/supervise.d.ts +3 -3
  10. package/dist/commands/supervise.js +8 -4
  11. package/dist/commands/supervise.js.map +1 -1
  12. package/dist/config.d.ts +29 -13
  13. package/dist/config.d.ts.map +1 -1
  14. package/dist/config.js +5 -2
  15. package/dist/config.js.map +1 -1
  16. package/dist/coverage/screens.d.ts +10 -5
  17. package/dist/coverage/screens.d.ts.map +1 -1
  18. package/dist/coverage/screens.js +18 -8
  19. package/dist/coverage/screens.js.map +1 -1
  20. package/dist/reporter/assets/report.css +11 -11
  21. package/dist/reporter/assets/report.js +81 -21
  22. package/dist/reporter/template.js +56 -45
  23. package/dist/reporter/template.js.map +1 -1
  24. package/dist/runner/approve.d.ts +5 -5
  25. package/dist/runner/approve.js +5 -5
  26. package/dist/runner/approveFrom.d.ts +8 -8
  27. package/dist/runner/approveFrom.js +32 -31
  28. package/dist/runner/approveFrom.js.map +1 -1
  29. package/dist/runner/breakpointPasses.d.ts +16 -9
  30. package/dist/runner/breakpointPasses.d.ts.map +1 -1
  31. package/dist/runner/breakpointPasses.js +17 -9
  32. package/dist/runner/breakpointPasses.js.map +1 -1
  33. package/dist/runner/bridges/devServers.d.ts +10 -1
  34. package/dist/runner/bridges/devServers.d.ts.map +1 -1
  35. package/dist/runner/bridges/devServers.js +16 -8
  36. package/dist/runner/bridges/devServers.js.map +1 -1
  37. package/dist/runner/coverage.d.ts +3 -3
  38. package/dist/runner/coverage.js +5 -5
  39. package/dist/runner/coverage.js.map +1 -1
  40. package/dist/runner/exitCode.d.ts +7 -7
  41. package/dist/runner/exitCode.js +7 -7
  42. package/dist/runner/interpolate.js +1 -1
  43. package/dist/runner/interpolate.js.map +1 -1
  44. package/dist/runner/manifest.d.ts +16 -16
  45. package/dist/runner/manifest.js +12 -12
  46. package/dist/runner/mode.d.ts +2 -2
  47. package/dist/runner/mode.js +4 -4
  48. package/dist/runner/orphanScan.d.ts +3 -3
  49. package/dist/runner/orphanScan.js +5 -5
  50. package/dist/runner/orphanScan.js.map +1 -1
  51. package/dist/runner/resolveLocator.d.ts +5 -5
  52. package/dist/runner/resolveLocator.js +6 -6
  53. package/dist/runner/resolveLocator.js.map +1 -1
  54. package/dist/runner/run.d.ts +55 -18
  55. package/dist/runner/run.d.ts.map +1 -1
  56. package/dist/runner/run.js +118 -67
  57. package/dist/runner/run.js.map +1 -1
  58. package/dist/runner/runAction.d.ts +4 -4
  59. package/dist/runner/runAction.d.ts.map +1 -1
  60. package/dist/runner/runAction.js +72 -34
  61. package/dist/runner/runAction.js.map +1 -1
  62. package/dist/runner/runStory.d.ts +29 -18
  63. package/dist/runner/runStory.d.ts.map +1 -1
  64. package/dist/runner/runStory.js +38 -42
  65. package/dist/runner/runStory.js.map +1 -1
  66. package/dist/runner/scheduler.d.ts +11 -0
  67. package/dist/runner/scheduler.d.ts.map +1 -1
  68. package/dist/runner/scheduler.js +1 -1
  69. package/dist/runner/scheduler.js.map +1 -1
  70. package/dist/runner/steps/intercept.d.ts +6 -4
  71. package/dist/runner/steps/intercept.d.ts.map +1 -1
  72. package/dist/runner/steps/intercept.js.map +1 -1
  73. package/dist/runner/steps/navigate.d.ts +5 -2
  74. package/dist/runner/steps/navigate.d.ts.map +1 -1
  75. package/dist/runner/steps/navigate.js +17 -3
  76. package/dist/runner/steps/navigate.js.map +1 -1
  77. package/dist/runner/steps/read.d.ts +1 -1
  78. package/dist/runner/steps/read.js +1 -1
  79. package/dist/runner/steps/scroll.d.ts +1 -1
  80. package/dist/runner/steps/scroll.d.ts.map +1 -1
  81. package/dist/runner/steps/scroll.js +3 -2
  82. package/dist/runner/steps/scroll.js.map +1 -1
  83. package/dist/schema/action.d.ts +2 -2
  84. package/dist/schema/action.d.ts.map +1 -1
  85. package/dist/schema/action.js +58 -13
  86. package/dist/schema/action.js.map +1 -1
  87. package/dist/schema/load.js +1 -1
  88. package/dist/schema/load.js.map +1 -1
  89. package/dist/schema/result.d.ts +22 -22
  90. package/dist/schema/result.d.ts.map +1 -1
  91. package/dist/schema/result.js +1 -1
  92. package/dist/schema/story.d.ts +1 -1
  93. package/dist/schema/story.js +5 -5
  94. package/dist/screenshots/baselineStore.d.ts +44 -21
  95. package/dist/screenshots/baselineStore.d.ts.map +1 -1
  96. package/dist/screenshots/baselineStore.js +59 -28
  97. package/dist/screenshots/baselineStore.js.map +1 -1
  98. package/dist/screenshots/capture.d.ts +35 -4
  99. package/dist/screenshots/capture.d.ts.map +1 -1
  100. package/dist/screenshots/capture.js +61 -9
  101. package/dist/screenshots/capture.js.map +1 -1
  102. package/dist/screenshots/diff.d.ts +57 -8
  103. package/dist/screenshots/diff.d.ts.map +1 -1
  104. package/dist/screenshots/diff.js +78 -28
  105. package/dist/screenshots/diff.js.map +1 -1
  106. package/dist/util.d.ts +23 -2
  107. package/dist/util.d.ts.map +1 -1
  108. package/dist/util.js +42 -2
  109. package/dist/util.js.map +1 -1
  110. package/package.json +4 -8
@@ -2,31 +2,34 @@ import pixelmatch from 'pixelmatch';
2
2
  import { PNG } from 'pngjs';
3
3
  import { ssim as computeSsim } from 'ssim.js';
4
4
  /**
5
- * Compares two PNG buffers. Computes SSIM (the perceptual gate) and a
6
- * pixelmatch overlay (the visualisation). Both are returned so the
7
- * runner can use SSIM for pass/changed decisions while the reporter
8
- * still has a red-highlight diff image to show the human.
5
+ * Diffing is split into two phases so the common case stays cheap:
9
6
  *
10
- * Dimension mismatch is a regression on its own — fail loudly.
7
+ * 1. {@link scoreDiff}: the gate. Computes SSIM (the pass/changed
8
+ * decision) plus the pixel metrics reported on every outcome
9
+ * (`diffPixels`, `diffRatio`). It runs pixelmatch in count-only mode
10
+ * (no output buffer) and never encodes a diff image. It also returns the
11
+ * {@link DecodedPair} it read, so the changed branch can render its
12
+ * overlay without a second decode.
13
+ * 2. {@link renderDiffOverlay}: the visualisation. Takes that same decoded
14
+ * pair, runs pixelmatch with an output buffer, and deflate-encodes the
15
+ * red-highlight overlay PNG for the human-facing report.
16
+ *
17
+ * The overlay is only kept when a comparison FAILS; a passing comparison
18
+ * deletes it. Because passing is the common case in a healthy suite,
19
+ * encoding the overlay on every comparison was pure waste, so the caller
20
+ * runs {@link scoreDiff} unconditionally and reaches for
21
+ * {@link renderDiffOverlay} only on the changed branch, where the image is
22
+ * actually written. The decode is shared across both, so the changed branch
23
+ * pays for exactly one `PNG.sync.read` pair; the pass branch keeps its
24
+ * no-allocate, no-encode shape (the overlay is never filled on a pass).
11
25
  */
12
- export function diffPngs(baseline, actual, pixelThreshold) {
13
- const baselinePng = PNG.sync.read(baseline);
14
- const actualPng = PNG.sync.read(actual);
15
- if (baselinePng.width !== actualPng.width ||
16
- baselinePng.height !== actualPng.height) {
17
- throw new ScreenshotSizeMismatchError({ width: baselinePng.width, height: baselinePng.height }, { width: actualPng.width, height: actualPng.height });
18
- }
19
- // SSIM and pixelmatch both assume tightly-packed RGBA (4 bytes/pixel). If a
20
- // future pngjs option changed channel depth, the buffer view and the
21
- // width/height would silently disagree and produce a garbage score rather
22
- // than throwing. Fail loudly instead.
23
- assertPackedRgba(baselinePng);
24
- assertPackedRgba(actualPng);
25
- const diffPng = new PNG({
26
- width: baselinePng.width,
27
- height: baselinePng.height,
28
- });
29
- const diffPixels = pixelmatch(baselinePng.data, actualPng.data, diffPng.data, baselinePng.width, baselinePng.height, { threshold: pixelThreshold });
26
+ export function scoreDiff(baseline, actual, pixelThreshold) {
27
+ const decoded = decodePair(baseline, actual);
28
+ const { baselinePng, actualPng } = decoded;
29
+ // Count-only: passing `undefined` as the output buffer makes pixelmatch
30
+ // tally differing pixels without allocating or filling an overlay. The
31
+ // metric feeds `diffPixels`/`diffRatio` reporting, not a written image.
32
+ const diffPixels = pixelmatch(baselinePng.data, actualPng.data, undefined, baselinePng.width, baselinePng.height, { threshold: pixelThreshold });
30
33
  const totalPixels = baselinePng.width * baselinePng.height;
31
34
  const ssimResult = computeSsim({
32
35
  data: new Uint8ClampedArray(baselinePng.data.buffer, baselinePng.data.byteOffset, baselinePng.data.byteLength),
@@ -38,13 +41,60 @@ export function diffPngs(baseline, actual, pixelThreshold) {
38
41
  height: actualPng.height,
39
42
  }, { ssim: 'bezkrovny' });
40
43
  return {
41
- diffPng: PNG.sync.write(diffPng),
42
- diffPixels,
43
- totalPixels,
44
- diffRatio: diffPixels / totalPixels,
45
- ssimScore: ssimResult.mssim,
44
+ score: {
45
+ diffPixels,
46
+ totalPixels,
47
+ diffRatio: diffPixels / totalPixels,
48
+ ssimScore: ssimResult.mssim,
49
+ },
50
+ decoded,
46
51
  };
47
52
  }
53
+ /**
54
+ * Renders the red-highlight overlay PNG for the report from the already-decoded
55
+ * pair {@link scoreDiff} produced. This is the expensive half (pixelmatch
56
+ * fills an output buffer and `PNG.sync.write` deflate-encodes it) so it runs
57
+ * only on the changed/failed branch where the diff image is actually written to
58
+ * disk.
59
+ *
60
+ * Takes the {@link DecodedPair} rather than raw buffers so it reuses
61
+ * {@link scoreDiff}'s decode: the changed branch reads the image pair once, not
62
+ * twice. The dimensions/packing invariants were already enforced by
63
+ * {@link decodePair} when the pair was decoded, so no re-validation is needed
64
+ * here. pixelmatch's count is output-independent, so filling the overlay here
65
+ * cannot disturb the `diffPixels` {@link scoreDiff} already reported.
66
+ */
67
+ export function renderDiffOverlay(decoded, pixelThreshold) {
68
+ const { baselinePng, actualPng } = decoded;
69
+ const diffPng = new PNG({
70
+ width: baselinePng.width,
71
+ height: baselinePng.height,
72
+ });
73
+ pixelmatch(baselinePng.data, actualPng.data, diffPng.data, baselinePng.width, baselinePng.height, { threshold: pixelThreshold });
74
+ return PNG.sync.write(diffPng);
75
+ }
76
+ /**
77
+ * Decodes both PNG buffers and enforces the invariants the diff phases
78
+ * share: dimensions must match and the pixel data must be tightly-packed
79
+ * RGBA. Dimension mismatch is a regression on its own. Fail loudly. Runs
80
+ * once per comparison inside {@link scoreDiff}; {@link renderDiffOverlay}
81
+ * reuses the result instead of decoding again.
82
+ */
83
+ function decodePair(baseline, actual) {
84
+ const baselinePng = PNG.sync.read(baseline);
85
+ const actualPng = PNG.sync.read(actual);
86
+ if (baselinePng.width !== actualPng.width ||
87
+ baselinePng.height !== actualPng.height) {
88
+ throw new ScreenshotSizeMismatchError({ width: baselinePng.width, height: baselinePng.height }, { width: actualPng.width, height: actualPng.height });
89
+ }
90
+ // SSIM and pixelmatch both assume tightly-packed RGBA (4 bytes/pixel). If a
91
+ // future pngjs option changed channel depth, the buffer view and the
92
+ // width/height would silently disagree and produce a garbage score rather
93
+ // than throwing. Fail loudly instead.
94
+ assertPackedRgba(baselinePng);
95
+ assertPackedRgba(actualPng);
96
+ return { baselinePng, actualPng };
97
+ }
48
98
  function assertPackedRgba(png) {
49
99
  const expected = png.width * png.height * 4;
50
100
  if (png.data.byteLength !== expected) {
@@ -1 +1 @@
1
- {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/screenshots/diff.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/screenshots/diff.ts"],"names":[],"mappings":"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"}
package/dist/util.d.ts CHANGED
@@ -9,8 +9,29 @@ export declare function parseHostPort(url: string): {
9
9
  };
10
10
  /**
11
11
  * Resolves `true` if a TCP connection to `host:port` opens within `timeoutMs`,
12
- * `false` on error or timeout. Used to gate dev-server readiness without an
13
- * HTTP round-trip, so self-signed certs and 404s don't block.
12
+ * `false` on error or timeout. This is a cheap up/down LIVENESS check, not a
13
+ * readiness gate: `supervise.ts`'s `probeAllHealthchecks` polls it against dev
14
+ * servers that were already proven to be serving routes at startup (via
15
+ * {@link probeHttp}), so once a server is HTTP-readiness-verified a bare TCP
16
+ * accept is sufficient to confirm the process is still up. The false-ready
17
+ * caveat that makes a TCP accept too weak for a cold-booting dev server (see
18
+ * {@link probeHttp}) does not apply here. The server is past boot. Staying on
19
+ * TCP also keeps the liveness poll agnostic to self-signed HTTPS certs and
20
+ * non-2xx routes, which `fetch` (and thus `probeHttp`) would reject.
14
21
  */
15
22
  export declare function probeTcp(host: string, port: number, timeoutMs: number): Promise<boolean>;
23
+ /**
24
+ * Resolves `true` if an HTTP `GET` to `url` returns any non-5xx status within
25
+ * `timeoutMs`, `false` on a 5xx status, connection refusal, TLS/DNS failure, or
26
+ * timeout. Unlike {@link probeTcp}, this proves the server is actually *serving*
27
+ * routes, a dev server (Vite/Next/webpack) binds its socket well before the
28
+ * bundle finishes compiling, so a bare TCP accept can succeed while requests
29
+ * still 503 or hang. Any received response (2xx/3xx/4xx) means it is up.
30
+ *
31
+ * Redirects are not followed (`redirect: 'manual'`): a 3xx already proves the
32
+ * server is serving, and chasing `Location` risks an external host or a slow
33
+ * login page. Note this does *not* accept self-signed HTTPS certs (fetch
34
+ * rejects them). An http health-check URL is expected.
35
+ */
36
+ export declare function probeHttp(url: string, timeoutMs: number): Promise<boolean>;
16
37
  //# sourceMappingURL=util.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAGA,wCAAwC;AACxC,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI/C;AAED,kEAAkE;AAClE,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO/D;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAQzE;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAalB"}
1
+ {"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAGA,wCAAwC;AACxC,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI/C;AAED,kEAAkE;AAClE,wBAAsB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO/D;AAED,sEAAsE;AACtE,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAQzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CACtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAalB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,SAAS,CAC7B,GAAG,EAAE,MAAM,EACX,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC,CAiBlB"}
package/dist/util.js CHANGED
@@ -28,8 +28,15 @@ export function parseHostPort(url) {
28
28
  }
29
29
  /**
30
30
  * Resolves `true` if a TCP connection to `host:port` opens within `timeoutMs`,
31
- * `false` on error or timeout. Used to gate dev-server readiness without an
32
- * HTTP round-trip, so self-signed certs and 404s don't block.
31
+ * `false` on error or timeout. This is a cheap up/down LIVENESS check, not a
32
+ * readiness gate: `supervise.ts`'s `probeAllHealthchecks` polls it against dev
33
+ * servers that were already proven to be serving routes at startup (via
34
+ * {@link probeHttp}), so once a server is HTTP-readiness-verified a bare TCP
35
+ * accept is sufficient to confirm the process is still up. The false-ready
36
+ * caveat that makes a TCP accept too weak for a cold-booting dev server (see
37
+ * {@link probeHttp}) does not apply here. The server is past boot. Staying on
38
+ * TCP also keeps the liveness poll agnostic to self-signed HTTPS certs and
39
+ * non-2xx routes, which `fetch` (and thus `probeHttp`) would reject.
33
40
  */
34
41
  export function probeTcp(host, port, timeoutMs) {
35
42
  return new Promise((resolveProbe) => {
@@ -45,4 +52,37 @@ export function probeTcp(host, port, timeoutMs) {
45
52
  socket.setTimeout(timeoutMs);
46
53
  });
47
54
  }
55
+ /**
56
+ * Resolves `true` if an HTTP `GET` to `url` returns any non-5xx status within
57
+ * `timeoutMs`, `false` on a 5xx status, connection refusal, TLS/DNS failure, or
58
+ * timeout. Unlike {@link probeTcp}, this proves the server is actually *serving*
59
+ * routes, a dev server (Vite/Next/webpack) binds its socket well before the
60
+ * bundle finishes compiling, so a bare TCP accept can succeed while requests
61
+ * still 503 or hang. Any received response (2xx/3xx/4xx) means it is up.
62
+ *
63
+ * Redirects are not followed (`redirect: 'manual'`): a 3xx already proves the
64
+ * server is serving, and chasing `Location` risks an external host or a slow
65
+ * login page. Note this does *not* accept self-signed HTTPS certs (fetch
66
+ * rejects them). An http health-check URL is expected.
67
+ */
68
+ export async function probeHttp(url, timeoutMs) {
69
+ const controller = new AbortController();
70
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
71
+ try {
72
+ const response = await fetch(url, {
73
+ method: 'GET',
74
+ redirect: 'manual',
75
+ signal: controller.signal,
76
+ });
77
+ // Drain the body so the socket doesn't linger in undici's connection pool.
78
+ await response.body?.cancel();
79
+ return response.status < 500;
80
+ }
81
+ catch {
82
+ return false;
83
+ }
84
+ finally {
85
+ clearTimeout(timer);
86
+ }
87
+ }
48
88
  //# sourceMappingURL=util.js.map
package/dist/util.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE5C,wCAAwC;AACxC,MAAM,UAAU,KAAK,CAAC,EAAU;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,kEAAkE;AAClE,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY;IAC3C,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI;QACtB,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;QACrB,CAAC,CAAC,MAAM,CAAC,QAAQ,KAAK,QAAQ;YAC5B,CAAC,CAAC,GAAG;YACL,CAAC,CAAC,EAAE,CAAC;IACT,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;AACzC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CACtB,IAAY,EACZ,IAAY,EACZ,SAAiB;IAEjB,OAAO,IAAI,OAAO,CAAC,CAAC,YAAY,EAAE,EAAE;QAClC,MAAM,MAAM,GAAG,gBAAgB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,CAAC,MAAe,EAAQ,EAAE;YACxC,MAAM,CAAC,kBAAkB,EAAE,CAAC;YAC5B,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,YAAY,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7C,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAE5C,wCAAwC;AACxC,MAAM,UAAU,KAAK,CAAC,EAAU;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;AACL,CAAC;AAED,kEAAkE;AAClE,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,IAAY;IAC3C,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,IAAI,CAAC,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI;QACtB,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;QACrB,CAAC,CAAC,MAAM,CAAC,QAAQ,KAAK,QAAQ;YAC5B,CAAC,CAAC,GAAG;YACL,CAAC,CAAC,EAAE,CAAC;IACT,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC;AACzC,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,QAAQ,CACtB,IAAY,EACZ,IAAY,EACZ,SAAiB;IAEjB,OAAO,IAAI,OAAO,CAAC,CAAC,YAAY,EAAE,EAAE;QAClC,MAAM,MAAM,GAAG,gBAAgB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,MAAM,OAAO,GAAG,CAAC,MAAe,EAAQ,EAAE;YACxC,MAAM,CAAC,kBAAkB,EAAE,CAAC;YAC5B,MAAM,CAAC,OAAO,EAAE,CAAC;YACjB,YAAY,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC,CAAC;QACF,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC5C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,MAAM,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7C,MAAM,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC;IAC/B,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,GAAW,EACX,SAAiB;IAEjB,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,SAAS,CAAC,CAAC;IAC9D,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YAChC,MAAM,EAAE,KAAK;YACb,QAAQ,EAAE,QAAQ;YAClB,MAAM,EAAE,UAAU,CAAC,MAAM;SAC1B,CAAC,CAAC;QACH,2EAA2E;QAC3E,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;QAC9B,OAAO,QAAQ,CAAC,MAAM,GAAG,GAAG,CAAC;IAC/B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tuffgal",
3
- "version": "0.2.0-alpha.2",
3
+ "version": "0.2.0-alpha.4",
4
4
  "description": "JSON-driven visual regression for web apps.",
5
5
  "license": "MIT",
6
6
  "author": "Nick Schneble",
@@ -13,10 +13,11 @@
13
13
  "build": "rm -rf dist && tsc -p tsconfig.build.json && mkdir -p dist/reporter/assets && cp src/reporter/assets/report.css src/reporter/assets/report.js dist/reporter/assets/",
14
14
  "format": "prettier --write \"**/*.{ts,tsx,json,md}\"",
15
15
  "install:browsers": "playwright install chromium",
16
- "lint": "eslint \"src/**/*.ts\" --max-warnings=0",
16
+ "lint": "eslint \"src/**/*.ts\" \"test/**/*.ts\" --max-warnings=0",
17
17
  "preview": "node --import tsx scripts/preview.ts",
18
- "prepublishOnly": "npm run lint && npm run typecheck && npm run build",
18
+ "prepublishOnly": "npm run lint && npm run typecheck && npm test && npm run build",
19
19
  "test": "node --import tsx --test \"src/**/*.test.ts\"",
20
+ "test:dom": "node --import tsx --test \"test/dom/**/*.test.ts\"",
20
21
  "typecheck": "tsc --noEmit"
21
22
  },
22
23
  "bin": {
@@ -71,10 +72,5 @@
71
72
  "tsx": "^4.22.4",
72
73
  "typescript": "~5.9.3",
73
74
  "typescript-eslint": "^8.49.0"
74
- },
75
- "allowScripts": {
76
- "esbuild@0.28.1": true,
77
- "fsevents@2.3.2": true,
78
- "fsevents@2.3.3": true
79
75
  }
80
76
  }