From 49d8c6c9b23b026d34170b2f9bc723579fea205d Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 9 Feb 2023 14:23:05 -0500 Subject: [PATCH 1/5] =?UTF-8?q?Don=E2=80=99t=20fire=20afterLeave=20event?= =?UTF-8?q?=20more=20than=20once=20for=20a=20given=20component?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/transitions/transition.test.ts | 89 +++++++++++++++++++ .../src/components/transitions/transition.ts | 2 +- 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/packages/@headlessui-vue/src/components/transitions/transition.test.ts b/packages/@headlessui-vue/src/components/transitions/transition.test.ts index e07a58f5b..fe6b32a50 100644 --- a/packages/@headlessui-vue/src/components/transitions/transition.test.ts +++ b/packages/@headlessui-vue/src/components/transitions/transition.test.ts @@ -1258,4 +1258,93 @@ describe('Events', () => { expect(leaveHookDiff).toBeLessThanOrEqual(leaveDuration * 3) }) ) + + it( + 'should fire only one event for a given component change', + suppressConsoleLogs(async () => { + let eventHandler = jest.fn() + let enterDuration = 50 + let leaveDuration = 75 + + withStyles(` + .enter-1 { transition-duration: ${enterDuration * 1}ms; } + .enter-2 { transition-duration: ${enterDuration * 2}ms; } + .enter-from { opacity: 0%; } + .enter-to { opacity: 100%; } + + .leave-1 { transition-duration: ${leaveDuration * 1}ms; } + .leave-2 { transition-duration: ${leaveDuration * 2}ms; } + .leave-from { opacity: 100%; } + .leave-to { opacity: 0%; } + `) + + let Example = defineComponent({ + components: { TransitionRoot, TransitionChild }, + template: html` + + + + + + + + + `, + setup() { + let show = ref(false) + let start = ref(Date.now()) + + onMounted(() => (start.value = Date.now())) + + return { show, start, eventHandler } + }, + }) + + renderTemplate(Example) + + fireEvent.click(getByTestId('show')) + + await new Promise((resolve) => setTimeout(resolve, 1000)) + + fireEvent.click(getByTestId('hide')) + + await new Promise((resolve) => setTimeout(resolve, 1000)) + + expect(eventHandler).toHaveBeenCalledTimes(4) + expect(eventHandler.mock.calls.map(([name]) => name)).toEqual([ + // Order is important here + 'beforeEnter', + 'afterEnter', + 'beforeLeave', + 'afterLeave', + ]) + }) + ) }) diff --git a/packages/@headlessui-vue/src/components/transitions/transition.ts b/packages/@headlessui-vue/src/components/transitions/transition.ts index 527c25070..875825200 100644 --- a/packages/@headlessui-vue/src/components/transitions/transition.ts +++ b/packages/@headlessui-vue/src/components/transitions/transition.ts @@ -188,7 +188,7 @@ export let TransitionChild = defineComponent({ let nesting = useNesting(() => { // When all children have been unmounted we can only hide ourselves if and only if we are not // transitioning ourselves. Otherwise we would unmount before the transitions are finished. - if (!isTransitioning.value) { + if (!isTransitioning.value && state.value !== TreeStates.Hidden) { state.value = TreeStates.Hidden unregister(id) emit('afterLeave') From 5ae130bfd192450b3c57cc9457e0d60e4ecf94ed Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 9 Feb 2023 14:32:56 -0500 Subject: [PATCH 2/5] Port test to React --- .../transitions/transition.test.tsx | 87 ++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/packages/@headlessui-react/src/components/transitions/transition.test.tsx b/packages/@headlessui-react/src/components/transitions/transition.test.tsx index 391c9ab2c..76c4a8832 100644 --- a/packages/@headlessui-react/src/components/transitions/transition.test.tsx +++ b/packages/@headlessui-react/src/components/transitions/transition.test.tsx @@ -1,4 +1,4 @@ -import React, { Fragment, useState, useRef, useLayoutEffect } from 'react' +import React, { Fragment, useState, useRef, useLayoutEffect, useEffect } from 'react' import { render, fireEvent } from '@testing-library/react' import { suppressConsoleLogs } from '../../test-utils/suppress-console-logs' @@ -1397,4 +1397,89 @@ describe('Events', () => { ]) }) ) + + fit( + 'should fire only one event for a given component change', + suppressConsoleLogs(async () => { + let eventHandler = jest.fn() + let enterDuration = 50 + let leaveDuration = 75 + + function Example() { + let [show, setShow] = useState(false) + let [start, setStart] = useState(Date.now()) + + useEffect(() => setStart(Date.now()), []) + + return ( + <> + + eventHandler('beforeEnter', Date.now() - start)} + afterEnter={() => eventHandler('afterEnter', Date.now() - start)} + beforeLeave={() => eventHandler('beforeLeave', Date.now() - start)} + afterLeave={() => eventHandler('afterLeave', Date.now() - start)} + enter="enter-2" + enterFrom="enter-from" + enterTo="enter-to" + leave="leave-2" + leaveFrom="leave-from" + leaveTo="leave-to" + > + + + + + + + + ) + } + + render() + + fireEvent.click(document.querySelector('[data-testid=show]')!) + + await new Promise((resolve) => setTimeout(resolve, 1000)) + + fireEvent.click(document.querySelector('[data-testid=hide]')!) + + await new Promise((resolve) => setTimeout(resolve, 1000)) + + expect(eventHandler).toHaveBeenCalledTimes(4) + expect(eventHandler.mock.calls.map(([name]) => name)).toEqual([ + // Order is important here + 'beforeEnter', + 'afterEnter', + 'beforeLeave', + 'afterLeave', + ]) + }) + ) }) From 2e87301e2ad285b087c275f3d69b5daaccd2f658 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 9 Feb 2023 14:36:27 -0500 Subject: [PATCH 3/5] Fix CS --- .../src/components/transitions/transition.test.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/@headlessui-react/src/components/transitions/transition.test.tsx b/packages/@headlessui-react/src/components/transitions/transition.test.tsx index 76c4a8832..24f9f125a 100644 --- a/packages/@headlessui-react/src/components/transitions/transition.test.tsx +++ b/packages/@headlessui-react/src/components/transitions/transition.test.tsx @@ -1454,10 +1454,14 @@ describe('Events', () => { leaveFrom="leave-from" leaveTo="leave-to" > - + - + ) } From e22e2542362e76e7ba69b88ff89efaec603e5667 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 10 Feb 2023 09:27:33 -0500 Subject: [PATCH 4/5] Remove focus on test --- .../src/components/transitions/transition.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@headlessui-react/src/components/transitions/transition.test.tsx b/packages/@headlessui-react/src/components/transitions/transition.test.tsx index 24f9f125a..a58b82163 100644 --- a/packages/@headlessui-react/src/components/transitions/transition.test.tsx +++ b/packages/@headlessui-react/src/components/transitions/transition.test.tsx @@ -1398,7 +1398,7 @@ describe('Events', () => { }) ) - fit( + it( 'should fire only one event for a given component change', suppressConsoleLogs(async () => { let eventHandler = jest.fn() From d48bc0286d00a7b93db7ababc2d02421ba75c85f Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 10 Feb 2023 09:29:56 -0500 Subject: [PATCH 5/5] Update changelog --- packages/@headlessui-vue/CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/@headlessui-vue/CHANGELOG.md b/packages/@headlessui-vue/CHANGELOG.md index 0ae9bd8f2..790e92c64 100644 --- a/packages/@headlessui-vue/CHANGELOG.md +++ b/packages/@headlessui-vue/CHANGELOG.md @@ -7,7 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -- Nothing yet! +### Fixed + +- Don’t fire `afterLeave` event more than once for a given transition ([#2267](https://github.com/tailwindlabs/headlessui/pull/2267)) ## [1.7.9] - 2023-02-03