diff --git a/src/lib/components/dialog/_ManagedDialog.svelte b/src/lib/components/dialog/_ManagedDialog.svelte
index 19a3000..a3d5a02 100644
--- a/src/lib/components/dialog/_ManagedDialog.svelte
+++ b/src/lib/components/dialog/_ManagedDialog.svelte
@@ -4,12 +4,13 @@
// This component is only for use in tests
export let initialOpen = false;
export let onClose = () => {};
+ export let buttonInside = false;
export let buttonText: string | null = null;
export let buttonProps = {};
let state = initialOpen;
-{#if buttonText !== null}
+{#if buttonText !== null && !buttonInside}
@@ -20,5 +21,10 @@
on:close={(e) => (state = e.detail)}
on:close={onClose}
>
+ {#if buttonText !== null && buttonInside}
+
+ {/if}
diff --git a/src/lib/components/dialog/dialog.test.ts b/src/lib/components/dialog/dialog.test.ts
index 3e799f2..2d25916 100644
--- a/src/lib/components/dialog/dialog.test.ts
+++ b/src/lib/components/dialog/dialog.test.ts
@@ -5,9 +5,11 @@ import { suppressConsoleLogs } from "$lib/test-utils/suppress-console-logs";
import { render } from "@testing-library/svelte";
import TestRenderer from "$lib/test-utils/TestRenderer.svelte";
import Button from "$lib/internal/elements/Button.svelte";
+import Div from "$lib/internal/elements/Div.svelte";
+import Form from "$lib/internal/elements/Form.svelte";
import P from "$lib/internal/elements/P.svelte";
import Input from "$lib/internal/elements/Input.svelte";
-import { assertDialog, assertDialogDescription, DialogState, getDialog } from "$lib/test-utils/accessibility-assertions";
+import { assertActiveElement, assertDialog, assertDialogDescription, DialogState, getByText, getDialog, getDialogOverlay } from "$lib/test-utils/accessibility-assertions";
import { click, Keys, press } from "$lib/test-utils/interactions";
import Transition from "$lib/components/transitions/TransitionRoot.svelte";
import { tick } from "svelte";
@@ -399,7 +401,7 @@ describe('Keyboard interactions', () => {
TestRenderer, {
allProps: [
[ManagedDialog, { buttonText: "Trigger", buttonProps: { id: "trigger" } }, [
- ["Contents"],
+ "Contents",
[Input, { id: "name" }],
[TestTabSentinel],
]],
@@ -432,7 +434,7 @@ describe('Keyboard interactions', () => {
TestRenderer, {
allProps: [
[ManagedDialog, { buttonText: "Trigger", buttonProps: { id: "trigger" } }, [
- ["Contents"],
+ "Contents",
[Input, { id: "name", onKeydown: (e: CustomEvent) => { e.preventDefault(); e.stopPropagation(); } }],
[TestTabSentinel],
]],
@@ -458,3 +460,220 @@ describe('Keyboard interactions', () => {
})
})
})
+
+describe('Mouse interactions', () => {
+ it(
+ 'should be possible to close a Dialog using a click on the Dialog.Overlay',
+ suppressConsoleLogs(async () => {
+ render(
+ TestRenderer, {
+ allProps: [
+ [ManagedDialog, { buttonText: "Trigger", buttonProps: { id: "trigger" } }, [
+ [DialogOverlay],
+ "Contents",
+ [TestTabSentinel],
+ ]],
+ ]
+ })
+
+ // Open dialog
+ await click(document.getElementById('trigger'))
+
+ // Verify it is open
+ assertDialog({ state: DialogState.Visible })
+
+ // Click to close
+ await click(getDialogOverlay())
+
+ // Verify it is closed
+ assertDialog({ state: DialogState.InvisibleUnmounted })
+ })
+ )
+
+ it(
+ 'should not close the Dialog when clicking on contents of the Dialog.Overlay',
+ suppressConsoleLogs(async () => {
+ render(
+ TestRenderer, {
+ allProps: [
+ [ManagedDialog, { buttonText: "Trigger", buttonProps: { id: "trigger" } }, [
+ [DialogOverlay, {}, [
+ [Button, {}, "hi"]
+ ]],
+ "Contents",
+ [TestTabSentinel],
+ ]],
+ ]
+ })
+
+ // Open dialog
+ await click(document.getElementById('trigger'))
+
+ // Verify it is open
+ assertDialog({ state: DialogState.Visible })
+
+ // Click on an element inside the overlay
+ await click(getByText('hi'))
+
+ // Verify it is still open
+ assertDialog({ state: DialogState.Visible })
+ })
+ )
+
+ it(
+ 'should be possible to close the dialog, and re-focus the button when we click outside on the body element',
+ suppressConsoleLogs(async () => {
+ render(
+ TestRenderer, {
+ allProps: [
+ [ManagedDialog, { buttonText: "Trigger", buttonProps: { id: "trigger" } }, [
+ "Contents",
+ [TestTabSentinel],
+ ]],
+ ]
+ })
+
+ // Open dialog
+ await click(getByText('Trigger'))
+
+ // Verify it is open
+ assertDialog({ state: DialogState.Visible })
+
+ // Click the body to close
+ await click(document.body)
+
+ // Verify it is closed
+ assertDialog({ state: DialogState.InvisibleUnmounted })
+
+ // Verify the button is focused
+ assertActiveElement(getByText('Trigger'))
+ })
+ )
+
+ it(
+ 'should be possible to close the dialog, and keep focus on the focusable element',
+ suppressConsoleLogs(async () => {
+ render(
+ TestRenderer, {
+ allProps: [
+ [Button, {}, "Hello"],
+ [ManagedDialog, { buttonText: "Trigger", buttonProps: { id: "trigger" } }, [
+ "Contents",
+ [TestTabSentinel],
+ ]],
+ ]
+ })
+
+ // Open dialog
+ await click(getByText('Trigger'))
+
+ // Verify it is open
+ assertDialog({ state: DialogState.Visible })
+
+ // Click the button to close (outside click)
+ await click(getByText('Hello'))
+
+ // Verify it is closed
+ assertDialog({ state: DialogState.InvisibleUnmounted })
+
+ // Verify the button is focused
+ assertActiveElement(getByText('Hello'))
+ })
+ )
+
+ it(
+ 'should stop propagating click events when clicking on the Dialog.Overlay',
+ suppressConsoleLogs(async () => {
+ let wrapperFn = jest.fn()
+ render(
+ TestRenderer, {
+ allProps: [
+ [Div, { onClick: wrapperFn }, [
+ [ManagedDialog, { initialOpen: true }, [
+ "Contents",
+ [DialogOverlay],
+ [TestTabSentinel],
+ ]],
+ ]]
+ ]
+ })
+
+ // Verify it is open
+ assertDialog({ state: DialogState.Visible })
+
+ // Verify that the wrapper function has not been called yet
+ expect(wrapperFn).toHaveBeenCalledTimes(0)
+
+ // Click the Dialog.Overlay to close the Dialog
+ await click(getDialogOverlay())
+
+ // Verify it is closed
+ assertDialog({ state: DialogState.InvisibleUnmounted })
+
+ // Verify that the wrapper function has not been called yet
+ expect(wrapperFn).toHaveBeenCalledTimes(0)
+ })
+ )
+
+ it(
+ 'should be possible to submit a form inside a Dialog',
+ suppressConsoleLogs(async () => {
+ let submitFn = jest.fn()
+ render(
+ TestRenderer, {
+ allProps: [
+ [ManagedDialog, { initialOpen: true }, [
+ [Form, { onSubmit: submitFn }, [
+ [Input, { type: "hidden", value: "abc" }],
+ [Button, { type: "submit" }, "Submit"]
+ ]],
+ [TestTabSentinel],
+ ]],
+ ]
+ })
+
+ // Verify it is open
+ assertDialog({ state: DialogState.Visible })
+
+ // Submit the form
+ await click(getByText('Submit'))
+
+ // Verify that the submitFn function has been called
+ expect(submitFn).toHaveBeenCalledTimes(1)
+ })
+ )
+
+ it(
+ 'should stop propagating click events when clicking on an element inside the Dialog',
+ suppressConsoleLogs(async () => {
+ let wrapperFn = jest.fn()
+ render(
+ TestRenderer, {
+ allProps: [
+ [Div, { onClick: wrapperFn }, [
+ [ManagedDialog, { initialOpen: true, buttonInside: true, buttonText: "Inside" }, [
+ "Contents",
+ [TestTabSentinel],
+ ]],
+ ]]
+ ]
+ })
+
+ // Verify it is open
+ assertDialog({ state: DialogState.Visible })
+
+ // Verify that the wrapper function has not been called yet
+ expect(wrapperFn).toHaveBeenCalledTimes(0)
+
+ // Click the button inside the the Dialog
+ await click(getByText('Inside'))
+
+ // Verify it is closed
+ assertDialog({ state: DialogState.InvisibleUnmounted })
+
+ // Verify that the wrapper function has not been called yet
+ expect(wrapperFn).toHaveBeenCalledTimes(0)
+ })
+ )
+})
+
diff --git a/src/lib/test-utils/TestRenderer.svelte b/src/lib/test-utils/TestRenderer.svelte
index 33adca9..7fa3ac7 100644
--- a/src/lib/test-utils/TestRenderer.svelte
+++ b/src/lib/test-utils/TestRenderer.svelte
@@ -5,6 +5,7 @@
onClose?: HandlerType;
onFocus?: HandlerType;
onKeydown?: HandlerType;
+ onSubmit?: HandlerType;
}
type SingleComponent =
| string
@@ -36,12 +37,14 @@
let onClose: HandlerType = () => {};
let onFocus: HandlerType = () => {};
let onKeydown: HandlerType = () => {};
+ let onSubmit: HandlerType = () => {};
if (allProps && typeof allProps !== "string" && isSingleComponent(allProps)) {
({
onChange = onChange,
onClose = onClose,
onFocus = onFocus,
onKeydown = onKeydown,
+ onSubmit = onSubmit,
...spreadProps
} = allProps[1] || {});
}
@@ -59,6 +62,7 @@
on:close={onClose}
on:focus={onFocus}
on:keydown={onKeydown}
+ on:submit={onSubmit}
>