diff --git a/2nd-gen/packages/swc/components/asset/test/asset.test.ts b/2nd-gen/packages/swc/components/asset/test/asset.test.ts
index 2aa734bfc45..cc24584f52c 100644
--- a/2nd-gen/packages/swc/components/asset/test/asset.test.ts
+++ b/2nd-gen/packages/swc/components/asset/test/asset.test.ts
@@ -47,9 +47,15 @@ export const OverviewTest: Story = {
await step('renders slotted content when no variant is set', async () => {
const img = asset.querySelector('img');
- expect(asset.variant).toBeUndefined();
- expect(img).toBeTruthy();
- expect(img?.getAttribute('alt')?.length).toBeGreaterThan(0);
+ expect(
+ asset.variant,
+ 'variant when not set'
+ ).toBeUndefined();
+ expect(img, 'slotted img element is rendered').toBeTruthy();
+ expect(
+ img?.getAttribute('alt')?.length,
+ 'slotted img has a non-empty alt attribute'
+ ).toBeGreaterThan(0);
});
},
};
@@ -69,21 +75,82 @@ export const DefaultLabelFallbackTest: Story = {
await step('file variant falls back to default aria-label', async () => {
const fileAsset = assets.find(
(a) => a.getAttribute('variant') === 'file'
- ) as Asset;
- const svg = fileAsset.shadowRoot?.querySelector('svg');
- expect(svg?.getAttribute('aria-label')).toBe('File');
+ ) as Asset | null;
+ expect(fileAsset, 'file asset is rendered').toBeTruthy();
+ const svg = fileAsset?.shadowRoot?.querySelector('svg');
+ expect(svg, 'file asset has an SVG in shadow DOM').toBeTruthy();
+ expect(
+ svg?.getAttribute('aria-label'),
+ 'file asset SVG aria-label'
+ ).toBe('File');
});
await step('folder variant falls back to default aria-label', async () => {
const folderAsset = assets.find(
(a) => a.getAttribute('variant') === 'folder'
- ) as Asset;
- const svg = folderAsset.shadowRoot?.querySelector('svg');
- expect(svg?.getAttribute('aria-label')).toBe('Folder');
+ ) as Asset | null;
+ expect(folderAsset, 'folder asset is rendered').toBeTruthy();
+ const svg = folderAsset?.shadowRoot?.querySelector('svg');
+ expect(svg, 'folder asset has an SVG in shadow DOM').toBeTruthy();
+ expect(
+ svg?.getAttribute('aria-label'),
+ 'folder asset SVG defaults to "Folder" aria-label'
+ ).toBe('Folder');
});
},
};
+// ──────────────────────────────────────────────────────────────
+// TEST: Properties / Attributes
+// ──────────────────────────────────────────────────────────────
+
+export const LabelMutationTest: Story = {
+ render: () => html`
+
+ `,
+ play: async ({ canvasElement, step }) => {
+ const asset = await getComponent(canvasElement, 'swc-asset');
+
+ await step(
+ 'SVG aria-label reflects default "File" when label is empty',
+ async () => {
+ const svg = asset.shadowRoot?.querySelector('svg');
+ expect(svg, 'SVG is rendered initially').toBeTruthy();
+ expect(
+ svg?.getAttribute('aria-label'),
+ 'SVG aria-label defaults to "File" when no label is set'
+ ).toBe('File');
+ }
+ );
+
+ await step(
+ 'SVG aria-label updates when label property is set',
+ async () => {
+ asset.label = 'Q4 Budget Report';
+ await asset.updateComplete;
+ const svg = asset.shadowRoot?.querySelector('svg');
+ expect(
+ svg?.getAttribute('aria-label'),
+ 'SVG aria-label after label update'
+ ).toBe('Q4 Budget Report');
+ }
+ );
+
+ await step(
+ 'SVG aria-label reverts to default when label is cleared',
+ async () => {
+ asset.label = '';
+ await asset.updateComplete;
+ const svg = asset.shadowRoot?.querySelector('svg');
+ expect(
+ svg?.getAttribute('aria-label'),
+ 'SVG aria-label reverts to "File" when label is cleared'
+ ).toBe('File');
+ }
+ );
+ },
+};
+
// ──────────────────────────────────────────────────────────────
// TEST: Variants / States
// ──────────────────────────────────────────────────────────────
@@ -101,8 +168,74 @@ export const VariantsTest: Story = {
(item) => item.getAttribute('variant') === 'folder'
);
- expect(fileAsset).toBeTruthy();
- expect(folderAsset).toBeTruthy();
+ expect(fileAsset, 'file variant asset is rendered').toBeTruthy();
+ expect(folderAsset, 'folder variant asset is rendered').toBeTruthy();
+ });
+ },
+};
+
+export const VariantMutationTest: Story = {
+ render: () => html`
+
+ `,
+ play: async ({ canvasElement, step }) => {
+ const asset = await getComponent(canvasElement, 'swc-asset');
+
+ await step('initially renders slot when no variant is set', async () => {
+ expect(asset.variant, 'variant is initially undefined').toBeUndefined();
+ const svg = asset.shadowRoot?.querySelector('svg');
+ expect(svg, 'no SVG is rendered when variant is not set').toBeNull();
+ });
+
+ await step('renders file SVG after variant is set to "file"', async () => {
+ asset.variant = 'file';
+ await asset.updateComplete;
+ expect(
+ asset.getAttribute('variant'),
+ 'variant attribute is "file" after mutation'
+ ).toBe('file');
+ const svg = asset.shadowRoot?.querySelector('svg');
+ expect(svg, 'file SVG is rendered after variant mutation').toBeTruthy();
+ expect(
+ svg?.getAttribute('aria-label'),
+ 'file SVG has default "File" aria-label'
+ ).toBe('File');
+ });
+
+ await step(
+ 'renders folder SVG after variant is set to "folder"',
+ async () => {
+ asset.variant = 'folder';
+ await asset.updateComplete;
+ expect(
+ asset.getAttribute('variant'),
+ 'variant attribute is "folder" after mutation'
+ ).toBe('folder');
+ const svg = asset.shadowRoot?.querySelector('svg');
+ expect(
+ svg,
+ 'folder SVG is rendered after variant mutation'
+ ).toBeTruthy();
+ expect(
+ svg?.getAttribute('aria-label'),
+ 'folder SVG has default "Folder" aria-label'
+ ).toBe('Folder');
+ }
+ );
+
+ await step('reverts to slot content when variant is cleared', async () => {
+ asset.variant = undefined;
+ await asset.updateComplete;
+ expect(
+ asset.variant,
+ 'variant is undefined after clearing'
+ ).toBeUndefined();
+ expect(
+ asset.hasAttribute('variant'),
+ 'variant attribute is absent after clearing'
+ ).toBe(false);
+ const svg = asset.shadowRoot?.querySelector('svg');
+ expect(svg, 'no SVG is rendered after variant is cleared').toBeNull();
});
},
};
@@ -123,8 +256,14 @@ export const InvalidVariantWarningTest: Story = {
asset.variant = 'not-a-variant' as Asset['variant'];
await asset.updateComplete;
- expect(warnCalls.length).toBeGreaterThan(0);
- expect(String(warnCalls[0]?.[1] || '')).toContain('variant');
+ expect(
+ warnCalls.length,
+ 'at least one warning is emitted for invalid variant'
+ ).toBeGreaterThan(0);
+ expect(
+ String(warnCalls[0]?.[1] || ''),
+ 'warning message references variant'
+ ).toContain('variant');
})
);
},
@@ -142,7 +281,10 @@ export const ValidVariantNoWarningTest: Story = {
asset.variant = 'folder';
await asset.updateComplete;
- expect(warnCalls.length).toBe(0);
+ expect(
+ warnCalls.length,
+ 'no warnings are emitted for valid variant'
+ ).toBe(0);
})
);
},
diff --git a/2nd-gen/packages/swc/components/avatar/stories/avatar.stories.ts b/2nd-gen/packages/swc/components/avatar/stories/avatar.stories.ts
index 5050aea58bb..ae74aa92a1f 100644
--- a/2nd-gen/packages/swc/components/avatar/stories/avatar.stories.ts
+++ b/2nd-gen/packages/swc/components/avatar/stories/avatar.stories.ts
@@ -50,7 +50,7 @@ argTypes.decorative = {
/**
* An avatar displays a circular profile image representing a person or entity.
*/
-export const meta: Meta = {
+const meta: Meta = {
title: 'Avatar',
component: 'swc-avatar',
args,
@@ -67,11 +67,7 @@ export const meta: Meta = {
tags: ['migrated'],
};
-export default {
- ...meta,
- title: 'Avatar',
- excludeStories: ['meta'],
-} as Meta;
+export default meta;
// ────────────────────
// HELPERS
diff --git a/2nd-gen/packages/swc/components/avatar/test/avatar.test.ts b/2nd-gen/packages/swc/components/avatar/test/avatar.test.ts
index f8e7647b472..364e3aa3bfe 100644
--- a/2nd-gen/packages/swc/components/avatar/test/avatar.test.ts
+++ b/2nd-gen/packages/swc/components/avatar/test/avatar.test.ts
@@ -23,10 +23,9 @@ import {
getComponents,
withWarningSpy,
} from '../../../utils/test-utils.js';
-import {
+import meta, {
Decorative,
Disabled,
- meta,
Overview,
Sizes,
} from '../stories/avatar.stories.js';
diff --git a/2nd-gen/packages/swc/components/badge/stories/badge.stories.ts b/2nd-gen/packages/swc/components/badge/stories/badge.stories.ts
index 7759f86684d..deae43f63fd 100644
--- a/2nd-gen/packages/swc/components/badge/stories/badge.stories.ts
+++ b/2nd-gen/packages/swc/components/badge/stories/badge.stories.ts
@@ -90,7 +90,7 @@ argTypes['icon-slot'] = {
* Choose one style consistently within a product - `outline` and `subtle` fill draw similar attention levels.
* Reserve bold fill for high-attention badging only.
*/
-export const meta: Meta = {
+const meta: Meta = {
title: 'Badge',
component: 'swc-badge',
args,
@@ -112,11 +112,7 @@ export const meta: Meta = {
tags: ['migrated'],
};
-export default {
- ...meta,
- title: 'Badge',
- excludeStories: ['meta'],
-} as Meta;
+export default meta;
// ────────────────────
// HELPERS
diff --git a/2nd-gen/packages/swc/components/badge/test/badge.test.ts b/2nd-gen/packages/swc/components/badge/test/badge.test.ts
index d8e3ccd1da9..a878b481783 100644
--- a/2nd-gen/packages/swc/components/badge/test/badge.test.ts
+++ b/2nd-gen/packages/swc/components/badge/test/badge.test.ts
@@ -29,8 +29,7 @@ import {
getComponents,
withWarningSpy,
} from '../../../utils/test-utils.js';
-import { meta } from '../stories/badge.stories.js';
-import {
+import meta, {
Anatomy,
Fixed,
NonSemanticVariants,
@@ -61,9 +60,12 @@ export const OverviewTest: Story = {
const badge = await getComponent(canvasElement, 'swc-badge');
await step('renders expected default values and slot content', async () => {
- expect(badge.variant).toBe('neutral');
- expect(badge.size).toBe('m');
- expect(badge.textContent?.trim()).toBeTruthy();
+ expect(badge.variant, 'default variant is neutral').toBe('neutral');
+ expect(badge.size, 'default size is m').toBe('m');
+ expect(
+ badge.textContent?.trim(),
+ 'default slot has text content'
+ ).toBeTruthy();
});
},
};
@@ -80,23 +82,35 @@ export const PropertyMutationTest: Story = {
await step('variant reflects to attribute after mutation', async () => {
badge.variant = 'positive';
await badge.updateComplete;
- expect(badge.getAttribute('variant')).toBe('positive');
+ expect(
+ badge.getAttribute('variant'),
+ 'variant attribute is positive after mutation'
+ ).toBe('positive');
badge.variant = 'notice';
await badge.updateComplete;
- expect(badge.getAttribute('variant')).toBe('notice');
+ expect(
+ badge.getAttribute('variant'),
+ 'variant attribute is notice after second mutation'
+ ).toBe('notice');
});
await step('subtle reflects to attribute after mutation', async () => {
badge.subtle = true;
await badge.updateComplete;
- expect(badge.hasAttribute('subtle')).toBe(true);
+ expect(
+ badge.hasAttribute('subtle'),
+ 'subtle attribute is present after setting subtle=true'
+ ).toBe(true);
});
await step('outline reflects to attribute after mutation', async () => {
badge.outline = true;
await badge.updateComplete;
- expect(badge.hasAttribute('outline')).toBe(true);
+ expect(
+ badge.hasAttribute('outline'),
+ 'outline attribute is present after setting outline=true'
+ ).toBe(true);
});
},
};
@@ -109,16 +123,24 @@ export const FixedClearingTest: Story = {
const badge = await getComponent(canvasElement, 'swc-badge');
await step('initially has fixed attribute', async () => {
- expect(badge.fixed).toBe('block-start');
- expect(badge.hasAttribute('fixed')).toBe(true);
+ expect(badge.fixed, 'fixed property is block-start initially').toBe(
+ 'block-start'
+ );
+ expect(
+ badge.hasAttribute('fixed'),
+ 'fixed attribute is present initially'
+ ).toBe(true);
});
await step('removes fixed attribute when set to undefined', async () => {
badge.fixed = undefined;
await badge.updateComplete;
- expect(badge.fixed).toBeFalsy();
- expect(badge.hasAttribute('fixed')).toBe(false);
+ expect(badge.fixed, 'fixed property is falsy after clearing').toBeFalsy();
+ expect(
+ badge.hasAttribute('fixed'),
+ 'fixed attribute is absent after clearing'
+ ).toBe(false);
});
},
};
@@ -136,10 +158,16 @@ export const AnatomyTest: Story = {
const badgeWithIcon = badges.find((item) =>
item.querySelector('[slot="icon"]')
);
- expect(badgeWithIcon).toBeTruthy();
+ expect(
+ badgeWithIcon,
+ 'at least one badge has icon slot content'
+ ).toBeTruthy();
const slottedIcon = badgeWithIcon?.querySelector('[slot="icon"]');
- expect(slottedIcon).toBeTruthy();
- expect(slottedIcon?.children.length).toBeGreaterThan(0);
+ expect(slottedIcon, 'icon slot element is present').toBeTruthy();
+ expect(
+ slottedIcon?.children.length,
+ 'icon slot has child elements'
+ ).toBeGreaterThan(0);
});
},
};
@@ -155,9 +183,15 @@ export const SemanticVariantsTest: Story = {
for (const variant of BADGE_VARIANTS_SEMANTIC) {
const badge = canvasElement.querySelector(
`swc-badge[variant="${variant}"]`
- ) as Badge;
- await badge.updateComplete;
- expect(badge.variant).toBe(variant);
+ ) as Badge | null;
+ expect(
+ badge,
+ `badge with variant="${variant}" is rendered`
+ ).toBeTruthy();
+ await badge?.updateComplete;
+ expect(badge?.variant, `badge variant property is "${variant}"`).toBe(
+ variant
+ );
}
});
},
@@ -172,9 +206,16 @@ export const OutlineTest: Story = {
for (const variant of BADGE_VARIANTS_SEMANTIC) {
const badge = canvasElement.querySelector(
`swc-badge[variant="${variant}"]`
- ) as Badge;
- await badge.updateComplete;
- expect(badge.hasAttribute('outline')).toBe(true);
+ ) as Badge | null;
+ expect(
+ badge,
+ `badge with variant="${variant}" is rendered`
+ ).toBeTruthy();
+ await badge?.updateComplete;
+ expect(
+ badge?.hasAttribute('outline'),
+ `badge with variant="${variant}" has outline attribute`
+ ).toBe(true);
}
}
);
@@ -194,9 +235,10 @@ export const SizesTest: Story = {
for (const size of BADGE_VALID_SIZES) {
const badge = canvasElement.querySelector(
`swc-badge[size="${size}"]`
- ) as Badge;
- await badge.updateComplete;
- expect(badge.size).toBe(size);
+ ) as Badge | null;
+ expect(badge, `badge with size="${size}" is rendered`).toBeTruthy();
+ await badge?.updateComplete;
+ expect(badge?.size, `badge size property is "${size}"`).toBe(size);
}
});
},
@@ -209,9 +251,16 @@ export const SubtleTest: Story = {
for (const variant of BADGE_VARIANTS) {
const badge = canvasElement.querySelector(
`swc-badge[variant="${variant}"]`
- ) as Badge;
- await badge.updateComplete;
- expect(badge.hasAttribute('subtle')).toBe(true);
+ ) as Badge | null;
+ expect(
+ badge,
+ `badge with variant="${variant}" is rendered`
+ ).toBeTruthy();
+ await badge?.updateComplete;
+ expect(
+ badge?.hasAttribute('subtle'),
+ `badge with variant="${variant}" has subtle attribute`
+ ).toBe(true);
}
});
},
@@ -224,9 +273,10 @@ export const FixedTest: Story = {
for (const value of FIXED_VALUES) {
const badge = canvasElement.querySelector(
`swc-badge[fixed="${value}"]`
- ) as Badge;
- await badge.updateComplete;
- expect(badge.fixed).toBe(value);
+ ) as Badge | null;
+ expect(badge, `badge with fixed="${value}" is rendered`).toBeTruthy();
+ await badge?.updateComplete;
+ expect(badge?.fixed, `badge fixed property is "${value}"`).toBe(value);
}
});
},
@@ -236,15 +286,19 @@ export const NonSemanticVariantsTest: Story = {
...NonSemanticVariants,
play: async ({ canvasElement, step }) => {
await step('renders all color variant values', async () => {
- await Promise.all(
- BADGE_VARIANTS_COLOR.map(async (variant) => {
- const badge = canvasElement.querySelector(
- `swc-badge[variant="${variant}"]`
- ) as Badge;
- await badge.updateComplete;
- expect(badge.variant).toBe(variant);
- })
- );
+ for (const variant of BADGE_VARIANTS_COLOR) {
+ const badge = canvasElement.querySelector(
+ `swc-badge[variant="${variant}"]`
+ ) as Badge | null;
+ expect(
+ badge,
+ `badge with variant="${variant}" is rendered`
+ ).toBeTruthy();
+ await badge?.updateComplete;
+ expect(badge?.variant, `badge variant property is "${variant}"`).toBe(
+ variant
+ );
+ }
});
},
};
@@ -265,8 +319,14 @@ export const InvalidVariantWarningTest: Story = {
badge.variant = 'not-a-variant' as unknown as Badge['variant'];
await badge.updateComplete;
- expect(warnCalls.length).toBeGreaterThan(0);
- expect(String(warnCalls[0]?.[1] || '')).toContain('variant');
+ expect(
+ warnCalls.length,
+ 'at least one warning is emitted for invalid variant'
+ ).toBeGreaterThan(0);
+ expect(
+ String(warnCalls[0]?.[1] || ''),
+ 'warning message references variant'
+ ).toContain('variant');
})
);
},
@@ -284,7 +344,10 @@ export const ValidVariantNoWarningTest: Story = {
badge.variant = 'negative';
await badge.updateComplete;
- expect(warnCalls.length).toBe(0);
+ expect(
+ warnCalls.length,
+ 'no warnings are emitted for valid variant'
+ ).toBe(0);
})
);
},
@@ -302,8 +365,14 @@ export const OutlineNonSemanticWarningTest: Story = {
badge.requestUpdate();
await badge.updateComplete;
- expect(warnCalls.length).toBeGreaterThan(0);
- expect(String(warnCalls[0]?.[1] || '')).toContain('outline');
+ expect(
+ warnCalls.length,
+ 'at least one warning is emitted for outline with non-semantic variant'
+ ).toBeGreaterThan(0);
+ expect(
+ String(warnCalls[0]?.[1] || ''),
+ 'warning message references outline'
+ ).toContain('outline');
})
);
},
diff --git a/2nd-gen/packages/swc/components/divider/test/divider.test.ts b/2nd-gen/packages/swc/components/divider/test/divider.test.ts
index 8f823f0805e..58ddd0fdafc 100644
--- a/2nd-gen/packages/swc/components/divider/test/divider.test.ts
+++ b/2nd-gen/packages/swc/components/divider/test/divider.test.ts
@@ -17,10 +17,18 @@ import { Divider } from '@adobe/spectrum-wc/divider';
import '@adobe/spectrum-wc/divider';
-import { getComponent, getComponents } from '../../../utils/test-utils.js';
-import meta from '../stories/divider.stories.js';
import {
+ DIVIDER_STATIC_COLORS,
+ DIVIDER_VALID_SIZES,
+} from '../../../../core/components/divider/Divider.types.js';
+import {
+ getComponent,
+ getComponents,
+ withWarningSpy,
+} from '../../../utils/test-utils.js';
+import meta, {
Overview,
+ Sizes,
StaticColors,
Vertical,
} from '../stories/divider.stories.js';
@@ -46,8 +54,10 @@ export const OverviewTest: Story = {
const divider = await getComponent(canvasElement, 'swc-divider');
await step('renders a separator with expected attributes', async () => {
- expect(divider.getAttribute('role')).toBe('separator');
- expect(divider.size).toBe('m');
+ expect(divider.getAttribute('role'), 'divider has role="separator"').toBe(
+ 'separator'
+ );
+ expect(divider.size, 'default size is m').toBe('m');
});
},
};
@@ -56,20 +66,83 @@ export const OverviewTest: Story = {
// TEST: Properties / Attributes
// ──────────────────────────────────────────────────────────────
+export const SizesTest: Story = {
+ ...Sizes,
+ play: async ({ canvasElement, step }) => {
+ await step('renders dividers in all valid sizes', async () => {
+ for (const size of DIVIDER_VALID_SIZES) {
+ const divider = canvasElement.querySelector(
+ `swc-divider[size="${size}"]`
+ ) as Divider | null;
+ expect(divider, `divider with size="${size}" is rendered`).toBeTruthy();
+ await divider?.updateComplete;
+ expect(divider?.size, `divider size property is "${size}"`).toBe(size);
+ }
+ });
+ },
+};
+
export const VerticalTest: Story = {
...Vertical,
play: async ({ canvasElement, step }) => {
const dividers = await getComponents(canvasElement, 'swc-divider');
await step('reflects vertical orientation attributes', async () => {
- dividers.forEach((divider) => {
- expect(divider.hasAttribute('vertical')).toBe(true);
- expect(divider.getAttribute('aria-orientation')).toBe('vertical');
- });
+ for (const divider of dividers) {
+ expect(
+ divider.hasAttribute('vertical'),
+ 'divider has vertical attribute'
+ ).toBe(true);
+ expect(
+ divider.getAttribute('aria-orientation'),
+ 'aria-orientation is set to vertical'
+ ).toBe('vertical');
+ }
});
},
};
+export const VerticalMutationTest: Story = {
+ render: () => html`
+
+ `,
+ play: async ({ canvasElement, step }) => {
+ const divider = await getComponent(canvasElement, 'swc-divider');
+
+ await step('initially has no aria-orientation', async () => {
+ expect(divider.vertical, 'vertical is false by default').toBe(false);
+ expect(
+ divider.hasAttribute('aria-orientation'),
+ 'aria-orientation is absent when not vertical'
+ ).toBe(false);
+ });
+
+ await step(
+ 'sets aria-orientation="vertical" when vertical is enabled',
+ async () => {
+ divider.vertical = true;
+ await divider.updateComplete;
+ expect(
+ divider.getAttribute('aria-orientation'),
+ 'aria-orientation is set to vertical after enabling'
+ ).toBe('vertical');
+ }
+ );
+
+ await step(
+ 'removes aria-orientation when vertical is disabled',
+ async () => {
+ divider.vertical = false;
+ await divider.updateComplete;
+ expect(
+ divider.hasAttribute('aria-orientation'),
+ 'aria-orientation is absent after disabling vertical'
+ ).toBe(false);
+ }
+ );
+ },
+};
+
export const StaticColorsTest: Story = {
...StaticColors,
play: async ({ canvasElement, step }) => {
@@ -79,11 +152,14 @@ export const StaticColorsTest: Story = {
);
await step('reflects expected static-color attribute values', async () => {
- dividers.forEach((divider) => {
+ for (const divider of dividers) {
const staticColor = divider.getAttribute('static-color');
- expect(staticColor).toBeTruthy();
- expect(['white', 'black']).toContain(staticColor);
- });
+ expect(staticColor, 'static-color attribute is present').toBeTruthy();
+ expect(
+ ['white', 'black'],
+ `static-color "${staticColor}" is a valid value`
+ ).toContain(staticColor);
+ }
});
},
};
@@ -96,15 +172,79 @@ export const StaticColorToggleTest: Story = {
const divider = await getComponent(canvasElement, 'swc-divider');
await step('renders with static-color attribute', async () => {
- expect(divider.getAttribute('static-color')).toBe('black');
+ expect(
+ divider.getAttribute('static-color'),
+ 'initial static-color attribute'
+ ).toBe('black');
});
await step('clears static-color when attribute is removed', async () => {
divider.removeAttribute('static-color');
divider.requestUpdate();
await divider.updateComplete;
- expect(divider.getAttribute('static-color')).toBeNull();
- expect(divider.hasAttribute('static-color')).toBe(false);
+ expect(
+ divider.getAttribute('static-color'),
+ 'static-color attribute is null after removal'
+ ).toBeNull();
+ expect(
+ divider.hasAttribute('static-color'),
+ 'static-color attribute is absent after removal'
+ ).toBe(false);
});
},
};
+
+// ──────────────────────────────────────────────────────────────
+// TEST: Dev mode warnings
+// ──────────────────────────────────────────────────────────────
+
+export const InvalidStaticColorWarningTest: Story = {
+ render: () => html`
+
+ `,
+ play: async ({ canvasElement, step }) => {
+ const divider = await getComponent(canvasElement, 'swc-divider');
+
+ await step('warns when an invalid static-color is set in DEBUG mode', () =>
+ withWarningSpy(async (warnCalls) => {
+ divider.staticColor =
+ 'not-a-color' as unknown as Divider['staticColor'];
+ await divider.updateComplete;
+
+ expect(
+ warnCalls.length,
+ 'at least one warning is emitted for invalid static-color'
+ ).toBeGreaterThan(0);
+ expect(
+ String(warnCalls[0]?.[1] || ''),
+ 'warning message references static-color attribute'
+ ).toContain('static-color');
+ })
+ );
+ },
+};
+
+export const ValidStaticColorNoWarningTest: Story = {
+ render: () => html`
+
+ `,
+ play: async ({ canvasElement, step }) => {
+ const divider = await getComponent(canvasElement, 'swc-divider');
+
+ await step(
+ 'does not warn for any valid static-color value in DEBUG mode',
+ () =>
+ withWarningSpy(async (warnCalls) => {
+ for (const color of DIVIDER_STATIC_COLORS) {
+ divider.staticColor = color;
+ await divider.updateComplete;
+ }
+
+ expect(
+ warnCalls.length,
+ 'no warnings are emitted for valid static-color values'
+ ).toBe(0);
+ })
+ );
+ },
+};
diff --git a/2nd-gen/packages/swc/components/icon/test/icon.a11y.spec.ts b/2nd-gen/packages/swc/components/icon/test/icon.a11y.spec.ts
new file mode 100644
index 00000000000..0b700a5a9c6
--- /dev/null
+++ b/2nd-gen/packages/swc/components/icon/test/icon.a11y.spec.ts
@@ -0,0 +1,55 @@
+/**
+ * Copyright 2026 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+import { expect, test } from '@playwright/test';
+
+import { gotoStory } from '../../../utils/a11y-helpers.js';
+
+/**
+ * Accessibility tests for Icon component (2nd Generation)
+ *
+ * ARIA snapshot tests validate the accessibility tree structure.
+ * aXe WCAG compliance and color contrast validation are run via
+ * test-storybook (see .storybook/test-runner.ts). Both are included
+ * in the `test:a11y` command.
+ */
+
+test.describe('Icon - ARIA Snapshots', () => {
+ test('should expose labelled icon as img with aria-label in accessibility tree', async ({
+ page,
+ }) => {
+ const root = await gotoStory(page, 'components-icon--overview', 'swc-icon');
+ await expect(root).toMatchAriaSnapshot(`
+ - img "Search"
+ `);
+ });
+
+ test('should expose anatomy icon with correct aria-label', async ({
+ page,
+ }) => {
+ const root = await gotoStory(page, 'components-icon--anatomy', 'swc-icon');
+ await expect(root).toMatchAriaSnapshot(`
+ - img "Chevron icon"
+ `);
+ });
+
+ test('should expose all sizes with correct aria-labels', async ({ page }) => {
+ const root = await gotoStory(page, 'components-icon--sizes', 'swc-icon');
+ await expect(root).toMatchAriaSnapshot(`
+ - img "Extra-small"
+ - img "Small"
+ - img "Medium"
+ - img "Large"
+ - img "Extra-large"
+ `);
+ });
+});
diff --git a/2nd-gen/packages/swc/components/icon/test/icon.test.ts b/2nd-gen/packages/swc/components/icon/test/icon.test.ts
index 15b07e21756..eeb5c905ced 100644
--- a/2nd-gen/packages/swc/components/icon/test/icon.test.ts
+++ b/2nd-gen/packages/swc/components/icon/test/icon.test.ts
@@ -42,8 +42,8 @@ export const OverviewTest: Story = {
const icon = await getComponent(canvasElement, 'swc-icon');
await step('renders with expected default properties', async () => {
- expect(icon.label).toBe('Search');
- expect(icon.shadowRoot).toBeTruthy();
+ expect(icon.label, 'label property is "Search"').toBe('Search');
+ expect(icon.shadowRoot, 'shadow root is attached').toBeTruthy();
});
},
};
@@ -66,7 +66,7 @@ export const SizeAttributeTest: Story = {
const icon = await getComponent(canvasElement, 'swc-icon');
await step('reflects size attribute on host', async () => {
- expect(icon.getAttribute('size')).toBe('xl');
+ expect(icon.getAttribute('size'), 'size attribute is "xl"').toBe('xl');
});
},
};
@@ -90,8 +90,13 @@ export const SlottedSvgAccessibilityTest: Story = {
await step('applies aria attributes to slotted svg', async () => {
const svg = icon.querySelector('svg')!;
- expect(svg.getAttribute('role')).toBe('img');
- expect(svg.getAttribute('aria-label')).toBe('Search');
+ expect(svg.getAttribute('role'), 'slotted SVG has role="img"').toBe(
+ 'img'
+ );
+ expect(
+ svg.getAttribute('aria-label'),
+ 'slotted SVG aria-label matches icon label'
+ ).toBe('Search');
});
},
};
@@ -111,9 +116,18 @@ export const NoLabelAriaHiddenTest: Story = {
await step('applies aria-hidden when no label', async () => {
const svg = icon.querySelector('svg')!;
- expect(svg.getAttribute('aria-hidden')).toBe('true');
- expect(svg.hasAttribute('aria-label')).toBe(false);
- expect(icon.getAttribute('aria-hidden')).toBe('true');
+ expect(
+ svg.getAttribute('aria-hidden'),
+ 'slotted SVG has aria-hidden="true" when no label'
+ ).toBe('true');
+ expect(
+ svg.hasAttribute('aria-label'),
+ 'slotted SVG has no aria-label when icon has no label'
+ ).toBe(false);
+ expect(
+ icon.getAttribute('aria-hidden'),
+ 'host element has aria-hidden="true" when no label'
+ ).toBe('true');
});
},
};
@@ -133,22 +147,40 @@ export const LabelTogglingTest: Story = {
const svg = () => icon.querySelector('svg')!;
await step('initial label "x" sets aria-label on svg', async () => {
- expect(svg().getAttribute('aria-label')).toBe('x');
- expect(svg().getAttribute('aria-hidden')).toBeNull();
+ expect(
+ svg().getAttribute('aria-label'),
+ 'SVG aria-label is "x" initially'
+ ).toBe('x');
+ expect(
+ svg().getAttribute('aria-hidden'),
+ 'SVG has no aria-hidden when label is set'
+ ).toBeNull();
});
await step('clearing label sets aria-hidden on svg', async () => {
icon.label = '';
await icon.updateComplete;
- expect(svg().getAttribute('aria-hidden')).toBe('true');
- expect(svg().hasAttribute('aria-label')).toBe(false);
+ expect(
+ svg().getAttribute('aria-hidden'),
+ 'SVG has aria-hidden="true" after label is cleared'
+ ).toBe('true');
+ expect(
+ svg().hasAttribute('aria-label'),
+ 'SVG has no aria-label after label is cleared'
+ ).toBe(false);
});
await step('setting label "y" restores aria-label on svg', async () => {
icon.label = 'y';
await icon.updateComplete;
- expect(svg().getAttribute('aria-label')).toBe('y');
- expect(svg().getAttribute('aria-hidden')).toBeNull();
+ expect(
+ svg().getAttribute('aria-label'),
+ 'SVG aria-label is "y" after setting new label'
+ ).toBe('y');
+ expect(
+ svg().getAttribute('aria-hidden'),
+ 'SVG has no aria-hidden after label is restored'
+ ).toBeNull();
});
},
};
diff --git a/2nd-gen/packages/swc/components/progress-circle/test/progress-circle.test.ts b/2nd-gen/packages/swc/components/progress-circle/test/progress-circle.test.ts
index 5a137eb37a4..1e757bf48bd 100644
--- a/2nd-gen/packages/swc/components/progress-circle/test/progress-circle.test.ts
+++ b/2nd-gen/packages/swc/components/progress-circle/test/progress-circle.test.ts
@@ -82,7 +82,7 @@ export const SizesTest: Story = {
await step('renders expected size attributes', async () => {
circles.forEach((circle) => {
const size = circle.getAttribute('size');
- expect(size).toBeTruthy();
+ expect(size, `progress circle has a size attribute`).toBeTruthy();
});
});
},
@@ -99,8 +99,14 @@ export const StaticColorsTest: Story = {
await step('reflects expected static-color attribute values', async () => {
circles.forEach((circle) => {
const staticColor = circle.getAttribute('static-color');
- expect(staticColor).toBeTruthy();
- expect(['white', 'black']).toContain(staticColor);
+ expect(
+ staticColor,
+ 'progress circle has a static-color attribute'
+ ).toBeTruthy();
+ expect(
+ ['white', 'black'],
+ `static-color "${staticColor}" is a valid value`
+ ).toContain(staticColor);
});
});
},
@@ -120,7 +126,10 @@ export const LabelClearingTest: Story = {
);
await step('initially has aria-label set from label', async () => {
- expect(progressCircle.getAttribute('aria-label')).toBe('Uploading file');
+ expect(
+ progressCircle.getAttribute('aria-label'),
+ 'aria-label is set from the label property'
+ ).toBe('Uploading file');
});
await step(
@@ -154,7 +163,10 @@ export const AriaLabelAccessibleNameTest: Story = {
progressCircle.progress = 40;
await progressCircle.updateComplete;
- expect(warnCalls.length).toBe(0);
+ expect(
+ warnCalls.length,
+ 'no warnings are emitted when aria-label provides the accessible name'
+ ).toBe(0);
})
);
},
@@ -180,7 +192,10 @@ export const AriaLabelledbyAccessibleNameTest: Story = {
progressCircle.progress = 70;
await progressCircle.updateComplete;
- expect(warnCalls.length).toBe(0);
+ expect(
+ warnCalls.length,
+ 'no warnings are emitted when aria-labelledby provides the accessible name'
+ ).toBe(0);
})
);
},
@@ -244,10 +259,14 @@ export const LightDomWithLabelDeprecationOnlyTest: Story = {
progressCircle.progress = 6;
await progressCircle.updateComplete;
- expect(warnCalls.length).toBe(1);
- expect(String(warnCalls[0]?.[1] ?? '')).toContain(
- 'no longer has a default slot'
- );
+ expect(
+ warnCalls.length,
+ 'exactly one warning is emitted for light DOM when label provides the accessible name'
+ ).toBe(1);
+ expect(
+ String(warnCalls[0]?.[1] ?? ''),
+ 'the warning message references the removed default slot'
+ ).toContain('no longer has a default slot');
})
);
},
@@ -308,13 +327,25 @@ export const ProgressClampTest: Story = {
);
await step('clamps progress above 100 to 100', async () => {
- expect(circles[0].progress).toBe(100);
- expect(circles[0].getAttribute('aria-valuenow')).toBe('100');
+ expect(
+ circles[0].progress,
+ 'progress property is clamped to 100 when set to 150'
+ ).toBe(100);
+ expect(
+ circles[0].getAttribute('aria-valuenow'),
+ 'aria-valuenow reflects clamped value of 100'
+ ).toBe('100');
});
await step('clamps progress below 0 to 0', async () => {
- expect(circles[1].progress).toBe(0);
- expect(circles[1].getAttribute('aria-valuenow')).toBe('0');
+ expect(
+ circles[1].progress,
+ 'progress property is clamped to 0 when set to -20'
+ ).toBe(0);
+ expect(
+ circles[1].getAttribute('aria-valuenow'),
+ 'aria-valuenow reflects clamped value of 0'
+ ).toBe('0');
});
},
};
@@ -345,8 +376,14 @@ export const ReturnToIndeterminateTest: Story = {
);
await step('renders determinate progress with aria-valuenow', async () => {
- expect(progressCircle.hasAttribute('aria-valuenow')).toBe(true);
- expect(progressCircle.getAttribute('aria-valuenow')).toBe('50');
+ expect(
+ progressCircle.hasAttribute('aria-valuenow'),
+ 'aria-valuenow is present in determinate state'
+ ).toBe(true);
+ expect(
+ progressCircle.getAttribute('aria-valuenow'),
+ 'aria-valuenow reflects the initial progress of 50'
+ ).toBe('50');
});
await step(
@@ -355,7 +392,10 @@ export const ReturnToIndeterminateTest: Story = {
progressCircle.progress = null;
await progressCircle.updateComplete;
- expect(progressCircle.hasAttribute('aria-valuenow')).toBe(false);
+ expect(
+ progressCircle.hasAttribute('aria-valuenow'),
+ 'aria-valuenow is removed after switching to indeterminate'
+ ).toBe(false);
}
);
},
@@ -389,8 +429,14 @@ export const AccessibilityWarningTest: Story = {
progressCircle.label = '';
await progressCircle.updateComplete;
- expect(warnCalls.length).toBeGreaterThan(0);
- expect(String(warnCalls[0]?.[1] || '')).toContain('accessible');
+ expect(
+ warnCalls.length,
+ 'at least one warning is emitted when there is no accessible name'
+ ).toBeGreaterThan(0);
+ expect(
+ String(warnCalls[0]?.[1] || ''),
+ 'the warning message references the accessible name requirement'
+ ).toContain('accessible');
})
);
},
diff --git a/2nd-gen/packages/swc/components/status-light/stories/status-light.stories.ts b/2nd-gen/packages/swc/components/status-light/stories/status-light.stories.ts
index e79b2b7d353..469e0647480 100644
--- a/2nd-gen/packages/swc/components/status-light/stories/status-light.stories.ts
+++ b/2nd-gen/packages/swc/components/status-light/stories/status-light.stories.ts
@@ -63,7 +63,7 @@ argTypes.size = {
/**
* Status lights describe the condition of an entity. Much like [badges](../?path=/docs/components-badge--readme), they can be used to convey semantic meaning, such as statuses and categories.
*/
-export const meta: Meta = {
+const meta: Meta = {
title: 'Status light',
component: 'swc-status-light',
parameters: {
@@ -85,11 +85,7 @@ export const meta: Meta = {
tags: ['migrated'],
};
-export default {
- ...meta,
- title: 'Status light',
- excludeStories: ['meta'],
-} as Meta;
+export default meta;
// ────────────────────
// HELPERS
@@ -215,12 +211,13 @@ export const Sizes: Story = {
*/
export const SemanticVariants: Story = {
render: (args) => html`
- ${STATUS_LIGHT_VARIANTS_SEMANTIC.map((variant: StatusLightSemanticVariant) =>
- template({
- ...args,
- variant,
- 'default-slot': semanticLabels[variant],
- })
+ ${STATUS_LIGHT_VARIANTS_SEMANTIC.map(
+ (variant: StatusLightSemanticVariant) =>
+ template({
+ ...args,
+ variant,
+ 'default-slot': semanticLabels[variant],
+ })
)}
`,
parameters: {
diff --git a/2nd-gen/packages/swc/components/status-light/test/status-light.test.ts b/2nd-gen/packages/swc/components/status-light/test/status-light.test.ts
index a54c2270afa..f5c94c2634a 100644
--- a/2nd-gen/packages/swc/components/status-light/test/status-light.test.ts
+++ b/2nd-gen/packages/swc/components/status-light/test/status-light.test.ts
@@ -21,9 +21,20 @@ import { StatusLight } from '@adobe/spectrum-wc/status-light';
import '@adobe/spectrum-wc/status-light';
+import {
+ STATUS_LIGHT_VALID_SIZES,
+ STATUS_LIGHT_VARIANTS_COLOR,
+ STATUS_LIGHT_VARIANTS_SEMANTIC,
+} from '../../../../core/components/status-light/StatusLight.types.js';
import { getComponent, withWarningSpy } from '../../../utils/test-utils.js';
-import { meta } from '../stories/status-light.stories.js';
-import { Overview, Playground } from '../stories/status-light.stories.js';
+import meta, {
+ Anatomy,
+ NonSemanticVariants,
+ Overview,
+ Playground,
+ SemanticVariants,
+ Sizes,
+} from '../stories/status-light.stories.js';
// This file defines dev-only test stories that reuse the main story metadata.
export default {
@@ -49,9 +60,36 @@ export const DefaultTest: Story = {
);
await step('renders default properties and slot content', async () => {
- expect(statusLight.variant).toBe('info');
- expect(statusLight.size).toBe('m');
- expect(statusLight.textContent?.trim()).toBeTruthy();
+ expect(statusLight.variant, 'default variant is info').toBe('info');
+ expect(statusLight.size, 'default size is m').toBe('m');
+ expect(
+ statusLight.textContent?.trim(),
+ 'default slot has text content'
+ ).toBeTruthy();
+ });
+ },
+};
+
+// ──────────────────────────────────────────────────────────────
+// TEST: Slots
+// ──────────────────────────────────────────────────────────────
+
+export const AnatomyTest: Story = {
+ ...Anatomy,
+ play: async ({ canvasElement, step }) => {
+ const statusLight = await getComponent(
+ canvasElement,
+ 'swc-status-light'
+ );
+
+ await step('renders with correct variant and slot content', async () => {
+ expect(statusLight.variant, 'anatomy story variant is positive').toBe(
+ 'positive'
+ );
+ expect(
+ statusLight.textContent?.trim(),
+ 'default slot text content is present'
+ ).toBeTruthy();
});
},
};
@@ -61,22 +99,27 @@ export const DefaultTest: Story = {
// ──────────────────────────────────────────────────────────────
export const SizesTest: Story = {
- render: () => html`
- ${StatusLight.VALID_SIZES.map(
- (size) => html`
- ${size}
- `
- )}
- `,
+ ...Sizes,
play: async ({ canvasElement, step }) => {
await step('renders and reflects each size correctly', async () => {
- StatusLight.VALID_SIZES.forEach((size) => {
+ for (const size of STATUS_LIGHT_VALID_SIZES) {
const statusLight = canvasElement.querySelector(
`swc-status-light[size="${size}"]`
- ) as StatusLight;
- expect(statusLight.variant).toBe('info');
- expect(statusLight.size).toBe(size);
- });
+ ) as StatusLight | null;
+ expect(
+ statusLight,
+ `status light with size="${size}" is rendered`
+ ).toBeTruthy();
+ await statusLight?.updateComplete;
+ expect(
+ statusLight?.variant,
+ `status light with size="${size}" has variant info`
+ ).toBe('info');
+ expect(
+ statusLight?.size,
+ `status light size property is "${size}"`
+ ).toBe(size);
+ }
});
},
};
@@ -105,13 +148,115 @@ export const ComposedComponentTest: Story = {
);
await step('renders within composed content', async () => {
- expect(statusLight.variant).toBe('positive');
- expect(statusLight.size).toBe('m');
- expect(statusLight.textContent?.trim()).toBeTruthy();
+ expect(
+ statusLight.variant,
+ 'variant is positive in composed context'
+ ).toBe('positive');
+ expect(statusLight.size, 'size is m in composed context').toBe('m');
+ expect(
+ statusLight.textContent?.trim(),
+ 'slot content is present in composed context'
+ ).toBeTruthy();
});
},
};
+// ──────────────────────────────────────────────────────────────
+// TEST: Variants / States
+// ──────────────────────────────────────────────────────────────
+
+export const SemanticVariantsTest: Story = {
+ ...SemanticVariants,
+ play: async ({ canvasElement, step }) => {
+ await step('renders all semantic variants', async () => {
+ for (const variant of STATUS_LIGHT_VARIANTS_SEMANTIC) {
+ const statusLight = canvasElement.querySelector(
+ `swc-status-light[variant="${variant}"]`
+ ) as StatusLight | null;
+ expect(
+ statusLight,
+ `status light with variant="${variant}" is rendered`
+ ).toBeTruthy();
+ await statusLight?.updateComplete;
+ expect(
+ statusLight?.variant,
+ `status light variant property is "${variant}"`
+ ).toBe(variant);
+ }
+ });
+ },
+};
+
+export const NonSemanticVariantsTest: Story = {
+ ...NonSemanticVariants,
+ play: async ({ canvasElement, step }) => {
+ await step('renders all non-semantic color variants', async () => {
+ for (const variant of STATUS_LIGHT_VARIANTS_COLOR) {
+ const statusLight = canvasElement.querySelector(
+ `swc-status-light[variant="${variant}"]`
+ ) as StatusLight | null;
+ expect(
+ statusLight,
+ `status light with variant="${variant}" is rendered`
+ ).toBeTruthy();
+ await statusLight?.updateComplete;
+ expect(
+ statusLight?.variant,
+ `status light variant property is "${variant}"`
+ ).toBe(variant);
+ }
+ });
+ },
+};
+
+export const VariantMutationTest: Story = {
+ render: () => html`
+ Active
+ `,
+ play: async ({ canvasElement, step }) => {
+ const statusLight = await getComponent(
+ canvasElement,
+ 'swc-status-light'
+ );
+
+ await step(
+ 'reflects variant attribute after mutation to positive',
+ async () => {
+ statusLight.variant = 'positive';
+ await statusLight.updateComplete;
+ expect(
+ statusLight.getAttribute('variant'),
+ 'variant attribute is positive after mutation'
+ ).toBe('positive');
+ }
+ );
+
+ await step(
+ 'reflects variant attribute after mutation to negative',
+ async () => {
+ statusLight.variant = 'negative';
+ await statusLight.updateComplete;
+ expect(
+ statusLight.getAttribute('variant'),
+ 'variant attribute is negative after second mutation'
+ ).toBe('negative');
+ }
+ );
+
+ await step(
+ 'reflects variant attribute after mutation to non-semantic color variant',
+ async () => {
+ statusLight.variant = 'seafoam';
+ await statusLight.updateComplete;
+ expect(
+ statusLight.getAttribute('variant'),
+ 'variant attribute is seafoam after mutation to color variant'
+ ).toBe('seafoam');
+ }
+ );
+ },
+};
+
// ──────────────────────────────────────────────────────────────
// TEST: Dev mode warnings
// ──────────────────────────────────────────────────────────────
@@ -130,12 +275,63 @@ export const UnsupportedVariantWarningTest: Story = {
statusLight.setAttribute('variant', 'accent');
await statusLight.updateComplete;
- expect(warnCalls.length).toBeGreaterThan(0);
- expect(warnCalls[0][0]).toBe(statusLight);
- expect(warnCalls[0][1]).toBe(
+ expect(
+ warnCalls.length,
+ 'at least one warning is emitted for unsupported variant'
+ ).toBeGreaterThan(0);
+ expect(
+ warnCalls[0][0],
+ 'warning is emitted from the status light element'
+ ).toBe(statusLight);
+ expect(
+ warnCalls[0][1],
+ 'warning message references the variant attribute'
+ ).toBe(
`<${statusLight.localName}> element expects the "variant" attribute to be one of the following:`
);
})
);
},
};
+
+export const ValidVariantNoWarningTest: Story = {
+ render: () => html`
+ Active
+ `,
+ play: async ({ canvasElement, step }) => {
+ const statusLight = await getComponent(
+ canvasElement,
+ 'swc-status-light'
+ );
+
+ await step(
+ 'does not warn when a valid semantic variant is set in DEBUG mode',
+ () =>
+ withWarningSpy(async (warnCalls) => {
+ for (const variant of STATUS_LIGHT_VARIANTS_SEMANTIC) {
+ statusLight.variant = variant;
+ await statusLight.updateComplete;
+ }
+
+ expect(
+ warnCalls.length,
+ 'no warnings are emitted for valid semantic variants'
+ ).toBe(0);
+ })
+ );
+
+ await step(
+ 'does not warn when a valid color variant is set in DEBUG mode',
+ () =>
+ withWarningSpy(async (warnCalls) => {
+ statusLight.variant = 'seafoam';
+ await statusLight.updateComplete;
+
+ expect(
+ warnCalls.length,
+ 'no warnings are emitted for valid color variants'
+ ).toBe(0);
+ })
+ );
+ },
+};
diff --git a/2nd-gen/packages/swc/components/typography/test/typography.a11y.spec.ts b/2nd-gen/packages/swc/components/typography/test/typography.a11y.spec.ts
new file mode 100644
index 00000000000..4fa791bf227
--- /dev/null
+++ b/2nd-gen/packages/swc/components/typography/test/typography.a11y.spec.ts
@@ -0,0 +1,92 @@
+/**
+ * Copyright 2026 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+
+import type { Locator, Page } from '@playwright/test';
+import { expect, test } from '@playwright/test';
+
+/**
+ * Accessibility tests for Typography styles (2nd Generation)
+ *
+ * Typography is a CSS-only utility — there is no `` custom element.
+ * This local helper navigates to a story and waits for the `.typography-samples`
+ * container to appear rather than using `customElements.whenDefined()`, which only
+ * works for custom elements with a hyphen in the tag name.
+ *
+ * ARIA snapshot tests validate the accessibility tree structure.
+ * aXe WCAG compliance and color contrast validation are run via
+ * test-storybook (see .storybook/test-runner.ts). Both are included
+ * in the `test:a11y` command.
+ */
+async function gotoTypographyStory(
+ page: Page,
+ storyId: string
+): Promise {
+ await page.goto(`/iframe.html?id=${storyId}&viewMode=story`, {
+ waitUntil: 'domcontentloaded',
+ });
+
+ // Wait for the Storybook root to contain rendered content
+ await page.waitForFunction(() => {
+ const root = document.querySelector('#storybook-root');
+ return root && root.children.length > 0;
+ });
+
+ // Wait for the typography wrapper to be visible (CSS-only component)
+ await page
+ .locator('.typography-samples')
+ .first()
+ .waitFor({ state: 'visible' });
+
+ return page.locator('#storybook-root');
+}
+
+test.describe('Typography - ARIA Snapshots', () => {
+ test('heading variant renders an accessible level-2 heading', async ({
+ page,
+ }) => {
+ const root = await gotoTypographyStory(
+ page,
+ 'components-typography--playground'
+ );
+ await expect(root).toMatchAriaSnapshot(`
+ - heading /Reserved for main page heading/ [level=2]
+ `);
+ });
+
+ test('heading variant with all sizes renders multiple accessible headings', async ({
+ page,
+ }) => {
+ const root = await gotoTypographyStory(
+ page,
+ 'components-typography--heading-variant'
+ );
+ await expect(root).toMatchAriaSnapshot(`
+ - heading /Reserved for main page heading/ [level=2]
+ - heading /Reserved for main page heading/ [level=2]
+ `);
+ });
+
+ test('prose container renders nested semantic heading hierarchy', async ({
+ page,
+ }) => {
+ const root = await gotoTypographyStory(
+ page,
+ 'components-typography--prose-container'
+ );
+ await expect(root).toMatchAriaSnapshot(`
+ - heading "Semantic H1" [level=1]
+ - heading "Semantic H2" [level=2]
+ - heading "Semantic H3" [level=3]
+ - heading "Semantic H4" [level=4]
+ `);
+ });
+});
diff --git a/2nd-gen/packages/swc/components/typography/test/typography.test.ts b/2nd-gen/packages/swc/components/typography/test/typography.test.ts
new file mode 100644
index 00000000000..18e5976350e
--- /dev/null
+++ b/2nd-gen/packages/swc/components/typography/test/typography.test.ts
@@ -0,0 +1,251 @@
+/**
+ * Copyright 2026 Adobe. All rights reserved.
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License. You may obtain a copy
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
+ * OF ANY KIND, either express or implied. See the License for the specific language
+ * governing permissions and limitations under the License.
+ */
+import { html } from 'lit';
+import { expect } from '@storybook/test';
+import type { Meta, StoryObj as Story } from '@storybook/web-components';
+
+import meta, {
+ CodeVariant,
+ Defaults,
+ HeadingHeavy,
+ HeadingVariant,
+ Playground,
+} from '../stories/typography.stories.js';
+import { template } from '../stories/typography.template.js';
+
+// This file defines dev-only test stories that reuse the main story metadata.
+export default {
+ ...meta,
+ title: 'Typography/Tests',
+ parameters: {
+ ...meta.parameters,
+ docs: { disable: true, page: null },
+ },
+ tags: ['!autodocs', 'dev'],
+} as Meta;
+
+// ──────────────────────────────────────────────────────────────
+// TEST: Defaults
+// ──────────────────────────────────────────────────────────────
+
+export const PlaygroundTest: Story = {
+ ...Playground,
+ play: async ({ canvasElement, step }) => {
+ await step('renders heading variant at default size M', async () => {
+ const heading = canvasElement.querySelector('h2');
+ expect(heading, 'heading element is rendered').toBeTruthy();
+ expect(
+ heading?.className,
+ 'heading has base swc-Heading class'
+ ).toContain('swc-Heading');
+ // Size M is the default and does not add a size suffix class
+ expect(
+ heading?.className,
+ 'no explicit size class for default M'
+ ).not.toContain('--size');
+ });
+ },
+};
+
+export const DefaultsTest: Story = {
+ ...Defaults,
+ play: async ({ canvasElement, step }) => {
+ await step(
+ 'renders all typography variants at default size M',
+ async () => {
+ const headings = canvasElement.querySelectorAll('h2.swc-Heading');
+ expect(headings.length, 'heading variant is rendered').toBeGreaterThan(
+ 0
+ );
+
+ const titleEls = canvasElement.querySelectorAll('[class*="swc-Title"]');
+ expect(titleEls.length, 'title variant is rendered').toBeGreaterThan(0);
+
+ const bodyEls = canvasElement.querySelectorAll('[class*="swc-Body"]');
+ expect(bodyEls.length, 'body variant is rendered').toBeGreaterThan(0);
+
+ const detailEls = canvasElement.querySelectorAll(
+ '[class*="swc-Detail"]'
+ );
+ expect(detailEls.length, 'detail variant is rendered').toBeGreaterThan(
+ 0
+ );
+
+ const codeEls = canvasElement.querySelectorAll('code.swc-Code');
+ expect(codeEls.length, 'code variant is rendered').toBeGreaterThan(0);
+ }
+ );
+ },
+};
+
+// ──────────────────────────────────────────────────────────────
+// TEST: Properties / Attributes
+// ──────────────────────────────────────────────────────────────
+
+export const HeadingVariantTest: Story = {
+ ...HeadingVariant,
+ play: async ({ canvasElement, step }) => {
+ await step('renders heading at all allowed sizes', async () => {
+ // Heading allowed sizes: XS, S, M, L, XL, XXL, XXXL, XXXXL = 8 sizes
+ const headingElements = canvasElement.querySelectorAll('h2.swc-Heading');
+ expect(headingElements.length, 'heading renders 8 size variants').toBe(8);
+ });
+
+ await step('size M heading has no size suffix class', async () => {
+ const headingM = canvasElement.querySelector(
+ 'h2.swc-Heading:not([class*="--size"])'
+ );
+ expect(
+ headingM,
+ 'size M heading has no explicit size suffix'
+ ).toBeTruthy();
+ });
+
+ await step('non-M sizes have explicit size classes', async () => {
+ const headingXL = canvasElement.querySelector('h2[class*="--sizeXL"]');
+ expect(headingXL, 'size XL heading has --sizeXL class').toBeTruthy();
+ });
+ },
+};
+
+export const HeadingHeavyTest: Story = {
+ ...HeadingHeavy,
+ play: async ({ canvasElement, step }) => {
+ await step('renders heading with heavy modifier class', async () => {
+ const heading = canvasElement.querySelector('h2.swc-Heading');
+ expect(heading, 'heading element is rendered').toBeTruthy();
+ expect(
+ heading?.className,
+ 'heading has the heavy modifier class'
+ ).toContain('swc-Heading--heavy');
+ expect(heading?.className, 'heading has the size L class').toContain(
+ 'swc-Heading--sizeL'
+ );
+ });
+ },
+};
+
+export const CodeVariantTest: Story = {
+ ...CodeVariant,
+ play: async ({ canvasElement, step }) => {
+ await step('renders code variant at all allowed sizes', async () => {
+ // Code allowed sizes: XS, S, M, L, XL = 5 sizes
+ const codeElements = canvasElement.querySelectorAll('code.swc-Code');
+ expect(codeElements.length, 'code renders 5 size variants').toBe(5);
+ });
+
+ await step('size M code has no size suffix class', async () => {
+ const codeM = canvasElement.querySelector(
+ 'code.swc-Code:not([class*="--size"])'
+ );
+ expect(
+ codeM,
+ 'size M code element has no explicit size suffix'
+ ).toBeTruthy();
+ });
+ },
+};
+
+// ──────────────────────────────────────────────────────────────
+// TEST: Branches (coverage of template.ts uncovered paths)
+// ──────────────────────────────────────────────────────────────
+
+/**
+ * Tests that coerceSize falls back to 'M' when the requested size is not
+ * in the allowed sizes for the given variant. 'XXXL' is a valid size in
+ * the SIZES array but is not allowed for the 'code' variant (only XS–XL).
+ *
+ * Coverage target: coerceSize fallback branch in typography.template.ts
+ */
+export const InvalidSizeFallbackTest: Story = {
+ render: () => html`
+ ${template({ variant: 'code', size: 'XXXL' })}
+ `,
+ play: async ({ canvasElement, step }) => {
+ await step(
+ 'coerces invalid size XXXL to fallback M for code variant',
+ async () => {
+ const codeEl = canvasElement.querySelector('code');
+ expect(
+ codeEl,
+ 'code element is rendered when XXXL is coerced to M'
+ ).toBeTruthy();
+ expect(
+ codeEl?.className,
+ 'code element has base swc-Code class'
+ ).toContain('swc-Code');
+ // Size M does not add a --sizeXXXL class; fallback renders with no size suffix
+ expect(
+ codeEl?.className,
+ 'code element has no --sizeXXXL class'
+ ).not.toContain('sizeXXXL');
+ }
+ );
+ },
+};
+
+/**
+ * Tests that the `heavy && !caps.supportsHeavy` filter in the variants loop
+ * removes all variants except 'heading' (the only one that supportsHeavy).
+ *
+ * Coverage target: filter branch `if (heavy && !caps.supportsHeavy) return false`
+ * in typography.template.ts
+ */
+export const HeadingHeavyAllVariantsTest: Story = {
+ render: () => html`
+ ${template({ showAllVariants: true, heavy: true })}
+ `,
+ play: async ({ canvasElement, step }) => {
+ await step(
+ 'renders only heading when showAllVariants=true and heavy=true',
+ async () => {
+ // Heading supports heavy → passes the filter
+ const headings = canvasElement.querySelectorAll('h2.swc-Heading');
+ expect(
+ headings.length,
+ 'heading is rendered since it supports heavy'
+ ).toBeGreaterThan(0);
+ }
+ );
+
+ await step(
+ 'filters out title, body, detail, and code since they do not support heavy',
+ async () => {
+ // Title, body, detail render as p; code renders as code
+ // With heavy=true, all non-heading variants are filtered out
+ const titleSamples = canvasElement.querySelectorAll('p.swc-Title');
+ expect(
+ titleSamples.length,
+ 'title variant is filtered out because it does not support heavy'
+ ).toBe(0);
+
+ const bodySamples = canvasElement.querySelectorAll('p.swc-Body');
+ expect(
+ bodySamples.length,
+ 'body variant is filtered out because it does not support heavy'
+ ).toBe(0);
+
+ const detailSamples = canvasElement.querySelectorAll('p.swc-Detail');
+ expect(
+ detailSamples.length,
+ 'detail variant is filtered out because it does not support heavy'
+ ).toBe(0);
+
+ const codeSamples = canvasElement.querySelectorAll('code.swc-Code');
+ expect(
+ codeSamples.length,
+ 'code variant is filtered out because it does not support heavy'
+ ).toBe(0);
+ }
+ );
+ },
+};