Skip to content

Add Cypress test and fix for win dialog null crash (render.js fadeIn)#187

Open
Coteh wants to merge 1 commit into
masterfrom
claude/fix-win-dialog-null-SpI8W
Open

Add Cypress test and fix for win dialog null crash (render.js fadeIn)#187
Coteh wants to merge 1 commit into
masterfrom
claude/fix-win-dialog-null-SpI8W

Conversation

@Coteh

@Coteh Coteh commented Mar 15, 2026

Copy link
Copy Markdown
Owner

The 10ms fadeIn setTimeout in renderDialog re-queried the DOM with
document.querySelector(".dialog") instead of closing over the dialog
variable already in scope. If the dialog was removed before that timer
fired (e.g. the overlay became visible while the dialog was still at
opacity:0, and a click landed on it), the query returned null and threw
"can't access property 'style', dialog is null".

Fix: drop the re-declaration inside the setTimeout so it closes over
the existing dialog reference. A detached element reference is safe to
write styles to; a null one is not.

The new Cypress describe freezes all timers with cy.clock() so the
fadeIn callback can be triggered at an exact moment via cy.tick(10),
making the race condition reliably reproducible.

https://claude.ai/code/session_01DYsKXcjAhF95UkPuT3v4Wk

Summary by CodeRabbit

  • Bug Fixes

    • Resolved uncaught exceptions in win dialog handling during animation edge cases.
  • Tests

    • Added comprehensive test coverage for win dialog fade-in animation behavior and error scenarios.

The 10ms fadeIn setTimeout in renderDialog re-queried the DOM with
document.querySelector(".dialog") instead of closing over the dialog
variable already in scope. If the dialog was removed before that timer
fired (e.g. the overlay became visible while the dialog was still at
opacity:0, and a click landed on it), the query returned null and threw
"can't access property 'style', dialog is null".

Fix: drop the re-declaration inside the setTimeout so it closes over
the existing dialog reference. A detached element reference is safe to
write styles to; a null one is not.

The new Cypress describe freezes all timers with cy.clock() so the
fadeIn callback can be triggered at an exact moment via cy.tick(10),
making the race condition reliably reproducible.

https://claude.ai/code/session_01DYsKXcjAhF95UkPuT3v4Wk
@coderabbitai

coderabbitai Bot commented Mar 15, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This pull request adds comprehensive test coverage for win dialog fade-in animation timing, including race condition validation, and removes a redundant DOM query in the render function to use an outer-scoped reference instead.

Changes

Cohort / File(s) Summary
Test Suite Addition
cypress/e2e/game/dialog.cy.js
New test suite "win dialog fadeIn animation" with three test cases covering dialog fade-in timing, successful win dialog display, and race condition handling when dialog is removed before the fade-in timer fires.
DOM Query Optimization
src/render.js
Removed redundant DOM re-query inside setTimeout callback within renderDialog's fade-in branch, relying instead on the dialog reference already obtained from the outer function scope.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 Timer frozen, dialog caught mid-fade,
Race conditions tested, race condition slayed!
One query removed from the setTimeout's hand,
Scoped references make the DOM more grand!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly summarizes the two main changes: adding a Cypress test and fixing a null crash in render.js's fadeIn logic related to the win dialog.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch claude/fix-win-dialog-null-SpI8W
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
cypress/e2e/game/dialog.cy.js (3)

24-29: Consider reducing repeated key-click boilerplate.

This sequence is clear, but a tiny helper/loop would keep test setup shorter and easier to update.

♻️ Small refactor
-        cy.keyboardItem("l").click();
-        cy.keyboardItem("e").click();
-        cy.keyboardItem("a").click();
-        cy.keyboardItem("f").click();
-        cy.keyboardItem("y").click();
+        "leafy".split("").forEach((ch) => {
+            cy.keyboardItem(ch).click();
+        });
         cy.keyboardItem("enter").click();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cypress/e2e/game/dialog.cy.js` around lines 24 - 29, The repeated
cy.keyboardItem("x").click() calls are boilerplate; replace them with a small
helper or loop to iterate over the characters of the input string (e.g., "leafy"
plus "enter") and call cy.keyboardItem(char).click() for each, or add a custom
Cypress command like cy.typeKeyboardSequence(sequence) that maps each char to
cy.keyboardItem and clicks it; update the test to use that helper and remove the
six explicit cy.keyboardItem calls to make the setup concise and easier to
change.

39-47: Add an explicit invisibility precondition before closing.

This test intent is “close while invisible”; asserting that state before the click will make the race repro more robust against UI behavior drift.

✅ Make the race precondition explicit
         cy.get(".dialog").should("exist");
+        cy.get(".dialog").should("not.be.visible");

         // Close the dialog before the timer fires (simulates overlay click on the invisible dialog)
         cy.get(".overlay-back").click();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cypress/e2e/game/dialog.cy.js` around lines 39 - 47, The test should assert
