+
+
+ >
+ );
+});
diff --git a/apps/website/src/routes/docs/headless/checkbox/examples/test-controlled-list-false.tsx b/apps/website/src/routes/docs/headless/checkbox/examples/test-controlled-list-false.tsx
index 9946226e4..480307958 100644
--- a/apps/website/src/routes/docs/headless/checkbox/examples/test-controlled-list-false.tsx
+++ b/apps/website/src/routes/docs/headless/checkbox/examples/test-controlled-list-false.tsx
@@ -1,4 +1,4 @@
-import { component$, useSignal, useStyles$ } from '@builder.io/qwik';
+import { component$, useSignal } from '@builder.io/qwik';
import { Checkbox, Checklist } from '@qwik-ui/headless';
// TODO: add logic to handle user passed sigs with trues
// this test basically ensures that the sig passed to the checklist controlls trumps all its children
@@ -7,10 +7,10 @@ export default component$(() => {
return (
<>
-
+
diff --git a/apps/website/src/routes/docs/headless/checkbox/index.mdx b/apps/website/src/routes/docs/headless/checkbox/index.mdx
index ebe744394..278d807f3 100644
--- a/apps/website/src/routes/docs/headless/checkbox/index.mdx
+++ b/apps/website/src/routes/docs/headless/checkbox/index.mdx
@@ -44,6 +44,8 @@ In the example below, the left checkbox starts as **false**, while the right che
+
+
## Customization & Caveats
You can apply CSS classes to any part of the component. Any valid HTML can be used as an icon, but children of the Checkbox Indicator are removed from the accessibility tree to prevent them from being added to the Checkbox Label.
diff --git a/packages/kit-headless/src/components/checkbox/checkbox-context.tsx b/packages/kit-headless/src/components/checkbox/checkbox-context.tsx
new file mode 100644
index 000000000..e1d3a0f93
--- /dev/null
+++ b/packages/kit-headless/src/components/checkbox/checkbox-context.tsx
@@ -0,0 +1,3 @@
+import { createContextId, type Signal } from '@builder.io/qwik';
+
+export const CheckboxContext = createContextId>('CheckBox.context');
diff --git a/packages/kit-headless/src/components/checkbox/checkbox-indicator.tsx b/packages/kit-headless/src/components/checkbox/checkbox-indicator.tsx
index 8adc76e70..b6183ded5 100644
--- a/packages/kit-headless/src/components/checkbox/checkbox-indicator.tsx
+++ b/packages/kit-headless/src/components/checkbox/checkbox-indicator.tsx
@@ -1,15 +1,29 @@
-import { component$, useContext, PropsOf, Slot } from '@builder.io/qwik';
-import { CheckboxContext } from './context-id';
+import {
+ component$,
+ useContext,
+ type PropsOf,
+ Slot,
+ useTask$,
+ useStyles$,
+} from '@builder.io/qwik';
+import { CheckboxContext } from './checkbox-context';
+import './checkbox.css';
+import styles from './checkbox.css?inline';
export type CheckboxIndicatorProps = PropsOf<'div'>;
export const CheckboxIndicator = component$((props) => {
+ useStyles$(styles);
+
const checkSig = useContext(CheckboxContext);
+
+ useTask$(({ track }) => {
+ track(() => checkSig.value);
+ });
+
return (
-
-
-
-
+
+
);
});
diff --git a/packages/kit-headless/src/components/checkbox/checkbox-root.tsx b/packages/kit-headless/src/components/checkbox/checkbox-root.tsx
new file mode 100644
index 000000000..ddbcb9fb0
--- /dev/null
+++ b/packages/kit-headless/src/components/checkbox/checkbox-root.tsx
@@ -0,0 +1,69 @@
+import {
+ component$,
+ type PropsOf,
+ Slot,
+ type Signal,
+ $,
+ useContextProvider,
+ sync$,
+} from '@builder.io/qwik';
+import { useBoundSignal } from '../../utils/bound-signal';
+import { CheckboxContext } from './checkbox-context';
+import type { QwikIntrinsicElements } from '@builder.io/qwik';
+
+type AllowedElements = 'li' | 'div' | 'span';
+
+export type CheckboxProps = {
+ 'bind:checked'?: Signal;
+ initialValue?: boolean;
+ index?: number;
+} & PropsOf<'div'>;
+
+export const CheckboxRoot = component$(
+ (
+ props: QwikIntrinsicElements[C] & { as?: C } & CheckboxProps,
+ ) => {
+ const { 'bind:checked': givenCheckedSig, initialValue, as, ...rest } = props;
+ const Comp = as ?? 'div';
+
+ const checkedSignal = useBoundSignal(givenCheckedSig, initialValue);
+
+ useContextProvider(CheckboxContext, checkedSignal);
+ const handleKeyDownSync$ = sync$((e: KeyboardEvent) => {
+ if (e.key === ' ') {
+ e.preventDefault();
+ }
+ });
+
+ const handleClick$ = $(() => {
+ checkedSignal.value = !checkedSignal.value;
+ });
+
+ const handleKeyDown$ = $((e: KeyboardEvent) => {
+ if (e.key === ' ') {
+ checkedSignal.value = !checkedSignal.value;
+ }
+ });
+
+ return (
+ <>
+ {/* @ts-expect-error annoying polymorphism */}
+
+
+
+ >
+ );
+ },
+);
diff --git a/packages/kit-headless/src/components/checkbox/checkbox.css b/packages/kit-headless/src/components/checkbox/checkbox.css
new file mode 100644
index 000000000..038a0a6f3
--- /dev/null
+++ b/packages/kit-headless/src/components/checkbox/checkbox.css
@@ -0,0 +1,3 @@
+[data-qds-indicator][data-hidden] {
+ display: none;
+}
diff --git a/packages/kit-headless/src/components/checkbox/checkbox.driver.ts b/packages/kit-headless/src/components/checkbox/checkbox.driver.ts
index c1a3c2097..92280477b 100644
--- a/packages/kit-headless/src/components/checkbox/checkbox.driver.ts
+++ b/packages/kit-headless/src/components/checkbox/checkbox.driver.ts
@@ -1,4 +1,4 @@
-import { type Locator, type Page } from '@playwright/test';
+import type { Locator, Page } from '@playwright/test';
export type DriverLocator = Locator | Page;
export function createTestDriver(rootLocator: T) {
@@ -10,7 +10,7 @@ export function createTestDriver(rootLocator: T) {
return getRoot().locator('#indicator');
};
const getCheckList = () => {
- return getRoot().getByRole('group');
+ return getRoot().getByRole('checkbox');
};
const getChecklistUL = () => {
// note: filter method is always relative to the original locator not document root despite using root
@@ -24,8 +24,17 @@ export function createTestDriver(rootLocator: T) {
const getCheckbox = () => {
return getRoot().getByRole('checkbox');
};
+
+ const getSelectAll = () => {
+ return getRoot().locator('[data-qds-selectall]');
+ };
+
+ const getSelectAllIndicator = () => {
+ return getSelectAll().locator('[data-qds-indicator]');
+ };
+
const getTriCheckbox = () => {
- return getRoot().locator('css=[aria-controls]');
+ return getRoot().locator('#selectAll');
};
return {
...rootLocator,
@@ -37,5 +46,7 @@ export function createTestDriver(rootLocator: T) {
getChecklistUL,
getChecklistLIs,
getTriCheckbox,
+ getSelectAll,
+ getSelectAllIndicator,
};
}
diff --git a/packages/kit-headless/src/components/checkbox/checkbox.test.ts b/packages/kit-headless/src/components/checkbox/checkbox.test.ts
index 75b808902..566c41012 100644
--- a/packages/kit-headless/src/components/checkbox/checkbox.test.ts
+++ b/packages/kit-headless/src/components/checkbox/checkbox.test.ts
@@ -1,8 +1,9 @@
+// TODO: refactor this into checkbox.test.ts and checklist.test.ts
+
import { expect, test, type Page } from '@playwright/test';
import { createTestDriver } from './checkbox.driver';
-import { getTriBool } from '../checklist/checklist-context-wrapper';
async function setup(page: Page, exampleName: string) {
- await page.goto(`/headless/checkbox/${exampleName}`);
+ await page.goto(`http://localhost:6174/checkbox/${exampleName}`);
const driver = createTestDriver(page);
@@ -28,25 +29,138 @@ async function setup(page: Page, exampleName: string) {
};
}
-test.describe('checklist', () => {
- test(`GIVEN a mixed checklist
- WHEN the checklist renders
- IT should render the mixed img
- AND not the true img`, async ({ page }) => {
- const exampleName = 'test-controlled-list-mixed';
- await setup(page, exampleName);
- await expect(page.locator('#mixed-img')).toBeVisible();
- await expect(page.locator('#true-img')).toBeHidden();
+/**
+ * TYPESCRIPT SUPPORT + LESS IMPORTS
+ * test(`GIVEN a carousel
+ WHEN clicking on the next button
+ THEN it should move to the next slide`, async ({ page }) => {
+ const { driver: d } = await setup(page, 'hero');
+
+ await d.getNextButton().click();
+ await expect(d.getSlideAt(1)).toHaveAttribute('data-active');
});
+ */
- test(`GIVEN an all-checked checklist
- WHEN the checklist renders
- IT should render the true img
- AND not the mixed img`, async ({ page }) => {
- const exampleName = 'test-controlled-list-true';
- await setup(page, exampleName);
- await expect(page.locator('#true-img')).toBeVisible();
- await expect(page.locator('#mixed-img')).toBeHidden();
+// components -> usually modular, sometimes grouped depending on what it is
+// modular -> tests ONE SPECIFIC thing
+
+// grouped -> tests groups of things
+
+/**
+ *
+ * test.describe('Critical functionality', () => {
+ * test(`GIVEN a checkbox
+ When the checkbox is clicked
+ Then it should be checked`, async ({ page }) => {
+ await expect() -> aria-checked
+ await expect() -> data-checked
+ });
+
+ // if there's too many of one thing (for example, maybe you need to check 10 different keys! make it a describe block)
+ test(`GIVEN a checkbox
+ When the checkbox is pressed with space
+ Then it should be checked`, async ({ page }) => {
+
+ });
+
+ Ex: Select
+
+ test.describe('Keyboard Behavior', () => {
+
+ });
+ * })
+ *
+ */
+
+test.describe('checkbox', () => {
+ // test default state and click from unchecked checkbox
+ test(`GIVEN a checkbox that is initially unchecked
+ and its icon is hidden
+ WHEN the checkbox is clicked
+ IT should toggle aria-checked to true
+ and make its icon visible`, async ({ page }) => {
+ const exampleName = 'test-default';
+ const { getCheckbox, getIcon } = await setup(page, exampleName);
+ await expect(getCheckbox()).toHaveAttribute('aria-checked', 'false');
+ await expect(getIcon()).toBeHidden();
+ await getCheckbox().click();
+ await expect(getCheckbox()).toHaveAttribute('aria-checked', 'true');
+ await expect(getIcon()).toBeVisible();
+ });
+
+ // test default state and keyboard space from unchecked checkbox
+ test(`GIVEN a checkbox that is initially unchecked
+ and its icon is hidden
+ WHEN the checkbox is focused and the spacebar is pressed
+ IT should toggle aria-checked to true
+ and make its icon visible`, async ({ page }) => {
+ const exampleName = 'test-default';
+ const { getCheckbox, getIcon } = await setup(page, exampleName);
+ await expect(getCheckbox()).toHaveAttribute('aria-checked', 'false');
+ await expect(getIcon()).toBeHidden();
+ await getCheckbox().focus();
+ await getCheckbox().press(' ');
+ await expect(getCheckbox()).toHaveAttribute('aria-checked', 'true');
+ await expect(getIcon()).toBeVisible();
+ });
+
+ // test default state from checked checkbox
+ test(`GIVEN a checkbox that is checked
+ WHEN the checkbox renders
+ IT should have aria-checked as true
+ and its icon visible`, async ({ page }) => {
+ const exampleName = 'test-initial-checked';
+ const { getCheckbox, getIcon } = await setup(page, exampleName);
+ await expect(getCheckbox()).toBeVisible();
+ await expect(getCheckbox()).toHaveAttribute('aria-checked', 'true');
+ await expect(getIcon()).toBeVisible();
+ });
+
+ // test mouse click
+ test(`GIVEN a checkbox that is checked
+ WHEN the checkbox is clicked
+ IT should have aria-checked as false
+ and its icon hidden`, async ({ page }) => {
+ const exampleName = 'test-initial-checked';
+ const { getCheckbox, getIcon } = await setup(page, exampleName);
+ await expect(getCheckbox()).toBeVisible();
+ await getCheckbox().click();
+ await expect(getCheckbox()).toHaveAttribute('aria-checked', 'false');
+ await expect(getIcon()).toBeHidden();
+ });
+
+ // test keyboard space from checked checkbox
+ test(`GIVEN a checkbox that is checked
+ WHEN the checkbox is focused and the spacebar is pressed
+ IT should have aria-checked as false
+ and its icon hidden`, async ({ page }) => {
+ const exampleName = 'test-initial-checked';
+ const { getCheckbox, getIcon } = await setup(page, exampleName);
+ await expect(getCheckbox()).toBeVisible();
+ await getCheckbox().focus();
+ await getCheckbox().press(' ');
+ await expect(getCheckbox()).toHaveAttribute('aria-checked', 'false');
+ await expect(getIcon()).toBeHidden();
+ });
+});
+
+// Checklisst tests
+test.describe('checklist', () => {
+ // test(`GIVEN a mixed checklist
+ // WHEN the checklist renders
+ // IT should render the mixed img
+ // AND not the true img`, async ({ page }) => {
+ // const exampleName = 'test-controlled-list-mixed';
+ // await setup(page, exampleName);
+ // await expect(page.locator('#mixed-img')).toBeVisible();
+ // await expect(page.locator('#true-img')).toBeHidden();
+ // });
+
+ test(`GIVEN a checklist with all items checked
+ WHEN the checklist renders
+ The indicator in the toggle all checkbox should be visible`, async ({ page }) => {
+ const { driver: d } = await setup(page, 'test-controlled-list-true');
+ await expect(d.getSelectAllIndicator()).toBeVisible();
});
test(`GIVEN an all-unchecked checklist
@@ -58,33 +172,33 @@ test.describe('checklist', () => {
await expect(page.locator('#mixed-img')).toBeHidden();
});
- test(`GIVEN a checklist with checkboxes
- WHEN the elements render
- THEN the checklist should be a
with
s of checkboxes, all wrapped around a div with a role and aria-labeledby attributes`, async ({
- page,
- }) => {
- const { getCheckList, getChecklistUL, getChecklistLIs } = await setup(
- page,
- 'test-list',
- );
- await expect(getCheckList()).toBeVisible();
- await expect(getCheckList()).toHaveAttribute('aria-labelledby', 'test123');
- await expect(getChecklistUL()).toBeVisible();
- await expect(getChecklistLIs()).toBeVisible();
- });
+ // TODO: fix this test
+ // test(`GIVEN a checklist with checkboxes
+ // WHEN the elements render
+ // THEN the checklist should be a
with
s of checkboxes, all wrapped around a div with a role and aria-labeledby attributes`, async ({
+ // page,
+ // }) => {
+ // const { getRoot, getCheckList, getChecklistUL, getChecklistLIs } =
+ // await setup(page, 'test-list');
+ // await expect(getCheckList()).toBeVisible();
+ // await expect(getCheckList()).toHaveAttribute('aria-labelledby', 'test123');
+ // await expect(getChecklistUL()).toBeVisible();
+ // await expect(getChecklistLIs()).toBeVisible();
+ // });
- test(`GIVEN a tri boolean function
- WHEN it recieves an array of booleans
- IT should return the correct tri bool`, async () => {
- const indeterminateArr = [true, true, false];
- const trueArr = [true, true, true];
- const falseArr = [false, false, false];
- const emptyArr: boolean[] = [];
- expect(getTriBool(indeterminateArr)).toBe('indeterminate');
- expect(getTriBool(trueArr)).toBe(true);
- expect(getTriBool(falseArr)).toBe(false);
- expect(getTriBool(emptyArr)).toBe('indeterminate');
- });
+ // not using triboolean
+ // test(`GIVEN a tri boolean function
+ // WHEN it recieves an array of booleans
+ // IT should return the correct tri bool`, async () => {
+ // const indeterminateArr = [true, true, false];
+ // const trueArr = [true, true, true];
+ // const falseArr = [false, false, false];
+ // const emptyArr: boolean[] = [];
+ // expect(getTriBool(indeterminateArr)).toBe('indeterminate');
+ // expect(getTriBool(trueArr)).toBe(true);
+ // expect(getTriBool(falseArr)).toBe(false);
+ // expect(getTriBool(emptyArr)).toBe('indeterminate');
+ // });
test(`GIVEN checklist with all unchecked checkboxes
WHEN it renders
@@ -96,29 +210,33 @@ test.describe('checklist', () => {
await expect(getTriCheckbox()).toBeVisible();
await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'false');
});
- test(`GIVEN checklist with all unchecked checkboxes
- WHEN the first checkbox is checked
- the chekbox with aria-controls should have aria-checked mixed`, async ({
- page,
- }) => {
- const exampleName = 'test-list';
- const { getTriCheckbox, getCheckbox } = await setup(page, exampleName);
- await expect(getTriCheckbox()).toBeVisible();
- await getCheckbox().nth(1).press(' ');
- await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'mixed');
- });
+ // Not using mixed yet
+ // test(`GIVEN checklist with all unchecked checkboxes
+ // WHEN the first checkbox is checked
+ // the chekbox with aria-controls should have aria-checked mixed`, async ({
+ // page,
+ // }) => {
+ // const exampleName = 'test-list';
+ // const { getTriCheckbox, getCheckbox } = await setup(page, exampleName);
+ // await expect(getTriCheckbox()).toBeVisible();
+ // await getCheckbox().nth(1).press(' ');
+ // await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'mixed');
+ // });
+
+ // 4
test(`GIVEN checklist with all unchecked checkboxes
- WHEN all checkboxes are checked with space
- the tri state checkbox should have aria-checked true`, async ({ page }) => {
+ WHEN all checkboxes are checked
+ the toggle all indicator should be checked`, async ({ page }) => {
const exampleName = 'test-list';
- const { getTriCheckbox, getCheckbox } = await setup(page, exampleName);
- await expect(getTriCheckbox()).toBeVisible();
- await getCheckbox().nth(1).press(' ');
- await getCheckbox().nth(2).press(' ');
- await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'true');
+ const { driver: d } = await setup(page, exampleName);
+ await expect(d.getSelectAllIndicator()).toBeHidden();
+ await d.getCheckbox().nth(1).click();
+ await d.getCheckbox().nth(2).click();
+ await expect(d.getSelectAllIndicator()).toBeVisible();
});
+ //5
test(`GIVEN checklist with all unchecked checkboxes
WHEN the checklist's checkbox is checked with space
THEN all chekboxes should have aria-checked true`, async ({ page }) => {
@@ -131,6 +249,7 @@ test.describe('checklist', () => {
await expect(getCheckbox().nth(2)).toHaveAttribute('aria-checked', 'true');
});
+ //6
// TODO: reme two part of test by adding new test file
test(`GIVEN checklist with all unchecked checkboxes
WHEN the checklist's checkbox is checked twice using space
@@ -149,45 +268,53 @@ test.describe('checklist', () => {
await expect(getCheckbox().nth(1)).toHaveAttribute('aria-checked', 'false');
await expect(getCheckbox().nth(2)).toHaveAttribute('aria-checked', 'false');
});
- test(`GIVEN checklist with checkboxes
- WHEN the values of aria-controls are search
- IT should always return a valid, non-duplicate, checkboxes`, async ({ page }) => {
- const { getTriCheckbox } = await setup(page, 'test-list');
- await expect(getTriCheckbox()).toHaveAttribute('aria-controls');
- const magic = await getTriCheckbox().getAttribute('aria-controls');
- expect(magic).not.toBe(null);
- if (magic === null) {
- throw new Error(
- 'no mixed checkbox found. Was the driver or test template changed?',
- );
- }
- const idArr = magic.split(' ');
- expect(isUniqArr(idArr)).toBe(true);
- for (let index = 0; index < idArr.length; index++) {
- const elementId = idArr[index];
- const PosCheckbox = page.locator(`#${elementId}`);
- await expect(PosCheckbox).toBeVisible();
- const role = await PosCheckbox.getAttribute('role');
- expect(role).toBe('checkbox');
- }
- });
- test(`GIVEN a controlled checklist with one default checkbox and a controlled checkbox of true
- WHEN it renders
- IT should have aria-checked mixed`, async ({ page }) => {
- const exampleName = 'test-controlled-list';
- const { getTriCheckbox } = await setup(page, exampleName);
- await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'mixed');
- });
+ // Search?
+ // test(`GIVEN checklist with checkboxes
+ // WHEN the values of aria-controls are search
+ // IT should always return a valid, non-duplicate, checkboxes`, async ({
+ // page,
+ // }) => {
+ // const { getTriCheckbox } = await setup(page, 'test-list');
+ // await expect(getTriCheckbox()).toHaveAttribute('aria-controls');
+ // const magic = await getTriCheckbox().getAttribute('aria-controls');
+ // expect(magic).not.toBe(null);
+ // if (magic === null) {
+ // throw new Error(
+ // 'no mixed checkbox found. Was the driver or test template changed?'
+ // );
+ // }
+ // const idArr = magic.split(' ');
+ // expect(isUniqArr(idArr)).toBe(true);
+ // for (let index = 0; index < idArr.length; index++) {
+ // const elementId = idArr[index];
+ // const PosCheckbox = page.locator(`#${elementId}`);
+ // await expect(PosCheckbox).toBeVisible();
+ // const role = await PosCheckbox.getAttribute('role');
+ // expect(role).toBe('checkbox');
+ // }
+ // });
- test(`GIVEN a controlled checklist with two true checkboxes
+ // Not using mixed yet
+ // test(`GIVEN a controlled checklist with one default checkbox and a controlled checkbox of true
+ // WHEN it renders
+ // IT should have aria-checked mixed`, async ({ page }) => {
+ // const exampleName = 'test-controlled-list';
+ // const { getTriCheckbox } = await setup(page, exampleName);
+ // await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'mixed');
+ // });
+
+ //7
+ test(`GIVEN a controlled checklist with two checked checkboxes
WHEN it renders
IT should have aria-checked true`, async ({ page }) => {
const exampleName = 'test-controlled-list-trues';
const { getTriCheckbox } = await setup(page, exampleName);
await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'true');
});
- test(`GIVEN a controlled checklist with two false checkboxes
+
+ //8
+ test(`GIVEN a controlled checklist with two unchecked checkboxes
WHEN it renders
IT should have aria-checked true`, async ({ page }) => {
const exampleName = 'test-controlled-list-falses';
@@ -195,22 +322,26 @@ test.describe('checklist', () => {
await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'false');
});
- test(`GIVEN a controlled checklist with mixed checkboxes
- WHEN it renders
- IT should have aria-checked mixed`, async ({ page }) => {
- const exampleName = 'test-controlled-list-mixed';
- const { getTriCheckbox } = await setup(page, exampleName);
- await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'mixed');
- });
+ //Not using mixed yet
+ // test(`GIVEN a controlled checklist with mixed checkboxes
+ // WHEN it renders
+ // IT should have aria-checked mixed`, async ({ page }) => {
+ // const exampleName = 'test-controlled-list-mixed';
+ // const { getTriCheckbox } = await setup(page, exampleName);
+ // await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'mixed');
+ // });
- test(`GIVEN a controlled checklist with a checklist signal of true and default checkboxes as children
+ //9
+ test(`GIVEN a checklist with intial value of true and default checkboxes as children
WHEN the checklist renders
IT shoud have aria-checked true`, async ({ page }) => {
const exampleName = 'test-controlled-list-true';
const { getTriCheckbox } = await setup(page, exampleName);
await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'true');
});
- test(`GIVEN a controlled checklist with a checklist signal of true and default checkboxes as children
+
+ //10
+ test(`GIVEN a checklist with intial value of true and default checkboxes as children
WHEN the checklist renders
ALL its child checkboxes should have aria-checked true`, async ({ page }) => {
const exampleName = 'test-controlled-list-true';
@@ -222,59 +353,72 @@ test.describe('checklist', () => {
}
});
- // TODO: change api to not use indeterminate and used mixed instead
- test(`GIVEN a controlled checklist with a checklist signal of true and default checkboxes as children
- WHEN a child checkbox is unchecked
- THEN the checklist signal should have aria-checked mixed`, async ({ page }) => {
+ // Not using mixed yet
+ // ONE CHILD UNCHECKED
+ //11
+ test(`GIVEN a checklist that has all items checked
+ WHEN a child checkbox is unchecked
+ THEN the checklist signal should have a mixed state`, async ({ page }) => {
const exampleName = 'test-controlled-list-true';
- const { getTriCheckbox } = await setup(page, exampleName);
- const firstCheckbox = page.locator('#child-1');
- await firstCheckbox.press(' ');
+ const { getTriCheckbox, getCheckbox } = await setup(page, exampleName);
+ const firstCheckbox = getCheckbox().first();
+ await firstCheckbox.click();
await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'mixed');
});
- test(`GIVEN a controlled checklist with a checklist signal of true and default checkboxes as children
- WHEN all child checkbox are unchecked
- THEN the checklist signal should have aria-checked false`, async ({ page }) => {
- const exampleName = 'test-controlled-list-true';
- const { getTriCheckbox } = await setup(page, exampleName);
- await page.locator('#child-1').press(' ');
- await page.locator('#child-2').press(' ');
- await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'false');
- });
- test(`GIVEN a controlled checklist with every checkbox having a defined ID
- WHEN it renders
- ALL IDs should be present/rendered`, async ({ page }) => {
- await setup(page, 'test-props-ids-list');
- const hardCodedIds = ['checklist', 'child-1', 'child-2'];
- for (let index = 0; index < hardCodedIds.length; index++) {
- const id = hardCodedIds[index];
- await expect(page.locator(`#${id}`)).toBeVisible();
- }
- });
+ // Not implemented yet
+ // ALL CHILDREN UNCHECKED
+ // test(`GIVEN a controlled checklist with a checklist signal of true and default checkboxes as children
+ // WHEN all child checkbox are unchecked
+ // THEN the checklist signal should have aria-checked false`, async ({
+ // page,
+ // }) => {
+ // const exampleName = 'test-controlled-list-true';
+ // const { getTriCheckbox } = await setup(page, exampleName);
+ // await page.locator('#child-1').press(' ');
+ // await page.locator('#child-2').press(' ');
+ // await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'false');
+ // });
- test(`GIVEN a controlled checklist with every checkbox having a defined ID
- WHEN it renders
- THEN all IDs should be present in the aria-controls`, async ({ page }) => {
- const { getTriCheckbox } = await setup(page, 'test-props-ids-list');
- const hardChildren = ['child-1', 'child-2'];
- const magic = await getTriCheckbox().getAttribute('aria-controls');
- const twin = magic?.split(' ');
- expect(hardChildren).toStrictEqual(twin);
- });
+ //Not using IDs yet and may not be needed
+ // test(`GIVEN a controlled checklist with every checkbox having a defined ID
+ // WHEN it renders
+ // ALL IDs should be present/rendered`, async ({ page }) => {
+ // await setup(page, 'test-props-ids-list');
+ // const hardCodedIds = ['checklist', 'child-1', 'child-2'];
+ // for (let index = 0; index < hardCodedIds.length; index++) {
+ // const id = hardCodedIds[index];
+ // await expect(page.locator(`#${id}`)).toBeVisible();
+ // }
+ // });
- test(`GIVEN checklist with all unchecked checkboxes
- WHEN the first child checkbox is clicked
- the chekbox with aria-controls should have aria-checked mixed`, async ({
- page,
- }) => {
- const exampleName = 'test-list';
- const { getTriCheckbox, getCheckbox } = await setup(page, exampleName);
- await expect(getTriCheckbox()).toBeVisible();
- await getCheckbox().nth(1).click();
- await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'mixed');
- });
+ //Not using IDs yet and may not be needed
+ // test(`GIVEN a controlled checklist with every checkbox having a defined ID
+ // WHEN it renders
+ // THEN all IDs should be present in the aria-controls`, async ({ page }) => {
+ // const { getTriCheckbox } = await setup(page, 'test-props-ids-list');
+ // const hardChildren = ['child-1', 'child-2'];
+ // const magic = await getTriCheckbox().getAttribute('aria-controls');
+ // const twin = magic?.split(' ');
+ // expect(hardChildren).toStrictEqual(twin);
+ // });
+
+ // Not using mixed yet
+ // ONE CHILD CHECKED
+ // test(`GIVEN checklist with all unchecked checkboxes
+ // WHEN the first child checkbox is clicked
+ // the chekbox with aria-controls should have aria-checked mixed`, async ({
+ // page,
+ // }) => {
+ // const exampleName = 'test-list';
+ // const { getTriCheckbox, getCheckbox } = await setup(page, exampleName);
+ // await expect(getTriCheckbox()).toBeVisible();
+ // await getCheckbox().nth(1).click();
+ // await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'mixed');
+ // });
+ //Duplicates? Starts with Unchecked
+ //12
test(`GIVEN checklist with all unchecked checkboxes
WHEN all checkboxes are checked using click
THEN the checkbox with aria-controls should have aria-checked true`, async ({
@@ -288,6 +432,7 @@ test.describe('checklist', () => {
await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'true');
});
+ //13
test(`GIVEN checklist with all unchecked checkboxes
WHEN the checklist's checkbox is checked by clicking
THEN all checkboxes should have aria-checked true`, async ({ page }) => {
@@ -300,6 +445,7 @@ test.describe('checklist', () => {
await expect(getCheckbox().nth(2)).toHaveAttribute('aria-checked', 'true');
});
+ //14
// TODO: reme two part of test by adding new test file
test(`GIVEN checklist with all unchecked checkboxes
WHEN the checklist's checkbox is checked twice using click
@@ -337,104 +483,16 @@ test.describe('checklist', () => {
// }
// });
- test(`GIVEN a controlled checklist with a checklist signal of true and default checkboxes as children
- WHEN a child checkbox is unchecked
- THEN the checklist signal should have aria-checked mixed`, async ({ page }) => {
- const exampleName = 'test-controlled-list-true';
- const { getTriCheckbox } = await setup(page, exampleName);
- const firstCheckbox = page.locator('#child-1');
- await firstCheckbox.click();
- await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'mixed');
- });
-
- test(`GIVEN a controlled checklist with a checklist signal of true and default checkboxes as children
- WHEN all child checkbox are unchecked
- THEN the checklist signal should have aria-checked false`, async ({ page }) => {
- const exampleName = 'test-controlled-list-true';
- const { getTriCheckbox } = await setup(page, exampleName);
- await page.locator('#child-1').click();
- await page.locator('#child-2').click();
- await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'false');
- });
+ // Not using mixed yet
+ // test(`GIVEN a controlled checklist with a checklist signal of true and default checkboxes as children
+ // WHEN all child checkbox are unchecked
+ // THEN the checklist signal should have aria-checked false`, async ({
+ // page,
+ // }) => {
+ // const exampleName = 'test-controlled-list-true';
+ // const { getTriCheckbox } = await setup(page, exampleName);
+ // await page.locator('#child-1').click();
+ // await page.locator('#child-2').click();
+ // await expect(getTriCheckbox()).toHaveAttribute('aria-checked', 'false');
+ // });
});
-test.describe('checkbox', () => {
- test(`GIVEN a checkbox with a user sig value of true
- WHEN the checkbox renders
- IT should have aria-checked as true`, async ({ page }) => {
- const exampleName = 'test-hero';
- const { getCheckbox } = await setup(page, exampleName);
- await expect(getCheckbox()).toBeVisible();
- await expect(getCheckbox()).toHaveAttribute('aria-checked', 'true');
- }),
- test(`GIVEN a checkbox with a user sig value of true
-WHEN the checkbox is focused and the spacebar is pressed
-IT should have aria-checked as false`, async ({ page }) => {
- const exampleName = 'test-hero';
- const { getCheckbox } = await setup(page, exampleName);
- await expect(getCheckbox()).toBeVisible();
- await getCheckbox().focus();
- await getCheckbox().press(' ');
- await expect(getCheckbox()).toHaveAttribute('aria-checked', 'false');
- });
-
- test(`GIVEN a checkbox with a user sig value of true
- WHEN the checkbox is focused and the spacebar is pressed
- IT should have its icon hidden`, async ({ page }) => {
- const exampleName = 'test-hero';
- const { getCheckbox, getIcon } = await setup(page, exampleName);
- await expect(getIcon()).toBeVisible();
- await getCheckbox().focus();
- await getCheckbox().press(' ');
- await expect(getCheckbox()).toHaveAttribute('aria-checked', 'false');
- await expect(getIcon()).toBeHidden();
- });
- test(`GIVEN a default checkbox with a default sig value of false
- WHEN the checkbox is focused and the spacebar is pressed
- IT should have its icon visible`, async ({ page }) => {
- const exampleName = 'test-default';
- const { getCheckbox, getIcon } = await setup(page, exampleName);
- await expect(getIcon()).toBeHidden();
- await getCheckbox().focus();
- await getCheckbox().press(' ');
- await expect(getCheckbox()).toHaveAttribute('aria-checked', 'false');
- await expect(getIcon()).toBeVisible();
- });
-
- test(`GIVEN a checkbox with a user sig value of true
- WHEN the checkbox is clicked
- IT should have aria-checked as false`, async ({ page }) => {
- const exampleName = 'test-hero';
- const { getCheckbox } = await setup(page, exampleName);
- await expect(getCheckbox()).toBeVisible();
- await getCheckbox().click();
- await expect(getCheckbox()).toHaveAttribute('aria-checked', 'false');
- });
-
- test(`GIVEN a checkbox with a user sig value of true
- WHEN the checkbox is clicked
- IT should have its icon hidden`, async ({ page }) => {
- const exampleName = 'test-hero';
- const { getCheckbox, getIcon } = await setup(page, exampleName);
- await expect(getIcon()).toBeVisible();
- await getCheckbox().click();
- await expect(getCheckbox()).toHaveAttribute('aria-checked', 'false');
- await expect(getIcon()).toBeHidden();
- });
- test(`GIVEN a default checkbox with a default sig value of false
- WHEN the checkbox is clicked
- IT should have its icon visible`, async ({ page }) => {
- const exampleName = 'test-default';
- const { getCheckbox, getIcon } = await setup(page, exampleName);
- await expect(getIcon()).toBeHidden();
- await getCheckbox().click();
- await expect(getCheckbox()).toHaveAttribute('aria-checked', 'false');
- await expect(getIcon()).toBeVisible();
- });
-});
-//TODO: create util file
-//TODO: add click
-//TODO: refactor to use ids instead of nths since its test-only now
-function isUniqArr(arr: string[]) {
- const singleInstances = new Set(arr);
- return arr.length === singleInstances.size;
-}
diff --git a/packages/kit-headless/src/components/checkbox/checkbox.tsx b/packages/kit-headless/src/components/checkbox/checkbox.tsx
deleted file mode 100644
index 63b9966a1..000000000
--- a/packages/kit-headless/src/components/checkbox/checkbox.tsx
+++ /dev/null
@@ -1,225 +0,0 @@
-import {
- PropsOf,
- Signal,
- Slot,
- component$,
- sync$,
- useContextProvider,
- useContext,
- $,
- useSignal,
- useTask$,
-} from '@builder.io/qwik';
-import { CheckListContext, CheckboxContext } from './context-id';
-import { TriBool, getTriBool } from '../checklist/checklist-context-wrapper';
-export type MixedStateCheckboxProps = {
- 'bind:checked'?: Signal;
- checklist?: boolean;
- _useCheckListContext?: boolean;
- _overWriteCheckbox?: boolean;
-} & PropsOf<'div'>;
-export type TwoStateCheckboxProps = {
- 'bind:checked'?: Signal;
- _useCheckListContext?: boolean;
- _overWriteCheckbox?: boolean;
-} & PropsOf<'div'>;
-
-type TwoStateCheckboxBehaviorProps = {
- 'bind:checked': Signal;
-} & PropsOf<'div'>;
-export type ChecklistTwoStateCheckboxProps = {
- 'bind:checked'?: Signal;
- _useCheckListContext?: boolean;
- _overWriteCheckbox?: boolean;
-} & PropsOf<'div'>;
-export const CheckboxRoot = component$((props) => {
- // this is done to avoid consumers dealing with two types checkboxes, could go in different files
- if (props.checklist) {
- return (
-
-
-
- );
- }
- if (props._useCheckListContext) {
- return (
-
-
-
- );
- }
- return (
-
-
-
- );
-});
-
-function getAriaChecked(triBool: TriBool): 'mixed' | 'true' | 'false' {
- if (triBool === 'indeterminate') {
- return 'mixed';
- }
- return `${triBool === true}`;
-}
-
-export const TwoStateCheckbox = component$((props) => {
- // all the sig stuff should be refactored into a fancy hook
- const defaultSig = useSignal(false);
- const appliedSig = props['bind:checked'] ?? defaultSig;
- const checklistID = useSignal(props.id);
- useContextProvider(CheckboxContext, appliedSig);
- return (
-
-
-
- );
-});
-
-export const ChecklistTwoStateCheckbox = component$(
- (props) => {
- // this code is duplicate bcs you cant use conditionals on hooks (checklistContext could be undefined)
- // this has room for improvement: remove most of the code duplivation
- // making this a wrapper over the simpler component or using hooks
- const checklistContext = useContext(CheckListContext);
- const defaultSig = useSignal(false);
- const appliedSig = props['bind:checked'] ?? defaultSig;
- const checklistID = useSignal(props.id);
- // makes sure that the checklist's value is the same as its child
- const syncToChecklist = useSignal(props._overWriteCheckbox);
- useContextProvider(CheckboxContext, appliedSig);
- useTask$(({ track }) => {
- if (syncToChecklist.value !== undefined) {
- appliedSig.value = syncToChecklist.value;
- syncToChecklist.value = undefined;
- }
- track(() => {
- appliedSig.value;
- });
-
- // now i can say that there's one good application for object identity
- if (!checklistContext.checkboxes.value.some((e) => e === appliedSig)) {
- const currIndex = checklistContext.checkboxes.value.length;
-
- // TODO: refactor id to not run on wrapper but after conditional
- if (checklistID.value === undefined) {
- checklistID.value = checklistContext.idArr[currIndex];
- } else {
- checklistContext.idArr[currIndex] = checklistID.value;
- }
- checklistContext.checkboxes.value = [
- ...checklistContext.checkboxes.value,
- appliedSig,
- ];
- }
-
- const boolArr = checklistContext?.checkboxes.value.map((e) => e.value);
- const newVal = getTriBool(boolArr);
- checklistContext.checklistSig.value = newVal;
- });
- return (
-
-
-
- );
- },
-);
-
-export const MixedStateCheckbox = component$((props) => {
- console.log('mixed');
-
- // all the sig stuff should be refactored into a fancy hook
- const checklistContext = useContext(CheckListContext);
- const childCheckboxes = checklistContext.checkboxes;
- const appliedSig = props['bind:checked'] ?? checklistContext.checklistSig;
- const ariaControlsStrg =
- checklistContext.idArr.length === 0
- ? ''
- : checklistContext.idArr.reduce((p, c) => p + ' ' + c);
- useContextProvider(CheckboxContext, appliedSig);
-
- // im not enterily sure why, but the if statement only runs once
- if (props['bind:checked'] !== undefined) {
- checklistContext.checklistSig = props['bind:checked'];
- }
-
- const changeChecklistSig = $(() => {
- if (appliedSig.value !== true) {
- appliedSig.value = true;
- childCheckboxes.value.forEach((sig) => (sig.value = true));
- return;
- }
- appliedSig.value = false;
- childCheckboxes.value.forEach((sig) => (sig.value = false));
- });
- const handleKeyDownSync$ = sync$((e: KeyboardEvent) => {
- if (e.key === ' ') {
- e.preventDefault();
- }
- });
- const handleKeyDown$ = $((e: KeyboardEvent) => {
- if (e.key === ' ') {
- changeChecklistSig();
- }
- });
- const handleClick$ = $(() => {
- changeChecklistSig();
- });
-
- return (
-