Skip to content

Commit 9ac6a50

Browse files
Merge pull request #676 from thejackshelton/collapsible
tests: larger collapsible test suite
2 parents 7d0ad88 + f28400d commit 9ac6a50

File tree

7 files changed

+127
-30
lines changed

7 files changed

+127
-30
lines changed

.changeset/eleven-bulldogs-hug.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@qwik-ui/headless': patch
3+
---
4+
5+
refactor: strip browser user agent styles relating to max-width

apps/website/src/routes/docs/headless/collapsible/examples/open-change.tsx

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,28 @@ import SVG from './svg';
66
export default component$(() => {
77
useStyles$(styles);
88
const count = useSignal<number>(0);
9+
const isOpen = useSignal<boolean>(false);
910

1011
const handleOpenChange$ = $((open: boolean) => {
11-
if (open) {
12-
count.value++;
13-
}
12+
isOpen.value = open;
13+
count.value++;
1414
});
1515

1616
return (
17-
<Collapsible class="collapsible" onOpenChange$={handleOpenChange$}>
18-
<CollapsibleTrigger class="collapsible-trigger">
19-
<span>Trigger</span>
20-
<SVG />
21-
</CollapsibleTrigger>
22-
<CollapsibleContent class="collapsible-content collapsible-content-outline ">
23-
Content: {count.value}
24-
</CollapsibleContent>
25-
</Collapsible>
17+
<>
18+
<p>
19+
count: <strong> {count.value}</strong>
20+
</p>
21+
22+
<Collapsible class="collapsible" onOpenChange$={handleOpenChange$}>
23+
<CollapsibleTrigger class="collapsible-trigger">
24+
<span>Trigger</span>
25+
<SVG />
26+
</CollapsibleTrigger>
27+
<CollapsibleContent class="collapsible-content collapsible-content-outline ">
28+
Content
29+
</CollapsibleContent>
30+
</Collapsible>
31+
</>
2632
);
2733
});

apps/website/src/routes/docs/headless/collapsible/examples/programmatic.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import styles from '../snippets/collapsible.css?inline';
44

