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]>
196 lines
6.6 KiB
TypeScript
196 lines
6.6 KiB
TypeScript
import { expect, test, type Page } from '@playwright/test'
|
|
import { setupPage } from './test-utils'
|
|
|
|
/**
|
|
* ANIMATION TIMING BUG FIX TESTS
|
|
*
|
|
* Tests for two bugs:
|
|
* 1. Enter/exit timing not respected - enter: '50ms' should actually be 50ms
|
|
* 2. Duration normalization - duration: 1 should be 1ms, not 1 second
|
|
*
|
|
* These tests run across all animation drivers (css, native, reanimated, motion).
|
|
*/
|
|
|
|
async function getOpacity(page: Page, testId: string): Promise<number> {
|
|
return page.evaluate((id) => {
|
|
const el = document.querySelector(`[data-testid="${id}"]`)
|
|
if (!el) return -1
|
|
return Number.parseFloat(getComputedStyle(el).opacity)
|
|
}, testId)
|
|
}
|
|
|
|
async function elementExists(page: Page, testId: string): Promise<boolean> {
|
|
return page.evaluate((id) => !!document.querySelector(`[data-testid="${id}"]`), testId)
|
|
}
|
|
|
|
test.describe('Animation Timing Bug Fixes', () => {
|
|
// animation timing tests are flaky in CI due to timing variations - add retries
|
|
test.describe.configure({ retries: 3 })
|
|
|
|
test.beforeEach(async ({ page }) => {
|
|
const driver = (test.info().project?.metadata as any)?.animationDriver
|
|
test.skip(driver === 'native', 'native driver has element detection issues on web')
|
|
|
|
await setupPage(page, {
|
|
name: 'AnimationComprehensiveCase',
|
|
type: 'useCase',
|
|
})
|
|
// wait for initial render - CI can be slow, allow more time
|
|
await page.waitForTimeout(1500)
|
|
})
|
|
|
|
test('scenario 50: enter timing (200ms) should be faster than exit timing (1000ms)', async ({
|
|
page,
|
|
}) => {
|
|
// first hide element (exit with 1000ms)
|
|
expect(await elementExists(page, 'scenario-50-target'), 'Initially visible').toBe(
|
|
true
|
|
)
|
|
|
|
// trigger exit and measure how long it takes
|
|
const exitStart = Date.now()
|
|
await page.getByTestId('scenario-50-trigger').click()
|
|
|
|
// wait for exit to complete (element disappears)
|
|
await page.waitForFunction(
|
|
(testId) => !document.querySelector(`[data-testid="${testId}"]`),
|
|
'scenario-50-target',
|
|
{ timeout: 3000 }
|
|
)
|
|
const exitDuration = Date.now() - exitStart
|
|
|
|
// exit animation is 1000ms - should take at least 800ms (allowing some tolerance)
|
|
// this verifies it's using the slow exit timing, not fast enter timing
|
|
expect(
|
|
exitDuration,
|
|
`Exit should take ~1000ms (got ${exitDuration}ms)`
|
|
).toBeGreaterThan(700)
|
|
|
|
// trigger enter and measure how long it takes
|
|
const enterStart = Date.now()
|
|
await page.getByTestId('scenario-50-trigger').click()
|
|
|
|
// wait for element to appear and reach full opacity
|
|
await page.waitForFunction(
|
|
(testId) => {
|
|
const el = document.querySelector(`[data-testid="${testId}"]`)
|
|
if (!el) return false
|
|
const opacity = Number.parseFloat(getComputedStyle(el).opacity)
|
|
return opacity > 0.9
|
|
},
|
|
'scenario-50-target',
|
|
{ timeout: 2000 }
|
|
)
|
|
const enterDuration = Date.now() - enterStart
|
|
|
|
// enter animation is 200ms - should complete in under 500ms (with overhead)
|
|
// more importantly, it should be significantly faster than exit
|
|
expect(
|
|
enterDuration,
|
|
`Enter should complete quickly (~200ms), got ${enterDuration}ms`
|
|
).toBeLessThan(600)
|
|
|
|
// the key assertion: enter should be significantly faster than exit
|
|
expect(
|
|
enterDuration,
|
|
`Enter (${enterDuration}ms) should be faster than exit (${exitDuration}ms)`
|
|
).toBeLessThan(exitDuration * 0.8)
|
|
})
|
|
|
|
test('scenario 51: duration: 1 should be 1ms (instant), not 1 second', async ({
|
|
page,
|
|
}) => {
|
|
expect(await elementExists(page, 'scenario-51-target'), 'Initially visible').toBe(
|
|
true
|
|
)
|
|
|
|
// trigger exit with duration: 1 (should be 1ms = instant)
|
|
await page.getByTestId('scenario-51-trigger').click()
|
|
|
|
// if duration is correctly 1ms, element should be gone almost immediately
|
|
// wait just 50ms - if it's treating 1 as 1 second, element would still exist
|
|
await page.waitForTimeout(50)
|
|
|
|
// BUG CHECK: element should be gone by now if duration is 1ms
|
|
// if duration is being treated as 1 second, element would still be visible
|
|
const stillExists = await elementExists(page, 'scenario-51-target')
|
|
|
|
// if element still exists, check opacity - should be near 0 if 1ms animation
|
|
if (stillExists) {
|
|
const opacity = await getOpacity(page, 'scenario-51-target')
|
|
// if duration is 1ms, opacity should be at 0 (animation complete)
|
|
// if duration is 1s, opacity would be ~0.95 at 50ms
|
|
expect(
|
|
opacity,
|
|
`Opacity should be near 0 at 50ms if duration is 1ms (got ${opacity})`
|
|
).toBeLessThan(0.2)
|
|
}
|
|
|
|
// definitely should be gone by 200ms
|
|
await page.waitForTimeout(200)
|
|
expect(
|
|
await elementExists(page, 'scenario-51-target'),
|
|
'Should be gone by 200ms'
|
|
).toBe(false)
|
|
})
|
|
|
|
test('scenario 51: verify animation is fast (not 1 second)', async ({ page }) => {
|
|
expect(await elementExists(page, 'scenario-51-target'), 'Initially visible').toBe(
|
|
true
|
|
)
|
|
|
|
const startTime = Date.now()
|
|
|
|
// trigger exit
|
|
await page.getByTestId('scenario-51-trigger').click()
|
|
|
|
// wait for element to disappear
|
|
await page.waitForFunction(
|
|
(testId) => !document.querySelector(`[data-testid="${testId}"]`),
|
|
'scenario-51-target',
|
|
{ timeout: 3000 }
|
|
)
|
|
|
|
const exitDuration = Date.now() - startTime
|
|
|
|
// BUG CHECK: if duration is 1ms, exit should complete well under 1 second
|
|
// if duration is 1 second, it would take > 1000ms
|
|
// CI environments can have 500-800ms+ overhead, so use generous threshold
|
|
const threshold = process.env.CI ? 2000 : 1000
|
|
expect(
|
|
exitDuration,
|
|
`Exit with duration:1 should complete quickly, not take ${exitDuration}ms`
|
|
).toBeLessThan(threshold)
|
|
})
|
|
|
|
test('scenario 52: inline duration override should be in milliseconds', async ({
|
|
page,
|
|
}) => {
|
|
// base is 100ms, override is duration: 50 (should be 50ms)
|
|
expect(await elementExists(page, 'scenario-52-target'), 'Initially visible').toBe(
|
|
true
|
|
)
|
|
|
|
const startTime = Date.now()
|
|
|
|
// trigger exit
|
|
await page.getByTestId('scenario-52-trigger').click()
|
|
|
|
// wait for exit
|
|
await page.waitForFunction(
|
|
(testId) => !document.querySelector(`[data-testid="${testId}"]`),
|
|
'scenario-52-target',
|
|
{ timeout: 3000 }
|
|
)
|
|
|
|
const exitDuration = Date.now() - startTime
|
|
|
|
// animation is 50ms + overhead, CI can add 500-800ms+ overhead
|
|
const threshold = process.env.CI ? 2000 : 1000
|
|
expect(
|
|
exitDuration,
|
|
`Exit with duration:50 should complete in well under 1s (got ${exitDuration}ms)`
|
|
).toBeLessThan(threshold)
|
|
})
|
|
})
|