Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 469
feat(headless): hold Popover contents while it closes#9365
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
95d41609653a8fbfe1013b171e7c3d3556da3d77c1abdc63dFile filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| --- | ||
| --- |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -31,6 +31,7 @@ export interface PopoverProps { | ||
| onOpenChange?: (open: boolean) => void; | ||
| placement?: Placement; | ||
| sideOffset?: number; | ||
| alignOffset?: number; | ||
alexcarpenter marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| modal?: boolean; | ||
| /** | ||
| * Where focus lands when the popup opens. | ||
| @@ -47,7 +48,14 @@ export interface PopoverProps { | ||
| function PopoverInner(props: PopoverProps) { | ||
| const nodeId = useFloatingNodeId(); | ||
| const { placement: placementProp = 'bottom', sideOffset = 4, modal = false, initialFocus = 'auto', children } = props; | ||
| const { | ||
| placement: placementProp = 'bottom', | ||
| sideOffset = 4, | ||
| alignOffset = 0, | ||
| modal = false, | ||
| initialFocus = 'auto', | ||
| children, | ||
| } = props; | ||
| const [open, setOpen] = useControllableState(props.open, props.defaultOpen ?? false, props.onOpenChange); | ||
| @@ -71,7 +79,7 @@ function PopoverInner(props: PopoverProps) { | ||
| onOpenChange: setOpen, | ||
| placement: placementProp, | ||
| middleware: [ | ||
| offset(sideOffset), | ||
| offset({ mainAxis: sideOffset, alignmentAxis: alignOffset }), | ||
| flip({ | ||
| crossAxis: placementProp.includes('-'), | ||
| fallbackAxisSideDirection: 'end', | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -6,7 +6,10 @@ import { afterEach, describe, expect, it, vi } from 'vitest'; | ||
| import { axe } from '../../test-utils/axe'; | ||
| import { Popover } from './index'; | ||
| afterEach(() => cleanup()); | ||
| afterEach(() => { | ||
| cleanup(); | ||
| vi.restoreAllMocks(); | ||
| }); | ||
| function renderPopover(props: Partial<React.ComponentProps<typeof Popover.Root>> = {}) { | ||
| return render( | ||
| @@ -243,6 +246,31 @@ describe('Popover', () => { | ||
| const positioner = document.querySelector('[data-testid="popover-positioner"]'); | ||
| expect(positioner).toHaveAttribute('data-side', 'bottom'); | ||
| }); | ||
| // jsdom reports a zero-sized viewport, which leaves `shift` no room and pins the alignment | ||
| // axis to its padding whatever the offset asked for. Giving it a viewport is what lets the | ||
| // offset show up in the transform at all. | ||
| async function transformWithViewport(props: Partial<React.ComponentProps<typeof Popover.Root>>) { | ||
| vi.spyOn(document.documentElement, 'clientWidth', 'get').mockReturnValue(1024); | ||
| vi.spyOn(document.documentElement, 'clientHeight', 'get').mockReturnValue(768); | ||
| renderPopover({ defaultOpen: true, ...props }); | ||
| const positioner = document.querySelector('[data-testid="popover-positioner"]'); | ||
| await waitFor(() => expect(positioner).toHaveStyle({ position: 'absolute' })); | ||
| return positioner instanceof HTMLElement ? positioner.style.transform : null; | ||
Comment on lines
+259
to
+260
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: set -eu
printf'%s\n''--- test file ---'
sed -n '1,330p' packages/headless/src/primitives/popover/popover.test.tsx
printf'%s\n''--- Floating UI references and package versions ---'
rg -n '"`@floating-ui`|floating-ui|computePosition|useFloating|transform' packages/headless package.json pnpm-lock.yaml packages/*/package.json 2>/dev/null | head -240Repository: clerk/javascript Length of output: 33328 🏁 Script executed: set -eu
printf'%s\n''--- popover implementation ---'
fd -i 'popover' packages/headless/src/primitives/popover --type f
forfin$(fd -i 'popover' packages/headless/src/primitives/popover --type f);doprintf'\n--- %s ---\n'"$f"
wc -l "$f"doneprintf'%s\n''--- positioning implementation references ---'
rg -n -C 5 'useFloating|Floating|alignOffset|sideOffset|transform|positioner' packages/headless/src/primitives/popover packages/headless/src | head -320
printf'%s\n''--- test commands and installed dependency state ---'
node --version 2>/dev/null ||truetest -d node_modules &&echo node_modules-present ||echo node_modules-absent
sed -n '1,130p' packages/headless/package.jsonRepository: clerk/javascript Length of output: 34130 🏁 Script executed: set -eu
printf'%s\n''--- installed Floating UI React implementation ---'
FLOATING_UI_REACT=$(readlink -f node_modules/.pnpm/@floating-ui+react@*/node_modules/@floating-ui/react 2>/dev/null | head -1)printf'path=%s\n'"$FLOATING_UI_REACT"
rg -n -C 8 'floatingStyles|useFloating\\('"$FLOATING_UI_REACT/dist"| head -260
printf'%s\n''--- installed package entry points ---'
cat "$FLOATING_UI_REACT/package.json"| sed -n '1,100p'Repository: clerk/javascript Length of output: 3322 🏁 Script executed: set -eu
FLOATING_UI_REACT=$(readlink -f node_modules/.pnpm/@floating-ui+react@*/node_modules/@floating-ui/react 2>/dev/null | head -1)printf'%s\n''--- useFloating source excerpts ---'
rg -n -F -C 10 'floatingStyles'"$FLOATING_UI_REACT/dist/floating-ui.react.mjs"| head -260
printf'%s\n''--- useFloating declaration ---'
rg -n -F -C 8 'function useFloating'"$FLOATING_UI_REACT/dist/floating-ui.react.mjs"| head -180
printf'%s\n''--- standalone timing probe ---'
node --input-type=module <<'JS'import { Window } from 'happy-dom';import React, { useRef } from 'react';import { flushSync } from 'react-dom';import { createRoot } from 'react-dom/client';import { offset, useFloating } from '`@floating-ui/react`';const window = new Window();Object.assign(globalThis, { window, document: window.document, HTMLElement: window.HTMLElement, Element: window.Element, Node: window.Node, MutationObserver: window.MutationObserver, navigator: window.navigator, getComputedStyle: window.getComputedStyle.bind(window),});Object.defineProperty(document.documentElement, 'clientWidth', { configurable: true, value: 1024 });Object.defineProperty(document.documentElement, 'clientHeight', { configurable: true, value: 768 });let snapshots = [];function Probe() { const reference = useRef(null); const floating = useRef(null); const { refs, floatingStyles } = useFloating({ open: true, placement: 'bottom-start', middleware: [offset({ mainAxis: 4, alignmentAxis: 24 })], }); snapshots.push({ phase: 'render', styles: { ...floatingStyles } }); return React.createElement( React.Fragment, null, React.createElement('button', { ref: node => { reference.current = node; refs.setReference(node); } }, 'Open'), React.createElement('div', { ref: node => { floating.current = node; refs.setFloating(node); }, 'data-testid': 'positioner', style: floatingStyles, }), );}const container = document.createElement('div');document.body.appendChild(container);flushSync(() => createRoot(container).render(React.createElement(Probe)));const positioner = container.querySelector('[data-testid="positioner"]');console.log('immediate', { position: positioner.style.position, transform: positioner.style.transform });await Promise.resolve();console.log('after-microtask', { position: positioner.style.position, transform: positioner.style.transform });await new Promise(resolve => setTimeout(resolve, 0));console.log('after-timeout', { position: positioner.style.position, transform: positioner.style.transform });console.log('snapshots', snapshots);JSRepository: clerk/javascript Length of output: 3973 🏁 Script executed: set -eu
FLOATING_UI_REACT=$(readlink -f node_modules/.pnpm/@floating-ui+react@*/node_modules/@floating-ui/react 2>/dev/null | head -1)
HAPPY_DOM=$(readlink -f node_modules/.pnpm/happy-dom@*/node_modules/happy-dom 2>/dev/null | head -1)printf'%s\n''--- useFloating implementation ---'
sed -n '2880,3025p'"$FLOATING_UI_REACT/dist/floating-ui.react.mjs"printf'%s\n''--- floating style update implementation ---'
rg -n -F -C 10 'floatingStyles'"$FLOATING_UI_REACT/dist/floating-ui.react.mjs"||trueprintf'%s\n''--- happy-dom package path ---'printf'%s\n'"$HAPPY_DOM"
cat "$HAPPY_DOM/package.json"| sed -n '1,55p'Repository: clerk/javascript Length of output: 6634 🏁 Script executed: set -eu
FLOATING_UI_REACT=$(readlink -f node_modules/.pnpm/@floating-ui+react@*/node_modules/@floating-ui/react 2>/dev/null | head -1)
FLOATING_UI_REACT_DOM=$(readlink -f node_modules/.pnpm/@floating-ui+react-dom@*/node_modules/@floating-ui/react-dom 2>/dev/null | head -1)
HAPPY_DOM=$(readlink -f node_modules/.pnpm/happy-dom@*/node_modules/happy-dom 2>/dev/null | head -1)printf'%s\n''--- useFloating DOM implementation ---'
rg -n -F -C 12 'function useFloating'"$FLOATING_UI_REACT_DOM/dist"| head -260
printf'%s\n''--- standalone timing probe ---'
node --input-type=module <<JSimport { Window } from '${HAPPY_DOM}/lib/index.js';import React, { useRef } from '${readlink -f node_modules/react/index.js}';import { flushSync } from '${readlink -f node_modules/react-dom/index.js}';import { createRoot } from '${readlink -f node_modules/react-dom/client.js}';import { offset, useFloating } from '${FLOATING_UI_REACT}/dist/floating-ui.react.mjs';const browserWindow = new Window();Object.assign(globalThis, { window: browserWindow, document: browserWindow.document, HTMLElement: browserWindow.HTMLElement, Element: browserWindow.Element, Node: browserWindow.Node, MutationObserver: browserWindow.MutationObserver, navigator: browserWindow.navigator, getComputedStyle: browserWindow.getComputedStyle.bind(browserWindow),});Object.defineProperty(document.documentElement, 'clientWidth', { configurable: true, value: 1024 });Object.defineProperty(document.documentElement, 'clientHeight', { configurable: true, value: 768 });function Probe() { const { refs, floatingStyles } = useFloating({ open: true, placement: 'bottom-start', middleware: [offset({ mainAxis: 4, alignmentAxis: 24 })], }); return React.createElement( React.Fragment, null, React.createElement('button', { ref: node => refs.setReference(node), }, 'Open'), React.createElement('div', { ref: node => refs.setFloating(node), 'data-testid': 'positioner', style: floatingStyles, }), );}const container = document.createElement('div');document.body.appendChild(container);flushSync(() => createRoot(container).render(React.createElement(Probe)));const positioner = container.querySelector('[data-testid="positioner"]');const print = label => console.log(label, { position: positioner.style.position, transform: positioner.style.transform, left: positioner.style.left, top: positioner.style.top,});print('immediate');await Promise.resolve();print('after-microtask');await new Promise(resolve => setTimeout(resolve, 0));print('after-timeout');JSRepository: clerk/javascript Length of output: 2295 🏁 Script executed: set -eu
FLOATING_UI_REACT=$(readlink -f node_modules/.pnpm/@floating-ui+react@*/node_modules/@floating-ui/react 2>/dev/null | head -1)
HAPPY_DOM=$(readlink -f node_modules/.pnpm/happy-dom@*/node_modules/happy-dom 2>/dev/null | head -1)
REACT_PATH=$(readlink -f node_modules/react/index.js)
REACT_DOM_PATH=$(readlink -f node_modules/react-dom/index.js)
REACT_DOM_CLIENT_PATH=$(readlink -f node_modules/react-dom/client.js)export FLOATING_UI_REACT HAPPY_DOM REACT_PATH REACT_DOM_PATH REACT_DOM_CLIENT_PATH
node --input-type=module <<'JS'import { Window } from `${process.env.HAPPY_DOM}/lib/index.js`;import React from `${process.env.REACT_PATH}`;import { flushSync } from `${process.env.REACT_DOM_PATH}`;import { createRoot } from `${process.env.REACT_DOM_CLIENT_PATH}`;import { offset, useFloating } from `${process.env.FLOATING_UI_REACT}/dist/floating-ui.react.mjs`;const browserWindow = new Window();Object.assign(globalThis, { window: browserWindow, document: browserWindow.document, HTMLElement: browserWindow.HTMLElement, Element: browserWindow.Element, Node: browserWindow.Node, MutationObserver: browserWindow.MutationObserver, navigator: browserWindow.navigator, getComputedStyle: browserWindow.getComputedStyle.bind(browserWindow),});Object.defineProperty(document.documentElement, 'clientWidth', { configurable: true, value: 1024 });Object.defineProperty(document.documentElement, 'clientHeight', { configurable: true, value: 768 });function Probe() { const { refs, floatingStyles } = useFloating({ open: true, placement: 'bottom-start', middleware: [offset({ mainAxis: 4, alignmentAxis: 24 })], }); return React.createElement( React.Fragment, null, React.createElement('button', { ref: node => refs.setReference(node) }, 'Open'), React.createElement('div', { ref: node => refs.setFloating(node), 'data-testid': 'positioner', style: floatingStyles, }), );}const container = document.createElement('div');document.body.appendChild(container);flushSync(() => createRoot(container).render(React.createElement(Probe)));const positioner = container.querySelector('[data-testid="positioner"]');const print = label => console.log(label, { position: positioner.style.position, transform: positioner.style.transform, left: positioner.style.left, top: positioner.style.top,});print('immediate');await Promise.resolve();print('after-microtask');await new Promise(resolve => setTimeout(resolve, 0));print('after-timeout');JSRepository: clerk/javascript Length of output: 979 🏁 Script executed: set -eu
export FLOATING_UI_REACT=$(readlink -f node_modules/.pnpm/@floating-ui+react@*/node_modules/@floating-ui/react 2>/dev/null | head -1)export HAPPY_DOM=$(readlink -f node_modules/.pnpm/happy-dom@*/node_modules/happy-dom 2>/dev/null | head -1)export REACT_PATH=$(readlink -f node_modules/react/index.js)export REACT_DOM_PATH=$(readlink -f node_modules/react-dom/index.js)export REACT_DOM_CLIENT_PATH=$(readlink -f node_modules/react-dom/client.js)
node --input-type=module <<'JS'const { Window } = await import(`${process.env.HAPPY_DOM}/lib/index.js`);const React = (await import(process.env.REACT_PATH)).default;const { flushSync } = await import(process.env.REACT_DOM_PATH);const { createRoot } = await import(process.env.REACT_DOM_CLIENT_PATH);const { offset, useFloating } = await import(`${process.env.FLOATING_UI_REACT}/dist/floating-ui.react.mjs`);const browserWindow = new Window();Object.assign(globalThis, { window: browserWindow, document: browserWindow.document, HTMLElement: browserWindow.HTMLElement, Element: browserWindow.Element, Node: browserWindow.Node, MutationObserver: browserWindow.MutationObserver, navigator: browserWindow.navigator, getComputedStyle: browserWindow.getComputedStyle.bind(browserWindow),});Object.defineProperty(document.documentElement, 'clientWidth', { configurable: true, value: 1024 });Object.defineProperty(document.documentElement, 'clientHeight', { configurable: true, value: 768 });function Probe() { const { refs, floatingStyles } = useFloating({ open: true, placement: 'bottom-start', middleware: [offset({ mainAxis: 4, alignmentAxis: 24 })], }); return React.createElement( React.Fragment, null, React.createElement('button', { ref: node => refs.setReference(node) }, 'Open'), React.createElement('div', { ref: node => refs.setFloating(node), 'data-testid': 'positioner', style: floatingStyles, }), );}const container = document.createElement('div');document.body.appendChild(container);flushSync(() => createRoot(container).render(React.createElement(Probe)));const positioner = container.querySelector('[data-testid="positioner"]');const print = label => console.log(label, { position: positioner.style.position, transform: positioner.style.transform, left: positioner.style.left, top: positioner.style.top,});print('immediate');await Promise.resolve();print('after-microtask');await new Promise(resolve => setTimeout(resolve, 0));print('after-timeout');JSRepository: clerk/javascript Length of output: 650 🏁 Script executed: set -eu
export FLOATING_UI_REACT=$(readlink -f node_modules/.pnpm/@floating-ui+react@*/node_modules/@floating-ui/react 2>/dev/null | head -1)export HAPPY_DOM=$(readlink -f node_modules/.pnpm/happy-dom@*/node_modules/happy-dom 2>/dev/null | head -1)export REACT_PATH=$(readlink -f node_modules/react/index.js)export REACT_DOM_PATH=$(readlink -f node_modules/react-dom/index.js)export REACT_DOM_CLIENT_PATH=$(readlink -f node_modules/react-dom/client.js)
node --input-type=module <<'JS'const { Window } = await import(`${process.env.HAPPY_DOM}/lib/index.js`);const React = (await import(process.env.REACT_PATH)).default;const { flushSync } = await import(process.env.REACT_DOM_PATH);const { createRoot } = await import(process.env.REACT_DOM_CLIENT_PATH);const { offset, useFloating } = await import(`${process.env.FLOATING_UI_REACT}/dist/floating-ui.react.mjs`);const browserWindow = new Window();for (const [key, value] of [ ['window', browserWindow], ['document', browserWindow.document], ['HTMLElement', browserWindow.HTMLElement], ['Element', browserWindow.Element], ['Node', browserWindow.Node], ['MutationObserver', browserWindow.MutationObserver], ['navigator', browserWindow.navigator], ['getComputedStyle', browserWindow.getComputedStyle.bind(browserWindow)],]) { Object.defineProperty(globalThis, key, { configurable: true, writable: true, value });}Object.defineProperty(document.documentElement, 'clientWidth', { configurable: true, value: 1024 });Object.defineProperty(document.documentElement, 'clientHeight', { configurable: true, value: 768 });function Probe() { const { refs, floatingStyles } = useFloating({ open: true, placement: 'bottom-start', middleware: [offset({ mainAxis: 4, alignmentAxis: 24 })], }); return React.createElement( React.Fragment, null, React.createElement('button', { ref: node => refs.setReference(node) }, 'Open'), React.createElement('div', { ref: node => refs.setFloating(node), 'data-testid': 'positioner', style: floatingStyles, }), );}const container = document.createElement('div');document.body.appendChild(container);flushSync(() => createRoot(container).render(React.createElement(Probe)));const positioner = container.querySelector('[data-testid="positioner"]');const print = label => console.log(label, { position: positioner.style.position, transform: positioner.style.transform, left: positioner.style.left, top: positioner.style.top,});print('immediate');await Promise.resolve();print('after-microtask');await new Promise(resolve => setTimeout(resolve, 0));print('after-timeout');JSRepository: clerk/javascript Length of output: 633 🏁 Script executed: set -eu
FLOATING_UI_REACT_DOM=$(readlink -f node_modules/.pnpm/@floating-ui+react-dom@*/node_modules/@floating-ui/react-dom 2>/dev/null | head -1)printf'%s\n''--- React DOM positioning implementation ---'
rg -n -C 12 'function useFloating|floatingStyles|isPositioned|computePosition'"$FLOATING_UI_REACT_DOM/dist"| head -360Repository: clerk/javascript Length of output: 199 🏁 Script executed: set -eu
FLOATING_UI_REACT_DOM=$(readlink -f node_modules/.pnpm/@floating-ui+react-dom@*/node_modules/@floating-ui/react-dom 2>/dev/null | head -1)
find "$FLOATING_UI_REACT_DOM" -maxdepth 2 -type f | head -80Repository: clerk/javascript Length of output: 1710 🏁 Script executed: set -eu
FLOATING_UI_REACT_DOM=$(readlink -f node_modules/.pnpm/@floating-ui+react-dom@*/node_modules/@floating-ui/react-dom 2>/dev/null | head -1)
rg -n -F -C 10 'useFloating'"$FLOATING_UI_REACT_DOM/dist/floating-ui.react-dom.mjs"| head -300
rg -n -F -C 10 'floatingStyles'"$FLOATING_UI_REACT_DOM/dist/floating-ui.react-dom.mjs"| head -300Repository: clerk/javascript Length of output: 2370 🏁 Script executed: set -eu
FLOATING_UI_REACT_DOM=$(readlink -f node_modules/.pnpm/@floating-ui+react-dom@*/node_modules/@floating-ui/react-dom 2>/dev/null | head -1)
sed -n '83,238p'"$FLOATING_UI_REACT_DOM/dist/floating-ui.react-dom.mjs"Repository: clerk/javascript Length of output: 4804 🏁 Script executed: set -eu
printf'%s\n''--- positioner style composition ---'
sed -n '1,110p' packages/headless/src/primitives/popover/popover-positioner.tsx
printf'%s\n''--- relevant test lines ---'
sed -n '240,280p' packages/headless/src/primitives/popover/popover.test.tsx
printf'%s\n''--- read-only timing invariant check ---'
python3 - <<'PY'from pathlib import Pathsource = next(Path('node_modules/.pnpm').glob('`@floating-ui`+react-dom@*/node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.mjs')).read_text()checks = { 'initial styles omit transform before floating element exists': "if (!elements.floating) {\n return initialStyles;" in source, 'initial styles include absolute position': "position: strategy" in source, 'positioning update resolves asynchronously': "computePosition(referenceRef.current, floatingRef.current, config).then(data =>" in source, 'resolved coordinates become transform': 'transform: "translate(" + x + "px, " + y + "px)"' in source,}for name, result in checks.items(): print(f'{result}: {name}')if not all(checks.values()): raise SystemExit(1)PYRepository: clerk/javascript Length of output: 4249 Wait for the expected transform before reading it.
🤖 Prompt for AI Agents | ||
| } | ||
| it('nudges along the alignment axis by alignOffset', async () => { | ||
| const transform = await transformWithViewport({ placement: 'bottom-start', alignOffset: 24 }); | ||
| expect(transform).toBe('translate(24px, 4px)'); | ||
| }); | ||
| it('keeps alignOffset off the side axis, which stays sideOffset', async () => { | ||
| const transform = await transformWithViewport({ placement: 'bottom-start', alignOffset: 24, sideOffset: 10 }); | ||
| expect(transform).toBe('translate(24px, 10px)'); | ||
| }); | ||
| }); | ||
| describe('focus management', () => { | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,119 @@ | ||
| import { act, cleanup, render, screen } from '@testing-library/react'; | ||
| import * as React from 'react'; | ||
| import { afterEach, describe, expect, it } from 'vitest'; | ||
| import { Freeze } from './freeze'; | ||
| afterEach(() => { | ||
| cleanup(); | ||
| }); | ||
| describe('Freeze', () => { | ||
| it('renders children while not frozen', () => { | ||
| render(<Freeze frozen={false}>Acme</Freeze>); | ||
| expect(screen.getByText('Acme')).toBeInTheDocument(); | ||
| }); | ||
| it('holds the committed DOM when children change while frozen', () => { | ||
| const { rerender } = render(<Freeze frozen={false}>Acme</Freeze>); | ||
| rerender(<Freeze frozen>Globex</Freeze>); | ||
| expect(screen.getByText('Acme')).toBeInTheDocument(); | ||
| expect(screen.queryByText('Globex')).toBeNull(); | ||
| }); | ||
| it('keeps the held DOM visible', () => { | ||
| const { rerender } = render(<Freeze frozen={false}>Acme</Freeze>); | ||
| rerender(<Freeze frozen>Globex</Freeze>); | ||
| expect(screen.getByText('Acme')).toBeVisible(); | ||
| }); | ||
| it('keeps the held DOM visible across further updates while frozen', () => { | ||
| const { rerender } = render(<Freeze frozen={false}>Acme</Freeze>); | ||
| rerender(<Freeze frozen>Globex</Freeze>); | ||
| rerender(<Freeze frozen>Initech</Freeze>); | ||
| expect(screen.getByText('Acme')).toBeVisible(); | ||
| }); | ||
| it('commits the pending children once unfrozen', () => { | ||
| const { rerender } = render(<Freeze frozen={false}>Acme</Freeze>); | ||
| rerender(<Freeze frozen>Globex</Freeze>); | ||
| rerender(<Freeze frozen={false}>Globex</Freeze>); | ||
| expect(screen.getByText('Globex')).toBeInTheDocument(); | ||
| expect(screen.queryByText('Acme')).toBeNull(); | ||
| }); | ||
| it('holds a state update raised by the subtree itself', () => { | ||
| let bump = () => {}; | ||
| function Counter() { | ||
| const [count, setCount] = React.useState(0); | ||
| bump = () => setCount(n => n + 1); | ||
| return <span>count: {count}</span>; | ||
| } | ||
| const { rerender } = render( | ||
| <Freeze frozen={false}> | ||
| <Counter /> | ||
| </Freeze>, | ||
| ); | ||
| rerender( | ||
| <Freeze frozen> | ||
| <Counter /> | ||
| </Freeze>, | ||
| ); | ||
| act(() => bump()); | ||
| expect(screen.getByText('count: 0')).toBeInTheDocument(); | ||
| rerender( | ||
| <Freeze frozen={false}> | ||
| <Counter /> | ||
| </Freeze>, | ||
| ); | ||
| expect(screen.getByText('count: 1')).toBeInTheDocument(); | ||
| }); | ||
coderabbitai[bot] marked this conversation as resolved.
Uh oh!There was an error while loading. Please reload this page. | ||
| it('holds a context change read from inside the frozen subtree', () => { | ||
| const NameContext = React.createContext('Acme'); | ||
| function Reader() { | ||
| return <span>{React.useContext(NameContext)}</span>; | ||
| } | ||
| const { rerender } = render( | ||
| <NameContext.Provider value='Acme'> | ||
| <Freeze frozen={false}> | ||
| <Reader /> | ||
| </Freeze> | ||
| </NameContext.Provider>, | ||
| ); | ||
| rerender( | ||
| <NameContext.Provider value='Globex'> | ||
| <Freeze frozen> | ||
| <Reader /> | ||
| </Freeze> | ||
| </NameContext.Provider>, | ||
| ); | ||
| expect(screen.getByText('Acme')).toBeInTheDocument(); | ||
| rerender( | ||
| <NameContext.Provider value='Globex'> | ||
| <Freeze frozen={false}> | ||
| <Reader /> | ||
| </Freeze> | ||
| </NameContext.Provider>, | ||
| ); | ||
| expect(screen.getByText('Globex')).toBeInTheDocument(); | ||
| }); | ||
| }); | ||
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.