vitest-plugin-vis 1.0.5 → 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.
Files changed (72) hide show
  1. package/esm/client/align_images.d.ts +2 -0
  2. package/esm/client/align_images.d.ts.map +1 -0
  3. package/esm/client/align_images.js +10 -0
  4. package/esm/client/compare_image.d.ts +7 -0
  5. package/esm/client/compare_image.d.ts.map +1 -0
  6. package/esm/client/compare_image.js +13 -0
  7. package/esm/client/expect/to_match_image_snapshot.d.ts.map +1 -1
  8. package/esm/client/expect/to_match_image_snapshot.js +2 -15
  9. package/esm/client/image_data.d.ts +3 -0
  10. package/esm/client/image_data.d.ts.map +1 -0
  11. package/esm/client/image_data.js +33 -0
  12. package/esm/client/image_resizer.d.ts +9 -0
  13. package/esm/client/image_resizer.d.ts.map +1 -0
  14. package/esm/client/image_resizer.js +49 -0
  15. package/esm/client/match_image_snapshot.d.ts +8 -0
  16. package/esm/client/match_image_snapshot.d.ts.map +1 -0
  17. package/esm/client/match_image_snapshot.js +63 -0
  18. package/esm/client/match_image_snapshot.logic.d.ts +3 -0
  19. package/esm/client/match_image_snapshot.logic.d.ts.map +1 -0
  20. package/esm/client/match_image_snapshot.logic.js +11 -0
  21. package/esm/client/task_id.d.ts +3 -0
  22. package/esm/client/task_id.d.ts.map +1 -1
  23. package/esm/client/task_id.js +3 -0
  24. package/esm/client/vitest_browser_context_proxy.d.ts +32 -0
  25. package/esm/client/vitest_browser_context_proxy.d.ts.map +1 -0
  26. package/esm/client/vitest_browser_context_proxy.js +11 -0
  27. package/esm/client.d.ts +1 -1
  28. package/esm/client.d.ts.map +1 -1
  29. package/esm/client.js +1 -1
  30. package/esm/config/vis.d.ts +2 -0
  31. package/esm/config/vis.d.ts.map +1 -1
  32. package/esm/server/browser_provider/playwright.js +1 -1
  33. package/esm/server/browser_provider/webdriverio.js +1 -1
  34. package/esm/server/commands/extend.d.ts +5 -1
  35. package/esm/server/commands/extend.d.ts.map +1 -1
  36. package/esm/server/commands/extend.js +4 -0
  37. package/esm/server/commands/match_image_snapshot.d.ts.map +1 -1
  38. package/esm/server/commands/match_image_snapshot.js +3 -26
  39. package/esm/server/commands/prepare_image_snapshot_comparison.d.ts +20 -0
  40. package/esm/server/commands/prepare_image_snapshot_comparison.d.ts.map +1 -0
  41. package/esm/server/commands/prepare_image_snapshot_comparison.js +32 -0
  42. package/esm/server/commands/write_image_snapshot.d.ts +6 -0
  43. package/esm/server/commands/write_image_snapshot.d.ts.map +1 -0
  44. package/esm/server/commands/write_image_snapshot.js +10 -0
  45. package/esm/server/snapshot.d.ts +7 -0
  46. package/esm/server/snapshot.d.ts.map +1 -0
  47. package/esm/server/snapshot.js +27 -0
  48. package/esm/shared/convert_threshold_unit.js +1 -1
  49. package/package.json +2 -2
  50. package/readme.md +2 -2
  51. package/src/client/align_images.ts +11 -0
  52. package/src/client/compare_image.ts +21 -0
  53. package/src/client/expect/to_match_image_snapshot.ts +2 -17
  54. package/src/client/image_data.ts +36 -0
  55. package/src/client/image_resizer.ts +59 -0
  56. package/src/client/match_image_snapshot.logic.ts +13 -0
  57. package/src/client/match_image_snapshot.ts +78 -0
  58. package/src/client/task_id.ts +3 -0
  59. package/src/client/vitest_browser_context_proxy.ts +44 -0
  60. package/src/client.ts +1 -1
  61. package/src/server/browser_provider/playwright.ts +1 -1
  62. package/src/server/browser_provider/webdriverio.ts +1 -1
  63. package/src/server/commands/extend.ts +9 -0
  64. package/src/server/commands/match_image_snapshot.ts +4 -40
  65. package/src/server/commands/prepare_image_snapshot_comparison.ts +55 -0
  66. package/src/server/commands/write_image_snapshot.ts +20 -0
  67. package/src/server/snapshot.ts +42 -0
  68. package/src/shared/convert_threshold_unit.ts +1 -1
  69. package/esm/client/expect/to_match_image_snapshot.logic.d.ts +0 -2
  70. package/esm/client/expect/to_match_image_snapshot.logic.d.ts.map +0 -1
  71. package/esm/client/expect/to_match_image_snapshot.logic.js +0 -11
  72. package/src/client/expect/to_match_image_snapshot.logic.ts +0 -11
