Skip to content

fix(transition): preserve placeholder for conditional explicit default slots#14748

Merged
edison1105 merged 2 commits into
mainfrom
edison/fix/14727_
Apr 22, 2026
Merged

fix(transition): preserve placeholder for conditional explicit default slots#14748
edison1105 merged 2 commits into
mainfrom
edison/fix/14727_

Conversation

@edison1105

@edison1105 edison1105 commented Apr 22, 2026

Copy link
Copy Markdown
Member

close #14727

Summary by CodeRabbit

  • Bug Fixes

    • Fixed an issue where rapidly toggling a conditional transition during a leave animation could produce errors and prevent correct class sequences from being applied on re-entry.
  • Tests

    • Added an end-to-end test covering conditional transitions with rapid toggles to ensure correct class sequencing, no runtime errors, and stable DOM after transitions.

@coderabbitai

coderabbitai Bot commented Apr 22, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Fixes a null-reference during rapid toggles of a by returning a comment VNode when the slot is empty but an existing instance subtree is present, and adds an e2e test reproducing the quick-toggle scenario.

Changes

Cohort / File(s) Summary
BaseTransition logic
packages/runtime-core/src/components/BaseTransition.ts
Import createCommentVNode and refactor child vnode computation to return a comment placeholder when children is empty but instance.subTree exists; remove redundant child lookup.
Transition e2e test
packages/vue/__tests__/e2e/Transition.spec.ts
Add #14727 test that mounts a <transition> with a conditional default slot, performs quick toggles to reproduce the issue, asserts enter-class sequence, waits for transition completion, and ensures no page errors.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

Suggested labels

ready to merge, scope: transition, :hammer: p3-minor-bug

Poem

🐰 Quick hops, a flicker, then a start—
A tiny comment plays its part.
No null to trip, no error caught,
Rapid flips now calm and taut.
Hooray — the transition keeps its heart! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main fix: preserving a placeholder for conditional explicit default slots in transitions, which directly addresses issue #14727.
Linked Issues check ✅ Passed The PR implements a fix for issue #14727 by modifying BaseTransition to handle conditional slots properly and adding a regression test that validates the rapid toggle scenario.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing the reported issue: the BaseTransition logic change handles the edge case, and the test validates the specific bug scenario.
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
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch edison/fix/14727_

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

@github-actions

Copy link
Copy Markdown

Size Report

Bundles

File Size Gzip Brotli
runtime-dom.global.prod.js 105 kB (+28 B) 39.9 kB (+13 B) 35.8 kB (+10 B)
vue.global.prod.js 164 kB (+28 B) 59.9 kB (+14 B) 53.3 kB (+38 B)

Usages

Name Size Gzip Brotli
createApp (CAPI only) 48.3 kB 18.8 kB 17.2 kB
createApp 56.5 kB 21.8 kB 20 kB
createSSRApp 60.7 kB 23.6 kB 21.5 kB
defineCustomElement 62.6 kB 23.8 kB 21.7 kB
overall 71.2 kB (+200 B) 27.2 kB (+55 B) 24.8 kB (+63 B)

@pkg-pr-new

pkg-pr-new Bot commented Apr 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@vue/compiler-core

pnpm add https://pkg.pr.new/@vue/compiler-core@14748
npm i https://pkg.pr.new/@vue/compiler-core@14748
yarn add https://pkg.pr.new/@vue/compiler-core@14748.tgz

@vue/compiler-dom

pnpm add https://pkg.pr.new/@vue/compiler-dom@14748
npm i https://pkg.pr.new/@vue/compiler-dom@14748
yarn add https://pkg.pr.new/@vue/compiler-dom@14748.tgz

@vue/compiler-sfc

pnpm add https://pkg.pr.new/@vue/compiler-sfc@14748
npm i https://pkg.pr.new/@vue/compiler-sfc@14748
yarn add https://pkg.pr.new/@vue/compiler-sfc@14748.tgz

@vue/compiler-ssr

pnpm add https://pkg.pr.new/@vue/compiler-ssr@14748
npm i https://pkg.pr.new/@vue/compiler-ssr@14748
yarn add https://pkg.pr.new/@vue/compiler-ssr@14748.tgz

@vue/reactivity

pnpm add https://pkg.pr.new/@vue/reactivity@14748
npm i https://pkg.pr.new/@vue/reactivity@14748
yarn add https://pkg.pr.new/@vue/reactivity@14748.tgz

@vue/runtime-core

pnpm add https://pkg.pr.new/@vue/runtime-core@14748
npm i https://pkg.pr.new/@vue/runtime-core@14748
yarn add https://pkg.pr.new/@vue/runtime-core@14748.tgz

@vue/runtime-dom

pnpm add https://pkg.pr.new/@vue/runtime-dom@14748
npm i https://pkg.pr.new/@vue/runtime-dom@14748
yarn add https://pkg.pr.new/@vue/runtime-dom@14748.tgz

@vue/server-renderer

pnpm add https://pkg.pr.new/@vue/server-renderer@14748
npm i https://pkg.pr.new/@vue/server-renderer@14748
yarn add https://pkg.pr.new/@vue/server-renderer@14748.tgz

@vue/shared

pnpm add https://pkg.pr.new/@vue/shared@14748
npm i https://pkg.pr.new/@vue/shared@14748
yarn add https://pkg.pr.new/@vue/shared@14748.tgz

vue

pnpm add https://pkg.pr.new/vue@14748
npm i https://pkg.pr.new/vue@14748
yarn add https://pkg.pr.new/vue@14748.tgz

@vue/compat