the dialog is actually invisible before simulating the overlay click: after the
existence check for ".dialog" add an explicit invisibility assertion (e.g.,
assert CSS opacity is "0" or use Cypress's visibility check) so the precondition
for "close while invisible" is enforced prior to clicking ".overlay-back";
update the block around the cy.get(".dialog") and
cy.get(".overlay-back").click() to include this invisibility assertion.

32-36: Assert pre-tick hidden state to validate the transition path.

Right now this can pass even if the dialog is already visible before the timer callback. Add a precondition check so the test explicitly verifies the fade-in behavior.

✅ Tighten transition verification
     it("should display win dialog after player wins", () => {
+        cy.get(".dialog").should("exist").and("not.be.visible");
         // Tick to fire the frozen fadeIn timer - dialog transitions from opacity:0 to visible
         cy.tick(10);
         cy.contains("You win!").should("be.visible");
     });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cypress/e2e/game/dialog.cy.js` around lines 32 - 36, The test "should display
win dialog after player wins" currently only checks visibility after
cy.tick(10), so add an explicit precondition that the dialog is hidden before
the tick to validate the fade-in transition path: before cy.tick(10) use
cy.contains("You win!").should("not.be.visible") (or assert CSS opacity: 0 with
cy.contains(...).should("have.css", "opacity", "0")) then call cy.tick(10) and
assert it becomes visible with cy.contains("You win!").should("be.visible");
keep the existing assertions and use the same selector text "You win!" to locate
the dialog.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@cypress/e2e/game/dialog.cy.js`:
- Around line 24-29: The repeated cy.keyboardItem("x").click() calls are
boilerplate; replace them with a small helper or loop to iterate over the
characters of the input string (e.g., "leafy" plus "enter") and call
cy.keyboardItem(char).click() for each, or add a custom Cypress command like
cy.typeKeyboardSequence(sequence) that maps each char to cy.keyboardItem and
clicks it; update the test to use that helper and remove the six explicit
cy.keyboardItem calls to make the setup concise and easier to change.
- Around line 39-47: The test should assert the dialog is actually invisible
before simulating the overlay click: after the existence check for ".dialog" add
an explicit invisibility assertion (e.g., assert CSS opacity is "0" or use
Cypress's visibility check) so the precondition for "close while invisible" is
enforced prior to clicking ".overlay-back"; update the block around the
cy.get(".dialog") and cy.get(".overlay-back").click() to include this
invisibility assertion.
- Around line 32-36: The test "should display win dialog after player wins"
currently only checks visibility after cy.tick(10), so add an explicit
precondition that the dialog is hidden before the tick to validate the fade-in
transition path: before cy.tick(10) use cy.contains("You
win!").should("not.be.visible") (or assert CSS opacity: 0 with
cy.contains(...).should("have.css", "opacity", "0")) then call cy.tick(10) and
assert it becomes visible with cy.contains("You win!").should("be.visible");
keep the existing assertions and use the same selector text "You win!" to locate
the dialog.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7d739dd3-ba42-4f25-8afd-77f975cdf776

📥 Commits

Reviewing files that changed from the base of the PR and between 0e2986a and 1a5de8a.

📒 Files selected for processing (2)
  • cypress/e2e/game/dialog.cy.js
  • src/render.js
💤 Files with no reviewable changes (1)
  • src/render.js

@github-actions

Copy link
Copy Markdown

Test Results

 15 files  ±0  100 suites  +1   5m 9s ⏱️ -15s
289 tests +2  289 ✅ +2  0 💤 ±0  0 ❌ ±0 
298 runs  +2  298 ✅ +2  0 💤 ±0  0 ❌ ±0 

Results for commit 1a5de8a. ± Comparison against base commit 0e2986a.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants