Skip to content

Commit 3c12a5d

Browse files
feat: modal data attrs + scope name change + collapsible data attrs (#312)
* feat: modal data attrs * feat: simplify tests for field and modal * feat: data attr tests for checkbox * feat: change opt-in to scope with data-qds-scope
1 parent a3b9f06 commit 3c12a5d

File tree

16 files changed

+344
-147
lines changed

16 files changed

+344
-147
lines changed

docs/src/routes/components/modal/examples/basic.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { component$ } from "@qwik.dev/core";
44
export default component$(() => {
55
return (
66
<Modal.Root>
7-
<Modal.Trigger>Open Modal</Modal.Trigger>
7+
<Modal.Trigger class="ui-open:bg-red-500">Open Modal</Modal.Trigger>
88
<Modal.Content>Some content</Modal.Content>
99
</Modal.Root>
1010
);

docs/src/routes/components/modal/examples/nested.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ export default component$(() => {
77
return (
88
<Modal.Root data-testid="root">
99
<Modal.Trigger data-testid="trigger">Open Modal</Modal.Trigger>
10-
<Modal.Content data-testid="content">
10+
<Modal.Content data-testid="content" class="ui-open:bg-green-500">
1111
<Modal.Title data-testid="title">First Modal</Modal.Title>
1212
<p>This is the first modal.</p>
1313

1414
{/* Nested Modal */}
1515
<Modal.Root bind:open={nestedOpen}>
1616
<Modal.Trigger>Nested Modal Trigger</Modal.Trigger>
17-
<Modal.Content>
17+
<Modal.Content class="ui-open:bg-blue-500">
1818
<Modal.Title>Nested Modal Title</Modal.Title>
1919
<p>Nested Modal Content</p>
2020
<Modal.Close>Close Nested</Modal.Close>

libs/components/src/checkbox/checkbox-root.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ export const CheckboxRoot = component$((props: PublicCheckboxRootProps) => {
8181
fallback="div"
8282
// Identifier for the root checkbox container
8383
data-qds-checkbox-root
84-
data-qds-root
84+
data-qds-scope
8585
// Indicates whether the checkbox is disabled
8686
aria-disabled={context.isDisabled.value ? "true" : "false"}
8787
data-checked={isChecked.value}

libs/components/src/checkbox/checkbox.browser.tsx

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,3 +634,107 @@ test("both description and error are linked via aria-describedby", async () => {
634634
expect(describedBy).toContain(descriptionId as string);
635635
expect(describedBy).toContain(errorId as string);
636636
});
637+
638+
test("checkbox root has data-qds-scope attribute", async () => {
639+
render(<BasicCheckbox />);
640+
641+
await expect.element(Root).toBeVisible();
642+
await expect.element(Root).toHaveAttribute("data-qds-scope");
643+
});
644+
645+
test("checkbox root has data-checked when checked", async () => {
646+
render(<BasicCheckbox checked />);
647+
648+
await expect.element(Root).toBeVisible();
649+
await expect.element(Root).toHaveAttribute("data-checked");
650+
});
651+
652+
test("checkbox root does not have data-checked when unchecked", async () => {
653+
render(<BasicCheckbox />);
654+
655+
await expect.element(Root).toBeVisible();
656+
await expect.element(Root).not.toHaveAttribute("data-checked");
657+
});
658+
659+
test("checkbox root has data-disabled when disabled", async () => {
660+
render(<BasicCheckbox disabled />);
661+
662+
await expect.element(Root).toBeVisible();
663+
await expect.element(Root).toHaveAttribute("data-disabled");
664+
});
665+
666+
test("checkbox root does not have data-disabled when not disabled", async () => {
667+
render(<BasicCheckbox />);
668+
669+
await expect.element(Root).toBeVisible();
670+
await expect.element(Root).not.toHaveAttribute("data-disabled");
671+
});
672+
673+
test("checkbox root has data-mixed when in mixed state", async () => {
674+
render(<BasicCheckboxWithBindChecked isChecked="mixed" />);
675+
676+
await expect.element(Root).toBeVisible();
677+
await expect.element(Root).toHaveAttribute("data-mixed");
678+
});
679+
680+
test("checkbox root does not have data-mixed when not in mixed state", async () => {
681+
render(<BasicCheckbox />);
682+
683+
await expect.element(Root).toBeVisible();
684+
await expect.element(Root).not.toHaveAttribute("data-mixed");
685+
});
686+
687+
test("checkbox root data-checked updates when clicked", async () => {
688+
render(<BasicCheckbox />);
689+
690+
await expect.element(Root).toBeVisible();
691+
await expect.element(Root).not.toHaveAttribute("data-checked");
692+
693+
await userEvent.click(Trigger);
694+
695+
await expect.element(Root).toHaveAttribute("data-checked");
696+
});
697+
698+
test("checkbox root data-checked updates when unchecked", async () => {
699+
render(<BasicCheckbox checked />);
700+
701+
await expect.element(Root).toBeVisible();
702+
await expect.element(Root).toHaveAttribute("data-checked");
703+
704+
await userEvent.click(Trigger);
705+
706+
await expect.element(Root).not.toHaveAttribute("data-checked");
707+
});
708+
709+
test("checkbox root data-disabled updates dynamically", async () => {
710+
render(<BasicCheckbox disabled isDisabled={true} />);
711+
712+
await expect.element(Root).toBeVisible();
713+
await expect.element(Root).toHaveAttribute("data-disabled");
714+
715+
await userEvent.click(ToggleDisabledButton);
716+
717+
await expect.element(Root).not.toHaveAttribute("data-disabled");
718+
});
719+
720+
test("checkbox root data-mixed transitions to data-checked when clicked", async () => {
721+
render(<BasicCheckboxWithBindChecked isChecked="mixed" />);
722+
723+
await expect.element(Root).toBeVisible();
724+
await expect.element(Root).toHaveAttribute("data-mixed");
725+
await expect.element(Root).not.toHaveAttribute("data-checked");
726+
727+
await userEvent.click(Trigger);
728+
729+
await expect.element(Root).not.toHaveAttribute("data-mixed");
730+
await expect.element(Root).toHaveAttribute("data-checked");
731+
});
732+
733+
test("checkbox root has all data attributes simultaneously", async () => {
734+
render(<BasicCheckbox checked disabled />);
735+
736+
await expect.element(Root).toBeVisible();
737+
await expect.element(Root).toHaveAttribute("data-qds-scope");
738+
await expect.element(Root).toHaveAttribute("data-checked");
739+
await expect.element(Root).toHaveAttribute("data-disabled");
740+
});

libs/components/src/collapsible/collapsible-content.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ export const CollapsibleContent = component$((props: CollapsibleContentProps) =>
1616
internalRef={context.contentRef}
1717
id={contentId}
1818
data-qds-collapsible-content
19-
data-disabled={context.disabled ? "" : undefined}
20-
data-open={context.isOpenSig.value}
21-
data-closed={!context.isOpenSig.value}
2219
aria-labelledby={triggerId}
2320
inert={!context.isOpenSig.value}
2421
>

libs/components/src/collapsible/collapsible-trigger.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,7 @@ export const CollapsibleTrigger = component$<PropsOf<"button">>(
2020
id={triggerId}
2121
internalRef={context.triggerRef}
2222
disabled={context.disabled}
23-
data-disabled={context.disabled ? "" : undefined}
2423
aria-disabled={context.disabled ? "true" : "false"}
25-
data-open={context.isOpenSig.value}
26-
data-closed={!context.isOpenSig.value}
2724
aria-expanded={context.isOpenSig.value}
2825
aria-controls={contentId}
2926
onClick$={[handleClick$, onClick$]}

libs/components/src/field/field-root.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export const FieldRoot = component$((props: FieldRootProps) => {
8484
<Render
8585
{...props}
8686
fallback="div"
87-
data-qds-root
87+
data-qds-scope
8888
data-disabled={isDisabled.value}
8989
data-required={isRequired.value}
9090
data-readonly={isReadOnly.value}

libs/components/src/field/field.browser.tsx

Lines changed: 19 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -556,76 +556,60 @@ test("textarea with local value prop changes independently from root", async ()
556556
await expect.element(Textarea).toHaveValue("value changed");
557557
});
558558

559-
test("field root has data-qds-root attribute", async () => {
559+
test("field root has data-qds-scope attribute", async () => {
560560
render(<BasicInput />);
561561

562562
await expect.element(Root).toBeVisible();
563-
564-
const rootElement = await Root.element();
565-
expect(rootElement?.hasAttribute("data-qds-root")).toBe(true);
563+
await expect.element(Root).toHaveAttribute("data-qds-scope");
566564
});
567565

568566
test("field root has data-disabled when disabled", async () => {
569567
render(<BasicInput disabled />);
570568

571569
await expect.element(Root).toBeVisible();
572-
573-
const rootElement = await Root.element();
574-
expect(rootElement?.hasAttribute("data-disabled")).toBe(true);
570+
await expect.element(Root).toHaveAttribute("data-disabled");
575571
});
576572

577573
test("field root does not have data-disabled when not disabled", async () => {
578574
render(<BasicInput />);
579575

580576
await expect.element(Root).toBeVisible();
581-
582-
const rootElement = await Root.element();
583-
expect(rootElement?.hasAttribute("data-disabled")).toBe(false);
577+
await expect.element(Root).not.toHaveAttribute("data-disabled");
584578
});
585579

586580
test("field root has data-required when required", async () => {
587581
render(<BasicInput required />);
588582

589583
await expect.element(Root).toBeVisible();
590-
591-
const rootElement = await Root.element();
592-
expect(rootElement?.hasAttribute("data-required")).toBe(true);
584+
await expect.element(Root).toHaveAttribute("data-required");
593585
});
594586

595587
test("field root does not have data-required when not required", async () => {
596588
render(<BasicInput />);
597589

598590
await expect.element(Root).toBeVisible();
599-
600-
const rootElement = await Root.element();
601-
expect(rootElement?.hasAttribute("data-required")).toBe(false);
591+
await expect.element(Root).not.toHaveAttribute("data-required");
602592
});
603593

604594
test("field root has data-readonly when readonly", async () => {
605595
render(<BasicInput readOnly />);
606596

607597
await expect.element(Root).toBeVisible();
608-
609-
const rootElement = await Root.element();
610-
expect(rootElement?.hasAttribute("data-readonly")).toBe(true);
598+
await expect.element(Root).toHaveAttribute("data-readonly");
611599
});
612600

613601
test("field root does not have data-readonly when not readonly", async () => {
614602
render(<BasicInput />);
615603

616604
await expect.element(Root).toBeVisible();
617-
618-
const rootElement = await Root.element();
619-
expect(rootElement?.hasAttribute("data-readonly")).toBe(false);
605+
await expect.element(Root).not.toHaveAttribute("data-readonly");
620606
});
621607

622608
test("field root has data-empty when value is empty", async () => {
623609
render(<BasicInput />);
624610

625611
await expect.element(Root).toBeVisible();
626-
627-
const rootElement = await Root.element();
628-
expect(rootElement?.hasAttribute("data-empty")).toBe(true);
612+
await expect.element(Root).toHaveAttribute("data-empty");
629613
});
630614

631615
const FilledValueInput = component$(() => {
@@ -642,35 +626,29 @@ test("field root does not have data-empty when value exists", async () => {
642626
render(<FilledValueInput />);
643627

644628
await expect.element(Root).toBeVisible();
645-
646-
const rootElement = await Root.element();
647-
expect(rootElement?.hasAttribute("data-empty")).toBe(false);
629+
await expect.element(Root).not.toHaveAttribute("data-empty");
648630
});
649631

650632
test("field root data-empty updates when input changes from empty to filled", async () => {
651633
render(<BasicInput />);
652634

653635
await expect.element(Root).toBeVisible();
654-
655-
const rootElement = await Root.element();
656-
expect(rootElement?.hasAttribute("data-empty")).toBe(true);
636+
await expect.element(Root).toHaveAttribute("data-empty");
657637

658638
await userEvent.fill(Input, "test value");
659639

660-
expect(rootElement?.hasAttribute("data-empty")).toBe(false);
640+
await expect.element(Root).not.toHaveAttribute("data-empty");
661641
});
662642

663643
test("field root data-empty updates when input changes from filled to empty", async () => {
664644
render(<FilledValueInput />);
665645

666646
await expect.element(Root).toBeVisible();
667-
668-
const rootElement = await Root.element();
669-
expect(rootElement?.hasAttribute("data-empty")).toBe(false);
647+
await expect.element(Root).not.toHaveAttribute("data-empty");
670648

671649
await userEvent.clear(Input);
672650

673-
expect(rootElement?.hasAttribute("data-empty")).toBe(true);
651+
await expect.element(Root).toHaveAttribute("data-empty");
674652
});
675653

676654
const DynamicStateInput = component$(() => {
@@ -767,11 +745,9 @@ test("field root has all data attributes simultaneously", async () => {
767745
render(<AllAttributesInput />);
768746

769747
await expect.element(Root).toBeVisible();
770-
771-
const rootElement = await Root.element();
772-
expect(rootElement?.hasAttribute("data-qds-root")).toBe(true);
773-
expect(rootElement?.hasAttribute("data-disabled")).toBe(true);
774-
expect(rootElement?.hasAttribute("data-required")).toBe(true);
775-
expect(rootElement?.hasAttribute("data-readonly")).toBe(true);
776-
expect(rootElement?.hasAttribute("data-empty")).toBe(true);
748+
await expect.element(Root).toHaveAttribute("data-qds-scope");
749+
await expect.element(Root).toHaveAttribute("data-disabled");
750+
await expect.element(Root).toHaveAttribute("data-required");
751+
await expect.element(Root).toHaveAttribute("data-readonly");
752+
await expect.element(Root).toHaveAttribute("data-empty");
777753
});

libs/components/src/modal/modal-close.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ export const ModalClose = component$((props: PropsOf<"button">) => {
1111

1212
return (
1313
<Render
14+
{...props}
1415
type="button"
1516
fallback="button"
1617
onClick$={[handleClose$, props.onClick$]}
17-
{...props}
1818
>
1919
<Slot />
2020
</Render>

libs/components/src/modal/modal-description.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export const ModalDescription = component$((props: PropsOf<"div">) => {
1111
});
1212

1313
return (
14-
<Render fallback="div" {...props} id={descriptionId}>
14+
<Render {...props} fallback="div" id={descriptionId}>
1515
<Slot />
1616
</Render>
1717
);

0 commit comments

Comments
 (0)