pnpm add https://pkg.pr.new/@vue/compat@14748
npm i https://pkg.pr.new/@vue/compat@14748
yarn add https://pkg.pr.new/@vue/compat@14748.tgz

commit: ce3eac7

@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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/vue/__tests__/e2e/Transition.spec.ts`:
- Around line 1438-1478: Wrap the code between currentPage.on('pageerror', spy)
and currentPage.off('pageerror', spy) in a try/finally so the listener is always
removed; specifically, after calling currentPage.on('pageerror', spy) create a
try block that contains the page().evaluate setup, the assertions (expects), the
clicks/nextTick/nextFrame/transitionFinish calls, and then place
currentPage.off('pageerror', spy) in the finally block to guarantee cleanup even
if an assertion fails; ensure the spy variable remains in scope and no other
logic is moved outside the try so behavior of click, nextTick, transitionFinish,
html, and $$eval remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 230207e7-c32c-4ed7-b60a-d957118c3506

📥 Commits

Reviewing files that changed from the base of the PR and between 7df0edd and c05bc5c.

📒 Files selected for processing (2)
  • packages/runtime-core/src/components/BaseTransition.ts
  • packages/vue/__tests__/e2e/Transition.spec.ts

Comment thread packages/vue/__tests__/e2e/Transition.spec.ts

@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.

♻️ Duplicate comments (1)
packages/vue/__tests__/e2e/Transition.spec.ts (1)

1438-1477: ⚠️ Potential issue | 🟡 Minor

Always detach pageerror listener via try/finally.

If any assertion throws before Line 1477, the listener remains attached and can leak into subsequent tests.

Proposed fix
         const spy = vi.fn()
         const currentPage = page()
         currentPage.on('pageerror', spy)

-        await page().evaluate(() => {
+        try {
+          await page().evaluate(() => {
           const { createApp, ref } = (window as any).Vue
           createApp({
             template: `
               <div id="container">
                 <transition name="test">
                   <template v-if="show" #>
                     <div class="test">text</div>
                   </template>
                 </transition>
               </div>
               <button id="toggleBtn" `@click`="show = !show">button</button>
             `,
             setup: () => {
               const show = ref(true)
               return { show }
             },
           }).mount('#app')
-        })
+          })

-        expect(await html('#container')).toBe('<div class="test">text</div>')
+          expect(await html('#container')).toBe('<div class="test">text</div>')

-        await click('#toggleBtn')
-        await nextTick()
-        await click('#toggleBtn')
+          await click('#toggleBtn')
+          await nextTick()
+          await click('#toggleBtn')

-        expect(
-          await page().$$eval('#container .test', nodes =>
-            nodes.map(node => node.className),
-          ),
-        ).toStrictEqual(['test test-enter-from test-enter-active'])
+          expect(
+            await page().$$eval('#container .test', nodes =>
+              nodes.map(node => node.className),
+            ),
+          ).toStrictEqual(['test test-enter-from test-enter-active'])

-        await nextFrame()
-        await transitionFinish()
-        await nextFrame()
+          await nextFrame()
+          await transitionFinish()
+          await nextFrame()

-        expect(spy).not.toHaveBeenCalled()
-        currentPage.off('pageerror', spy)
-        expect(await html('#container')).toBe('<div class="test">text</div>')
+          expect(spy).not.toHaveBeenCalled()
+          expect(await html('#container')).toBe('<div class="test">text</div>')
+        } finally {
+          currentPage.off('pageerror', spy)
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/vue/__tests__/e2e/Transition.spec.ts` around lines 1438 - 1477, Wrap
the lifecycle of the pageerror listener (currentPage.on('pageerror', spy)) in a
try/finally inside the test so the listener is always removed with
currentPage.off('pageerror', spy) in the finally block; update the test around
the lines that call currentPage.on/currentPage.off and assertions (where spy is
used) to ensure the try contains the evaluate/click/expect/transition steps and
the finally calls currentPage.off('pageerror', spy) to avoid leaking the
listener into other tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/vue/__tests__/e2e/Transition.spec.ts`:
- Around line 1438-1477: Wrap the lifecycle of the pageerror listener
(currentPage.on('pageerror', spy)) in a try/finally inside the test so the
listener is always removed with currentPage.off('pageerror', spy) in the finally
block; update the test around the lines that call currentPage.on/currentPage.off
and assertions (where spy is used) to ensure the try contains the
evaluate/click/expect/transition steps and the finally calls
currentPage.off('pageerror', spy) to avoid leaking the listener into other
tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dcc2a8a0-4eb3-4d93-a22a-ec3fc1102117

📥 Commits

Reviewing files that changed from the base of the PR and between c05bc5c and ce3eac7.

📒 Files selected for processing (1)
  • packages/vue/__tests__/e2e/Transition.spec.ts

@edison1105

Copy link
Copy Markdown
Member Author

/ecosystem-ci run

@edison1105 edison1105 added scope: transition 🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. labels Apr 22, 2026
@vue-bot

vue-bot commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

📝 Ran ecosystem CI: Open

suite result latest scheduled
pinia success success
primevue success success
test-utils success success
language-tools success success
vue-i18n success success
vitepress success success
radix-vue success success
quasar success success
vite-plugin-vue success success
router success success
nuxt success success
vue-macros success success
vant success success
vue-simple-compiler success success
vueuse success success
vuetify success success

@edison1105 edison1105 merged commit 45990ce into main Apr 22, 2026
15 of 16 checks passed
@edison1105 edison1105 deleted the edison/fix/14727_ branch April 22, 2026 06:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🍰 p2-nice-to-have Priority 2: this is not breaking anything but nice to have it addressed. scope: transition

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Quickly switch Transition

2 participants