From fa7f9e085f91d12e790a1d791d8197a539be3049 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Wed, 22 Oct 2025 19:53:28 -0400 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=A4=96=20Fix=20Modal=20story=20tests?= =?UTF-8?q?=20timing=20issues=20in=20Chromatic?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace fixed 100ms timeouts with waitFor() for reliable async testing. The Escape key test was failing in Chromatic because state updates weren't completing within the fixed timeout window. Changes: - Use waitFor() for EscapeKeyCloses and OverlayClickCloses tests - Replace 'let' with 'const' for modal queries - Improve code clarity with descriptive variable names --- src/components/Modal.stories.tsx | 50 +++++++++++++++----------------- 1 file changed, 24 insertions(+), 26 deletions(-) diff --git a/src/components/Modal.stories.tsx b/src/components/Modal.stories.tsx index d6d24b5fff..08671e25b8 100644 --- a/src/components/Modal.stories.tsx +++ b/src/components/Modal.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from "@storybook/react"; import { action } from "@storybook/addon-actions"; -import { expect, userEvent } from "@storybook/test"; +import { expect, userEvent, waitFor } from "@storybook/test"; import { useState } from "react"; import { Modal, @@ -202,18 +202,17 @@ export const EscapeKeyCloses: Story = { }, play: async () => { // Modal is initially open - let modal = document.querySelector('[role="dialog"]'); + const modal = document.querySelector('[role="dialog"]'); await expect(modal).toBeInTheDocument(); // Press Escape key await userEvent.keyboard("{Escape}"); - // Wait a bit for state update - await new Promise((resolve) => setTimeout(resolve, 100)); - - // Modal should be closed - modal = document.querySelector('[role="dialog"]'); - await expect(modal).not.toBeInTheDocument(); + // Wait for modal to be removed from DOM + await waitFor(() => { + const closedModal = document.querySelector('[role="dialog"]'); + expect(closedModal).not.toBeInTheDocument(); + }); }, }; @@ -241,7 +240,7 @@ export const OverlayClickCloses: Story = { }, play: async () => { // Modal is initially open - let modal = document.querySelector('[role="dialog"]'); + const modal = document.querySelector('[role="dialog"]'); await expect(modal).toBeInTheDocument(); // Click on overlay (role="presentation") @@ -249,12 +248,11 @@ export const OverlayClickCloses: Story = { await expect(overlay).toBeInTheDocument(); await userEvent.click(overlay!); - // Wait a bit for state update - await new Promise((resolve) => setTimeout(resolve, 100)); - - // Modal should be closed - modal = document.querySelector('[role="dialog"]'); - await expect(modal).not.toBeInTheDocument(); + // Wait for modal to be removed from DOM + await waitFor(() => { + const closedModal = document.querySelector('[role="dialog"]'); + expect(closedModal).not.toBeInTheDocument(); + }); }, }; @@ -283,18 +281,18 @@ export const ContentClickDoesNotClose: Story = { }, play: async () => { // Modal is initially open - let modal = document.querySelector('[role="dialog"]'); + const modal = document.querySelector('[role="dialog"]'); await expect(modal).toBeInTheDocument(); // Click on the modal content itself await userEvent.click(modal!); - // Wait a bit to ensure no state change + // Give time for any potential state change await new Promise((resolve) => setTimeout(resolve, 100)); // Modal should still be open - modal = document.querySelector('[role="dialog"]'); - await expect(modal).toBeInTheDocument(); + const stillOpenModal = document.querySelector('[role="dialog"]'); + await expect(stillOpenModal).toBeInTheDocument(); }, }; @@ -319,28 +317,28 @@ export const LoadingPreventsClose: Story = { }, play: async () => { // Modal is initially open - let modal = document.querySelector('[role="dialog"]'); + const modal = document.querySelector('[role="dialog"]'); await expect(modal).toBeInTheDocument(); // Try to press Escape (should not work due to isLoading=true) await userEvent.keyboard("{Escape}"); - // Wait a bit + // Give time for any potential state change await new Promise((resolve) => setTimeout(resolve, 100)); // Modal should still be open - modal = document.querySelector('[role="dialog"]'); - await expect(modal).toBeInTheDocument(); + const stillOpenModal1 = document.querySelector('[role="dialog"]'); + await expect(stillOpenModal1).toBeInTheDocument(); // Try to click overlay (should also not work) const overlay = document.querySelector('[role="presentation"]'); await userEvent.click(overlay!); - // Wait a bit + // Give time for any potential state change await new Promise((resolve) => setTimeout(resolve, 100)); // Modal should still be open - modal = document.querySelector('[role="dialog"]'); - await expect(modal).toBeInTheDocument(); + const stillOpenModal2 = document.querySelector('[role="dialog"]'); + await expect(stillOpenModal2).toBeInTheDocument(); }, }; From 4565662db2ceb899e6135fdfd04fbfdaabecbd65 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Wed, 22 Oct 2025 19:55:56 -0400 Subject: [PATCH 2/3] Fix lint errors: await expect inside waitFor callbacks --- src/components/Modal.stories.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/Modal.stories.tsx b/src/components/Modal.stories.tsx index 08671e25b8..861e1be91d 100644 --- a/src/components/Modal.stories.tsx +++ b/src/components/Modal.stories.tsx @@ -209,9 +209,9 @@ export const EscapeKeyCloses: Story = { await userEvent.keyboard("{Escape}"); // Wait for modal to be removed from DOM - await waitFor(() => { + await waitFor(async () => { const closedModal = document.querySelector('[role="dialog"]'); - expect(closedModal).not.toBeInTheDocument(); + await expect(closedModal).not.toBeInTheDocument(); }); }, }; @@ -249,9 +249,9 @@ export const OverlayClickCloses: Story = { await userEvent.click(overlay!); // Wait for modal to be removed from DOM - await waitFor(() => { + await waitFor(async () => { const closedModal = document.querySelector('[role="dialog"]'); - expect(closedModal).not.toBeInTheDocument(); + await expect(closedModal).not.toBeInTheDocument(); }); }, }; From d21669a26de028a986f1c16844a4d1e8e55471b2 Mon Sep 17 00:00:00 2001 From: Kyle Carberry Date: Wed, 22 Oct 2025 20:00:27 -0400 Subject: [PATCH 3/3] Add delay before keyboard/click events to ensure event listeners are attached --- src/components/Modal.stories.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/components/Modal.stories.tsx b/src/components/Modal.stories.tsx index 861e1be91d..f6460fd903 100644 --- a/src/components/Modal.stories.tsx +++ b/src/components/Modal.stories.tsx @@ -205,6 +205,9 @@ export const EscapeKeyCloses: Story = { const modal = document.querySelector('[role="dialog"]'); await expect(modal).toBeInTheDocument(); + // Wait for modal to be fully mounted and event listeners attached + await new Promise((resolve) => setTimeout(resolve, 100)); + // Press Escape key await userEvent.keyboard("{Escape}"); @@ -243,6 +246,9 @@ export const OverlayClickCloses: Story = { const modal = document.querySelector('[role="dialog"]'); await expect(modal).toBeInTheDocument(); + // Wait for modal to be fully mounted and event listeners attached + await new Promise((resolve) => setTimeout(resolve, 100)); + // Click on overlay (role="presentation") const overlay = document.querySelector('[role="presentation"]'); await expect(overlay).toBeInTheDocument();