Skip to content

Commit 56fd6ef

Browse files
authored
test(button-group): add visual regression tests (#6612)
1 parent 8fad8e0 commit 56fd6ef

2 files changed

Lines changed: 304 additions & 0 deletions

File tree

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/**
2+
* Copyright 2026 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import { html } from 'lit';
14+
import type { Meta, StoryObj as Story } from '@storybook/web-components';
15+
16+
import '@adobe/spectrum-wc/components/button-group/swc-button-group.js';
17+
import '@adobe/spectrum-wc/components/button/swc-button.js';
18+
19+
import type { CustomPropertyCase } from '../../../../.storybook/helpers/index.js';
20+
import {
21+
coveredCustomProperties,
22+
customPropertyRows,
23+
theme,
24+
verifyCustomPropertyCoverage,
25+
vrtParameters,
26+
} from '../../../../.storybook/helpers/index.js';
27+
import customElementsManifest from '../../../../dist/custom-elements.json';
28+
29+
// Metadata
30+
31+
const meta: Meta = {
32+
title: 'Button group/Button group VRT',
33+
component: 'swc-button-group',
34+
tags: ['dev'],
35+
};
36+
37+
export default meta;
38+
39+
// Helpers
40+
41+
// One row per public custom property: a reference group beside the same group
42+
// with that property overridden to an obviously different value, so a real
43+
// visual diff confirms the override still resolves. Fixed width gives
44+
// justify-content room to act.
45+
const CUSTOM_PROPERTY_CASES: readonly CustomPropertyCase<`--swc-button-group-${string}`>[] =
46+
[
47+
{ property: '--swc-button-group-gap', value: '40px' },
48+
{
49+
property: '--swc-button-group-justify-content',
50+
value: 'space-between',
51+
},
52+
];
53+
54+
const renderGroupCase = (_testCase: CustomPropertyCase, style?: string) => html`
55+
<swc-button-group style="inline-size: 320px; ${style ?? ''}">
56+
<swc-button>Save</swc-button>
57+
<swc-button>Cancel</swc-button>
58+
</swc-button-group>
59+
`;
60+
61+
const customPropertiesContent = () =>
62+
customPropertyRows(CUSTOM_PROPERTY_CASES, renderGroupCase);
63+
64+
const coveredButtonGroupCustomProperties = coveredCustomProperties(
65+
CUSTOM_PROPERTY_CASES
66+
);
67+
68+
const verifyCoverage = async () => {
69+
await verifyCustomPropertyCoverage({
70+
customElementsManifest,
71+
modulePath: 'components/button-group/ButtonGroup.ts',
72+
declarationName: 'ButtonGroup',
73+
coveredProperties: coveredButtonGroupCustomProperties,
74+
});
75+
};
76+
77+
// VRT stories
78+
79+
export const CustomProperties: Story = {
80+
render: () => theme(customPropertiesContent(), 'light', 'ltr'),
81+
parameters: vrtParameters,
82+
play: verifyCoverage,
83+
};
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/**
2+
* Copyright 2026 Adobe. All rights reserved.
3+
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License. You may obtain a copy
5+
* of the License at http://www.apache.org/licenses/LICENSE-2.0
6+
*
7+
* Unless required by applicable law or agreed to in writing, software distributed under
8+
* the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9+
* OF ANY KIND, either express or implied. See the License for the specific language
10+
* governing permissions and limitations under the License.
11+
*/
12+
13+
import { html } from 'lit';
14+
import { ifDefined } from 'lit/directives/if-defined.js';
15+
import type { Meta, StoryObj as Story } from '@storybook/web-components';
16+
17+
import {
18+
BUTTON_GROUP_ALIGNMENTS,
19+
BUTTON_GROUP_ORIENTATIONS,
20+
BUTTON_GROUP_SIZES,
21+
} from '@adobe/spectrum-wc-core/components/button-group';
22+
23+
import '@adobe/spectrum-wc/components/button-group/swc-button-group.js';
24+
import '@adobe/spectrum-wc/components/button/swc-button.js';
25+
26+
import {
27+
row,
28+
theme,
29+
vrtParameters,
30+
} from '../../../../.storybook/helpers/index.js';
31+
32+
// Metadata
33+
34+
const meta: Meta = {
35+
title: 'Button group/Button group VRT',
36+
component: 'swc-button-group',
37+
tags: ['dev'],
38+
};
39+
40+
export default meta;
41+
42+
// Helpers
43+
44+
type ButtonCase = {
45+
label: string;
46+
disabled?: boolean;
47+
lang?: string;
48+
};
49+
50+
// Plain children keep the snapshot on the group's own axes; each button's own
51+
// visuals are covered by button's VRT.
52+
const btn = ({ label, disabled = false, lang }: ButtonCase) => html`
53+
<swc-button ?disabled=${disabled} lang=${ifDefined(lang)}>
54+
${label}
55+
</swc-button>
56+
`;
57+
58+
type GroupCase = {
59+
size?: (typeof BUTTON_GROUP_SIZES)[number];
60+
orientation?: (typeof BUTTON_GROUP_ORIENTATIONS)[number];
61+
align?: (typeof BUTTON_GROUP_ALIGNMENTS)[number];
62+
disabled?: boolean;
63+
buttons: unknown;
64+
// Explicit width so alignment and wrapping have room to act; the group is
65+
// only wider than its content when told to be.
66+
width?: string;
67+
};
68+
69+
const renderGroup = ({
70+
size,
71+
orientation,
72+
align,
73+
disabled = false,
74+
buttons,
75+
width = '260px',
76+
}: GroupCase) => html`
77+
<swc-button-group
78+
size=${ifDefined(size)}
79+
orientation=${ifDefined(orientation)}
80+
align=${ifDefined(align)}
81+
?disabled=${disabled}
82+
style="inline-size: ${width};"
83+
>
84+
${buttons}
85+
</swc-button-group>
86+
`;
87+
88+
// One labeled row per axis the group owns: sizes, both orientations, alignment
89+
// in each orientation, disabled propagation, flex-wrapping, and CJK labels.
90+
const permutationContent = () => html`
91+
${row(
92+
BUTTON_GROUP_SIZES.map((size) =>
93+
renderGroup({
94+
size,
95+
width: '220px',
96+
buttons: html`
97+
${btn({ label: 'Save' })} ${btn({ label: 'Cancel' })}
98+
`,
99+
})
100+
),
101+
'Sizes'
102+
)}
103+
${row(
104+
[
105+
renderGroup({
106+
orientation: 'horizontal',
107+
width: '260px',
108+
buttons: html`
109+
${btn({ label: 'Save' })} ${btn({ label: 'Cancel' })}
110+
`,
111+
}),
112+
renderGroup({
113+
orientation: 'vertical',
114+
width: '160px',
115+
buttons: html`
116+
${btn({ label: 'Save' })} ${btn({ label: 'Cancel' })}
117+
`,
118+
}),
119+
],
120+
'Orientation'
121+
)}
122+
${row(
123+
BUTTON_GROUP_ALIGNMENTS.map((align) =>
124+
renderGroup({
125+
align,
126+
width: '320px',
127+
buttons: html`
128+
${btn({ label: 'Save' })} ${btn({ label: 'Cancel' })}
129+
`,
130+
})
131+
),
132+
'Alignment (horizontal)'
133+
)}
134+
${row(
135+
BUTTON_GROUP_ALIGNMENTS.map((align) =>
136+
renderGroup({
137+
orientation: 'vertical',
138+
align,
139+
width: '200px',
140+
buttons: html`
141+
${btn({ label: 'Save' })} ${btn({ label: 'Cancel' })}
142+
`,
143+
})
144+
),
145+
'Alignment (vertical)'
146+
)}
147+
${row(
148+
[
149+
renderGroup({
150+
disabled: true,
151+
width: '320px',
152+
buttons: html`
153+
${btn({ label: 'Save' })} ${btn({ label: 'Cancel' })}
154+
${btn({ label: 'Reset' })}
155+
`,
156+
}),
157+
renderGroup({
158+
width: '320px',
159+
buttons: html`
160+
${btn({ label: 'Copy' })} ${btn({ label: 'Paste', disabled: true })}
161+
`,
162+
}),
163+
],
164+
'Disabled'
165+
)}
166+
${row(
167+
[
168+
renderGroup({
169+
width: '240px',
170+
buttons: html`
171+
${btn({ label: 'One' })} ${btn({ label: 'Two' })}
172+
${btn({ label: 'Three' })} ${btn({ label: 'Four' })}
173+
${btn({ label: 'Five' })}
174+
`,
175+
}),
176+
],
177+
'Wrapping'
178+
)}
179+
${row(
180+
[
181+
renderGroup({
182+
width: '260px',
183+
buttons: html`
184+
${btn({ label: '保存', lang: 'ja' })}
185+
${btn({ label: 'キャンセル', lang: 'ja' })}
186+
`,
187+
}),
188+
renderGroup({
189+
width: '220px',
190+
buttons: html`
191+
${btn({ label: '저장', lang: 'ko' })}
192+
${btn({ label: '취소', lang: 'ko' })}
193+
`,
194+
}),
195+
renderGroup({
196+
width: '220px',
197+
buttons: html`
198+
${btn({ label: '保存', lang: 'zh' })}
199+
${btn({ label: '取消', lang: 'zh' })}
200+
`,
201+
}),
202+
],
203+
'CJK language'
204+
)}
205+
`;
206+
207+
// VRT stories
208+
209+
// Light/ltr and dark/rtl in one snapshot; the rtl pass doubles as directional
210+
// coverage since flow and start/end alignment mirror.
211+
export const Permutations: Story = {
212+
render: () => html`
213+
${theme(permutationContent(), 'light', 'ltr')}
214+
${theme(permutationContent(), 'dark', 'rtl')}
215+
`,
216+
parameters: vrtParameters,
217+
};
218+
219+
// No forced-colors story: the group's CSS is pure flexbox layout with no color
220+
// of its own, so only the child buttons shift under forced-colors, and that's
221+
// covered by button's VRT.

0 commit comments

Comments
 (0)