From a4ea85d7aa3a53293a56d91f57a6fb70df78cc69 Mon Sep 17 00:00:00 2001 From: Charlie JEANNEAU <35218368+JeSuisCharlie1@users.noreply.github.com> Date: Thu, 17 Sep 2026 10:49:51 +0200 Subject: [PATCH 1/4] + dialog-canClose e2e test --- .../dialog/dialog-canClose.stories.ts | 85 +++++++++++++++++++ 1 file changed, 85 insertions(+) diff --git a/stories/documentation/overlays/dialog/dialog-canClose.stories.ts b/stories/documentation/overlays/dialog/dialog-canClose.stories.ts index 138b315e45..48f003b163 100644 --- a/stories/documentation/overlays/dialog/dialog-canClose.stories.ts +++ b/stories/documentation/overlays/dialog/dialog-canClose.stories.ts @@ -18,6 +18,10 @@ import { import { FormFieldComponent } from '@lucca-front/ng/form-field'; import { LuSimpleSelectInputComponent } from '@lucca-front/ng/simple-select'; import { Meta, StoryObj, applicationConfig } from '@storybook/angular-vite'; +import { expect, screen, userEvent, waitFor, within } from 'storybook/test'; + +import { createTestStory } from '@/helpers/stories'; +import { sleep, waitForAngular } from '@/helpers/test'; @Component({ selector: 'sb-dialog-content', @@ -150,3 +154,84 @@ openDialog(): void { }, }, }; + +const openDialog = async (trigger: HTMLElement) => { + await userEvent.click(trigger); + await waitForAngular(); + return screen.findByRole('dialog'); +}; + +const expectClosed = () => waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()); + +/** Gives the close attempt time to go through before asserting the dialog is still there. */ +const expectStillOpen = async () => { + await sleep(100); + await expect(screen.getByRole('dialog')).toBeVisible(); +}; + +export const BasicTEST = createTestStory(Basic, async ({ canvasElement, step }) => { + await waitForAngular(); + + const canvas = within(canvasElement); + const trigger = canvas.getByRole('button', { name: 'Open dialog' }); + + await step('Escape closes the dialog when canClose allows it', async () => { + const dialog = await openDialog(trigger); + await expect(dialog).toBeVisible(); + await userEvent.keyboard('{Escape}'); + await expectClosed(); + }); + + await step('Cancel closes the dialog when canClose allows it', async () => { + await openDialog(trigger); + await userEvent.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Cancel' })); + await expectClosed(); + }); + + await step('Confirm closes the dialog through the injected LuDialogRef', async () => { + await openDialog(trigger); + await userEvent.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Confirm' })); + await expectClosed(); + }); +}); + +export const CannotCloseTEST = createTestStory({ ...Basic, args: { ...Basic.args, canClose: false } }, async ({ canvasElement, step }) => { + await waitForAngular(); + + const canvas = within(canvasElement); + const trigger = canvas.getByRole('button', { name: 'Open dialog' }); + await openDialog(trigger); + + await step('Escape does not close a dialog denied by canClose', async () => { + await userEvent.keyboard('{Escape}'); + await expectStillOpen(); + }); + + await step('Cancel does not close a dialog denied by canClose', async () => { + await userEvent.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Cancel' })); + await expectStillOpen(); + }); + + await step('Confirm still closes it, as LuDialogRef.close bypasses canClose', async () => { + await userEvent.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Confirm' })); + await expectClosed(); + }); +}); + +export const CannotCloseWithBackdropTEST = createTestStory({ ...Basic, args: { ...Basic.args, canCloseWithBackdrop: false } }, async ({ canvasElement, step }) => { + await waitForAngular(); + + const canvas = within(canvasElement); + const trigger = canvas.getByRole('button', { name: 'Open dialog' }); + await openDialog(trigger); + + await step('Escape is ignored, as it goes through the same stream as the backdrop click', async () => { + await userEvent.keyboard('{Escape}'); + await expectStillOpen(); + }); + + await step('Cancel still closes the dialog', async () => { + await userEvent.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Cancel' })); + await expectClosed(); + }); +}); From d2b8faee15c1c6fb9060cb941c57f86efac09497 Mon Sep 17 00:00:00 2001 From: Charlie JEANNEAU <35218368+JeSuisCharlie1@users.noreply.github.com> Date: Thu, 17 Sep 2026 10:55:04 +0200 Subject: [PATCH 2/4] + dialog-confirmation e2e test --- .../dialog/dialog-confirmation.stories.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/stories/documentation/overlays/dialog/dialog-confirmation.stories.ts b/stories/documentation/overlays/dialog/dialog-confirmation.stories.ts index f8c196c7c3..b8b9ea69c9 100644 --- a/stories/documentation/overlays/dialog/dialog-confirmation.stories.ts +++ b/stories/documentation/overlays/dialog/dialog-confirmation.stories.ts @@ -17,8 +17,11 @@ import { FormFieldComponent } from '@lucca-front/ng/form-field'; import { CheckboxInputComponent, TextInputComponent } from '@lucca-front/ng/forms'; import { IconComponent } from '@lucca-front/ng/icon'; import { Meta, StoryObj } from '@storybook/angular-vite'; +import { expect, screen, userEvent, waitFor, within } from 'storybook/test'; import { HiddenArgType } from '@/helpers/common-arg-types'; +import { createTestStory } from '@/helpers/stories'; +import { waitForAngular } from '@/helpers/test'; @Component({ selector: 'dialog-confirmation-story', @@ -138,3 +141,42 @@ export const Basic: StoryObj = { }, render: template, }; + +export const BasicTEST = createTestStory(Basic, async ({ canvasElement, step }) => { + await waitForAngular(); + + const canvas = within(canvasElement); + const trigger = canvas.getByRole('button', { name: 'Open Dialog with confirmation on dismiss' }); + + await step('The first dialog opens on click', async () => { + await userEvent.click(trigger); + await waitForAngular(); + const dialog = await screen.findByRole('dialog'); + await expect(within(dialog).getByRole('heading', { name: 'Dialog' })).toBeVisible(); + }); + + await step('Dismissing stacks a confirmation dialog on top of the first one', async () => { + await userEvent.click(screen.getByRole('button', { name: 'Cancel with confirmation' })); + await waitForAngular(); + await waitFor(() => expect(screen.getAllByRole('dialog')).toHaveLength(2)); + await expect(screen.getByRole('heading', { name: 'Confirmation' })).toBeVisible(); + }); + + await step('Confirming closes the confirmation dialog and leaves the first one open', async () => { + const confirmation = screen.getAllByRole('dialog')[1]; + await userEvent.click(within(confirmation).getByRole('button', { name: 'Confirm' })); + await waitFor(() => expect(screen.getAllByRole('dialog')).toHaveLength(1)); + await expect(screen.getByRole('heading', { name: 'Dialog' })).toBeVisible(); + }); + + await step('Escape closes the topmost dialog only, then the last one', async () => { + await userEvent.click(screen.getByRole('button', { name: 'Cancel with confirmation' })); + await waitFor(() => expect(screen.getAllByRole('dialog')).toHaveLength(2)); + + await userEvent.keyboard('{Escape}'); + await waitFor(() => expect(screen.getAllByRole('dialog')).toHaveLength(1)); + + await userEvent.keyboard('{Escape}'); + await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()); + }); +}); From 9904518f8e0668bfb17c79ef13bc6755c2c15c4e Mon Sep 17 00:00:00 2001 From: Charlie JEANNEAU <35218368+JeSuisCharlie1@users.noreply.github.com> Date: Thu, 17 Sep 2026 10:55:21 +0200 Subject: [PATCH 3/4] + dialog-routing e2e test --- .../overlays/dialog/dialog-routing.stories.ts | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/stories/documentation/overlays/dialog/dialog-routing.stories.ts b/stories/documentation/overlays/dialog/dialog-routing.stories.ts index 2bbe23fd12..f78e2aaefd 100644 --- a/stories/documentation/overlays/dialog/dialog-routing.stories.ts +++ b/stories/documentation/overlays/dialog/dialog-routing.stories.ts @@ -25,6 +25,9 @@ import { LinkComponent } from '@lucca-front/ng/link'; import { applicationConfig, Meta, StoryObj } from '@storybook/angular-vite'; import { map } from 'rxjs'; import { StoryModelDisplayComponent } from '@/helpers/story-model-display.component'; +import { createTestStory } from '@/helpers/stories'; +import { waitForAngular } from '@/helpers/test'; +import { expect, screen, userEvent, waitFor, within } from 'storybook/test'; @Injectable() class DataProvider { dummy = signal(42); @@ -371,3 +374,49 @@ Basic.parameters = { }, }, }; + +export const BasicTEST = createTestStory(Basic, async ({ canvasElement, step }) => { + await waitForAngular(); + + const canvas = within(canvasElement); + const trigger = canvas.getByRole('button', { name: 'Navigate to /dialog/12' }); + const openRoutedDialog = async () => { + await userEvent.click(trigger); + await waitForAngular(); + return screen.findByRole('dialog'); + }; + + await step('Navigating to the dialog route opens it with the data from its factory', async () => { + const dialog = await openRoutedDialog(); + await expect(within(dialog).getByRole('heading', { name: 'Dialog opened by route' })).toBeVisible(); + await expect(within(dialog).getByLabelText('Data received by dialog')).toHaveValue(42); + }); + + await step('Submitting closes the dialog and hands the form value to onClosed', async () => { + const dialog = screen.getByRole('dialog'); + await userEvent.type(within(dialog).getByLabelText('Additionnal data to submit'), 'lucca'); + await userEvent.click(within(dialog).getByRole('button', { name: 'Submit' })); + await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()); + await expect(await canvas.findByText('dialog onClosed() called')).toBeVisible(); + await expect(canvas.getByText(/"dataString": "lucca"/)).toBeVisible(); + }); + + await step('Dismissing routes to the onDismissed destination', async () => { + await openRoutedDialog(); + await userEvent.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Dismiss' })); + await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()); + await expect(await canvas.findByText('dialog onDismissed() called')).toBeVisible(); + }); + + await step('Escape dismisses the routed dialog as well', async () => { + trigger.focus(); + await expect(trigger).toHaveFocus(); + await userEvent.keyboard('{Enter}'); + await waitForAngular(); + await expect(await screen.findByRole('dialog')).toBeVisible(); + + await userEvent.keyboard('{Escape}'); + await waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()); + await expect(await canvas.findByText('dialog onDismissed() called')).toBeVisible(); + }); +}); From 0f6d743a9c8f5ef66dd33dc4a5e51f7d85e789b3 Mon Sep 17 00:00:00 2001 From: Charlie JEANNEAU <35218368+JeSuisCharlie1@users.noreply.github.com> Date: Thu, 17 Sep 2026 10:55:38 +0200 Subject: [PATCH 4/4] + dialog-service e2e test --- .../overlays/dialog/dialog-service.stories.ts | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/stories/documentation/overlays/dialog/dialog-service.stories.ts b/stories/documentation/overlays/dialog/dialog-service.stories.ts index 248409c0e8..58dcda5eb7 100644 --- a/stories/documentation/overlays/dialog/dialog-service.stories.ts +++ b/stories/documentation/overlays/dialog/dialog-service.stories.ts @@ -18,6 +18,10 @@ import { import { FormFieldComponent } from '@lucca-front/ng/form-field'; import { LuSimpleSelectInputComponent } from '@lucca-front/ng/simple-select'; import { Meta, StoryObj, applicationConfig } from '@storybook/angular-vite'; +import { expect, screen, userEvent, waitFor, within } from 'storybook/test'; + +import { createTestStory } from '@/helpers/stories'; +import { waitForAngular } from '@/helpers/test'; @Component({ selector: 'sb-dialog-content', @@ -110,3 +114,51 @@ export const Basic: StoryObj = { mode: 'default', }, }; + +export const BasicTEST = createTestStory(Basic, async ({ canvasElement, step }) => { + await waitForAngular(); + + const canvas = within(canvasElement); + const trigger = canvas.getByRole('button', { name: 'Open dialog' }); + const expectClosed = () => waitFor(() => expect(screen.queryByRole('dialog')).not.toBeInTheDocument()); + const openDialog = async () => { + await userEvent.click(trigger); + await waitForAngular(); + return screen.findByRole('dialog'); + }; + + await step('Opening through LuDialogService renders the dialog content component', async () => { + const dialog = await openDialog(); + await expect(dialog).toBeVisible(); + await expect(within(dialog).getByText('Header')).toBeVisible(); + await expect(within(dialog).getAllByRole('combobox')).toHaveLength(4); + }); + + await step('Confirm closes the dialog through the injected LuDialogRef', async () => { + await userEvent.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Confirm' })); + await expectClosed(); + }); + + await step('Cancel closes the dialog through luDialogDismiss', async () => { + await openDialog(); + await userEvent.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Cancel' })); + await expectClosed(); + }); + + await step('The header close button closes the dialog', async () => { + await openDialog(); + await userEvent.click(within(screen.getByRole('dialog')).getByRole('button', { name: 'Fermer' })); + await expectClosed(); + }); + + await step('The dialog opens with Enter and closes with Escape', async () => { + trigger.focus(); + await expect(trigger).toHaveFocus(); + await userEvent.keyboard('{Enter}'); + await waitForAngular(); + await expect(await screen.findByRole('dialog')).toBeVisible(); + + await userEvent.keyboard('{Escape}'); + await expectClosed(); + }); +});