@stackoverflow/stacks 2.0.8 → 2.1.0-rc.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 (103) hide show
  1. package/LICENSE.MD +1 -1
  2. package/README.md +7 -9
  3. package/dist/css/stacks.css +234 -214
  4. package/dist/css/stacks.min.css +1 -1
  5. package/dist/js/stacks.js +1 -1
  6. package/lib/atomic/misc.less +1 -1
  7. package/lib/components/activity-indicator/activity-indicator.a11y.test.ts +2 -3
  8. package/lib/components/activity-indicator/activity-indicator.less +5 -5
  9. package/lib/components/activity-indicator/activity-indicator.visual.test.ts +2 -3
  10. package/lib/components/anchor/anchor.a11y.test.ts +2 -4
  11. package/lib/components/anchor/anchor.visual.test.ts +2 -4
  12. package/lib/components/avatar/avatar.a11y.test.ts +2 -3
  13. package/lib/components/avatar/avatar.visual.test.ts +2 -3
  14. package/lib/components/award-bling/award-bling.a11y.test.ts +2 -4
  15. package/lib/components/award-bling/award-bling.visual.test.ts +2 -4
  16. package/lib/components/badge/badge.a11y.test.ts +7 -16
  17. package/lib/components/badge/badge.visual.test.ts +8 -21
  18. package/lib/components/banner/banner.a11y.test.ts +2 -3
  19. package/lib/components/banner/banner.visual.test.ts +2 -3
  20. package/lib/components/block-link/block-link.a11y.test.ts +4 -9
  21. package/lib/components/block-link/block-link.less +7 -10
  22. package/lib/components/block-link/block-link.visual.test.ts +4 -9
  23. package/lib/components/breadcrumbs/breadcrumbs.a11y.test.ts +2 -3
  24. package/lib/components/breadcrumbs/breadcrumbs.visual.test.ts +2 -3
  25. package/lib/components/button/button.a11y.test.ts +2 -3
  26. package/lib/components/button/button.less +70 -35
  27. package/lib/components/button/button.visual.test.ts +2 -3
  28. package/lib/components/card/card.a11y.test.ts +2 -3
  29. package/lib/components/card/card.visual.test.ts +3 -6
  30. package/lib/components/check-control/check-control.a11y.test.ts +2 -4
  31. package/lib/components/check-control/check-control.visual.test.ts +2 -4
  32. package/lib/components/check-group/check-group.a11y.test.ts +2 -4
  33. package/lib/components/check-group/check-group.visual.test.ts +2 -4
  34. package/lib/components/checkbox_radio/checkbox_radio.a11y.test.ts +2 -4
  35. package/lib/components/checkbox_radio/checkbox_radio.less +1 -13
  36. package/lib/components/checkbox_radio/checkbox_radio.visual.test.ts +2 -4
  37. package/lib/components/code-block/code-block.a11y.test.ts +2 -4
  38. package/lib/components/code-block/code-block.visual.test.ts +2 -4
  39. package/lib/components/description/description.a11y.test.ts +2 -4
  40. package/lib/components/description/description.visual.test.ts +2 -4
  41. package/lib/components/empty-state/empty-state.a11y.test.ts +2 -3
  42. package/lib/components/empty-state/empty-state.visual.test.ts +2 -3
  43. package/lib/components/expandable/expandable.a11y.test.ts +2 -3
  44. package/lib/components/expandable/expandable.visual.test.ts +2 -3
  45. package/lib/components/input-fill/input-fill.a11y.test.ts +2 -3
  46. package/lib/components/input-fill/input-fill.visual.test.ts +2 -3
  47. package/lib/components/input-message/input-message.a11y.test.ts +2 -3
  48. package/lib/components/input-message/input-message.visual.test.ts +2 -3
  49. package/lib/components/input_textarea/input_textarea.a11y.test.ts +4 -7
  50. package/lib/components/input_textarea/input_textarea.less +2 -20
  51. package/lib/components/input_textarea/input_textarea.visual.test.ts +4 -7
  52. package/lib/components/label/label.a11y.test.ts +2 -3
  53. package/lib/components/label/label.visual.test.ts +2 -3
  54. package/lib/components/link/link.a11y.test.ts +2 -3
  55. package/lib/components/link/link.visual.test.ts +2 -3
  56. package/lib/components/link-preview/link-preview.a11y.test.ts +2 -3
  57. package/lib/components/link-preview/link-preview.visual.test.ts +3 -3
  58. package/lib/components/menu/menu.a11y.test.ts +2 -3
  59. package/lib/components/menu/menu.visual.test.ts +2 -3
  60. package/lib/components/modal/modal.a11y.test.ts +2 -3
  61. package/lib/components/modal/modal.visual.test.ts +2 -3
  62. package/lib/components/navigation/navigation.a11y.test.ts +2 -3
  63. package/lib/components/navigation/navigation.less +3 -1
  64. package/lib/components/navigation/navigation.visual.test.ts +3 -6
  65. package/lib/components/notice/notice.a11y.test.ts +2 -3
  66. package/lib/components/notice/notice.visual.test.ts +2 -3
  67. package/lib/components/page-title/page-title.a11y.test.ts +2 -3
  68. package/lib/components/page-title/page-title.visual.test.ts +2 -3
  69. package/lib/components/pagination/pagination.a11y.test.ts +2 -3
  70. package/lib/components/pagination/pagination.less +9 -0
  71. package/lib/components/pagination/pagination.visual.test.ts +2 -3
  72. package/lib/components/progress-bar/progress-bar.a11y.test.ts +7 -18
  73. package/lib/components/progress-bar/progress-bar.less +1 -1
  74. package/lib/components/progress-bar/progress-bar.visual.test.ts +7 -18
  75. package/lib/components/select/select.less +1 -15
  76. package/lib/components/spinner/spinner.a11y.test.ts +2 -3
  77. package/lib/components/spinner/spinner.visual.test.ts +4 -7
  78. package/lib/components/table/table.a11y.test.ts +3 -4
  79. package/lib/components/table/table.visual.test.ts +2 -3
  80. package/lib/components/tag/tag.a11y.test.ts +2 -3
  81. package/lib/components/tag/tag.less +27 -21
  82. package/lib/components/tag/tag.visual.test.ts +3 -6
  83. package/lib/components/toast/toast.a11y.test.ts +2 -3
  84. package/lib/components/toast/toast.visual.test.ts +2 -3
  85. package/lib/components/toggle-switch/toggle-switch.a11y.test.ts +3 -6
  86. package/lib/components/toggle-switch/toggle-switch.less +5 -16
  87. package/lib/components/toggle-switch/toggle-switch.visual.test.ts +3 -7
  88. package/lib/components/topbar/topbar.less +61 -39
  89. package/lib/components/topbar/topbar.visual.test.ts +188 -0
  90. package/lib/components/uploader/uploader.less +1 -1
  91. package/lib/exports/__snapshots__/color-mixins.less.test.ts.snap +12 -0
  92. package/lib/exports/__snapshots__/color.less.test.ts.snap +45 -0
  93. package/lib/exports/color-mixins.less +2 -0
  94. package/lib/exports/color-sets.less +44 -7
  95. package/lib/exports/mixins.less +33 -0
  96. package/lib/input-utils.less +0 -3
  97. package/lib/test/a11y-test-utils.ts +94 -0
  98. package/lib/test/assertions.ts +10 -3
  99. package/lib/test/test-utils.ts +152 -300
  100. package/lib/test/visual-test-utils.ts +58 -0
  101. package/lib/tsconfig.json +3 -3
  102. package/package.json +12 -13
  103. package/lib/components/popover/tooltip.visual.test.ts +0 -31
