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]>
156 lines
5.5 KiB
TypeScript
156 lines
5.5 KiB
TypeScript
import { by, device, element, expect, waitFor } from 'detox'
|
|
import { safeLaunchApp, withSync } from './utils/detox'
|
|
import { remountDirectUseCase } from './utils/navigation'
|
|
|
|
const isAndroid = () => device.getPlatform() === 'android'
|
|
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms))
|
|
|
|
describe('SheetKeyboardFitContent', () => {
|
|
beforeAll(async () => {
|
|
if (isAndroid()) return
|
|
await safeLaunchApp({
|
|
newInstance: true,
|
|
launchArgs: { directUseCase: 'SheetKeyboardFitContentCase' },
|
|
})
|
|
await waitForSheetKeyboardFitContent()
|
|
})
|
|
|
|
// remount per test (launch once, deep-link remount) so a failed attempt that
|
|
// leaves the sheet open can't occlude the trigger on the jest retry - without
|
|
// this, any real failure cascades into a misleading "trigger not hittable".
|
|
beforeEach(async () => {
|
|
if (isAndroid()) return
|
|
await remountDirectUseCase('sheet-kb-fit-screen')
|
|
await device.disableSynchronization()
|
|
})
|
|
|
|
it('keeps fit Sheet.ScrollView stable through keyboard open, close, and scroll', async () => {
|
|
if (isAndroid()) return
|
|
|
|
await withSync(() => element(by.id('sheet-kb-fit-trigger')).tap())
|
|
await waitFor(element(by.id('sheet-kb-fit-frame')))
|
|
.toBeVisible()
|
|
.withTimeout(5000)
|
|
await sleep(500)
|
|
await expect(element(by.id('sheet-kb-fit-scroll-y'))).toHaveText('Scroll Y: 0')
|
|
await expect(element(by.id('sheet-kb-fit-max-scroll-y'))).toHaveText(
|
|
'Max scroll Y: 0'
|
|
)
|
|
|
|
await withSync(() => element(by.id('sheet-kb-fit-load-tall')).tap())
|
|
await waitFor(element(by.id('sheet-kb-fit-image')))
|
|
.toBeVisible()
|
|
.withTimeout(3000)
|
|
await sleep(300)
|
|
assertAtMost(await readScrollY(), 2, 'dynamic content current scroll')
|
|
assertAtMost(await readMaxScrollY(), 2, 'dynamic content max scroll')
|
|
const loadedViewportHeight = await readFrameHeight('sheet-kb-fit-scrollview')
|
|
|
|
await withSync(() => element(by.id('sheet-kb-fit-input')).tap())
|
|
await sleep(700)
|
|
assertAtMost(await readScrollY(), 2, 'keyboard open current scroll')
|
|
assertAtMost(await readMaxScrollY(), 2, 'keyboard open max scroll')
|
|
assertNear(
|
|
await readFrameHeight('sheet-kb-fit-scrollview'),
|
|
loadedViewportHeight,
|
|
2,
|
|
'keyboard open viewport height'
|
|
)
|
|
|
|
await withSync(() => element(by.id('sheet-kb-fit-input')).tapReturnKey())
|
|
await sleep(700)
|
|
assertAtMost(await readScrollY(), 2, 'keyboard close current scroll')
|
|
assertAtMost(await readMaxScrollY(), 2, 'keyboard close max scroll')
|
|
assertNear(
|
|
await readFrameHeight('sheet-kb-fit-scrollview'),
|
|
loadedViewportHeight,
|
|
2,
|
|
'keyboard close viewport height'
|
|
)
|
|
|
|
await withSync(() => element(by.id('sheet-kb-fit-input')).tap())
|
|
await sleep(700)
|
|
assertAtMost(await readScrollY(), 2, 'keyboard reopen current scroll')
|
|
assertAtMost(await readMaxScrollY(), 2, 'keyboard reopen max scroll')
|
|
assertNear(
|
|
await readFrameHeight('sheet-kb-fit-scrollview'),
|
|
loadedViewportHeight,
|
|
2,
|
|
'keyboard reopen viewport height'
|
|
)
|
|
|
|
await element(by.id('sheet-kb-fit-image')).swipe('up', 'slow', 0.7)
|
|
await waitFor(element(by.id('sheet-kb-fit-post')))
|
|
.toBeVisible()
|
|
.withTimeout(3000)
|
|
assertAtLeast(await readScrollY(), 20, 'scroll after swipe up')
|
|
|
|
await withSync(() => element(by.id('sheet-kb-fit-post')).tap())
|
|
const eventsText = await readText('sheet-kb-fit-events')
|
|
if (!eventsText.includes('post')) {
|
|
throw new Error(`expected post event, got "${eventsText}"`)
|
|
}
|
|
|
|
await element(by.id('sheet-kb-fit-post')).swipe('down', 'slow', 0.7)
|
|
await sleep(700)
|
|
assertAtMost(await readScrollY(), 5, 'scroll after swipe back to top')
|
|
})
|
|
})
|
|
|
|
async function waitForSheetKeyboardFitContent() {
|
|
await waitFor(element(by.id('sheet-kb-fit-trigger')))
|
|
.toExist()
|
|
.withTimeout(180000)
|
|
}
|
|
|
|
async function readText(id: string) {
|
|
const attributes = await element(by.id(id)).getAttributes()
|
|
const text = (attributes as any).text
|
|
if (typeof text !== 'string') {
|
|
throw new Error(`expected ${id} to expose text, got ${JSON.stringify(attributes)}`)
|
|
}
|
|
return text
|
|
}
|
|
|
|
async function readNumber(id: string, prefix: string) {
|
|
const text = await readText(id)
|
|
const value = Number.parseInt(text.replace(prefix, ''), 10)
|
|
if (Number.isNaN(value)) {
|
|
throw new Error(`expected ${id} to start with "${prefix}", got "${text}"`)
|
|
}
|
|
return value
|
|
}
|
|
|
|
const readScrollY = () => readNumber('sheet-kb-fit-scroll-y', 'Scroll Y: ')
|
|
const readMaxScrollY = () => readNumber('sheet-kb-fit-max-scroll-y', 'Max scroll Y: ')
|
|
|
|
async function readFrameHeight(id: string) {
|
|
const attributes = await element(by.id(id)).getAttributes()
|
|
const frame = (attributes as any).frame || (attributes as any).elementFrame
|
|
const height = frame?.height
|
|
if (typeof height !== 'number') {
|
|
throw new Error(
|
|
`expected ${id} to expose frame height, got ${JSON.stringify(attributes)}`
|
|
)
|
|
}
|
|
return Math.round(height)
|
|
}
|
|
|
|
function assertAtMost(value: number, max: number, label: string) {
|
|
if (value > max) {
|
|
throw new Error(`${label}: expected <= ${max}, got ${value}`)
|
|
}
|
|
}
|
|
|
|
function assertAtLeast(value: number, min: number, label: string) {
|
|
if (value < min) {
|
|
throw new Error(`${label}: expected >= ${min}, got ${value}`)
|
|
}
|
|
}
|
|
|
|
function assertNear(value: number, expected: number, tolerance: number, label: string) {
|
|
if (Math.abs(value - expected) > tolerance) {
|
|
throw new Error(`${label}: expected ${expected} +/- ${tolerance}, got ${value}`)
|
|
}
|
|
}
|