diff --git a/src/lib/components/radio-group/_ManagedRadioGroup.svelte b/src/lib/components/radio-group/_ManagedRadioGroup.svelte
new file mode 100644
index 0000000..49793ab
--- /dev/null
+++ b/src/lib/components/radio-group/_ManagedRadioGroup.svelte
@@ -0,0 +1,8 @@
+
+
+ (value = e.detail)} on:change>
+
+
diff --git a/src/lib/components/radio-group/radio-group.test.ts b/src/lib/components/radio-group/radio-group.test.ts
new file mode 100644
index 0000000..318c95c
--- /dev/null
+++ b/src/lib/components/radio-group/radio-group.test.ts
@@ -0,0 +1,843 @@
+import { assertActiveElement, assertFocusable, assertNotFocusable, assertRadioGroupLabel, getByText, getRadioGroupOptions } from "$lib/test-utils/accessibility-assertions";
+import { render } from "@testing-library/svelte";
+import { RadioGroup, RadioGroupLabel, RadioGroupOption } from ".";
+import { suppressConsoleLogs } from "$lib/test-utils/suppress-console-logs";
+import TestRenderer from "$lib/test-utils/TestRenderer.svelte";
+import { click, Keys, press, shift } from "$lib/test-utils/interactions";
+import Button from "$lib/internal/elements/Button.svelte";
+import ManagedRadioGroup from "./_ManagedRadioGroup.svelte";
+
+let id = 0;
+jest.mock('../../hooks/use-id', () => {
+ return {
+ useId: jest.fn(() => ++id),
+ }
+})
+
+beforeEach(() => id = 0)
+beforeAll(() => {
+ // jest.spyOn(window, 'requestAnimationFrame').mockImplementation(setImmediate as any)
+ // jest.spyOn(window, 'cancelAnimationFrame').mockImplementation(clearImmediate as any)
+})
+afterAll(() => jest.restoreAllMocks())
+
+describe('Safe guards', () => {
+ it.each([['RadioGroupOption', RadioGroupOption]])(
+ 'should error when we are using a <%s /> without a parent ',
+ suppressConsoleLogs((name, Component) => {
+ expect(() => render(Component)).toThrowError(
+ `<${name} /> is missing a parent component.`
+ )
+ })
+ )
+
+ it(
+ 'should be possible to render a RadioGroup without crashing',
+ suppressConsoleLogs(async () => {
+ render(
+ TestRenderer, {
+ allProps: [
+ [RadioGroup, { value: undefined, onChange: console.log }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ ]
+ })
+
+ assertRadioGroupLabel({ textContent: 'Pizza Delivery' })
+ })
+ )
+
+ it('should be possible to render a RadioGroup without options and without crashing', () => {
+ render(RadioGroup, { value: undefined })
+ })
+})
+
+describe('Rendering', () => {
+ it('should be possible to render a RadioGroup, where the first element is tabbable (value is undefined)', async () => {
+ render(
+ TestRenderer, {
+ allProps: [
+ [RadioGroup, { value: undefined, onChange: console.log }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ ]
+ })
+
+ expect(getRadioGroupOptions()).toHaveLength(3)
+
+ assertFocusable(getByText('Pickup'))
+ assertNotFocusable(getByText('Home delivery'))
+ assertNotFocusable(getByText('Dine in'))
+ })
+
+ it('should be possible to render a RadioGroup, where the first element is tabbable (value is null)', async () => {
+ render(
+ TestRenderer, {
+ allProps: [
+ [RadioGroup, { value: null, onChange: console.log }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ ]
+ })
+
+ expect(getRadioGroupOptions()).toHaveLength(3)
+
+ assertFocusable(getByText('Pickup'))
+ assertNotFocusable(getByText('Home delivery'))
+ assertNotFocusable(getByText('Dine in'))
+ })
+
+ it('should be possible to render a RadioGroup with an active value', async () => {
+ render(
+ TestRenderer, {
+ allProps: [
+ [RadioGroup, { value: "home-delivery", onChange: console.log }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ ]
+ })
+
+ expect(getRadioGroupOptions()).toHaveLength(3)
+
+ assertNotFocusable(getByText('Pickup'))
+ assertFocusable(getByText('Home delivery'))
+ assertNotFocusable(getByText('Dine in'))
+ })
+
+ // it('should guarantee the radio option order after a few unmounts', async () => {
+ // function Example() {
+ // let [showFirst, setShowFirst] = useState(false)
+ // let [active, setActive] = useState()
+
+ // return (
+ // <>
+ //
+ //
+ // Pizza Delivery
+ // {showFirst && Pickup}
+ // Home delivery
+ // Dine in
+ //
+ // >
+ // )
+ // }
+
+ // render()
+
+ // await click(getByText('Toggle')) // Render the pickup again
+
+ // await press(Keys.Tab) // Focus first element
+ // assertActiveElement(getByText('Pickup'))
+
+ // await press(Keys.ArrowUp) // Loop around
+ // assertActiveElement(getByText('Dine in'))
+
+ // await press(Keys.ArrowUp) // Up again
+ // assertActiveElement(getByText('Home delivery'))
+ // })
+
+ // it('should be possible to disable a RadioGroup', async () => {
+ // let changeFn = jest.fn()
+
+ // function Example() {
+ // let [disabled, setDisabled] = useState(true)
+ // return (
+ // <>
+ //
+ //
+ // Pizza Delivery
+ // Pickup
+ // Home delivery
+ // Dine in
+ //
+ // {JSON.stringify}
+ //
+ //
+ // >
+ // )
+ // }
+
+ // render()
+
+ // // Try to click one a few options
+ // await click(getByText('Pickup'))
+ // await click(getByText('Dine in'))
+
+ // // Verify that the RadioGroup.Option gets the disabled state
+ // expect(document.querySelector('[data-value="render-prop"]')).toHaveTextContent(
+ // JSON.stringify({
+ // checked: false,
+ // disabled: true,
+ // active: false,
+ // })
+ // )
+
+ // // Make sure that the onChange handler never got called
+ // expect(changeFn).toHaveBeenCalledTimes(0)
+
+ // // Make sure that all the options get an `aria-disabled`
+ // let options = getRadioGroupOptions()
+ // expect(options).toHaveLength(4)
+ // for (let option of options) expect(option).toHaveAttribute('aria-disabled', 'true')
+
+ // // Toggle the disabled state
+ // await click(getByText('Toggle'))
+
+ // // Verify that the RadioGroup.Option gets the disabled state
+ // expect(document.querySelector('[data-value="render-prop"]')).toHaveTextContent(
+ // JSON.stringify({
+ // checked: false,
+ // disabled: false,
+ // active: false,
+ // })
+ // )
+
+ // // Try to click one a few options
+ // await click(getByText('Pickup'))
+
+ // // Make sure that the onChange handler got called
+ // expect(changeFn).toHaveBeenCalledTimes(1)
+ // })
+
+ // it('should be possible to disable a RadioGroup.Option', async () => {
+ // let changeFn = jest.fn()
+
+ // function Example() {
+ // let [disabled, setDisabled] = useState(true)
+ // return (
+ // <>
+ //
+ //
+ // Pizza Delivery
+ // Pickup
+ // Home delivery
+ // Dine in
+ //
+ // {JSON.stringify}
+ //
+ //
+ // >
+ // )
+ // }
+
+ // render()
+
+ // // Try to click the disabled option
+ // await click(document.querySelector('[data-value="render-prop"]'))
+
+ // // Verify that the RadioGroup.Option gets the disabled state
+ // expect(document.querySelector('[data-value="render-prop"]')).toHaveTextContent(
+ // JSON.stringify({
+ // checked: false,
+ // disabled: true,
+ // active: false,
+ // })
+ // )
+
+ // // Make sure that the onChange handler never got called
+ // expect(changeFn).toHaveBeenCalledTimes(0)
+
+ // // Make sure that the option with value "render-prop" gets an `aria-disabled`
+ // let options = getRadioGroupOptions()
+ // expect(options).toHaveLength(4)
+ // for (let option of options) {
+ // if (option.dataset.value) {
+ // expect(option).toHaveAttribute('aria-disabled', 'true')
+ // } else {
+ // expect(option).not.toHaveAttribute('aria-disabled')
+ // }
+ // }
+
+ // // Toggle the disabled state
+ // await click(getByText('Toggle'))
+
+ // // Verify that the RadioGroup.Option gets the disabled state
+ // expect(document.querySelector('[data-value="render-prop"]')).toHaveTextContent(
+ // JSON.stringify({
+ // checked: false,
+ // disabled: false,
+ // active: false,
+ // })
+ // )
+
+ // // Try to click one a few options
+ // await click(document.querySelector('[data-value="render-prop"]'))
+
+ // // Make sure that the onChange handler got called
+ // expect(changeFn).toHaveBeenCalledTimes(1)
+ // })
+ // })
+
+})
+
+describe('Keyboard interactions', () => {
+ describe('`Tab` key', () => {
+ it('should be possible to tab to the first item', async () => {
+ render(
+ TestRenderer, {
+ allProps: [
+ [RadioGroup, { value: undefined, onChange: console.log }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ ]
+ })
+
+ await press(Keys.Tab)
+
+ assertActiveElement(getByText('Pickup'))
+ })
+
+ it('should not change the selected element on focus', async () => {
+ let changeFn = jest.fn()
+ render(
+ TestRenderer, {
+ allProps: [
+ [RadioGroup, { value: undefined, onChange: changeFn }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ ]
+ })
+
+ await press(Keys.Tab)
+
+ assertActiveElement(getByText('Pickup'))
+
+ expect(changeFn).toHaveBeenCalledTimes(0)
+ })
+
+ it('should be possible to tab to the active item', async () => {
+ render(
+ TestRenderer, {
+ allProps: [
+ [RadioGroup, { value: "home-delivery", onChange: console.log }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ ]
+ })
+
+ await press(Keys.Tab)
+
+ assertActiveElement(getByText('Home delivery'))
+ })
+
+ it('should not change the selected element on focus (when selecting the active item)', async () => {
+ let changeFn = jest.fn()
+ render(
+ TestRenderer, {
+ allProps: [
+ [RadioGroup, { value: "home-delivery", onChange: changeFn }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ ]
+ })
+
+ await press(Keys.Tab)
+
+ assertActiveElement(getByText('Home delivery'))
+
+ expect(changeFn).toHaveBeenCalledTimes(0)
+ })
+
+ it('should be possible to tab out of the radio group (no selected value)', async () => {
+ render(
+ TestRenderer, {
+ allProps: [
+ [Button, {}, "Before"],
+ [RadioGroup, { value: undefined, onChange: console.log }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ [Button, {}, "After"],
+ ]
+ })
+
+ await press(Keys.Tab)
+ assertActiveElement(getByText('Before'))
+
+ await press(Keys.Tab)
+ assertActiveElement(getByText('Pickup'))
+
+ await press(Keys.Tab)
+ assertActiveElement(getByText('After'))
+ })
+
+ it('should be possible to tab out of the radio group (selected value)', async () => {
+ render(
+ TestRenderer, {
+ allProps: [
+ [Button, {}, "Before"],
+ [RadioGroup, { value: "home-delivery", onChange: console.log }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ [Button, {}, "After"],
+ ]
+ })
+
+
+ await press(Keys.Tab)
+ assertActiveElement(getByText('Before'))
+
+ await press(Keys.Tab)
+ assertActiveElement(getByText('Home delivery'))
+
+ await press(Keys.Tab)
+ assertActiveElement(getByText('After'))
+ })
+ })
+
+ describe('`Shift+Tab` key', () => {
+ it('should be possible to tab to the first item', async () => {
+ render(
+ TestRenderer, {
+ allProps: [
+ [RadioGroup, { value: undefined, onChange: console.log }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ [Button, {}, "After"],
+ ]
+ })
+
+
+ getByText('After')?.focus()
+
+ await press(shift(Keys.Tab))
+
+ assertActiveElement(getByText('Pickup'))
+ })
+
+ it('should not change the selected element on focus', async () => {
+ let changeFn = jest.fn()
+ render(
+ TestRenderer, {
+ allProps: [
+ [RadioGroup, { value: undefined, onChange: changeFn }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ [Button, {}, "After"],
+ ]
+ })
+
+
+ getByText('After')?.focus()
+
+ await press(shift(Keys.Tab))
+
+ assertActiveElement(getByText('Pickup'))
+
+ expect(changeFn).toHaveBeenCalledTimes(0)
+ })
+
+ it('should be possible to tab to the active item', async () => {
+ render(
+ TestRenderer, {
+ allProps: [
+ [RadioGroup, { value: "home-delivery", onChange: console.log }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ [Button, {}, "After"],
+ ]
+ })
+
+
+ getByText('After')?.focus()
+
+ await press(shift(Keys.Tab))
+
+ assertActiveElement(getByText('Home delivery'))
+ })
+
+ it('should not change the selected element on focus (when selecting the active item)', async () => {
+ let changeFn = jest.fn()
+ render(
+ TestRenderer, {
+ allProps: [
+ [RadioGroup, { value: "home-delivery", onChange: changeFn }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ [Button, {}, "After"]
+ ]
+ })
+
+ getByText('After')?.focus()
+
+ await press(shift(Keys.Tab))
+
+ assertActiveElement(getByText('Home delivery'))
+
+ expect(changeFn).toHaveBeenCalledTimes(0)
+ })
+
+ it('should be possible to tab out of the radio group (no selected value)', async () => {
+ render(
+ TestRenderer, {
+ allProps: [
+ [Button, {}, "Before"],
+ [RadioGroup, { value: undefined, onChange: console.log }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ [Button, {}, "After"],
+ ]
+ })
+
+
+ getByText('After')?.focus()
+
+ await press(shift(Keys.Tab))
+ assertActiveElement(getByText('Pickup'))
+
+ await press(shift(Keys.Tab))
+ assertActiveElement(getByText('Before'))
+ })
+
+ it('should be possible to tab out of the radio group (selected value)', async () => {
+ render(
+ TestRenderer, {
+ allProps: [
+ [Button, {}, "Before"],
+ [RadioGroup, { value: "home-delivery", onChange: console.log }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ [Button, {}, "After"],
+ ]
+ })
+
+
+ getByText('After')?.focus()
+
+ await press(shift(Keys.Tab))
+ assertActiveElement(getByText('Home delivery'))
+
+ await press(shift(Keys.Tab))
+ assertActiveElement(getByText('Before'))
+ })
+ })
+
+ describe('`ArrowLeft` key', () => {
+ it('should go to the previous item when pressing the ArrowLeft key', async () => {
+ let changeFn = jest.fn()
+ render(
+ TestRenderer, {
+ allProps: [
+ [Button, {}, "Before"],
+ [RadioGroup, { value: undefined, onChange: (e: CustomEvent) => changeFn(e.detail) }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ [Button, {}, "After"],
+ ]
+ })
+
+
+ // Focus the "Before" button
+ await press(Keys.Tab)
+
+ // Focus the RadioGroup
+ await press(Keys.Tab)
+
+ assertActiveElement(getByText('Pickup'))
+
+ await press(Keys.ArrowLeft) // Loop around
+ assertActiveElement(getByText('Dine in'))
+
+ await press(Keys.ArrowLeft)
+ assertActiveElement(getByText('Home delivery'))
+
+ expect(changeFn).toHaveBeenCalledTimes(2)
+ expect(changeFn).toHaveBeenNthCalledWith(1, 'dine-in')
+ expect(changeFn).toHaveBeenNthCalledWith(2, 'home-delivery')
+ })
+ })
+
+ describe('`ArrowUp` key', () => {
+ it('should go to the previous item when pressing the ArrowUp key', async () => {
+ let changeFn = jest.fn()
+ render(
+ TestRenderer, {
+ allProps: [
+ [Button, {}, "Before"],
+ [RadioGroup, { value: undefined, onChange: (e: CustomEvent) => changeFn(e.detail) }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ [Button, {}, "After"],
+ ]
+ })
+
+
+ // Focus the "Before" button
+ await press(Keys.Tab)
+
+ // Focus the RadioGroup
+ await press(Keys.Tab)
+
+ assertActiveElement(getByText('Pickup'))
+
+ await press(Keys.ArrowUp) // Loop around
+ assertActiveElement(getByText('Dine in'))
+
+ await press(Keys.ArrowUp)
+ assertActiveElement(getByText('Home delivery'))
+
+ expect(changeFn).toHaveBeenCalledTimes(2)
+ expect(changeFn).toHaveBeenNthCalledWith(1, 'dine-in')
+ expect(changeFn).toHaveBeenNthCalledWith(2, 'home-delivery')
+ })
+ })
+
+ describe('`ArrowRight` key', () => {
+ it('should go to the next item when pressing the ArrowRight key', async () => {
+ let changeFn = jest.fn()
+ render(
+ TestRenderer, {
+ allProps: [
+ [Button, {}, "Before"],
+ [RadioGroup, { value: undefined, onChange: (e: CustomEvent) => changeFn(e.detail) }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ [Button, {}, "After"],
+ ]
+ })
+
+
+ // Focus the "Before" button
+ await press(Keys.Tab)
+
+ // Focus the RadioGroup
+ await press(Keys.Tab)
+
+ assertActiveElement(getByText('Pickup'))
+
+ await press(Keys.ArrowRight)
+ assertActiveElement(getByText('Home delivery'))
+
+ await press(Keys.ArrowRight)
+ assertActiveElement(getByText('Dine in'))
+
+ await press(Keys.ArrowRight) // Loop around
+ assertActiveElement(getByText('Pickup'))
+
+ expect(changeFn).toHaveBeenCalledTimes(3)
+ expect(changeFn).toHaveBeenNthCalledWith(1, 'home-delivery')
+ expect(changeFn).toHaveBeenNthCalledWith(2, 'dine-in')
+ expect(changeFn).toHaveBeenNthCalledWith(3, 'pickup')
+ })
+ })
+
+ describe('`ArrowDown` key', () => {
+ it('should go to the next item when pressing the ArrowDown key', async () => {
+ let changeFn = jest.fn()
+ render(
+ TestRenderer, {
+ allProps: [
+ [Button, {}, "Before"],
+ [RadioGroup, { value: undefined, onChange: (e: CustomEvent) => changeFn(e.detail) }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ [Button, {}, "After"],
+ ]
+ })
+
+
+ // Focus the "Before" button
+ await press(Keys.Tab)
+
+ // Focus the RadioGroup
+ await press(Keys.Tab)
+
+ assertActiveElement(getByText('Pickup'))
+
+ await press(Keys.ArrowDown)
+ assertActiveElement(getByText('Home delivery'))
+
+ await press(Keys.ArrowDown)
+ assertActiveElement(getByText('Dine in'))
+
+ await press(Keys.ArrowDown) // Loop around
+ assertActiveElement(getByText('Pickup'))
+
+ expect(changeFn).toHaveBeenCalledTimes(3)
+ expect(changeFn).toHaveBeenNthCalledWith(1, 'home-delivery')
+ expect(changeFn).toHaveBeenNthCalledWith(2, 'dine-in')
+ expect(changeFn).toHaveBeenNthCalledWith(3, 'pickup')
+ })
+ })
+
+ describe('`Space` key', () => {
+ it('should select the current option when pressing space', async () => {
+ let changeFn = jest.fn()
+ render(
+ TestRenderer, {
+ allProps: [
+ [Button, {}, "Before"],
+ [RadioGroup, { value: undefined, onChange: (e: CustomEvent) => changeFn(e.detail) }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ [Button, {}, "After"],
+ ]
+ })
+
+
+ // Focus the "Before" button
+ await press(Keys.Tab)
+
+ // Focus the RadioGroup
+ await press(Keys.Tab)
+
+ assertActiveElement(getByText('Pickup'))
+
+ await press(Keys.Space)
+ assertActiveElement(getByText('Pickup'))
+
+ expect(changeFn).toHaveBeenCalledTimes(1)
+ expect(changeFn).toHaveBeenNthCalledWith(1, 'pickup')
+ })
+
+ it('should select the current option only once when pressing space', async () => {
+ let changeFn = jest.fn()
+
+ render(
+ TestRenderer, {
+ allProps: [
+ [Button, {}, "Before"],
+ [ManagedRadioGroup, { value: undefined, onChange: (e: CustomEvent) => changeFn(e.detail) }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ [Button, {}, "After"],
+ ]
+ })
+
+ // Focus the "Before" button
+ await press(Keys.Tab)
+
+ // Focus the RadioGroup
+ await press(Keys.Tab)
+
+ assertActiveElement(getByText('Pickup'))
+
+ await press(Keys.Space)
+ await press(Keys.Space)
+ await press(Keys.Space)
+ await press(Keys.Space)
+ await press(Keys.Space)
+ assertActiveElement(getByText('Pickup'))
+
+ expect(changeFn).toHaveBeenCalledTimes(1)
+ expect(changeFn).toHaveBeenNthCalledWith(1, 'pickup')
+ })
+ })
+})
+
+describe('Mouse interactions', () => {
+ it('should be possible to change the current radio group value when clicking on a radio option', async () => {
+ let changeFn = jest.fn()
+ render(
+ TestRenderer, {
+ allProps: [
+ [Button, {}, "Before"],
+ [RadioGroup, { value: undefined, onChange: (e: CustomEvent) => changeFn(e.detail) }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ [Button, {}, "After"],
+ ]
+ })
+
+ await click(getByText('Home delivery'))
+
+ assertActiveElement(getByText('Home delivery'))
+
+ expect(changeFn).toHaveBeenNthCalledWith(1, 'home-delivery')
+ })
+
+ it('should be a no-op when clicking on the same item', async () => {
+ let changeFn = jest.fn()
+
+ render(
+ TestRenderer, {
+ allProps: [
+ [Button, {}, "Before"],
+ [ManagedRadioGroup, { value: undefined, onChange: (e: CustomEvent) => changeFn(e.detail) }, [
+ [RadioGroupLabel, {}, "Pizza Delivery"],
+ [RadioGroupOption, { value: "pickup" }, "Pickup"],
+ [RadioGroupOption, { value: "home-delivery" }, "Home delivery"],
+ [RadioGroupOption, { value: "dine-in" }, "Dine in"],
+ ]],
+ [Button, {}, "After"],
+ ]
+ })
+
+ await click(getByText('Home delivery'))
+ await click(getByText('Home delivery'))
+ await click(getByText('Home delivery'))
+ await click(getByText('Home delivery'))
+
+ assertActiveElement(getByText('Home delivery'))
+
+ expect(changeFn).toHaveBeenCalledTimes(1)
+ })
+})