mirror of
https://github.com/hanzoai/gui.git
synced 2026-10-25 02:31:41 +00:00
This repo could never sync. There was NO merge-base with tamagui — 372 commits
here against 15,791 there, unrelated histories — because it began as a
copy-paste rebrand with a flattened layout. Every "sync" since was a hand
re-application that drifted, which is why the tree sat on 7.3.1 while upstream
shipped fixes we then rediscovered by hand.
This commit's PARENT IS TAMAGUI. A future sync is `git merge tama/main` then
`node scripts/rebrand.mjs` — a real merge, because the history is now shared.
174 packages: 166 upstream, our 8 restored (chrome, shell, telemetry, recipes,
get, font-geist-{sans,mono}, @hanzo/booker), bento-get gained. 170/170 build.
Our patches to files UPSTREAM ALSO OWNS are carried, which the package-level
delta missed and which was where the value actually was:
- useMedia's server snapshot (`initState ?? getMedia()`). Upstream still
returns undefined there, so every Tamagui Dialog kills a prerender — the
exact crash hanzo.app spent a day on.
- data-slot on web host nodes, absent upstream entirely.
Both applied ONTO upstream's newer files (it rewrote useMedia +150/-63 and
createComponent +160/-55), marked `PATCH (ours, carried across the transplant)`.
Seven traps are encoded in scripts/rebrand.mjs, each found by a gate:
skip itself and lockfiles; special-case the double-branded names in BOTH scoped
and bare form; map path strings, not just paths; drop a level for the flattened
group; check disk rather than a stale index.
Rust build artifacts are NOT committed. An earlier draft of this commit swept
apps/desktop/src-tauri/target/debug in via `git add -A` — 3,859 files including
a 272MB .a, over GitHub's 100MB cap, so the push could never have landed. They
are gitignored now.
Co-Authored-By: Hanzo Dev <[email protected]>
139 lines
5.2 KiB
TypeScript
139 lines
5.2 KiB
TypeScript
import { expect, test } from '@playwright/test'
|
|
import { setupPage } from './test-utils'
|
|
|
|
/**
|
|
* Repro for: "Popover sheet body unmounts before exit animation completes"
|
|
*
|
|
* Bug context (sibling of DialogSheetAdaptUnmount):
|
|
* When a Gui Popover adapts to a Sheet (Adapt + Sheet + Adapt.Contents),
|
|
* the children mounted into Adapt.Contents are gated by PopoverContent's
|
|
* unmount logic, which is driven by the popup exit animation (isFullyHidden),
|
|
* NOT by Sheet.open. On close the popover tears its tree down on the first
|
|
* frame while the Sheet is still sliding out, so the sheet body vanishes
|
|
* mid-slide (no exit animation, it just disappears).
|
|
*
|
|
* What this test asserts:
|
|
* 1. Open the popover at a narrow viewport so Adapt when="maxMd" activates and
|
|
* swaps in the Sheet.
|
|
* 2. The marker text inside the body must be in the DOM (it lives in the sheet).
|
|
* 3. After closing, sample the marker for several frames during the slide-out.
|
|
* The marker must remain in the DOM for the duration, NOT vanish on the
|
|
* first frame.
|
|
* 4. After the animation finishes, the marker should unmount (no leak).
|
|
*/
|
|
test.describe('Popover Sheet Adapt - body persists during exit animation', () => {
|
|
// narrow viewport so `when="maxMd"` triggers the adapted code path
|
|
test.use({ viewport: { width: 600, height: 900 } })
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
await setupPage(page, {
|
|
name: 'PopoverAdaptSheetUnmountCase',
|
|
type: 'useCase',
|
|
})
|
|
})
|
|
|
|
test('marker stays mounted while the sheet slides out', async ({ page }) => {
|
|
const marker = page.getByTestId('popover-content-marker')
|
|
|
|
// open the popover (which adapts to a sheet on this viewport)
|
|
await page.getByTestId('open-popover').click()
|
|
|
|
// wait for the sheet to be open and the marker to exist in the DOM
|
|
await expect
|
|
.poll(
|
|
async () =>
|
|
page.evaluate(() =>
|
|
document.querySelector('.is_Sheet[data-state]')?.getAttribute('data-state')
|
|
),
|
|
{ timeout: 5000 }
|
|
)
|
|
.toBe('open')
|
|
|
|
await expect(marker).toBeAttached({ timeout: 5000 })
|
|
|
|
// drive the close imperatively (overlay covers the viewport; Pressable
|
|
// ignores synthetic clicks). __popoverSetOpen is exposed by the use case.
|
|
// sanity: the close call must actually flip the sheet's data-state, else
|
|
// the persistence assertions below are meaningless. start the sampler and
|
|
// close in the same in-page task so playwright round-trip latency cannot
|
|
// shift the samples past the short exit animation under parallel load.
|
|
type Sample = { t: number; exists: boolean; state: string | null }
|
|
const samples: Sample[] = await page.evaluate(
|
|
() =>
|
|
new Promise<Sample[]>((resolve) => {
|
|
const startedAt = performance.now()
|
|
const samples: Sample[] = []
|
|
|
|
const record = () => {
|
|
samples.push({
|
|
t: performance.now() - startedAt,
|
|
exists: !!document.querySelector('[data-testid="popover-content-marker"]'),
|
|
state:
|
|
document
|
|
.querySelector('.is_Sheet[data-state]')
|
|
?.getAttribute('data-state') ?? null,
|
|
})
|
|
}
|
|
|
|
;(window as any).__popoverSetOpen?.(false)
|
|
record()
|
|
|
|
const sample = () => {
|
|
record()
|
|
|
|
if (performance.now() - startedAt >= 500) {
|
|
resolve(samples)
|
|
return
|
|
}
|
|
|
|
requestAnimationFrame(sample)
|
|
}
|
|
|
|
requestAnimationFrame(sample)
|
|
})
|
|
)
|
|
|
|
const firstClosedAt = samples.find((s) => s.state === 'closed')?.t
|
|
expect(
|
|
firstClosedAt,
|
|
'close call should flip the sheet data-state while sampling'
|
|
).toBeDefined()
|
|
|
|
// `medium` css driver is ~400ms, so anything <250ms after the browser
|
|
// observes the closed state is solidly mid-slide. assert only that early
|
|
// window so we don't race the legitimate post-animation cleanup.
|
|
const midAnimationSamples = samples.filter(
|
|
(s) => firstClosedAt != null && s.t >= firstClosedAt && s.t - firstClosedAt <= 250
|
|
)
|
|
expect(midAnimationSamples.length).toBeGreaterThan(0)
|
|
|
|
// every early sample during the slide-out should still find the marker. if
|
|
// the bug reproduces, the Adapt.Contents slot is torn down on the first
|
|
// frame after popover.open flips false, because PopoverContent unmounts on
|
|
// its own (popup) exit, not on the sheet's slide-out.
|
|
for (const s of midAnimationSamples) {
|
|
expect
|
|
.soft(
|
|
s.exists,
|
|
`marker should still be in DOM at +${Math.round(
|
|
firstClosedAt == null ? s.t : s.t - firstClosedAt
|
|
)}ms after closed state (mid-animation, before sheet finishes sliding)`
|
|
)
|
|
.toBe(true)
|
|
}
|
|
|
|
// and after the slide-out completes, the marker SHOULD unmount, the other
|
|
// half of the fix (no permanent mount). SheetController.onAnimationComplete
|
|
// flips PopoverAdaptHiddenContext true, PopoverContent returns null.
|
|
await expect
|
|
.poll(
|
|
async () =>
|
|
page.evaluate(
|
|
() => !!document.querySelector('[data-testid="popover-content-marker"]')
|
|
),
|
|
{ timeout: 3000 }
|
|
)
|
|
.toBe(false)
|
|
})
|
|
})
|