@@ -0,0 +1,2 @@
1
+ export declare function alignImagesToSameSize(image1: ImageData, image2: ImageData): [image1: ImageData, image2: ImageData];
2
+ //# sourceMappingURL=align_images.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"align_images.d.ts","sourceRoot":"","sources":["../../src/client/align_images.ts"],"names":[],"mappings":"AAIA,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,CAMlH"}
@@ -0,0 +1,10 @@
1
+ import { getMaxSize } from "../shared/get_max_size.js";
2
+ import { isSameSize } from "../shared/is_same_size.js";
3
+ import { createImageResizer } from "./image_resizer.js";
4
+ export function alignImagesToSameSize(image1, image2) {
5
+ if (isSameSize(image1, image2))
6
+ return [image1, image2];
7
+ const size = getMaxSize(image1, image2);
8
+ const resize = createImageResizer(size);
9
+ return [resize(image1), resize(image2)];
10
+ }
@@ -0,0 +1,7 @@
1
+ import type { ImageSnapshotCompareOptions } from '../shared/types.ts';
2
+ export declare function compareImage(baselineImage: ImageData, resultImage: ImageData, { failureThreshold, failureThresholdType, diffOptions }?: ImageSnapshotCompareOptions): {
3
+ pass: boolean;
4
+ diffAmount: number;
5
+ diffImage: ImageData;
6
+ };
7
+ //# sourceMappingURL=compare_image.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compare_image.d.ts","sourceRoot":"","sources":["../../src/client/compare_image.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,oBAAoB,CAAA;AAErE,wBAAgB,YAAY,CAC3B,aAAa,EAAE,SAAS,EACxB,WAAW,EAAE,SAAS,EACtB,EAAE,gBAAoB,EAAE,oBAA8B,EAAE,WAAW,EAAE,GAAE,2BAAgC;;;;EAavG"}
@@ -0,0 +1,13 @@
1
+ import pixelmatch from 'pixelmatch';
2
+ import { convertThresholdUnit } from "../shared/convert_threshold_unit.js";
3
+ export function compareImage(baselineImage, resultImage, { failureThreshold = 0, failureThresholdType = 'pixel', diffOptions } = {}) {
4
+ const { width, height } = resultImage;
5
+ const diffImage = new ImageData(width, height);
6
+ const pixelDiff = pixelmatch(resultImage.data, baselineImage.data, diffImage.data, width, height, diffOptions);
7
+ const diffAmount = convertThresholdUnit({ failureThresholdType, width, height }, pixelDiff);
8
+ return {
9
+ pass: diffAmount <= failureThreshold,
10
+ diffAmount,
11
+ diffImage,
12
+ };
13
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"to_match_image_snapshot.d.ts","sourceRoot":"","sources":["../../../src/client/expect/to_match_image_snapshot.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAK5D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAA;AAErF,wBAAgB,oBAAoB;AACnC;;;GAGG;AACH,OAAO,EAAE,GAAG,EACZ,OAAO,CAAC,EAAE,2BAA2B,GAAG,SAAS,GAC/C,sBAAsB,CAgBxB"}
1
+ {"version":3,"file":"to_match_image_snapshot.d.ts","sourceRoot":"","sources":["../../../src/client/expect/to_match_image_snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAA;AAI5D,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,oCAAoC,CAAA;AAErF,wBAAgB,oBAAoB;AACnC;;;GAGG;AACH,OAAO,EAAE,GAAG,EACZ,OAAO,CAAC,EAAE,2BAA2B,GAAG,SAAS,GAC/C,sBAAsB,CAcxB"}
@@ -1,8 +1,6 @@
1
- import { commands } from '@vitest/browser/context';
2
1
  import { ctx } from "../ctx.js";
3
- import { toTaskId } from "../task_id.js";
2
+ import { matchImageSnapshot } from "../match_image_snapshot.js";
4
3
  import { success } from "./expectation_result.js";
5
- import { parseImageSnapshotSubject } from "./to_match_image_snapshot.logic.js";
6
4
  export function toMatchImageSnapshot(
7
5
  /**
8
6
  * The element or locator to take the snapshot of,
@@ -17,16 +15,5 @@ subject, options) {
17
15
  throw new Error('`toMatchImageSnapshot()` cannot be called in a concurrent test because ' +
18
16
  "concurrent tests run at the same time in the same iframe and affect each other's environment. ");
19
17
  }
20
- const s = parseImageSnapshotSubject(subject);
21
- const taskId = toTaskId(test);
22
- return toMatchImageSnapshotAsync(taskId, s, options).then(() => success);
23
- }
24
- async function toMatchImageSnapshotAsync(taskId, subject, options) {
25
- if (options?.customizeSnapshotId) {
26
- const index = await commands.imageSnapshotNextIndex(taskId);
27
- const { customizeSnapshotId, ...rest } = options;
28
- const snapshotFileId = customizeSnapshotId(taskId, index);
29
- return commands.matchImageSnapshot(taskId, subject, { ...rest, snapshotFileId });
30
- }
31
- return commands.matchImageSnapshot(taskId, subject, options);
18
+ return matchImageSnapshot(test, subject, options).then(() => success);
32
19
  }
@@ -0,0 +1,3 @@
1
+ export declare function toImageData(base64: string): Promise<ImageData>;
2
+ export declare function toDataURL(imageData: ImageData): Promise<string>;
3
+ //# sourceMappingURL=image_data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image_data.d.ts","sourceRoot":"","sources":["../../src/client/image_data.ts"],"names":[],"mappings":"AAAA,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,sBAkBzC;AAED,wBAAsB,SAAS,CAAC,SAAS,EAAE,SAAS,mBAenD"}
@@ -0,0 +1,33 @@
1
+ export function toImageData(base64) {
2
+ return new Promise((resolve, reject) => {
3
+ const img = new Image();
4
+ img.src = `data:image/png;base64,${base64}`;
5
+ img.onload = () => {
6
+ const canvas = document.createElement('canvas');
7
+ canvas.width = img.width;
8
+ canvas.height = img.height;
9
+ const ctx = canvas.getContext('2d');
10
+ ctx.drawImage(img, 0, 0);
11
+ resolve(ctx.getImageData(0, 0, canvas.width, canvas.height));
12
+ };
13
+ img.onerror = () => {
14
+ reject(new Error('Failed to load image'));
15
+ };
16
+ });
17
+ }
18
+ export async function toDataURL(imageData) {
19
+ const canvas = document.createElement('canvas');
20
+ canvas.width = imageData.width;
21
+ canvas.height = imageData.height;
22
+ const ctx = canvas.getContext('2d');
23
+ ctx.putImageData(imageData, 0, 0);
24
+ return new Promise((resolve) => {
25
+ canvas.toBlob((blob) => {
26
+ const reader = new FileReader();
27
+ reader.onload = () => {
28
+ resolve(reader.result);
29
+ };
30
+ reader.readAsDataURL(blob);
31
+ });
32
+ });
33
+ }
@@ -0,0 +1,9 @@
1
+ export declare const createImageResizer: ({ width, height }: {
2
+ width: number;
3
+ height: number;
4
+ }) => (image: ImageData) => ImageData;
5
+ export declare const createImageResizer2: ({ width, height }: {
6
+ width: number;
7
+ height: number;
8
+ }) => (image: ImageData) => ImageData;
9
+ //# sourceMappingURL=image_resizer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image_resizer.d.ts","sourceRoot":"","sources":["../../src/client/image_resizer.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,kBAAkB,sBACV;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,aAC7C,SAAS,cA2BhB,CAAA;AAGF,eAAO,MAAM,mBAAmB,sBACX;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,aAC7C,SAAS,cAsBhB,CAAA"}
@@ -0,0 +1,49 @@
1
+ import { isSameSize } from "../shared/is_same_size.js";
2
+ export const createImageResizer = ({ width, height }) => (image) => {
3
+ if (isSameSize(image, { width, height }))
4
+ return image;
5
+ const inArea = (x, y) => y <= image.height && x <= image.width;
6
+ const data = new Uint8ClampedArray(width * height * 4);
7
+ const result = new ImageData(data, width, height, { colorSpace: image.colorSpace });
8
+ for (let y = 0; y < height; y++) {
9
+ for (let x = 0; x < width; x++) {
10
+ const idx = (width * y + x) << 2;
11
+ if (inArea(x, y)) {
12
+ const old = (image.width * y + x) << 2;
13
+ result.data[idx] = image.data[old];
14
+ result.data[idx + 1] = image.data[old + 1];
15
+ result.data[idx + 2] = image.data[old + 2];
16
+ result.data[idx + 3] = image.data[old + 3];
17
+ }
18
+ else {
19
+ result.data[idx] = 0;
20
+ result.data[idx + 1] = 0;
21
+ result.data[idx + 2] = 0;
22
+ result.data[idx + 3] = 64;
23
+ }
24
+ }
25
+ }
26
+ return result;
27
+ };
28
+ // slower
29
+ export const createImageResizer2 = ({ width, height }) => (image) => {
30
+ if (isSameSize(image, { width, height }))
31
+ return image;
32
+ const inArea = (x, y) => y <= image.height && x <= image.width;
33
+ const data = new Uint8ClampedArray(width * height * 4);
34
+ const result = new ImageData(data, width, height, { colorSpace: image.colorSpace });
35
+ const copyBytes = image.width * 4;
36
+ for (let y = 0; y < height; y++) {
37
+ result.data.set(image.data.slice(y * image.width, copyBytes), y * width * 4);
38
+ for (let x = image.width; x < width; x++) {
39
+ const idx = (width * y + x) << 2;
40
+ if (!inArea(x, y)) {
41
+ result.data[idx] = 0;
42
+ result.data[idx + 1] = 0;
43
+ result.data[idx + 2] = 0;
44
+ result.data[idx + 3] = 64;
45
+ }
46
+ }
47
+ }
48
+ return result;
49
+ };
@@ -0,0 +1,8 @@
1
+ import type { Task } from 'vitest';
2
+ import type { ToMatchImageSnapshotOptions } from './expect/to_match_image_snapshot.types.ts';
3
+ export declare function matchImageSnapshot(test: Task, subject: any, options?: ToMatchImageSnapshotOptions): Promise<void>;
4
+ /**
5
+ * @deprecated internalized. Use `matchImageSnapshot` directly instead.
6
+ */
7
+ export declare function parseImageSnapshotSubject(subject: any): any;
8
+ //# sourceMappingURL=match_image_snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match_image_snapshot.d.ts","sourceRoot":"","sources":["../../src/client/match_image_snapshot.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAIlC,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2CAA2C,CAAA;AAO5F,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,CAAC,EAAE,2BAA2B,iBAuCvG;AAED;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,GAAG,OAOrD"}
@@ -0,0 +1,63 @@
1
+ import { commands } from '@vitest/browser/context';
2
+ import dedent from 'dedent';
3
+ import { resolve } from 'pathe';
4
+ import { isBase64String } from "../shared/base64.js";
5
+ import { alignImagesToSameSize } from "./align_images.js";
6
+ import { compareImage } from "./compare_image.js";
7
+ import { toDataURL, toImageData } from "./image_data.js";
8
+ import { prettifyOptions } from "./match_image_snapshot.logic.js";
9
+ import { convertElementToCssSelector } from "./selector.js";
10
+ import { toTaskId } from "./task_id.js";
11
+ import { server } from "./vitest_browser_context_proxy.js";
12
+ export async function matchImageSnapshot(test, subject, options) {
13
+ const taskId = toTaskId(test);
14
+ const info = await commands.prepareImageSnapshotComparison(taskId, parseImageSnapshotSubject(subject), options?.customizeSnapshotId ? await parseImageSnapshotOptions(taskId, options) : options);
15
+ if (!info)
16
+ return;
17
+ const baselineImage = await toImageData(info.baseline);
18
+ const resultImage = await toImageData(info.result);
19
+ const [baselineAlignedImage, resultAlignedImage] = alignImagesToSameSize(baselineImage, resultImage);
20
+ const { pass, diffAmount, diffImage } = compareImage(baselineAlignedImage, resultAlignedImage, options);
21
+ if (pass)
22
+ return;
23
+ if (server.config.snapshotOptions.updateSnapshot === 'all') {
24
+ await writeSnapshot(info.baselinePath, resultImage);
25
+ return;
26
+ }
27
+ await writeSnapshot(info.diffPath, diffImage);
28
+ throw new Error(dedent `Snapshot \`${taskId}\` mismatched
29
+
30
+ ${options?.failureThreshold
31
+ ? options?.failureThresholdType === 'percent'
32
+ ? `Expected image to match within ${options.failureThreshold}% but was differ by ${diffAmount}%.`
33
+ : `Expected image to match within ${options.failureThreshold} pixels but was differ by ${diffAmount} pixels.`
34
+ : `Expected image to match but was differ by ${options?.failureThresholdType === 'percent' ? `${diffAmount}%` : `${diffAmount} pixels`}.`}
35
+
36
+ Options: ${prettifyOptions(options)}
37
+
38
+ Expected: ${resolve(info.projectRoot, info.baselinePath)}
39
+ Actual: ${resolve(info.projectRoot, info.resultPath)}
40
+ Difference: ${resolve(info.projectRoot, info.diffPath)}`);
41
+ }
42
+ /**
43
+ * @deprecated internalized. Use `matchImageSnapshot` directly instead.
44
+ */
45
+ export function parseImageSnapshotSubject(subject) {
46
+ if (subject instanceof Element)
47
+ return convertElementToCssSelector(subject);
48
+ if (subject?.['selector'])
49
+ return subject['selector'];
50
+ if (isBase64String(subject))
51
+ return subject;
52
+ throw new Error(`'toMatchImageSnapshot()' expects the subject to be an element, locator, or image encoded in base64 string, but got: ${subject}`);
53
+ }
54
+ async function writeSnapshot(path, image) {
55
+ const content = (await toDataURL(image)).split(',')[1];
56
+ return commands.writeImageSnapshot(path, content);
57
+ }
58
+ async function parseImageSnapshotOptions(taskId, options) {
59
+ const index = await commands.imageSnapshotNextIndex(taskId);
60
+ const { customizeSnapshotId, ...rest } = options;
61
+ const snapshotFileId = customizeSnapshotId(taskId, index);
62
+ return { ...rest, snapshotFileId };
63
+ }
@@ -0,0 +1,3 @@
1
+ import type { ToMatchImageSnapshotOptions } from './expect/to_match_image_snapshot.types.ts';
2
+ export declare function prettifyOptions(options: ToMatchImageSnapshotOptions | undefined): string;
3
+ //# sourceMappingURL=match_image_snapshot.logic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"match_image_snapshot.logic.d.ts","sourceRoot":"","sources":["../../src/client/match_image_snapshot.logic.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,2CAA2C,CAAA;AAE5F,wBAAgB,eAAe,CAAC,OAAO,EAAE,2BAA2B,GAAG,SAAS,UAU/E"}
@@ -0,0 +1,11 @@
1
+ export function prettifyOptions(options) {
2
+ if (!options)
3
+ return 'none';
4
+ return [
5
+ `failureThreshold: ${options.failureThreshold ?? 0} ${options.failureThresholdType ?? 'pixel'}`,
6
+ options.diffOptions ? `diffOptions: ${JSON.stringify(options.diffOptions)}` : '',
7
+ options.timeout ? `timeout: ${options.timeout} ms` : '',
8
+ ]
9
+ .filter(Boolean)
10
+ .join(', ');
11
+ }
@@ -2,6 +2,9 @@ type Task = {
2
2
  name: string;
3
3
  suite?: Task | undefined;
4
4
  };
5
+ /**
6
+ * @deprecated not needed anymore. Use `matchImageSnapshot` directly instead.
7
+ */
5
8
  export declare function toTaskId(task: Task): string;
6
9
  export {};
7
10
  //# sourceMappingURL=task_id.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"task_id.d.ts","sourceRoot":"","sources":["../../src/client/task_id.ts"],"names":[],"mappings":"AAAA,KAAK,IAAI,GAAG;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,IAAI,GAAG,SAAS,CAAA;CACxB,CAAA;AAED,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,UAUlC"}
1
+ {"version":3,"file":"task_id.d.ts","sourceRoot":"","sources":["../../src/client/task_id.ts"],"names":[],"mappings":"AAAA,KAAK,IAAI,GAAG;IACX,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,IAAI,GAAG,SAAS,CAAA;CACxB,CAAA;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,IAAI,UAUlC"}
@@ -1,3 +1,6 @@
1
+ /**
2
+ * @deprecated not needed anymore. Use `matchImageSnapshot` directly instead.
3
+ */
1
4
  export function toTaskId(task) {
2
5
  const l = [];
3
6
  let t = task;
@@ -0,0 +1,32 @@
1
+ import type { BrowserCommands, Platform } from '@vitest/browser/context';
2
+ import type { SerializedConfig } from 'vitest';
3
+ export declare const server: {
4
+ /**
5
+ * Platform the Vitest server is running on.
6
+ * The same as calling `process.platform` on the server.
7
+ */
8
+ platform: Platform;
9
+ /**
10
+ * Runtime version of the Vitest server.
11
+ * The same as calling `process.version` on the server.
12
+ */
13
+ version: string;
14
+ /**
15
+ * Name of the browser provider.
16
+ */
17
+ provider: string;
18
+ /**
19
+ * Name of the current browser.
20
+ */
21
+ browser: string;
22
+ /**
23
+ * Available commands for the browser.
24
+ * @see {@link https://vitest.dev/guide/browser/commands}
25
+ */
26
+ commands: BrowserCommands;
27
+ /**
28
+ * Serialized test config.
29
+ */
30
+ config: SerializedConfig;
31
+ };
32
+ //# sourceMappingURL=vitest_browser_context_proxy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vitest_browser_context_proxy.d.ts","sourceRoot":"","sources":["../../src/client/vitest_browser_context_proxy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAA;AACxE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,QAAQ,CAAA;AAU9C,eAAO,MAAM,MAAM;IAClB;;;OAGG;cACO,QAAQ;IAClB;;;OAGG;aACM,MAAM;IACf;;OAEG;cACO,MAAM;IAChB;;OAEG;aACM,MAAM;IACf;;;OAGG;cACO,eAAe;IACzB;;OAEG;YACK,gBAAgB;CAKvB,CAAA"}
@@ -0,0 +1,11 @@
1
+ let ctx;
2
+ if (globalThis.__vitest_browser__) {
3
+ import('@vitest/browser/context').then((m) => {
4
+ ctx = m;
5
+ });
6
+ }
7
+ export const server = new Proxy({}, {
8
+ get(target, prop) {
9
+ return target[prop] ?? (ctx?.server)[prop];
10
+ },
11
+ });
package/esm/client.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from './client/expect/expectation_result.ts';
2
- export * from './client/expect/to_match_image_snapshot.logic.ts';
3
2
  export type * from './client/expect/to_match_image_snapshot.types.ts';
3
+ export * from './client/match_image_snapshot.ts';
4
4
  export * from './client/should_take_snapshot.ts';
5
5
  export * from './client/snapshot_options.ts';
6
6
  export * from './client/task_id.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,cAAc,uCAAuC,CAAA;AACrD,cAAc,kDAAkD,CAAA;AAChE,mBAAmB,kDAAkD,CAAA;AACrE,cAAc,kCAAkC,CAAA;AAChD,cAAc,8BAA8B,CAAA;AAC5C,cAAc,qBAAqB,CAAA;AACnC,mBAAmB,mBAAmB,CAAA"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,cAAc,uCAAuC,CAAA;AACrD,mBAAmB,kDAAkD,CAAA;AACrE,cAAc,kCAAkC,CAAA;AAChD,cAAc,kCAAkC,CAAA;AAChD,cAAc,8BAA8B,CAAA;AAC5C,cAAc,qBAAqB,CAAA;AACnC,mBAAmB,mBAAmB,CAAA"}
package/esm/client.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./client/expect/expectation_result.js";
2
- export * from "./client/expect/to_match_image_snapshot.logic.js";
2
+ export * from "./client/match_image_snapshot.js";
3
3
  export * from "./client/should_take_snapshot.js";
4
4
  export * from "./client/snapshot_options.js";
5
5
  export * from "./client/task_id.js";
@@ -16,6 +16,8 @@ export declare function vis(options?: VisOptions): {
16
16
  imageSnapshotNextIndex: import("vitest/node").BrowserCommand<[taskId: string]>;
17
17
  hasImageSnapshot: import("vitest/node").BrowserCommand<[taskId: string, snapshotId?: string | undefined]>;
18
18
  matchImageSnapshot: import("vitest/node").BrowserCommand<[taskId: string, subject: string, options?: import("../commands.ts").MatchImageSnapshotOptions | undefined]>;
19
+ prepareImageSnapshotComparison: import("vitest/node").BrowserCommand<[taskId: string, snapshotId: string, options?: import("../commands.ts").MatchImageSnapshotOptions | undefined]>;
20
+ writeImageSnapshot: import("vitest/node").BrowserCommand<[filePath: string, base64: string]>;
19
21
  };
20
22
  };
21
23
  setupFiles: string[] | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"vis.d.ts","sourceRoot":"","sources":["../../src/config/vis.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,OAAO,GAAE,UAA+B;;;;;sBASxB,MAAM;;;;;;;;;;;EAQzC"}
1
+ {"version":3,"file":"vis.d.ts","sourceRoot":"","sources":["../../src/config/vis.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAA;AAE5C;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,OAAO,GAAE,UAA+B;;;;;sBASxB,MAAM;;;;;;;;;;;;;EAQzC"}
@@ -2,7 +2,7 @@ export function playwright(context) {
2
2
  return {
3
3
  async takeScreenshot(filePath, selector, options) {
4
4
  // The `Locator` type from `vitest` has less props than the `Locator` in `playwright`
5
- const subject = context.iframe.locator(selector ?? 'body');
5
+ const subject = context.iframe.locator(selector);
6
6
  return subject.screenshot({
7
7
  timeout: options?.timeout,
8
8
  path: filePath,
@@ -2,7 +2,7 @@ export function webdriverio(context) {
2
2
  const page = context.provider.browser;
3
3
  return {
4
4
  async takeScreenshot(filePath, selector) {
5
- const element = await page.$(`${selector ?? 'body'}`);
5
+ const element = await page.$(`${selector}`);
6
6
  return element.saveScreenshot(filePath);
7
7
  },
8
8
  };
@@ -1,9 +1,11 @@
1
1
  import { type HasImageSnapshotCommand } from './has_image_snapshot.ts';
2
2
  import { type ImageSnapshotNextIndexCommand } from './image_snapshot_next_index.ts';
3
3
  import { type MatchImageSnapshotCommand } from './match_image_snapshot.ts';
4
+ import { type PrepareImageSnapshotComparisonCommand } from './prepare_image_snapshot_comparison.ts';
4
5
  import { type SetupVisSuiteCommand } from './setup_vis_suite.ts';
6
+ import { type WriteImageSnapshotCommand } from './write_image_snapshot.ts';
5
7
  declare module '@vitest/browser/context' {
6
- interface BrowserCommands extends SetupVisSuiteCommand, ImageSnapshotNextIndexCommand, HasImageSnapshotCommand, MatchImageSnapshotCommand {
8
+ interface BrowserCommands extends SetupVisSuiteCommand, ImageSnapshotNextIndexCommand, HasImageSnapshotCommand, PrepareImageSnapshotComparisonCommand, WriteImageSnapshotCommand, MatchImageSnapshotCommand {
7
9
  }
8
10
  }
9
11
  export declare const commands: {
@@ -11,5 +13,7 @@ export declare const commands: {
11
13
  imageSnapshotNextIndex: import("vitest/node").BrowserCommand<[taskId: string]>;
12
14
  hasImageSnapshot: import("vitest/node").BrowserCommand<[taskId: string, snapshotId?: string | undefined]>;
13
15
  matchImageSnapshot: import("vitest/node").BrowserCommand<[taskId: string, subject: string, options?: import("./match_image_snapshot.ts").MatchImageSnapshotOptions | undefined]>;
16
+ prepareImageSnapshotComparison: import("vitest/node").BrowserCommand<[taskId: string, snapshotId: string, options?: import("./match_image_snapshot.ts").MatchImageSnapshotOptions | undefined]>;
17
+ writeImageSnapshot: import("vitest/node").BrowserCommand<[filePath: string, base64: string]>;
14
18
  };
15
19
  //# sourceMappingURL=extend.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extend.d.ts","sourceRoot":"","sources":["../../../src/server/commands/extend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,uBAAuB,EAAoB,MAAM,yBAAyB,CAAA;AACxF,OAAO,EAAE,KAAK,6BAA6B,EAA0B,MAAM,gCAAgC,CAAA;AAC3G,OAAO,EAAE,KAAK,yBAAyB,EAAsB,MAAM,2BAA2B,CAAA;AAC9F,OAAO,EAAE,KAAK,oBAAoB,EAAiB,MAAM,sBAAsB,CAAA;AAE/E,OAAO,QAAQ,yBAAyB,CAAC;IACxC,UAAU,eACT,SAAQ,oBAAoB,EAC3B,6BAA6B,EAC7B,uBAAuB,EACvB,yBAAyB;KAAG;CAC9B;AAED,eAAO,MAAM,QAAQ;;;;;CAKpB,CAAA"}
1
+ {"version":3,"file":"extend.d.ts","sourceRoot":"","sources":["../../../src/server/commands/extend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,uBAAuB,EAAoB,MAAM,yBAAyB,CAAA;AACxF,OAAO,EAAE,KAAK,6BAA6B,EAA0B,MAAM,gCAAgC,CAAA;AAC3G,OAAO,EAAE,KAAK,yBAAyB,EAAsB,MAAM,2BAA2B,CAAA;AAC9F,OAAO,EACN,KAAK,qCAAqC,EAE1C,MAAM,wCAAwC,CAAA;AAC/C,OAAO,EAAE,KAAK,oBAAoB,EAAiB,MAAM,sBAAsB,CAAA;AAC/E,OAAO,EAAE,KAAK,yBAAyB,EAAsB,MAAM,2BAA2B,CAAA;AAE9F,OAAO,QAAQ,yBAAyB,CAAC;IACxC,UAAU,eACT,SAAQ,oBAAoB,EAC3B,6BAA6B,EAC7B,uBAAuB,EACvB,qCAAqC,EACrC,yBAAyB,EACzB,yBAAyB;KAAG;CAC9B;AAED,eAAO,MAAM,QAAQ;;;;;;;CAOpB,CAAA"}
@@ -1,10 +1,14 @@
1
1
  import { hasImageSnapshot } from "./has_image_snapshot.js";
2
2
  import { imageSnapshotNextIndex } from "./image_snapshot_next_index.js";
3
3
  import { matchImageSnapshot } from "./match_image_snapshot.js";
4
+ import { prepareImageSnapshotComparison, } from "./prepare_image_snapshot_comparison.js";
4
5
  import { setupVisSuite } from "./setup_vis_suite.js";
6
+ import { writeImageSnapshot } from "./write_image_snapshot.js";
5
7
  export const commands = {
6
8
  setupVisSuite,
7
9
  imageSnapshotNextIndex,
8
10
  hasImageSnapshot,
9
11
  matchImageSnapshot,
12
+ prepareImageSnapshotComparison,
13
+ writeImageSnapshot,
10
14
  };
@@ -1 +1 @@
1
- {"version":3,"file":"match_image_snapshot.d.ts","sourceRoot":"","sources":["../../../src/server/commands/match_image_snapshot.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAyB,MAAM,aAAa,CAAA;AAIxE,OAAO,KAAK,EACX,2BAA2B,EAC3B,sBAAsB,EACtB,2BAA2B,EAC3B,MAAM,uBAAuB,CAAA;AAM9B,MAAM,WAAW,yBAAyB;IACzC,kBAAkB,EAAE,CACnB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,yBAAyB,GAAG,SAAS,KAC3C,OAAO,CAAC,IAAI,CAAC,CAAA;CAClB;AAED,MAAM,WAAW,yBAChB,SAAQ,2BAA2B,EAClC,sBAAsB,EACtB,2BAA2B;IAC5B;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACnC;AAED,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAC9C;IAAC,MAAM,EAAE,MAAM;IAAE,OAAO,EAAE,MAAM;IAAE,OAAO,CAAC,EAAE,yBAAyB,GAAG,SAAS;CAAC,CAiElF,CAAA"}
1
+ {"version":3,"file":"match_image_snapshot.d.ts","sourceRoot":"","sources":["../../../src/server/commands/match_image_snapshot.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAIjD,OAAO,KAAK,EACX,2BAA2B,EAC3B,sBAAsB,EACtB,2BAA2B,EAC3B,MAAM,uBAAuB,CAAA;AAM9B,MAAM,WAAW,yBAAyB;IACzC,kBAAkB,EAAE,CACnB,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,yBAAyB,GAAG,SAAS,KAC3C,OAAO,CAAC,IAAI,CAAC,CAAA;CAClB;AAED,MAAM,WAAW,yBAChB,SAAQ,2BAA2B,EAClC,sBAAsB,EACtB,2BAA2B;IAC5B;;OAEG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;CACnC;AAED,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAC9C;IAAC,MAAM,EAAE,MAAM;IAAE,OAAO,EAAE,MAAM;IAAE,OAAO,CAAC,EAAE,yBAAyB,GAAG,SAAS;CAAC,CAiElF,CAAA"}
@@ -1,14 +1,13 @@
1
1
  import dedent from 'dedent';
2
2
  import ci from 'is-ci';
3
- import { mkdirp } from 'mkdirp';
4
- import { dirname, resolve } from 'pathe';
3
+ import { resolve } from 'pathe';
5
4
  import { PNG } from 'pngjs';
6
5
  import { isBase64String } from "../../shared/base64.js";
7
6
  import { getMaxSize } from "../../shared/get_max_size.js";
8
7
  import { isSameSize } from "../../shared/is_same_size.js";
9
- import { browserApi } from "../browser_provider/browser_api.js";
10
8
  import { compareImage } from "../compare_image.js";
11
9
  import { file } from "../file.js";
10
+ import { takeSnapshot, takeSnapshotByBrowser, writeSnapshot, writeSnapshotBuffer } from "../snapshot.js";
12
11
  import { visContext } from "../vis_context.js";
13
12
  export const matchImageSnapshot = async (context, taskId, subject, options) => {
14
13
  if (!context.testPath) {
@@ -29,7 +28,7 @@ export const matchImageSnapshot = async (context, taskId, subject, options) => {
29
28
  }
30
29
  return;
31
30
  }
32
- const resultBuffer = await takeSnapshot(context, subject, info.resultPath, options);
31
+ const resultBuffer = await takeSnapshot(context, info.resultPath, subject, options);
33
32
  const baselineImage = PNG.sync.read(baselineBuffer, { skipRescale: true, checkCRC: false });
34
33
  const resultImage = PNG.sync.read(resultBuffer, { skipRescale: true, checkCRC: false });
35
34
  const [baselineAlignedImage, resultAlignedImage] = alignImageSizes(baselineImage, resultImage);
@@ -64,28 +63,6 @@ export const matchImageSnapshot = async (context, taskId, subject, options) => {
64
63
  Actual: ${resolve(context.project.runner.root, info.resultPath)}
65
64
  Difference: ${resolve(context.project.runner.root, info.diffPath)}`);
66
65
  };
67
- async function takeSnapshot(context, subject, filePath, options) {
68
- if (isBase64String(subject)) {
69
- await writeSnapshot(filePath, subject);
70
- return Buffer.from(subject, 'base64');
71
- }
72
- return takeSnapshotByBrowser(context, filePath, subject, options);
73
- }
74
- async function takeSnapshotByBrowser(context, filePath, subject, options) {
75
- await mkdirp(dirname(filePath));
76
- const browser = browserApi(context);
77
- return browser.takeScreenshot(filePath, subject, {
78
- timeout: options?.timeout,
79
- });
80
- }
81
- async function writeSnapshot(filePath, subject) {
82
- await mkdirp(dirname(filePath));
83
- await file.writeFile(filePath, subject, { encoding: 'base64' });
84
- }
85
- async function writeSnapshotBuffer(filePath, subject) {
86
- await mkdirp(dirname(filePath));
87
- await file.writeFile(filePath, subject);
88
- }
89
66
  function alignImageSizes(baseline, result) {
90
67
  if (isSameSize(baseline, result))
91
68
  return [baseline, result];
@@ -0,0 +1,20 @@
1
+ import type { BrowserCommand } from 'vitest/node';
2
+ import type { MatchImageSnapshotOptions } from './match_image_snapshot.ts';
3
+ type ImageSnapshotComparisonInfo = {
4
+ projectRoot: string;
5
+ baselinePath: string;
6
+ resultPath: string;
7
+ diffPath: string;
8
+ baseline: string;
9
+ result: string;
10
+ };
11
+ export interface PrepareImageSnapshotComparisonCommand {
12
+ prepareImageSnapshotComparison: (taskId: string | undefined, subject: string, options?: MatchImageSnapshotOptions | undefined) => Promise<ImageSnapshotComparisonInfo | undefined>;
13
+ }
14
+ export declare const prepareImageSnapshotComparison: BrowserCommand<[
15
+ taskId: string,
16
+ snapshotId: string,
17
+ options?: MatchImageSnapshotOptions | undefined
18
+ ]>;
19
+ export {};
20
+ //# sourceMappingURL=prepare_image_snapshot_comparison.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepare_image_snapshot_comparison.d.ts","sourceRoot":"","sources":["../../../src/server/commands/prepare_image_snapshot_comparison.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAKjD,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,2BAA2B,CAAA;AAE1E,KAAK,2BAA2B,GAAG;IAClC,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;IAClB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,MAAM,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,WAAW,qCAAqC;IACrD,8BAA8B,EAAE,CAC/B,MAAM,EAAE,MAAM,GAAG,SAAS,EAC1B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,yBAAyB,GAAG,SAAS,KAC3C,OAAO,CAAC,2BAA2B,GAAG,SAAS,CAAC,CAAA;CACrD;AAED,eAAO,MAAM,8BAA8B,EAAE,cAAc,CAC1D;IAAC,MAAM,EAAE,MAAM;IAAE,UAAU,EAAE,MAAM;IAAE,OAAO,CAAC,EAAE,yBAAyB,GAAG,SAAS;CAAC,CA4BrF,CAAA"}
@@ -0,0 +1,32 @@
1
+ import ci from 'is-ci';
2
+ import { isBase64String } from "../../shared/base64.js";
3
+ import { file } from "../file.js";
4
+ import { takeSnapshot, takeSnapshotByBrowser, writeSnapshot } from "../snapshot.js";
5
+ import { visContext } from "../vis_context.js";
6
+ export const prepareImageSnapshotComparison = async (context, taskId, subject, options) => {
7
+ if (!context.testPath) {
8
+ throw new Error('Cannot match snapshot without testPath');
9
+ }
10
+ // vitest:browser passes in `null` when not defined
11
+ if (!options)
12
+ options = {};
13
+ options.timeout = options.timeout ?? (ci ? 30000 : 5000);
14
+ const info = visContext.getSnapshotInfo(context.testPath, taskId, options);
15
+ const baselineBuffer = await file.tryReadFile(info.baselinePath);
16
+ if (!baselineBuffer) {
17
+ if (isBase64String(subject)) {
18
+ await writeSnapshot(info.baselinePath, subject);
19
+ }
20
+ else {
21
+ await takeSnapshotByBrowser(context, info.baselinePath, subject, options);
22
+ }
23
+ return;
24
+ }
25
+ const resultBuffer = await takeSnapshot(context, info.resultPath, subject, options);
26
+ return {
27
+ ...info,
28
+ projectRoot: context.project.runner.root,
29
+ baseline: baselineBuffer.toString('base64'),
30
+ result: resultBuffer.toString('base64'),
31
+ };
32
+ };
@@ -0,0 +1,6 @@
1
+ import type { BrowserCommand } from 'vitest/node';
2
+ export interface WriteImageSnapshotCommand {
3
+ writeImageSnapshot(taskId: string, snapshotId?: string | undefined): Promise<boolean>;
4
+ }
5
+ export declare const writeImageSnapshot: BrowserCommand<[filePath: string, base64: string]>;
6
+ //# sourceMappingURL=write_image_snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"write_image_snapshot.d.ts","sourceRoot":"","sources":["../../../src/server/commands/write_image_snapshot.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAGjD,MAAM,WAAW,yBAAyB;IACzC,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;CACrF;AAED,eAAO,MAAM,kBAAkB,EAAE,cAAc,CAAC,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAUjF,CAAA"}
@@ -0,0 +1,10 @@
1
+ import { mkdirp } from 'mkdirp';
2
+ import { dirname } from 'pathe';
3
+ import { file } from "../file.js";
4
+ export const writeImageSnapshot = async (context, filePath, base64) => {
5
+ if (!context.testPath) {
6
+ throw new Error('Cannot take snapshot without testPath');
7
+ }
8
+ await mkdirp(dirname(filePath));
9
+ await file.writeFile(filePath, base64, { encoding: 'base64' });
10
+ };
@@ -0,0 +1,7 @@
1
+ import type { BrowserCommandContext } from 'vitest/node';
2
+ import type { ImageSnapshotTimeoutOptions } from '../client.ts';
3
+ export declare function takeSnapshot(context: BrowserCommandContext, filePath: string, subject: string, options: ImageSnapshotTimeoutOptions | undefined): Promise<Buffer<ArrayBufferLike>>;
4
+ export declare function takeSnapshotByBrowser(context: BrowserCommandContext, filePath: string, subject: string, options: ImageSnapshotTimeoutOptions | undefined): Promise<Buffer<ArrayBufferLike>>;
5
+ export declare function writeSnapshot(filePath: string, subject: string): Promise<void>;
6
+ export declare function writeSnapshotBuffer(filePath: string, subject: Buffer): Promise<void>;
7
+ //# sourceMappingURL=snapshot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../../src/server/snapshot.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AACxD,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,cAAc,CAAA;AAK/D,wBAAsB,YAAY,CACjC,OAAO,EAAE,qBAAqB,EAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,2BAA2B,GAAG,SAAS,oCAOhD;AAED,wBAAsB,qBAAqB,CAC1C,OAAO,EAAE,qBAAqB,EAC9B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,2BAA2B,GAAG,SAAS,oCAOhD;AAED,wBAAsB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,iBAGpE;AACD,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,iBAG1E"}