@ng-prism/plugin-visual-regression 22.2.0-beta.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.
Files changed (55) hide show
  1. package/README.md +106 -0
  2. package/dist/asset-url.d.ts +14 -0
  3. package/dist/asset-url.d.ts.map +1 -0
  4. package/dist/asset-url.js +25 -0
  5. package/dist/index.browser.d.ts +7 -0
  6. package/dist/index.browser.d.ts.map +1 -0
  7. package/dist/index.browser.js +8 -0
  8. package/dist/index.d.ts +7 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +5 -0
  11. package/dist/report-reader.d.ts +18 -0
  12. package/dist/report-reader.d.ts.map +1 -0
  13. package/dist/report-reader.js +55 -0
  14. package/dist/schematics/ng-add/index.d.ts +4 -0
  15. package/dist/schematics/ng-add/index.d.ts.map +1 -0
  16. package/dist/schematics/ng-add/index.js +25 -0
  17. package/dist/schematics/ng-add/schema.d.ts +4 -0
  18. package/dist/schematics/ng-add/schema.d.ts.map +1 -0
  19. package/dist/schematics/ng-add/schema.js +1 -0
  20. package/dist/thresholds.d.ts +16 -0
  21. package/dist/thresholds.d.ts.map +1 -0
  22. package/dist/thresholds.js +20 -0
  23. package/dist/visual-regression-header-badge.component.d.ts +14 -0
  24. package/dist/visual-regression-header-badge.component.d.ts.map +1 -0
  25. package/dist/visual-regression-header-badge.component.js +73 -0
  26. package/dist/visual-regression-panel.component.d.ts +22 -0
  27. package/dist/visual-regression-panel.component.d.ts.map +1 -0
  28. package/dist/visual-regression-panel.component.js +191 -0
  29. package/dist/visual-regression-plugin.browser.d.ts +11 -0
  30. package/dist/visual-regression-plugin.browser.d.ts.map +1 -0
  31. package/dist/visual-regression-plugin.browser.js +34 -0
  32. package/dist/visual-regression-plugin.d.ts +11 -0
  33. package/dist/visual-regression-plugin.d.ts.map +1 -0
  34. package/dist/visual-regression-plugin.js +67 -0
  35. package/dist/visual-regression.types.d.ts +97 -0
  36. package/dist/visual-regression.types.d.ts.map +1 -0
  37. package/dist/visual-regression.types.js +1 -0
  38. package/dist/vrt-bg.d.ts +37 -0
  39. package/dist/vrt-bg.d.ts.map +1 -0
  40. package/dist/vrt-bg.js +53 -0
  41. package/dist/vrt-compare.component.d.ts +70 -0
  42. package/dist/vrt-compare.component.d.ts.map +1 -0
  43. package/dist/vrt-compare.component.js +602 -0
  44. package/dist/vrt-summarize.d.ts +48 -0
  45. package/dist/vrt-summarize.d.ts.map +1 -0
  46. package/dist/vrt-summarize.js +97 -0
  47. package/dist/vrt-summary.component.d.ts +17 -0
  48. package/dist/vrt-summary.component.d.ts.map +1 -0
  49. package/dist/vrt-summary.component.js +67 -0
  50. package/dist/vrt-zoom.d.ts +36 -0
  51. package/dist/vrt-zoom.d.ts.map +1 -0
  52. package/dist/vrt-zoom.js +48 -0
  53. package/package.json +54 -0
  54. package/schematics/collection.json +10 -0
  55. package/schematics/ng-add/schema.json +16 -0
