From 1bed7dbea71fd9b1009a6d1ab3a8f31cd24dada5 Mon Sep 17 00:00:00 2001 From: Ryan Gossiaux Date: Sun, 26 Dec 2021 23:35:59 -0800 Subject: [PATCH] Initial Disclosure tests --- .../disclosure/_TransitionDebug.svelte | 12 + .../components/disclosure/disclosure.test.ts | 598 ++++++++++++++++++ 2 files changed, 610 insertions(+) create mode 100644 src/lib/components/disclosure/_TransitionDebug.svelte create mode 100644 src/lib/components/disclosure/disclosure.test.ts diff --git a/src/lib/components/disclosure/_TransitionDebug.svelte b/src/lib/components/disclosure/_TransitionDebug.svelte new file mode 100644 index 0000000..28288a2 --- /dev/null +++ b/src/lib/components/disclosure/_TransitionDebug.svelte @@ -0,0 +1,12 @@ + + + diff --git a/src/lib/components/disclosure/disclosure.test.ts b/src/lib/components/disclosure/disclosure.test.ts new file mode 100644 index 0000000..774e0d9 --- /dev/null +++ b/src/lib/components/disclosure/disclosure.test.ts @@ -0,0 +1,598 @@ +import { Disclosure, DisclosureButton, DisclosurePanel } from "."; +import { suppressConsoleLogs } from "$lib/test-utils/suppress-console-logs"; +import { render } from "@testing-library/svelte"; +import TestRenderer from "$lib/test-utils/TestRenderer.svelte"; +import { assertDisclosureButton, assertDisclosurePanel, DisclosureState, getDisclosureButton, getDisclosurePanel } from "$lib/test-utils/accessibility-assertions"; +import { click } from "$lib/test-utils/interactions"; +import { Transition, TransitionChild } from "../transitions"; +import TransitionDebug from "./_TransitionDebug.svelte"; + +let id = 0; +jest.mock('../../hooks/use-id', () => { + return { + useId: jest.fn(() => ++id), + } +}) + + +beforeEach(() => id = 0) +afterAll(() => jest.restoreAllMocks()) + +function nextFrame() { + return new Promise(resolve => { + requestAnimationFrame(() => { + requestAnimationFrame(() => { + resolve() + }) + }) + }) +} + +describe('Safe guards', () => { + it.each([ + ['DisclosureButton', DisclosureButton], + ['DisclosurePanel', DisclosurePanel], + ])( + '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 Disclosure without crashing', + suppressConsoleLogs(async () => { + render( + TestRenderer, { + allProps: [ + Disclosure, + {}, + [ + [DisclosureButton, {}, "Trigger"], + [DisclosurePanel, {}, "Contents"], + ] + ] + }) + + assertDisclosureButton({ + state: DisclosureState.InvisibleUnmounted, + attributes: { id: 'headlessui-disclosure-button-1' }, + }) + assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted }) + }) + ) +}) + +describe('Rendering', () => { + // describe('Disclosure', () => { + // it( + // 'should be possible to render a Disclosure using a render prop', + // suppressConsoleLogs(async () => { + // render( + // + // {({ open }) => ( + // <> + // Trigger + // Panel is: {open ? 'open' : 'closed'} + // + // )} + // + // ) + + // assertDisclosureButton({ + // state: DisclosureState.InvisibleUnmounted, + // attributes: { id: 'headlessui-disclosure-button-1' }, + // }) + // assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted }) + + // await click(getDisclosureButton()) + + // assertDisclosureButton({ + // state: DisclosureState.Visible, + // attributes: { id: 'headlessui-disclosure-button-1' }, + // }) + // assertDisclosurePanel({ state: DisclosureState.Visible, textContent: 'Panel is: open' }) + // }) + // ) + + // it('should be possible to render a Disclosure in an open state by default', async () => { + // render( + // + // {({ open }) => ( + // <> + // Trigger + // Panel is: {open ? 'open' : 'closed'} + // + // )} + // + // ) + + // assertDisclosureButton({ + // state: DisclosureState.Visible, + // attributes: { id: 'headlessui-disclosure-button-1' }, + // }) + // assertDisclosurePanel({ state: DisclosureState.Visible, textContent: 'Panel is: open' }) + + // await click(getDisclosureButton()) + + // assertDisclosureButton({ state: DisclosureState.InvisibleUnmounted }) + // }) + + // it( + // 'should expose a close function that closes the disclosure', + // suppressConsoleLogs(async () => { + // render( + // + // {({ close }) => ( + // <> + // Trigger + // + // + // + // + // )} + // + // ) + + // // Focus the button + // getDisclosureButton()?.focus() + + // // Ensure the button is focused + // assertActiveElement(getDisclosureButton()) + + // // Open the disclosure + // await click(getDisclosureButton()) + + // // Ensure we can click the close button + // await click(getByText('Close me')) + + // // Ensure the disclosure is closed + // assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted }) + + // // Ensure the DisclosureButton got the restored focus + // assertActiveElement(getByText('Trigger')) + // }) + // ) + + // it( + // 'should expose a close function that closes the disclosure and restores to a specific element', + // suppressConsoleLogs(async () => { + // render( + // <> + // + // + // {({ close }) => ( + // <> + // Trigger + // + // + // + // + // )} + // + // + // ) + + // // Focus the button + // getDisclosureButton()?.focus() + + // // Ensure the button is focused + // assertActiveElement(getDisclosureButton()) + + // // Open the disclosure + // await click(getDisclosureButton()) + + // // Ensure we can click the close button + // await click(getByText('Close me')) + + // // Ensure the disclosure is closed + // assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted }) + + // // Ensure the restoreable button got the restored focus + // assertActiveElement(getByText('restoreable')) + // }) + // ) + + // it( + // 'should expose a close function that closes the disclosure and restores to a ref', + // suppressConsoleLogs(async () => { + // function Example() { + // let elementRef = useRef(null) + // return ( + // <> + // + // + // {({ close }) => ( + // <> + // Trigger + // + // + // + // + // )} + // + // + // ) + // } + + // render() + + // // Focus the button + // getDisclosureButton()?.focus() + + // // Ensure the button is focused + // assertActiveElement(getDisclosureButton()) + + // // Open the disclosure + // await click(getDisclosureButton()) + + // // Ensure we can click the close button + // await click(getByText('Close me')) + + // // Ensure the disclosure is closed + // assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted }) + + // // Ensure the restoreable button got the restored focus + // assertActiveElement(getByText('restoreable')) + // }) + // ) + // }) + + describe('DisclosureButton', () => { + // it( + // 'should be possible to render a DisclosureButton using a render prop', + // suppressConsoleLogs(async () => { + // render( + // + // {JSON.stringify} + // + // + // ) + + // assertDisclosureButton({ + // state: DisclosureState.InvisibleUnmounted, + // attributes: { id: 'headlessui-disclosure-button-1' }, + // textContent: JSON.stringify({ open: false }), + // }) + // assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted }) + + // await click(getDisclosureButton()) + + // assertDisclosureButton({ + // state: DisclosureState.Visible, + // attributes: { id: 'headlessui-disclosure-button-1' }, + // textContent: JSON.stringify({ open: true }), + // }) + // assertDisclosurePanel({ state: DisclosureState.Visible }) + // }) + // ) + + // it( + // 'should be possible to render a DisclosureButton using a render prop and an `as` prop', + // suppressConsoleLogs(async () => { + // render( + // + // + // {JSON.stringify} + // + // + // + // ) + + // assertDisclosureButton({ + // state: DisclosureState.InvisibleUnmounted, + // attributes: { id: 'headlessui-disclosure-button-1' }, + // textContent: JSON.stringify({ open: false }), + // }) + // assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted }) + + // await click(getDisclosureButton()) + + // assertDisclosureButton({ + // state: DisclosureState.Visible, + // attributes: { id: 'headlessui-disclosure-button-1' }, + // textContent: JSON.stringify({ open: true }), + // }) + // assertDisclosurePanel({ state: DisclosureState.Visible }) + // }) + // ) + + describe('`type` attribute', () => { + it('should set the `type` to "button" by default', async () => { + render( + TestRenderer, { + allProps: [ + Disclosure, {}, + [ + [DisclosureButton, {}, "Trigger"] + ] + ] + }) + + expect(getDisclosureButton()).toHaveAttribute('type', 'button') + }) + + it('should not set the `type` to "button" if it already contains a `type`', async () => { + render( + TestRenderer, { + allProps: [ + Disclosure, {}, + [ + [DisclosureButton, { type: "submit" }, "Trigger"] + ] + ] + }) + + expect(getDisclosureButton()).toHaveAttribute('type', 'submit') + }) + + it('should not set the type if the "as" prop is not a "button"', async () => { + render( + TestRenderer, { + allProps: [ + Disclosure, {}, + [ + [DisclosureButton, { as: "div" }, "Trigger"] + ] + ] + }) + + expect(getDisclosureButton()).not.toHaveAttribute('type') + }) + + }) + }) + + describe('DisclosurePanel', () => { + // it( + // 'should be possible to render DisclosurePanel using a render prop', + // suppressConsoleLogs(async () => { + // render( + // + // Trigger + // {JSON.stringify} + // + // ) + + // assertDisclosureButton({ + // state: DisclosureState.InvisibleUnmounted, + // attributes: { id: 'headlessui-disclosure-button-1' }, + // }) + // assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted }) + + // await click(getDisclosureButton()) + + // assertDisclosureButton({ + // state: DisclosureState.Visible, + // attributes: { id: 'headlessui-disclosure-button-1' }, + // }) + // assertDisclosurePanel({ + // state: DisclosureState.Visible, + // textContent: JSON.stringify({ open: true }), + // }) + // }) + // ) + + it('should be possible to always render the DisclosurePanel if we provide it a `static` prop', () => { + render( + TestRenderer, { + allProps: [ + Disclosure, {}, + [ + [DisclosureButton, {}, "Trigger"], + [DisclosurePanel, { static: true }, "Contents"] + ] + ] + }) + + // Let's verify that the Disclosure is already there + expect(getDisclosurePanel()).not.toBe(null) + }) + + it('should be possible to use a different render strategy for the DisclosurePanel', async () => { + render( + TestRenderer, { + allProps: [ + Disclosure, {}, + [ + [DisclosureButton, {}, "Trigger"], + [DisclosurePanel, { unmount: false }, "Contents"] + ] + ] + }) + + assertDisclosureButton({ state: DisclosureState.InvisibleHidden }) + assertDisclosurePanel({ state: DisclosureState.InvisibleHidden }) + + // Let's open the Disclosure, to see if it is not hidden anymore + await click(getDisclosureButton()) + + assertDisclosureButton({ state: DisclosureState.Visible }) + assertDisclosurePanel({ state: DisclosureState.Visible }) + + // Let's re-click the Disclosure, to see if it is hidden again + await click(getDisclosureButton()) + + assertDisclosureButton({ state: DisclosureState.InvisibleHidden }) + assertDisclosurePanel({ state: DisclosureState.InvisibleHidden }) + }) + + // it( + // 'should expose a close function that closes the disclosure', + // suppressConsoleLogs(async () => { + // render( + // + // Trigger + // + // {({ close }) => } + // + // + // ) + + // // Focus the button + // getDisclosureButton()?.focus() + + // // Ensure the button is focused + // assertActiveElement(getDisclosureButton()) + + // // Open the disclosure + // await click(getDisclosureButton()) + + // // Ensure we can click the close button + // await click(getByText('Close me')) + + // // Ensure the disclosure is closed + // assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted }) + + // // Ensure the DisclosureButton got the restored focus + // assertActiveElement(getByText('Trigger')) + // }) + // ) + + // it( + // 'should expose a close function that closes the disclosure and restores to a specific element', + // suppressConsoleLogs(async () => { + // render( + // <> + // + // + // Trigger + // + // {({ close }) => ( + // + // )} + // + // + // + // ) + + // // Focus the button + // getDisclosureButton()?.focus() + + // // Ensure the button is focused + // assertActiveElement(getDisclosureButton()) + + // // Open the disclosure + // await click(getDisclosureButton()) + + // // Ensure we can click the close button + // await click(getByText('Close me')) + + // // Ensure the disclosure is closed + // assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted }) + + // // Ensure the restoreable button got the restored focus + // assertActiveElement(getByText('restoreable')) + // }) + // ) + + // it( + // 'should expose a close function that closes the disclosure and restores to a ref', + // suppressConsoleLogs(async () => { + // function Example() { + // let elementRef = useRef(null) + // return ( + // <> + // + // + // Trigger + // + // {({ close }) => } + // + // + // + // ) + // } + + // render() + + // // Focus the button + // getDisclosureButton()?.focus() + + // // Ensure the button is focused + // assertActiveElement(getDisclosureButton()) + + // // Open the disclosure + // await click(getDisclosureButton()) + + // // Ensure we can click the close button + // await click(getByText('Close me')) + + // // Ensure the disclosure is closed + // assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted }) + + // // Ensure the restoreable button got the restored focus + // assertActiveElement(getByText('restoreable')) + // }) + // ) + }) +}) + +describe('Composition', () => { + it( + 'should be possible to control the DisclosurePanel by wrapping it in a Transition component', + suppressConsoleLogs(async () => { + let orderFn = jest.fn() + // render( + // + // Trigger + // + // + // + // + // + // + // + // + // + // + // ) + render( + TestRenderer, { + allProps: [ + Disclosure, {}, [ + [DisclosureButton, {}, "Trigger"], + [TransitionDebug, { name: "Disclosure", fn: orderFn }], + [Transition, {}, [ + [TransitionDebug, { name: "Transition", fn: orderFn }], + [DisclosurePanel, {}, [ + [TransitionChild, {}, [ + [TransitionDebug, { name: "TransitionChild", fn: orderFn }], + ]] + ]] + ]] + ] + ] + }) + + // Verify the Disclosure is hidden + assertDisclosurePanel({ state: DisclosureState.InvisibleUnmounted }) + + // Open the Disclosure component + await click(getDisclosureButton()) + + // Verify the Disclosure is visible + assertDisclosurePanel({ state: DisclosureState.Visible }) + + // Unmount the full tree + await click(getDisclosureButton()) + + // Wait for all transitions to finish + await nextFrame() + + // Verify that we tracked the `mounts` and `unmounts` in the correct order + // Note that with Svelte the components are unmounted top-down instead of bottom-up as with React + expect(orderFn.mock.calls).toEqual([ + ['Mounting - Disclosure'], + ['Mounting - Transition'], + ['Mounting - TransitionChild'], + ['Unmounting - Transition'], + ['Unmounting - TransitionChild'], + ]) + }) + ) +})