Skip to content

Commit 9f02cb6

Browse files
committed
test(checkbox): add tests
1 parent 4305687 commit 9f02cb6

File tree

1 file changed

+126
-0
lines changed

1 file changed

+126
-0
lines changed
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { newSpecPage } from '@stencil/core/testing';
2+
import { Checkbox } from './checkbox';
3+
4+
describe('limel-checkbox (aria semantics)', () => {
5+
async function setup(props: Partial<Checkbox> = {}) {
6+
const page = await newSpecPage({
7+
components: [Checkbox],
8+
html: `<limel-checkbox></limel-checkbox>`,
9+
});
10+
const host = page.root as HTMLLimelCheckboxElement;
11+
Object.assign(host, props);
12+
await page.waitForChanges();
13+
const input: HTMLInputElement | null = host.shadowRoot?.querySelector(
14+
'input[type="checkbox"]'
15+
);
16+
return { page, host, input };
17+
}
18+
19+
it('sets aria-checked="false" when unchecked', async () => {
20+
const { input } = await setup({ checked: false });
21+
expect(input?.getAttribute('aria-checked')).toBe('false');
22+
});
23+
24+
it('sets aria-checked="true" when checked', async () => {
25+
const { host, page } = await setup({ checked: false });
26+
host.checked = true;
27+
await page.waitForChanges();
28+
const input = host.shadowRoot?.querySelector('input[type="checkbox"]');
29+
expect(input?.getAttribute('aria-checked')).toBe('true');
30+
});
31+
32+
it('sets aria-checked="mixed" and checked property true when indeterminate', async () => {
33+
const { host, page } = await setup({ checked: false });
34+
host.indeterminate = true;
35+
await page.waitForChanges();
36+
const input = host.shadowRoot?.querySelector(
37+
'input[type="checkbox"]'
38+
) as HTMLInputElement;
39+
expect(input.getAttribute('aria-checked')).toBe('mixed');
40+
// Visual hook: component forces input.checked when indeterminate for CSS
41+
expect(input.checked).toBe(true);
42+
expect(input.indeterminate).toBe(true);
43+
});
44+
45+
it('returns to aria-checked="false" when indeterminate cleared and still unchecked', async () => {
46+
const { host, page } = await setup({
47+
checked: false,
48+
indeterminate: true,
49+
});
50+
host.indeterminate = false;
51+
await page.waitForChanges();
52+
const input = host.shadowRoot?.querySelector('input[type="checkbox"]');
53+
expect(input?.getAttribute('aria-checked')).toBe('false');
54+
});
55+
56+
it('emits change event with correct detail when toggled', async () => {
57+
const { host, input, page } = await setup({ checked: false });
58+
const handler = jest.fn();
59+
host.addEventListener('change', (e: CustomEvent<boolean>) =>
60+
handler(e.detail)
61+
);
62+
(input as HTMLInputElement).checked = true;
63+
input?.dispatchEvent(
64+
new Event('change', { bubbles: true, composed: true })
65+
);
66+
await page.waitForChanges();
67+
expect(handler).toHaveBeenCalledTimes(1);
68+
expect(handler).toHaveBeenCalledWith(true);
69+
});
70+
71+
it('renders dynamic-label instead of native input when readonly', async () => {
72+
const { host } = await setup({ readonly: true, checked: true });
73+
const input = host.shadowRoot?.querySelector('input[type="checkbox"]');
74+
const dyn = host.shadowRoot?.querySelector('limel-dynamic-label');
75+
expect(input).toBeNull();
76+
expect(dyn).not.toBeNull();
77+
});
78+
79+
it('does not emit change when disabled', async () => {
80+
const { host, input } = await setup({ disabled: true, checked: false });
81+
const handler = jest.fn();
82+
host.addEventListener('change', (e: CustomEvent<boolean>) =>
83+
handler(e.detail)
84+
);
85+
// Even if we simulate a change event, component logic should still emit
86+
// because we currently don't guard in handler, but native input wouldn't fire in real UI.
87+
// This test documents current behavior; adjust if handler changes.
88+
(input as HTMLInputElement).checked = true;
89+
input?.dispatchEvent(new Event('change'));
90+
expect(handler).toHaveBeenCalledWith(true);
91+
});
92+
93+
it('marks invalid when required and unchecked after interaction', async () => {
94+
const { host, input, page } = await setup({
95+
required: true,
96+
checked: false,
97+
});
98+
// Simulate user interaction (toggle true then false) to set modified
99+
(input as HTMLInputElement).checked = true;
100+
input?.dispatchEvent(new Event('change', { bubbles: true }));
101+
await page.waitForChanges();
102+
(input as HTMLInputElement).checked = false;
103+
input?.dispatchEvent(new Event('change', { bubbles: true }));
104+
await page.waitForChanges();
105+
// invalid state applied to wrapper div
106+
const wrapper = host.shadowRoot?.querySelector('.checkbox');
107+
expect(wrapper?.classList.contains('invalid')).toBe(true);
108+
});
109+
110+
it('clears indeterminate state properties when toggled from mixed to checked', async () => {
111+
const { host, page } = await setup({
112+
indeterminate: true,
113+
checked: false,
114+
});
115+
// Simulate consumer changing to checked true and indeterminate false
116+
host.checked = true;
117+
host.indeterminate = false;
118+
await page.waitForChanges();
119+
const input = host.shadowRoot?.querySelector(
120+
'input[type="checkbox"]'
121+
) as HTMLInputElement;
122+
expect(input.indeterminate).toBe(false);
123+
expect(input.checked).toBe(true);
124+
expect(input.getAttribute('aria-checked')).toBe('true');
125+
});
126+
});

0 commit comments

Comments
 (0)