@@ -1,108 +1,11 @@
1
- import { html, fixture, expect, unsafeStatic } from "@open-wc/testing";
2
- import { screen } from "@testing-library/dom";
3
- import { visualDiff } from "@web/test-runner-visual-regression";
4
- import type { TemplateResult } from "lit-html";
5
- import axe from "axe-core";
6
- import registerAPCACheck from "apca-check";
1
+ import { html, unsafeStatic } from "@open-wc/testing";
7
2
 
8
- const customConformanceThresholdFn = (fontSize: string): number | null => {
9
- return parseFloat(fontSize) >= 32 ? 45 : 60;
10
- };
11
-
12
- registerAPCACheck("custom", customConformanceThresholdFn);
13
-
14
- const colorThemes = ["dark", "light"];
15
- const baseThemes = ["", "highcontrast"];
16
- export const defaultOptions = {
17
- testColorThemes: true,
18
- testHighContrast: true,
19
- includeNullVariant: true,
20
- includeNullModifier: true,
21
- };
22
-
23
- type Themes = ["light" | "dark" | "highcontrast" | ""];
24
- type TestTypes = "visual" | "a11y";
25
- export type AdditionalAssertion = {
26
- description: string;
27
- assertion: (node: HTMLElement) => Promise<void> | void;
28
- };
29
-
30
- type TestOptions = {
31
- /**
32
- * Enable tests for all color themes
33
- * default: true
34
- */
35
- testColorThemes: boolean;
36
- /**
37
- * Enable tests for high contrast
38
- * default: true
39
- */
40
- testHighContrast: boolean;
41
- /**
42
- * Provide a custom testid suffix
43
- * default: undefined
44
- */
45
- testidSuffix?: string;
46
- /**
47
- * Include tests for the component without any variants applied
48
- * default: true
49
- */
50
- includeNullVariant: boolean;
51
- /**
52
- * Include tests for the component without any modifiers applied
53
- * default: true
54
- */
55
- includeNullModifier: boolean;
56
- };
57
-
58
- interface ComponentTestVariationArgs {
3
+ type TestVariationArgs = {
59
4
  /**
60
5
  * Base class of the component
61
6
  * (e.g. "s-component")
62
7
  */
63
8
  baseClass: string;
64
- /**
65
- * Variants of the component
66
- * (e.g. ["primary", "secondary"])
67
- */
68
- variants?: string[];
69
- /**
70
- * Modifiers of the component
71
- * (e.g. { primary: ["filled", "outlined"], secondary: ["xs", "sm", "md"] })
72
- */
73
- modifiers?: ComponentTestModifiers;
74
- /**
75
- * Options for the test
76
- */
77
- options?: TestOptions;
78
- }
79
-
80
- type ComponentTestArgs = {
81
- /**
82
- * The element to test
83
- * use the `html` template tag to render the element
84
- */
85
- element: TemplateResult;
86
- /**
87
- * testid of the test
88
- * (e.g. "s-component-primary-important")
89
- */
90
- testid: string;
91
- /**
92
- * Theme to apply to the test element
93
- */
94
- theme?: Themes;
95
- /**
96
- * Type of test to run
97
- */
98
- type: TestTypes;
99
- /**
100
- * Additional assertions to run against the test element
101
- */
102
- additionalAssertions: AdditionalAssertion[];
103
- };
104
-
105
- interface ComponentTestsArgs extends ComponentTestVariationArgs {
106
9
  /**
107
10
  * Additional html attributes applied to the test element
108
11
  * (e.g. { role: "button", id: "id" } -> <element role="button" id="id"> )
@@ -137,16 +40,22 @@ interface ComponentTestsArgs extends ComponentTestVariationArgs {
137
40
  testid: string;
138
41
  }) => ReturnType<typeof html>;
139
42
  /**
140
- * Type of test to run
43
+ * Variants of the component
44
+ * (e.g. ["primary", "secondary"])
141
45
  */
142
- type: TestTypes;
46
+ variants?: string[];
143
47
  /**
144
- * Additional assertions to run against the test element
48
+ * Modifiers of the component
49
+ * (e.g. { primary: ["filled", "outlined"], secondary: ["xs", "sm", "md"] })
145
50
  */
146
- additionalAssertions?: AdditionalAssertion[];
147
- }
51
+ modifiers?: Modifiers;
52
+ /**
53
+ * Options for the test
54
+ */
55
+ options?: Partial<TestOptions>;
56
+ };
148
57
 