package/README.md ADDED
@@ -0,0 +1,106 @@
1
+ # @ng-prism/plugin-visual-regression
2
+
3
+ Visual regression plugin for [@ng-prism/core](https://github.com/dyingangel666/ng-prism). Reads a per-variant screenshot comparison report at build time and renders it inside the styleguide — so diffs live next to the component they belong to instead of in a CI log.
4
+
5
+ > **Full documentation:** [ng-prism Docs — Visual Regression Plugin](https://dyingangel666.github.io/ng-prism/#/plugins/visual-regression)
6
+
7
+ The plugin **only renders a report someone else produced.** It performs no image comparison of its own — that belongs to your runner, which owns the baselines and the pinned container. See [Visual Regression Testing](https://dyingangel666.github.io/ng-prism/#/guide/visual-regression) for how to capture screenshots that do not flake, including the `?capture=1` isolation mode.
8
+
9
+ ## Installation
10
+
11
+ ```bash
12
+ ng add @ng-prism/plugin-visual-regression
13
+ ```
14
+
15
+ Or manually: `npm install @ng-prism/plugin-visual-regression`
16
+
17
+ ### Peer Dependencies
18
+
19
+ | Package | Version |
20
+ | ---------------- | ----------- |
21
+ | `@ng-prism/core` | `^22.1.4-0` |
22
+ | `@angular/core` | `>=20.0.0` |
23
+
24
+ ## Setup
25
+
26
+ ```typescript
27
+ // ng-prism.config.ts
28
+ import { defineConfig } from '@ng-prism/core/config';
29
+ import { visualRegressionPlugin } from '@ng-prism/plugin-visual-regression';
30
+
31
+ export default defineConfig({
32
+ plugins: [
33
+ visualRegressionPlugin({
34
+ reportPath: 'coverage/my-lib/vrt-report.json',
35
+ assetBaseUrl: 'assets/',
36
+ }),
37
+ ],
38
+ });
39
+ ```
40
+
41
+ ## Options
42
+
43
+ | Option | Default | Description |
44
+ | -------------- | ------------------- | ---------------------------------------------------------------------------------- |
45
+ | `reportPath` | `'vrt-report.json'` | Path to the report JSON, relative to the workspace root. |
46
+ | `assetBaseUrl` | `''` | Prefix applied to the image paths in the report so they resolve against the build. |
47
+ | `thresholds` | `{ score: 100 }` | Minimum score before the header badge stops being green. |
48
+
49
+ ## Report Format
50
+
51
+ ```jsonc
52
+ {
53
+ "total": {
54
+ "auditedVariants": 85,
55
+ "auditedComponents": 10,
56
+ "unchanged": 84,
57
+ "changed": 1,
58
+ "sizeMismatch": 0,
59
+ "new": 0,
60
+ "maxDiffRatio": 0.5,
61
+ "score": 99
62
+ },
63
+ "byVariant": [
64
+ {
65
+ "className": "ButtonComponent",
66
+ "variantName": "Primary",
67
+ "variantIndex": 0,
68
+ "status": "changed",
69
+ "diffRatio": 0.5,
70
+ "baselinePath": "vrt/baseline/ButtonComponent/00-primary.png",
71
+ "currentPath": "vrt/current/ButtonComponent/00-primary.png",
72
+ "diffPath": "vrt/diff/ButtonComponent/00-primary.png",
73
+ "bg": "light",
74
+ "baselineBg": "light"
75
+ }
76
+ ]
77
+ }
78
+ ```
79
+
80
+ Results are matched to components by `className`. All three image paths are optional and the panel degrades gracefully: baseline + current gives a wipe comparison, baseline + diff falls back to comparing against the diff mask, and a single image is shown on its own.
81
+
82
+ `status: "new"` — a variant with no baseline yet — renders as a **neutral** state, never as a failure.
83
+
84
+ `bg` and `baselineBg` are optional and record the canvas background a capture was taken on — `bg` for this run, `baselineBg` for the stored baseline. A runner gets `bg` for free: it is the resolved `bg` on the variant it already read from `__PRISM_MANIFEST__`. Given them, the panel frames the images in that surface instead of the transparency checkerboard (which is what makes a dark variant's diff mask readable), and when the two differ it names the background change rather than leaving a 100% diff unexplained.
85
+
86
+ ## Serving the Images
87
+
88
+ The report references images by path; those files must be reachable from the built styleguide. Copy them in via the `assets` array of your Prism app's build target, then point `assetBaseUrl` at where they land.
89
+
90
+ `assetBaseUrl` is **prepended to the path in the report** — it is not a replacement for it. The three values have to line up:
91
+
92
+ ```jsonc
93
+ // angular.json — my-lib-prism build target
94
+ "assets": [
95
+ { "glob": "**/*", "input": "vrt", "output": "assets/vrt" }
96
+ ]
97
+ ```
98
+
99
+ | Recorded in the report | `assetBaseUrl` | Requested URL |
100
+ | ---------------------- | -------------- | ------------------------------ |
101
+ | `vrt/baseline/x.png` | `assets/` | `assets/vrt/baseline/x.png` ✅ |
102
+ | `vrt/baseline/x.png` | `vrt/` | `vrt/vrt/baseline/x.png` ❌ |
103
+
104
+ ## License
105
+
106
+ MIT
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Resolves an image path from the report against the served styleguide.
3
+ *
4
+ * The report records whatever paths the runner chose; where those files end up
5
+ * in the build output is a separate decision made in `angular.json`. This is
6
+ * the seam between the two, and it assumes nothing about either layout.
7
+ *
8
+ * Paths that carry their own origin — `https:`, `data:` and protocol-relative
9
+ * — are returned untouched. A root-relative path is *not* one of them: it is
10
+ * still relative to wherever the styleguide is served from, so it is joined to
11
+ * the base like any other path (without doubling the separator).
12
+ */
13
+ export declare function resolveAssetUrl(baseUrl: string, path: string | undefined): string | undefined;
14
+ //# sourceMappingURL=asset-url.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-url.d.ts","sourceRoot":"","sources":["../src/asset-url.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAC7B,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GAAG,SAAS,GACvB,MAAM,GAAG,SAAS,CAMpB"}
@@ -0,0 +1,25 @@
1
+ /** Matches anything that already addresses a location on its own. */
2
+ const ABSOLUTE = /^([a-z][a-z0-9+.-]*:|\/\/)/i;
3
+ /**
4
+ * Resolves an image path from the report against the served styleguide.
5
+ *
6
+ * The report records whatever paths the runner chose; where those files end up
7
+ * in the build output is a separate decision made in `angular.json`. This is
8
+ * the seam between the two, and it assumes nothing about either layout.
9
+ *
10
+ * Paths that carry their own origin — `https:`, `data:` and protocol-relative
11
+ * — are returned untouched. A root-relative path is *not* one of them: it is
12
+ * still relative to wherever the styleguide is served from, so it is joined to
13
+ * the base like any other path (without doubling the separator).
14
+ */
15
+ export function resolveAssetUrl(baseUrl, path) {
16
+ if (!path)
17
+ return undefined;
18
+ if (ABSOLUTE.test(path))
19
+ return path;
20
+ if (!baseUrl)
21
+ return path;
22
+ if (path.startsWith('/'))
23
+ return `${baseUrl.replace(/\/+$/, '')}${path}`;
24
+ return `${baseUrl.replace(/\/+$/, '')}/${path}`;
25
+ }
@@ -0,0 +1,7 @@
1
+ export { visualRegressionPlugin } from './visual-regression-plugin.browser.js';
2
+ export { resolveVrtThresholds, DEFAULT_VRT_THRESHOLDS } from './thresholds.js';
3
+ export { VisualRegressionPanelComponent } from './visual-regression-panel.component.js';
4
+ export { VisualRegressionHeaderBadgeComponent } from './visual-regression-header-badge.component.js';
5
+ export { resolveAssetUrl } from './asset-url.js';
6
+ export type { VisualRegressionPluginOptions, VrtComponentMeta, VrtManifestMeta, VrtReport, VrtStatus, VrtThresholds, VrtTotals, VrtVariantResult, } from './visual-regression.types.js';
7
+ //# sourceMappingURL=index.browser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.browser.d.ts","sourceRoot":"","sources":["../src/index.browser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,uCAAuC,CAAC;AAI/E,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAC;AACxF,OAAO,EAAE,oCAAoC,EAAE,MAAM,+CAA+C,CAAC;AACrG,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,SAAS,EACT,aAAa,EACb,SAAS,EACT,gBAAgB,GACjB,MAAM,8BAA8B,CAAC"}
@@ -0,0 +1,8 @@
1
+ export { visualRegressionPlugin } from './visual-regression-plugin.browser.js';
2
+ // `package.json` resolves "types" to index.d.ts for every condition, so this
3
+ // entry has to carry every value that file declares — otherwise a browser
4
+ // consumer type-checks clean and the bundler fails on a missing export.
5
+ export { resolveVrtThresholds, DEFAULT_VRT_THRESHOLDS } from './thresholds.js';
6
+ export { VisualRegressionPanelComponent } from './visual-regression-panel.component.js';
7
+ export { VisualRegressionHeaderBadgeComponent } from './visual-regression-header-badge.component.js';
8
+ export { resolveAssetUrl } from './asset-url.js';
@@ -0,0 +1,7 @@
1
+ export { visualRegressionPlugin } from './visual-regression-plugin.js';
2
+ export { resolveVrtThresholds, DEFAULT_VRT_THRESHOLDS } from './thresholds.js';
3
+ export { VisualRegressionPanelComponent } from './visual-regression-panel.component.js';
4
+ export { VisualRegressionHeaderBadgeComponent } from './visual-regression-header-badge.component.js';
5
+ export { resolveAssetUrl } from './asset-url.js';
6
+ export type { VisualRegressionPluginOptions, VrtComponentMeta, VrtManifestMeta, VrtReport, VrtStatus, VrtThresholds, VrtTotals, VrtVariantResult, } from './visual-regression.types.js';
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,sBAAsB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,oBAAoB,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAC/E,OAAO,EAAE,8BAA8B,EAAE,MAAM,wCAAwC,CAAC;AACxF,OAAO,EAAE,oCAAoC,EAAE,MAAM,+CAA+C,CAAC;AACrG,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,YAAY,EACV,6BAA6B,EAC7B,gBAAgB,EAChB,eAAe,EACf,SAAS,EACT,SAAS,EACT,aAAa,EACb,SAAS,EACT,gBAAgB,GACjB,MAAM,8BAA8B,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ export { visualRegressionPlugin } from './visual-regression-plugin.js';
2
+ export { resolveVrtThresholds, DEFAULT_VRT_THRESHOLDS } from './thresholds.js';
3
+ export { VisualRegressionPanelComponent } from './visual-regression-panel.component.js';
4
+ export { VisualRegressionHeaderBadgeComponent } from './visual-regression-header-badge.component.js';
5
+ export { resolveAssetUrl } from './asset-url.js';
@@ -0,0 +1,18 @@
1
+ import type { VrtTotals, VrtVariantResult } from './visual-regression.types.js';
2
+ /**
3
+ * All recorded results for one component, ordered by variant index.
4
+ *
5
+ * Components are matched on `className` — the report and the scanner agree on
6
+ * it exactly, so there is no need for the path-matching heuristics the coverage
7
+ * plugin uses.
8
+ *
9
+ * The report's shape is checked rather than trusted. It is written by someone
10
+ * else's runner, and this runs inside `onComponentScanned`, where a throw is
11
+ * rethrown by the plugin runner and fails the whole styleguide build. A report
12
+ * that is merely *missing* already degrades to "no results"; a truncated or
13
+ * malformed one must not be punished harder than a missing one.
14
+ */
15
+ export declare function readVariantsForComponent(reportPath: string, className: string): VrtVariantResult[];
16
+ export declare function readTotals(reportPath: string): VrtTotals | null;
17
+ export declare function clearReportCache(): void;
18
+ //# sourceMappingURL=report-reader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report-reader.d.ts","sourceRoot":"","sources":["../src/report-reader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,SAAS,EACT,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;AAsBtC;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CACtC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,GAChB,gBAAgB,EAAE,CAUpB;AAED,wBAAgB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,CAG/D;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
@@ -0,0 +1,55 @@
1
+ import { readFileSync, statSync } from 'node:fs';
2
+ const cache = new Map();
3
+ /**
4
+ * Reads and parses the report, memoised on the file's mtime so a watch-mode
5
+ * rebuild picks up a fresh run without re-parsing on every scanned component.
6
+ */
7
+ function loadReport(reportPath) {
8
+ try {
9
+ const mtime = statSync(reportPath).mtimeMs;
10
+ const cached = cache.get(reportPath);
11
+ if (cached && cached.mtime === mtime)
12
+ return cached.data;
13
+ const parsed = JSON.parse(readFileSync(reportPath, 'utf-8'));
14
+ cache.set(reportPath, { mtime, data: parsed });
15
+ return parsed;
16
+ }
17
+ catch {
18
+ return null;
19
+ }
20
+ }
21
+ /**
22
+ * All recorded results for one component, ordered by variant index.
23
+ *
24
+ * Components are matched on `className` — the report and the scanner agree on
25
+ * it exactly, so there is no need for the path-matching heuristics the coverage
26
+ * plugin uses.
27
+ *
28
+ * The report's shape is checked rather than trusted. It is written by someone
29
+ * else's runner, and this runs inside `onComponentScanned`, where a throw is
30
+ * rethrown by the plugin runner and fails the whole styleguide build. A report
31
+ * that is merely *missing* already degrades to "no results"; a truncated or
32
+ * malformed one must not be punished harder than a missing one.
33
+ */
34
+ export function readVariantsForComponent(reportPath, className) {
35
+ const entries = loadReport(reportPath)?.byVariant;
36
+ if (!Array.isArray(entries))
37
+ return [];
38
+ return entries
39
+ .filter((entry) => isRecord(entry) && entry['className'] === className)
40
+ .sort((a, b) => variantIndexOf(a) - variantIndexOf(b));
41
+ }
42
+ export function readTotals(reportPath) {
43
+ const total = loadReport(reportPath)?.total;
44
+ return isRecord(total) ? total : null;
45
+ }
46
+ export function clearReportCache() {
47
+ cache.clear();
48
+ }
49
+ function isRecord(value) {
50
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
51
+ }
52
+ /** Missing or non-numeric indices sort first rather than poisoning the sort. */
53
+ function variantIndexOf(variant) {
54
+ return typeof variant.variantIndex === 'number' ? variant.variantIndex : 0;
55
+ }
@@ -0,0 +1,4 @@
1
+ import { type Rule } from '@angular-devkit/schematics';
2
+ import type { NgAddSchemaOptions } from './schema.js';
3
+ export declare function ngAdd(options: NgAddSchemaOptions): Rule;
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/schematics/ng-add/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,IAAI,EAGV,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAyBtD,wBAAgB,KAAK,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI,CAEvD"}
@@ -0,0 +1,25 @@
1
+ import { chain, } from '@angular-devkit/schematics';
2
+ import { addPluginToConfig } from '@ng-prism/core/schematics/utils';
3
+ const PLUGIN_IMPORT_NAME = 'visualRegressionPlugin';
4
+ const PLUGIN_IMPORT_FROM = '@ng-prism/plugin-visual-regression';
5
+ const PLUGIN_CALL = 'visualRegressionPlugin()';
6
+ function injectPlugin(options) {
7
+ return (tree, context) => {
8
+ const configPath = options.configPath ?? 'ng-prism.config.ts';
9
+ const changed = addPluginToConfig(tree, configPath, {
10
+ importName: PLUGIN_IMPORT_NAME,
11
+ importFrom: PLUGIN_IMPORT_FROM,
12
+ call: PLUGIN_CALL,
13
+ });
14
+ if (changed) {
15
+ context.logger.info(` ${PLUGIN_IMPORT_FROM} → added to ${configPath}`);
16
+ }
17
+ else {
18
+ context.logger.info(` ${PLUGIN_IMPORT_FROM} already configured — skipped`);
19
+ }
20
+ return tree;
21
+ };
22
+ }
23
+ export function ngAdd(options) {
24
+ return chain([injectPlugin(options)]);
25
+ }
@@ -0,0 +1,4 @@
1
+ export interface NgAddSchemaOptions {
2
+ configPath?: string;
3
+ }
4
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/schematics/ng-add/schema.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,16 @@
1
+ import type { VrtThresholds } from './visual-regression.types.js';
2
+ /**
3
+ * Threshold resolution lives in its own module so both entry points can export
4
+ * it. `visual-regression-plugin.ts` carries the build-time hooks that read the
5
+ * report from disk and must never be reachable from a browser bundle, so
6
+ * `index.browser.ts` cannot re-export anything through it — but the public type
7
+ * surface is shared between the two, and a symbol the `.d.ts` promises has to
8
+ * exist in whichever bundle the consumer actually loads.
9
+ */
10
+ /**
11
+ * Any regression is worth seeing, so the badge only stays green at a perfect
12
+ * score. It degrades to warn, then danger, as the score falls.
13
+ */
14
+ export declare const DEFAULT_VRT_THRESHOLDS: VrtThresholds;
15
+ export declare function resolveVrtThresholds(input?: number | Partial<VrtThresholds>): VrtThresholds;
16
+ //# sourceMappingURL=thresholds.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"thresholds.d.ts","sourceRoot":"","sources":["../src/thresholds.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE;;;;;;;GAOG;AAEH;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,aAA8B,CAAC;AAEpE,wBAAgB,oBAAoB,CAClC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,GACtC,aAAa,CAIf"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Threshold resolution lives in its own module so both entry points can export
3
+ * it. `visual-regression-plugin.ts` carries the build-time hooks that read the
4
+ * report from disk and must never be reachable from a browser bundle, so
5
+ * `index.browser.ts` cannot re-export anything through it — but the public type
6
+ * surface is shared between the two, and a symbol the `.d.ts` promises has to
7
+ * exist in whichever bundle the consumer actually loads.
8
+ */
9
+ /**
10
+ * Any regression is worth seeing, so the badge only stays green at a perfect
11
+ * score. It degrades to warn, then danger, as the score falls.
12
+ */
13
+ export const DEFAULT_VRT_THRESHOLDS = { score: 100 };
14
+ export function resolveVrtThresholds(input) {
15
+ if (input === undefined)
16
+ return { ...DEFAULT_VRT_THRESHOLDS };
17
+ if (typeof input === 'number')
18
+ return { score: input };
19
+ return { ...DEFAULT_VRT_THRESHOLDS, ...input };
20
+ }
@@ -0,0 +1,14 @@
1
+ import * as i0 from "@angular/core";
2
+ type Variant = 'ok' | 'warn' | 'danger';
3
+ export declare class VisualRegressionHeaderBadgeComponent {
4
+ private readonly manifest;
5
+ protected readonly data: import("@angular/core").Signal<{
6
+ score: number;
7
+ variant: Variant;
8
+ title: string;
9
+ } | null>;
10
+ static ɵfac: i0.ɵɵFactoryDeclaration<VisualRegressionHeaderBadgeComponent, never>;
11
+ static ɵcmp: i0.ɵɵComponentDeclaration<VisualRegressionHeaderBadgeComponent, "prism-visual-regression-header-badge", never, {}, {}, never, never, true, never>;
12
+ }
13
+ export {};
14
+ //# sourceMappingURL=visual-regression-header-badge.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual-regression-header-badge.component.d.ts","sourceRoot":"","sources":["../src/visual-regression-header-badge.component.ts"],"names":[],"mappings":";AAUA,KAAK,OAAO,GAAG,IAAI,GAAG,MAAM,GAAG,QAAQ,CAAC;AAExC,qBAqFa,oCAAoC;IAC/C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA2C;IAEpE,SAAS,CAAC,QAAQ,CAAC,IAAI;;;;cA2BpB;yCA9BQ,oCAAoC;2CAApC,oCAAoC;CA+BhD"}
@@ -0,0 +1,73 @@
1
+ import { ChangeDetectionStrategy, Component, computed, inject, } from '@angular/core';
2
+ import { PRISM_MANIFEST } from '@ng-prism/core/plugin';
3
+ import * as i0 from "@angular/core";
4
+ function VisualRegressionHeaderBadgeComponent_Conditional_0_Template(rf, ctx) { if (rf & 1) {
5
+ i0.ɵɵdomElementStart(0, "span", 1);
6
+ i0.ɵɵdomElement(1, "span", 2);
7
+ i0.ɵɵdomElementStart(2, "span", 3);
8
+ i0.ɵɵtext(3, "VRT");
9
+ i0.ɵɵdomElementEnd();
10
+ i0.ɵɵdomElementStart(4, "span", 4);
11
+ i0.ɵɵtext(5);
12
+ i0.ɵɵdomElementStart(6, "small");
13
+ i0.ɵɵtext(7, "%");
14
+ i0.ɵɵdomElementEnd()()();
15
+ } if (rf & 2) {
16
+ const d_r1 = ctx;
17
+ i0.ɵɵclassProp("vrt-pill--ok", d_r1.variant === "ok")("vrt-pill--warn", d_r1.variant === "warn")("vrt-pill--danger", d_r1.variant === "danger");
18
+ i0.ɵɵdomProperty("title", d_r1.title);
19
+ i0.ɵɵadvance(5);
20
+ i0.ɵɵtextInterpolate(d_r1.score);
21
+ } }
22
+ export class VisualRegressionHeaderBadgeComponent {
23
+ manifest = inject(PRISM_MANIFEST);
24
+ data = computed(() => {
25
+ const meta = this.manifest.meta?.['visualRegression'];
26
+ if (!meta?.found || !meta.total)
27
+ return null;
28
+ const { total, thresholds } = meta;
29
+ const variant = total.score >= thresholds.score
30
+ ? 'ok'
31
+ : total.score >= thresholds.score * 0.75
32
+ ? 'warn'
33
+ : 'danger';
34
+ // `new` and `excluded` stay out of the headline counts on purpose — neither
35
+ // has regressed, so both are reported but never framed as a fault.
36
+ const parts = [
37
+ `Visual regression: ${total.score}% unchanged (target ${thresholds.score}%)`,
38
+ `${total.unchanged} unchanged · ${total.changed} changed · ${total.sizeMismatch} resized`,
39
+ `${total.new} new (no baseline yet)`,
40
+ ];
41
+ if (total.excluded)
42
+ parts.push(`${total.excluded} excluded`);
43
+ parts.push(`${total.auditedVariants} variants across ${total.auditedComponents} components`);
44
+ return { score: total.score, variant, title: parts.join('\n') };
45
+ }, /* @ts-ignore */
46
+ ...(ngDevMode ? [{ debugName: "data" }] : /* istanbul ignore next */ []));
47
+ static ɵfac = function VisualRegressionHeaderBadgeComponent_Factory(__ngFactoryType__) { return new (__ngFactoryType__ || VisualRegressionHeaderBadgeComponent)(); };
48
+ static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: VisualRegressionHeaderBadgeComponent, selectors: [["prism-visual-regression-header-badge"]], decls: 1, vars: 1, consts: [[1, "vrt-pill", 3, "vrt-pill--ok", "vrt-pill--warn", "vrt-pill--danger", "title"], [1, "vrt-pill", 3, "title"], [1, "vrt-pill__dot"], [1, "vrt-pill__label"], [1, "vrt-pill__value"]], template: function VisualRegressionHeaderBadgeComponent_Template(rf, ctx) { if (rf & 1) {
49
+ i0.ɵɵconditionalCreate(0, VisualRegressionHeaderBadgeComponent_Conditional_0_Template, 8, 8, "span", 0);
50
+ } if (rf & 2) {
51
+ let tmp_0_0;
52
+ i0.ɵɵconditional((tmp_0_0 = ctx.data()) ? 0 : -1, tmp_0_0);
53
+ } }, styles: ["[_nghost-%COMP%] { display: inline-flex; align-items: center; }\n\n .vrt-pill[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: var(--font-mono);\n font-size: var(--fs-xs);\n padding: 2px 10px;\n border-radius: 9999px;\n line-height: 1.4;\n border: 1px solid var(--prism-border);\n background: var(--prism-input-bg);\n color: var(--prism-text-muted);\n white-space: nowrap;\n }\n\n .vrt-pill__dot[_ngcontent-%COMP%] {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: currentColor;\n }\n\n .vrt-pill__label[_ngcontent-%COMP%] {\n color: var(--prism-text-ghost);\n letter-spacing: 0.02em;\n }\n\n .vrt-pill__value[_ngcontent-%COMP%] {\n color: var(--prism-text);\n font-weight: 600;\n }\n .vrt-pill__value[_ngcontent-%COMP%] small[_ngcontent-%COMP%] {\n font-size: 9px;\n margin-left: 1px;\n color: var(--prism-text-muted);\n font-weight: 500;\n }\n\n .vrt-pill--ok[_ngcontent-%COMP%] {\n color: var(--prism-success);\n border-color: color-mix(in srgb, var(--prism-success) 35%, transparent);\n background: color-mix(in srgb, var(--prism-success) 12%, transparent);\n }\n .vrt-pill--ok[_ngcontent-%COMP%] .vrt-pill__label[_ngcontent-%COMP%], \n .vrt-pill--ok[_ngcontent-%COMP%] .vrt-pill__value[_ngcontent-%COMP%] { color: var(--prism-success); }\n\n .vrt-pill--warn[_ngcontent-%COMP%] {\n color: var(--prism-warn);\n border-color: color-mix(in srgb, var(--prism-warn) 35%, transparent);\n background: color-mix(in srgb, var(--prism-warn) 12%, transparent);\n }\n .vrt-pill--warn[_ngcontent-%COMP%] .vrt-pill__label[_ngcontent-%COMP%], \n .vrt-pill--warn[_ngcontent-%COMP%] .vrt-pill__value[_ngcontent-%COMP%] { color: var(--prism-warn); }\n\n .vrt-pill--danger[_ngcontent-%COMP%] {\n color: var(--prism-danger);\n border-color: color-mix(in srgb, var(--prism-danger) 35%, transparent);\n background: color-mix(in srgb, var(--prism-danger) 12%, transparent);\n }\n .vrt-pill--danger[_ngcontent-%COMP%] .vrt-pill__label[_ngcontent-%COMP%], \n .vrt-pill--danger[_ngcontent-%COMP%] .vrt-pill__value[_ngcontent-%COMP%] { color: var(--prism-danger); }"] });
54
+ }
55
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(VisualRegressionHeaderBadgeComponent, [{
56
+ type: Component,
57
+ args: [{ selector: 'prism-visual-regression-header-badge', standalone: true, changeDetection: ChangeDetectionStrategy.OnPush, template: `
58
+ @if (data(); as d) {
59
+ <span
60
+ class="vrt-pill"
61
+ [class.vrt-pill--ok]="d.variant === 'ok'"
62
+ [class.vrt-pill--warn]="d.variant === 'warn'"
63
+ [class.vrt-pill--danger]="d.variant === 'danger'"
64
+ [title]="d.title"
65
+ >
66
+ <span class="vrt-pill__dot"></span>
67
+ <span class="vrt-pill__label">VRT</span>
68
+ <span class="vrt-pill__value">{{ d.score }}<small>%</small></span>
69
+ </span>
70
+ }
71
+ `, styles: ["\n :host { display: inline-flex; align-items: center; }\n\n .vrt-pill {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-family: var(--font-mono);\n font-size: var(--fs-xs);\n padding: 2px 10px;\n border-radius: 9999px;\n line-height: 1.4;\n border: 1px solid var(--prism-border);\n background: var(--prism-input-bg);\n color: var(--prism-text-muted);\n white-space: nowrap;\n }\n\n .vrt-pill__dot {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: currentColor;\n }\n\n .vrt-pill__label {\n color: var(--prism-text-ghost);\n letter-spacing: 0.02em;\n }\n\n .vrt-pill__value {\n color: var(--prism-text);\n font-weight: 600;\n }\n .vrt-pill__value small {\n font-size: 9px;\n margin-left: 1px;\n color: var(--prism-text-muted);\n font-weight: 500;\n }\n\n .vrt-pill--ok {\n color: var(--prism-success);\n border-color: color-mix(in srgb, var(--prism-success) 35%, transparent);\n background: color-mix(in srgb, var(--prism-success) 12%, transparent);\n }\n .vrt-pill--ok .vrt-pill__label,\n .vrt-pill--ok .vrt-pill__value { color: var(--prism-success); }\n\n .vrt-pill--warn {\n color: var(--prism-warn);\n border-color: color-mix(in srgb, var(--prism-warn) 35%, transparent);\n background: color-mix(in srgb, var(--prism-warn) 12%, transparent);\n }\n .vrt-pill--warn .vrt-pill__label,\n .vrt-pill--warn .vrt-pill__value { color: var(--prism-warn); }\n\n .vrt-pill--danger {\n color: var(--prism-danger);\n border-color: color-mix(in srgb, var(--prism-danger) 35%, transparent);\n background: color-mix(in srgb, var(--prism-danger) 12%, transparent);\n }\n .vrt-pill--danger .vrt-pill__label,\n .vrt-pill--danger .vrt-pill__value { color: var(--prism-danger); }\n "] }]
72
+ }], null, null); })();
73
+ (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(VisualRegressionHeaderBadgeComponent, { className: "VisualRegressionHeaderBadgeComponent", filePath: "visual-regression-header-badge.component.ts", lineNumber: 98 }); })();
@@ -0,0 +1,22 @@
1
+ import type { VrtComponentMeta, VrtVariantResult } from './visual-regression.types.js';
2
+ import * as i0 from "@angular/core";
3
+ export declare class VisualRegressionPanelComponent {
4
+ readonly activeComponent: import("@angular/core").InputSignal<unknown>;
5
+ protected readonly selectedKey: import("@angular/core").WritableSignal<string | null>;
6
+ protected readonly meta: import("@angular/core").Signal<VrtComponentMeta | null>;
7
+ protected readonly assetBaseUrl: import("@angular/core").Signal<string>;
8
+ private readonly variants;
9
+ protected readonly summary: import("@angular/core").Signal<import("./vrt-summarize.js").VrtSummary>;
10
+ protected readonly rows: import("@angular/core").Signal<{
11
+ key: string;
12
+ variant: VrtVariantResult;
13
+ label: string;
14
+ statusLabel: string;
15
+ tone: import("./vrt-summarize.js").VrtTone;
16
+ diffLabel: string;
17
+ }[]>;
18
+ protected readonly selected: import("@angular/core").Signal<VrtVariantResult | null>;
19
+ static ɵfac: i0.ɵɵFactoryDeclaration<VisualRegressionPanelComponent, never>;
20
+ static ɵcmp: i0.ɵɵComponentDeclaration<VisualRegressionPanelComponent, "prism-visual-regression-panel", never, { "activeComponent": { "alias": "activeComponent"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
21
+ }
22
+ //# sourceMappingURL=visual-regression-panel.component.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"visual-regression-panel.component.d.ts","sourceRoot":"","sources":["../src/visual-regression-panel.component.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EACV,gBAAgB,EAChB,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;;AAEtC,qBA2La,8BAA8B;IACzC,QAAQ,CAAC,eAAe,+CAAwB;IAEhD,SAAS,CAAC,QAAQ,CAAC,WAAW,wDAA+B;IAE7D,SAAS,CAAC,QAAQ,CAAC,IAAI,0DASpB;IAEH,SAAS,CAAC,QAAQ,CAAC,YAAY,yCAE7B;IAEF,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAA+C;IAExE,SAAS,CAAC,QAAQ,CAAC,OAAO,0EAA8C;IAExE,SAAS,CAAC,QAAQ,CAAC,IAAI;;;;;;;SAYrB;IAEF,SAAS,CAAC,QAAQ,CAAC,QAAQ,0DAWxB;yCAjDQ,8BAA8B;2CAA9B,8BAA8B;CAkD1C"}