Skip to content

Commit 64aa8e9

Browse files
authored
🤖 Fix Modal story tests timing issues in Chromatic (#385)
## Problem The `EscapeKeyCloses` and `OverlayClickCloses` story tests were failing in Chromatic with this error: ``` expect(element).not.toBeInTheDocument() expected document not to contain element, found <div role="dialog"> ``` The tests were using fixed 100ms timeouts after user interactions, which wasn't reliable in the Chromatic rendering environment. React state updates are asynchronous and timing can vary across environments. ## Solution Replace fixed timeouts with `waitFor()` from `@storybook/test`, which polls until the condition is met or times out. Also add a small delay before triggering user actions to ensure the Modal's `useEffect` has attached event listeners. ## Changes - **EscapeKeyCloses test**: Add setup delay + use `waitFor()` to wait for modal removal - **OverlayClickCloses test**: Add setup delay + use `waitFor()` to wait for modal removal - **Code quality**: Replace `let` with `const` for modal queries and use descriptive variable names - **Consistency**: Improve comments in ContentClickDoesNotClose and LoadingPreventsClose tests This makes the tests more reliable across different rendering environments including CI, local development, and Chromatic. _Generated with `cmux`_
1 parent e67e729 commit 64aa8e9

File tree

1 file changed

+30
-26
lines changed

1 file changed

+30
-26
lines changed

src/components/Modal.stories.tsx

Lines changed: 30 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Meta, StoryObj } from "@storybook/react";
22
import { action } from "@storybook/addon-actions";
3-
import { expect, userEvent } from "@storybook/test";
3+
import { expect, userEvent, waitFor } from "@storybook/test";
44
import { useState } from "react";
55
import {
66
Modal,
@@ -202,18 +202,20 @@ export const EscapeKeyCloses: Story = {
202202
},
203203
play: async () => {
204204
// Modal is initially open
205-
let modal = document.querySelector('[role="dialog"]');
205+
const modal = document.querySelector('[role="dialog"]');
206206
await expect(modal).toBeInTheDocument();
207207

208+
// Wait for modal to be fully mounted and event listeners attached
209+
await new Promise((resolve) => setTimeout(resolve, 100));
210+
208211
// Press Escape key
209212
await userEvent.keyboard("{Escape}");
210213

211-
// Wait a bit for state update
212-
await new Promise((resolve) => setTimeout(resolve, 100));
213-
214-
// Modal should be closed
215-
modal = document.querySelector('[role="dialog"]');
216-
await expect(modal).not.toBeInTheDocument();
214+
// Wait for modal to be removed from DOM
215+
await waitFor(async () => {
216+
const closedModal = document.querySelector('[role="dialog"]');
217+
await expect(closedModal).not.toBeInTheDocument();
218+
});
217219
},
218220
};
219221

@@ -241,20 +243,22 @@ export const OverlayClickCloses: Story = {
241243
},
242244
play: async () => {
243245
// Modal is initially open
244-
let modal = document.querySelector('[role="dialog"]');
246+
const modal = document.querySelector('[role="dialog"]');
245247
await expect(modal).toBeInTheDocument();
246248

249+
// Wait for modal to be fully mounted and event listeners attached
250+
await new Promise((resolve) => setTimeout(resolve, 100));
251+
247252
// Click on overlay (role="presentation")
248253
const overlay = document.querySelector('[role="presentation"]');
249254
await expect(overlay).toBeInTheDocument();
250255
await userEvent.click(overlay!);
251256

252-
// Wait a bit for state update
253-
await new Promise((resolve) => setTimeout(resolve, 100));
254-
255-
// Modal should be closed
256-
modal = document.querySelector('[role="dialog"]');
257-
await expect(modal).not.toBeInTheDocument();
257+
// Wait for modal to be removed from DOM
258+
await waitFor(async () => {
259+
const closedModal = document.querySelector('[role="dialog"]');
260+
await expect(closedModal).not.toBeInTheDocument();
261+
});
258262
},
259263
};
260264

@@ -283,18 +287,18 @@ export const ContentClickDoesNotClose: Story = {
283287
},
284288
play: async () => {
285289
// Modal is initially open
286-
let modal = document.querySelector('[role="dialog"]');
290+
const modal = document.querySelector('[role="dialog"]');
287291
await expect(modal).toBeInTheDocument();
288292

289293
// Click on the modal content itself
290294
await userEvent.click(modal!);
291295

292-
// Wait a bit to ensure no state change
296+
// Give time for any potential state change
293297
await new Promise((resolve) => setTimeout(resolve, 100));
294298

295299
// Modal should still be open
296-
modal = document.querySelector('[role="dialog"]');
297-
await expect(modal).toBeInTheDocument();
300+
const stillOpenModal = document.querySelector('[role="dialog"]');
301+
await expect(stillOpenModal).toBeInTheDocument();
298302
},
299303
};
300304

@@ -319,28 +323,28 @@ export const LoadingPreventsClose: Story = {
319323
},
320324
play: async () => {
321325
// Modal is initially open
322-
let modal = document.querySelector('[role="dialog"]');
326+
const modal = document.querySelector('[role="dialog"]');
323327
await expect(modal).toBeInTheDocument();
324328

325329
// Try to press Escape (should not work due to isLoading=true)
326330
await userEvent.keyboard("{Escape}");
327331

328-
// Wait a bit
332+
// Give time for any potential state change
329333
await new Promise((resolve) => setTimeout(resolve, 100));
330334

331335
// Modal should still be open
332-
modal = document.querySelector('[role="dialog"]');
333-
await expect(modal).toBeInTheDocument();
336+
const stillOpenModal1 = document.querySelector('[role="dialog"]');
337+
await expect(stillOpenModal1).toBeInTheDocument();
334338

335339
// Try to click overlay (should also not work)
336340
const overlay = document.querySelector('[role="presentation"]');
337341
await userEvent.click(overlay!);
338342

339-
// Wait a bit
343+
// Give time for any potential state change
340344
await new Promise((resolve) => setTimeout(resolve, 100));
341345

342346
// Modal should still be open
343-
modal = document.querySelector('[role="dialog"]');
344-
await expect(modal).toBeInTheDocument();
347+
const stillOpenModal2 = document.querySelector('[role="dialog"]');
348+
await expect(stillOpenModal2).toBeInTheDocument();
345349
},
346350
};

0 commit comments

Comments
 (0)