149
- type ComponentTestModifiers = {
58
+ type Modifiers = {
150
59
  /**
151
60
  * Primary grouping of modifiers to test
152
61
  * The base class will be used as a prefix for these modifiers
@@ -168,12 +77,46 @@ type ComponentTestModifiers = {
168
77
  standalone?: string[];
169
78
  };
170
79
 
171
- type ComponentTestProps = {
172
- classes: string;
173
- testid: string;
174
- theme?: Themes;
80
+ type TestOptions = {
81
+ /**
82
+ * Enable tests for all color themes
83
+ * default: true
84
+ */
85
+ testColorThemes: boolean;
86
+ /**
87
+ * Enable tests for high contrast
88
+ * default: true
89
+ */
90
+ testHighContrast: boolean;
91
+ /**
92
+ * Provide a custom testid suffix
93
+ * default: undefined
94
+ */
95
+ testidSuffix?: string;
96
+ /**
97
+ * Include tests for the component without any variants applied
98
+ * default: true
99
+ */
100
+ includeNullVariant: boolean;
101
+ /**
102
+ * Include tests for the component without any modifiers applied
103
+ * default: true
104
+ */
105
+ includeNullModifier: boolean;
175
106
  };
176
107
 
108
+ export const DEFAULT_OPTIONS = {
109
+ testColorThemes: true,
110
+ testHighContrast: true,
111
+ includeNullVariant: true,
112
+ includeNullModifier: true,
113
+ };
114
+
115
+ // TODO: refactor using MODES as opposed to THEMES
116
+ const COLOR_THEMES = ["dark", "light"];
117
+ const BASE_THEMES = ["", "highcontrast"];
118
+ type Themes = ["light" | "dark" | "highcontrast" | ""];
119
+
177
120
  const attrObjToString = (attrs: Record<string, string>): string => {
178
121
  const attrString = Object.keys(attrs).map((key) => {
179
122
  return `${key}="${attrs[key]}"` || "";
@@ -181,6 +124,22 @@ const attrObjToString = (attrs: Record<string, string>): string => {
181
124
  return attrString.join(" ") || "";
182
125
  };
183
126
 
127
+ const matchTestidByPattern = ({
128
+ testid,
129
+ pattern,
130
+ }: {
131
+ testid: string;
132
+ pattern: string | RegExp;
133
+ }): boolean => {
134
+ if (pattern instanceof RegExp) {
135
+ return pattern.test(testid);
136
+ } else {
137
+ return pattern === testid;
138
+ }
139
+ };
140
+
141
+ const buildTestid = (arr: string[]) => arr.filter(Boolean).join("-");
142
+
184
143
  const buildClasses = ({
185
144
  baseClass,
186
145
  prefixed = [],
@@ -214,34 +173,44 @@ const buildTestElement = ({
214
173
  };
215
174
 
216
175
  return html`
217
- <${unsafe.tag}
218
- ${unsafe.attributes}
219
- data-testid="${testid}"
220
- >${unsafe.children}</${unsafe.tag}>
221
- `;
176
+ <${unsafe.tag}
177
+ ${unsafe.attributes}
178
+ data-testid="${testid}"
179
+ >${unsafe.children}</${unsafe.tag}>
180
+ `;
222
181
  };
223
182
 
224
- const buildTestid = (arr: string[]) => arr.filter(Boolean).join("-");
183
+ type PrimitiveVariation = {
184
+ classes: string;
185
+ testid: string;
186
+ theme: Themes;
187
+ };
188
+
189
+ type PrimitiveVariationArgs = Pick<
190
+ TestVariationArgs,
191
+ "baseClass" | "variants" | "modifiers" | "options"
192
+ >;
225
193
 
226
- const getComponentTestVariations = ({
194
+ const generatePrimitiveVariations = ({
227
195
  baseClass,
228
196
  variants = [],
229
197
  modifiers,
230
- options = defaultOptions,
231
- }: ComponentTestVariationArgs): ComponentTestProps[] => {
232
- const testVariations: ComponentTestProps[] = [];
198
+ options = {},
199
+ }: PrimitiveVariationArgs): PrimitiveVariation[] => {
200
+ const primitiveVariations: PrimitiveVariation[] = [];
201
+ const opts = { ...DEFAULT_OPTIONS, ...options };
233
202
  // Test default, high contrast themes
234
- [...(options.testHighContrast ? baseThemes : [""])].forEach((baseTheme) => {
203
+ [...(opts.testHighContrast ? BASE_THEMES : [""])].forEach((baseTheme) => {
235
204
  // Test light, dark theme
236
- [...(options.testColorThemes ? colorThemes : [""])].forEach(
205
+ [...(opts.testColorThemes ? COLOR_THEMES : [""])].forEach(
237
206
  (colorTheme) => {
238
207
  const theme = [baseTheme, colorTheme].filter(Boolean) as Themes;
239
208
  const testidBase = buildTestid([baseClass, ...theme]);
240
- const allVariants = options.includeNullVariant
209
+ const allVariants = opts.includeNullVariant
241
210
  ? ["", ...variants]
242
211
  : variants;
243
212
  const primaryModifiers = modifiers?.primary
244
- ? options.includeNullModifier
213
+ ? opts.includeNullModifier
245
214
  ? ["", ...(<[]>modifiers.primary)]
246
215
  : modifiers.primary
247
216
  : [""];
@@ -256,7 +225,7 @@ const getComponentTestVariations = ({
256
225
  secondaryModifiers.forEach((secondaryModifier) => {
257
226
  globalModifiers.forEach((globalModifier) => {
258
227
  allVariants.forEach((variant) => {
259
- testVariations.push({
228
+ primitiveVariations.push({
260
229
  classes: buildClasses({
261
230
  baseClass,
262
231
  prefixed: [
@@ -286,7 +255,7 @@ const getComponentTestVariations = ({
286
255
 
287
256
  // create standalone modifiers test props
288
257
  modifiers?.standalone?.forEach((standaloneModifier) => {
289
- testVariations.push({
258
+ primitiveVariations.push({
290
259
  testid: buildTestid([testidBase, standaloneModifier]),
291
260
  classes: buildClasses({
292
261
  baseClass,
@@ -300,209 +269,92 @@ const getComponentTestVariations = ({
300
269
  });
301
270
 
302
271
  // Sorting for readability
303
- return testVariations.sort((a, b) => a.testid.localeCompare(b.testid));
272
+ return primitiveVariations.sort((a, b) => a.testid.localeCompare(b.testid));
304
273
  };
305
274
 
306
- /**
307
- * Constructs and runs an individual test for a component
308
- */
309
- const runComponentTest = ({
310
- element,
311
- testid,
312
- theme,
313
- type,
314
- additionalAssertions,
315
- }: ComponentTestArgs) => {
316
- const getDescription = (type: TestTypes) => {
317
- switch (type) {
318
- case "a11y":
319
- return "should be accessible";
320
- case "visual":
321
- return "should not introduce visual regressions";
322
- default:
323
- return "";
324
- }
325
- };
326
-
327
- it(`${type}: ${testid} ${getDescription(type)}`, async () => {
328
- await fixture(element);
329
- const el = screen.getByTestId(testid);
330
-
331
- document.body.className = "";
332
-
333
- if (theme?.length) {
334
- const prefixedThemes = theme.map((t) => `theme-${t}`);
335
- document.body.classList.add(...prefixedThemes);
336
- }
337
-
338
- if (type === "a11y") {
339
- const highcontrast = theme?.includes("highcontrast");
340
- axe.configure({
341
- rules: [
342
- // for non-high contrast, we disable WCAG 2.1 AA (4.5:1)
343
- // and use a Stacks-specific APCA custom level instead
344
- { id: "color-contrast", enabled: false },
345
- {
346
- id: "color-contrast-apca-custom",
347
- enabled: !highcontrast,
348
- },
349
- // for high contrast, we check against WCAG 2.1 AAA (7:1)
350
- { id: "color-contrast-enhanced", enabled: highcontrast },
351
- ],
352
- });
353
- await expect(el).to.be.accessible();
354
- }
355
-
356
- if (type === "visual") {
357
- await visualDiff(el, testid);
358
- }
359
- });
360
-
361
- additionalAssertions.forEach((assertion) => {
362
- it(`${type}: ${testid} ${assertion.description}`, async () => {
363
- await fixture(element);
364
- const el = screen.getByTestId(testid);
365
- await assertion.assertion(el);
366
- });
367
- });
275
+ type TestVariation = {
276
+ testid: string;
277
+ element: ReturnType<typeof html>;
278
+ skipped: boolean;
279
+ theme: Themes;
368
280
  };
369
281
 
370
- /**
371
- * Constructs and runs tests for a component with a each provided combination
372
- */
373
- const runComponentTests = ({
282
+ const generateTestVariations = ({
374
283
  baseClass,
375
284
  variants = [],
376
285
  modifiers,
377
- options = defaultOptions,
286
+ options = DEFAULT_OPTIONS,
378
287
  attributes,
379
288
  children,
380
289
  excludedTestids = [],
381
290
  skippedTestids = [],
382
291
  tag,
383
292
  template,
384
- type,
385
- additionalAssertions = [],
386
- }: ComponentTestsArgs) => {
387
- getComponentTestVariations({
293
+ }: TestVariationArgs): TestVariation[] => {
294
+ return generatePrimitiveVariations({
388
295
  baseClass,
389
296
  variants,
390
297
  modifiers,
391
298
  options,
392
- }).forEach(({ testid, classes, theme }) => {
299
+ }).flatMap(({ testid, classes, theme }) => {
393
300
  const allChildren: {
394
301
  [key: string]: string;
395
302
  } = children ? { ...children } : { default: "" };
396
303
  const { testidSuffix } = options;
397
304
 
398
- Object.keys(allChildren).forEach((key) => {
399
- let testidModified = (
400
- key !== "default" ? `${testid}-${key}` : testid
401
- ).replace(" ", "-");
402
- testidModified = testidSuffix
403
- ? `${testidModified}-${testidSuffix}`
404
- : testidModified;
405
-
406
- const children = allChildren[key];
407
-
408
- const shouldSkipTest = excludeOrSkipTest({
409
- patterns: skippedTestids,
410
- skip: true,
411
- testid: testidModified,
412
- type,
413
- });
414
-
415
- const shouldExcludeTest = excludeOrSkipTest({
416
- patterns: excludedTestids,
417
- testid: testidModified,
418
- type,
419
- });
420
-
421
- if (shouldSkipTest || shouldExcludeTest) {
422
- return;
423
- }
424
-
425
- const element = template
426
- ? html`${template({
427
- testid: testidModified,
428
- component: buildTestElement({
305
+ return Object.keys(allChildren)
306
+ .map((key) => {
307
+ let testidModified = (
308
+ key !== "default" ? `${testid}-${key}` : testid
309
+ ).replace(" ", "-");
310
+ testidModified = testidSuffix
311
+ ? `${testidModified}-${testidSuffix}`
312
+ : testidModified;
313
+
314
+ const children = allChildren[key];
315
+
316
+ const element = template
317
+ ? html`${template({
318
+ testid: testidModified,
319
+ component: buildTestElement({
320
+ attributes: {
321
+ ...attributes,
322
+ class: `${classes} ${attributes?.class || ""}`,
323
+ },
324
+ children,
325
+ testid: `${testidModified}-nested`,
326
+ tag,
327
+ }),
328
+ })}`
329
+ : buildTestElement({
429
330
  attributes: {
430
331
  ...attributes,
431
332
  class: `${classes} ${attributes?.class || ""}`,
432
333
  },
433
334
  children,
434
- testid: `${testidModified}-nested`,
335
+ testid: testidModified,
435
336
  tag,
436
- }),
437
- })}`
438
- : buildTestElement({
439
- attributes: {
440
- ...attributes,
441
- class: `${classes} ${attributes?.class || ""}`,
442
- },
443
- children,
444
- testid: testidModified,
445
- tag,
446
- });
447
-
448
- runComponentTest({
449
- element,
450
- testid: testidModified,
451
- theme,
452
- type,
453
- additionalAssertions,
454
- });
455
- });
337
+ });
338
+
339
+ const skipped = skippedTestids.some((pattern) =>
340
+ matchTestidByPattern({ testid: testidModified, pattern })
341
+ );
342
+
343
+ return {
344
+ element,
345
+ testid: testidModified,
346
+ theme,
347
+ skipped,
348
+ };
349
+ })
350
+ .filter(
351
+ ({ testid }) =>
352
+ !excludedTestids.some((pattern) =>
353
+ matchTestidByPattern({ testid, pattern })
354
+ )
355
+ );
456
356
  });
457
357
  };
458
358
 
459
- const matchTestidByPattern = ({
460
- testid,
461
- pattern,
462
- }: {
463
- testid: string;
464
- pattern: string | RegExp;
465
- }): boolean => {
466
- if (pattern instanceof RegExp) {
467
- return pattern.test(testid);
468
- } else {
469
- return pattern === testid;
470
- }
471
- };
472
-
473
- const excludeOrSkipTest = ({
474
- patterns,
475
- skip = false,
476
- testid,
477
- type,
478
- }: {
479
- patterns: (string | RegExp)[];
480
- skip?: boolean;
481
- testid: string;
482
- type: TestTypes;
483
- }): boolean => {
484
- const matchesTest = patterns.some((pattern) => {
485
- return matchTestidByPattern({ testid, pattern });
486
- });
487
-
488
- if (matchesTest && skip) {
489
- it.skip(`${type}: ${testid} (skipped)`, () => {
490
- return;
491
- });
492
- }
493
-
494
- return matchesTest;
495
- };
496
-
497
- export { runComponentTest, runComponentTests };
498
-
499
- /**
500
- * Convert a const array of strings into a union type of the array's values.
501
- *
502
- * @example
503
- * ```
504
- * const arrayOfStrings = ['Stacky', 'Ben', 'Dan', 'Giamir'] as const;
505
- * type StringLiterals = AsLiterals<typeof arrayOfStrings>; // 'Stacky' | 'Ben' | 'Dan' | 'Giamir'
506
- * ```
507
- */
508
- export type AsLiterals<T extends Readonly<string[]>> = T[number];
359
+ export type { TestVariationArgs, TestVariation };
360
+ export { generateTestVariations };
@@ -0,0 +1,58 @@
1
+ import { html, fixture } from "@open-wc/testing";
2
+ import { visualDiff } from "@web/test-runner-visual-regression";
3
+ import { screen } from "@testing-library/dom";
4
+ import { generateTestVariations, type TestVariationArgs } from "./test-utils";
5
+
6
+ type VisualTestArgs = TestVariationArgs;
7
+
8
+ const scheduleVisualTest = ({
9
+ element,
10
+ testid,
11
+ theme,
12
+ }: {
13
+ element: ReturnType<typeof html>;
14
+ testid: string;
15
+ theme: string[];
16
+ }) => {
17
+ it(`visual: ${testid} should not introduce visual regressions`, async () => {
18
+ document.body.className = "";
19
+
20
+ if (theme?.length) {
21
+ const prefixedThemes = theme.map((t) => `theme-${t}`);
22
+ document.body.classList.add(...prefixedThemes);
23
+ }
24
+
25
+ let retryAttempts = 3;
26
+
27
+ do {
28
+ await fixture(element);
29
+ const el = screen.getByTestId(testid);
30
+ try {
31
+ await visualDiff(el, testid);
32
+ return;
33
+ } catch (error) {
34
+ const e = error as Error;
35
+ // if the error is not a visual diff failure, retry
36
+ // this is to prevent flaky tests due to snapshot capturing
37
+ if (
38
+ retryAttempts > 0 &&
39
+ !e.message.includes("Visual diff failed.")
40
+ ) {
41
+ retryAttempts--;
42
+ continue;
43
+ } else {
44
+ throw e;
45
+ }
46
+ } finally {
47
+ el.remove();
48
+ }
49
+ } while (retryAttempts > 0);
50
+ });
51
+ };
52
+
53
+ const runVisualTests = (args: VisualTestArgs) => {
54
+ const testVariations = generateTestVariations(args);
55
+ testVariations.forEach(scheduleVisualTest);
56
+ };
57
+
58
+ export { runVisualTests };
package/lib/tsconfig.json CHANGED
@@ -10,8 +10,8 @@
10
10
  "declaration": true,
11
11
  "allowSyntheticDefaultImports": true,
12
12
  "paths": {
13
- "@open-wc/testing": ["./test/open-wc-testing-patch.d.ts"]
14
- }
13
+ "@open-wc/testing": ["./test/open-wc-testing-patch.d.ts"],
14
+ },
15
15
  },
16
- "include": ["**/*.ts"]
16
+ "include": ["**/*.ts"],
17
17
  }