55
export default component$(() => {
66
useStyles$(styles);
7-
const isOpen = useSignal<boolean>(true);
7+
const isOpen = useSignal<boolean>(false);
88

99
return (
1010
<>

apps/website/src/routes/docs/headless/modal/index.mdx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,20 @@ This means that if a user is navigating through the modal using the Tab key, rea
154154

155155
> Focus locking behavior is provided by default in Qwik UI. If you encounter any use cases that require customized focus lock behavior, please [submit an issue](https://github.com/qwikifiers/qwik-ui/issues) on our GitHub repository.
156156
157+
## Stripped Styles
158+
159+
As a headless library, we intentionally try not to add any styles to the components.
160+
161+
However, because Qwik UI builds on top of native solutions when they are well-supported, feasible, and performant, some of the widgets may inclue browser user-agent styles.
162+
163+
These styles can be unintuitive tricky to debug. Which has been the case with Qwik UI's own docs site. As a result, we have stripped these styles from the Modal component.
164+
165+
<CodeSnippet name="stripped-styles.css" />
166+
167+
While in most cases, this would be up to a consumer's CSS reset to solve, in this case we are **stripping** the max-width and max-height styles on the dialog element under the hood.
168+
169+
This is done in a separate layer so that styles are easily overridable in consumer facing applications.
170+
157171
## Animations
158172

159173
Animating things to display none has historically been a significant challenge on the web. This is because display none is a `discrete` property, and is **unanimatable**.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
@layer qwik-ui {
2+
/* browsers automatically set an interesting max-width and max-height for dialogs
3+
https://twitter.com/t3dotgg/status/1774350919133691936
4+
*/
5+
dialog:modal {
6+
max-width: unset;
7+
max-height: unset;
8+
}
9+
}

packages/kit-headless/src/components/collapsible/collapsible.test.ts

Lines changed: 72 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ async function setup(page: Page, exampleName: string) {
1212
}
1313

1414
test.describe('Mouse Behavior', () => {
15-
test(`GIVEN a hero collapsible
15+
test(`GIVEN a collapsible
1616
WHEN clicking on the trigger
1717
THEN the content should be visible
1818
AND aria-expanded is true`, async ({ page }) => {
@@ -24,13 +24,11 @@ test.describe('Mouse Behavior', () => {
2424
await expect(d.getTrigger()).toHaveAttribute('aria-expanded', 'true');
2525
});
2626

27-
test(`GIVEN an open hero collapsible
27+
test(`GIVEN an open collapsible
2828
WHEN clicking on the trigger
2929
THEN the content should be hidden
3030
AND aria-expanded is false`, async ({ page }) => {
31-
const { driver: d } = await setup(page, 'hero');
32-
await d.openCollapsible('click');
33-
31+
const { driver: d } = await setup(page, 'open');
3432
await d.getTrigger().click();
3533

3634
await expect(d.getContent()).toBeHidden();
@@ -55,9 +53,7 @@ test.describe('Keyboard Behavior', () => {
5553
WHEN pressing the space key
5654
THEN the content should be hidden
5755
AND aria-expanded is false`, async ({ page }) => {
58-
const { driver: d } = await setup(page, 'hero');
59-
await d.openCollapsible('Space');
60-
56+
const { driver: d } = await setup(page, 'open');
6157
await d.getTrigger().press('Space');
6258

6359
await expect(d.getContent()).toBeHidden();
@@ -80,9 +76,7 @@ test.describe('Keyboard Behavior', () => {
8076
WHEN pressing the enter key
8177
THEN the content should be hidden
8278
AND aria-expanded is false`, async ({ page }) => {
83-
const { driver: d } = await setup(page, 'hero');
84-
await d.openCollapsible('Enter');
85-
79+
const { driver: d } = await setup(page, 'open');
8680
await d.getTrigger().press('Enter');
8781

8882
await expect(d.getContent()).toBeHidden();
@@ -111,7 +105,6 @@ test.describe('Animations', () => {
111105
THEN the content should open`, async ({ page }) => {
112106
const { driver: d } = await setup(page, 'animation');
113107

114-
await d.getTrigger().focus();
115108
await d.getTrigger().click();
116109
await d.waitForAnimationEnd('[data-collapsible-content]');
117110
await expect(d.getContent()).toBeVisible();
@@ -123,11 +116,76 @@ test.describe('Animations', () => {
123116
const { driver: d } = await setup(page, 'animation');
124117
await d.openCollapsible('click');
125118

126-
await d.getTrigger().focus();
127119
await d.getTrigger().click();
128120
await d.waitForAnimationEnd('[data-collapsible-content]');
129121
await expect(d.getContent()).toBeHidden();
130122
});
131123
});
132124

133-
test.describe('Content resizing', () => {});
125+
test.describe('Reactive values', () => {
126+
test(`GIVEN a collapsible with a bind:open prop
127+
WHEN the signal value changes to true
128+
THEN the content should be visible
129+
`, async ({ page }) => {
130+
const { driver: d } = await setup(page, 'programmatic');
131+
132+
// our example uses bind:checked on the checkbox with our same signal.
133+
134+
await d.locator.getByRole('checkbox').check();
135+
await expect(d.getContent()).toBeVisible();
136+
});
137+
138+
test(`GIVEN a collapsible with a bind:open prop
139+
WHEN the signal value changes to false
140+
THEN the content should be hidden
141+
`, async ({ page }) => {
142+
const { driver: d } = await setup(page, 'programmatic');
143+
144+
await d.locator.getByRole('checkbox').uncheck();
145+
await expect(d.getContent()).toBeHidden();
146+
});
147+
});
148+
149+
test.describe('Handlers', () => {
150+
test(`GIVEN a collapsible with an onOpenChange$ prop
151+
WHEN the content is opened
152+
THEN the handler should be called
153+
`, async ({ page }) => {
154+
const { driver: d } = await setup(page, 'open-change');
155+
156+
const countText = d.locator.getByRole('paragraph');
157+
await expect(countText).toHaveText('count: 0');
158+
await d.openCollapsible('click');
159+
160+
await expect(countText).toHaveText('count: 1');
161+
});
162+
163+
test(`GIVEN a collapsible with an onOpenChange$ prop
164+
WHEN the content is closed
165+
THEN the handler should be called
166+
`, async ({ page }) => {
167+
const { driver: d } = await setup(page, 'open-change');
168+
169+
const countText = d.locator.getByRole('paragraph');
170+
await d.openCollapsible('click');
171+
await expect(countText).toHaveText('count: 1');
172+
await d.getTrigger().click();
173+
174+
await expect(countText).toHaveText('count: 2');
175+
});
176+
});
177+
178+
test.describe('Disabled', () => {
179+
test(`GIVEN a collapsible with a disabled prop
180+
WHEN the trigger is clicked
181+
THEN the content should remain closed
182+
`, async ({ page }) => {
183+
const { driver: d } = await setup(page, 'disabled');
184+
185+
await expect(d.getTrigger()).toBeDisabled();
186+
187+
// actionability checks are only for enabled elements
188+
await d.getTrigger().click({ force: true });
189+
await expect(d.getContent()).toBeHidden();
190+
});
191+
});
Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1-
dialog {
2-
animation: placeholder 0s;
3-
transition: placeholder 0s;
1+
@layer qwik-ui {
2+
/* browsers automatically set an interesting max-width and max-height for dialogs
3+
https://twitter.com/t3dotgg/status/1774350919133691936
4+
*/
5+
dialog:modal {
6+
max-width: unset;
7+
max-height: unset;
8+
}
49
}

0 commit comments

Comments
 (0)