Files
gui/apps/kitchen-sink/e2e/SheetKeyboardDrag.test.ts
zeekayandHanzo Dev 69c6e860d7 transplant onto tamagui v2.6.0, brand as a re-runnable transform
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]>
2026-07-30 18:25:06 -07:00

242 lines
8.3 KiB
TypeScript

/**
* Detox E2E Test for Sheet + Keyboard interaction
*
* Tests smooth keyboard + sheet coordination:
* 1. Open keyboard (tap input) -> sheet moves up smoothly
* 2. Drag sheet down while keyboard open -> keyboard dismisses FIRST
* 3. Dismiss keyboard (tap outside) -> sheet returns to original position
* 4. Fast keyboard show/hide should be smooth
*
* When keyboard-controller is installed, these should be extra smooth (60/120 FPS).
* Without it, falls back to basic Keyboard API (still functional, less smooth).
*
* TODO: This test is WIP - keyboard-controller causes Detox sync issues.
* Need to investigate disableSynchronization() timing or alternative approach.
*/
import { by, device, element, expect, waitFor } from 'detox'
import { remountDirectUseCase } from './utils/navigation'
import { safeLaunchApp } from './utils/detox'
// only run on iOS - keyboard behavior differs on Android
const isAndroid = () => device.getPlatform() === 'android'
// All other tests boot with disableKeyboardController: true so the main thread
// stays idle (KeyboardProvider's continuous keyboard polling otherwise causes
// Detox launch/reload to hang). This file needs the keyboard so it opts in
// explicitly. Currently still skipped pending separate keyboard-sync work.
describe.skip('SheetKeyboardDrag - Keyboard + Sheet Integration', () => {
beforeAll(async () => {
if (isAndroid()) return
await safeLaunchApp({
newInstance: true,
launchArgs: {
directUseCase: 'SheetKeyboardDragCase',
disableKeyboardController: false,
},
})
await waitFor(element(by.id('sheet-keyboard-drag-trigger')))
.toExist()
.withTimeout(180000)
})
beforeEach(async () => {
if (isAndroid()) return
await remountDirectUseCase('sheet-keyboard-drag-trigger', { skipEnableSync: true })
})
it('should show status indicators', async () => {
if (isAndroid()) return
// RNGH should be enabled
await expect(element(by.id('sheet-keyboard-drag-rngh-status'))).toHaveText('RNGH: ✓')
// KBC status shows whether keyboard-controller is installed
// either ✓ (enabled) or ○ (not installed, using fallback)
})
it('Case 1: open sheet and verify initial state', async () => {
if (isAndroid()) return
await element(by.id('sheet-keyboard-drag-trigger')).tap()
await waitFor(element(by.id('sheet-keyboard-drag-frame')))
.toBeVisible()
.withTimeout(3000)
// wait for animation
await new Promise((r) => setTimeout(r, 400))
// should be at position 0, keyboard hidden
await expect(element(by.id('sheet-keyboard-drag-position'))).toHaveText(
'Sheet position: 0'
)
await expect(element(by.id('sheet-keyboard-drag-kb-visible'))).toHaveText(
'Keyboard: hidden'
)
})
it('Case 2: tap input shows keyboard, sheet moves up', async () => {
if (isAndroid()) return
// open sheet
await element(by.id('sheet-keyboard-drag-trigger')).tap()
await waitFor(element(by.id('sheet-keyboard-drag-frame')))
.toBeVisible()
.withTimeout(3000)
await new Promise((r) => setTimeout(r, 400))
await device.takeScreenshot('case2-before-keyboard')
// tap input to show keyboard
await element(by.id('sheet-keyboard-drag-input')).tap()
await new Promise((r) => setTimeout(r, 500)) // wait for keyboard animation
await device.takeScreenshot('case2-after-keyboard')
// keyboard should be visible
await expect(element(by.id('sheet-keyboard-drag-kb-visible'))).toHaveText(
'Keyboard: visible'
)
// keyboard height should be > 0
const kbHeightAttr = await element(
by.id('sheet-keyboard-drag-kb-height')
).getAttributes()
const kbHeightText = (kbHeightAttr as any).text as string
const kbHeight = parseInt(kbHeightText.replace('Keyboard height: ', ''), 10)
console.log('Case 2: keyboard height =', kbHeight)
if (kbHeight < 200) {
throw new Error(`Expected keyboard height > 200, got ${kbHeight}`)
}
})
it('Case 3: dismiss keyboard returns sheet to original position', async () => {
if (isAndroid()) return
// open sheet
await element(by.id('sheet-keyboard-drag-trigger')).tap()
await waitFor(element(by.id('sheet-keyboard-drag-frame')))
.toBeVisible()
.withTimeout(3000)
await new Promise((r) => setTimeout(r, 400))
// tap input to show keyboard
await element(by.id('sheet-keyboard-drag-input')).tap()
await new Promise((r) => setTimeout(r, 500))
await expect(element(by.id('sheet-keyboard-drag-kb-visible'))).toHaveText(
'Keyboard: visible'
)
await device.takeScreenshot('case3-keyboard-visible')
// dismiss keyboard via button
await element(by.id('sheet-keyboard-drag-dismiss-kb')).tap()
await new Promise((r) => setTimeout(r, 400))
await device.takeScreenshot('case3-keyboard-dismissed')
// keyboard should be hidden
await expect(element(by.id('sheet-keyboard-drag-kb-visible'))).toHaveText(
'Keyboard: hidden'
)
// sheet should still be at position 0 (restored)
await expect(element(by.id('sheet-keyboard-drag-position'))).toHaveText(
'Sheet position: 0'
)
})
it('Case 4: drag sheet down while keyboard open dismisses keyboard first', async () => {
if (isAndroid()) return
// open sheet
await element(by.id('sheet-keyboard-drag-trigger')).tap()
await waitFor(element(by.id('sheet-keyboard-drag-frame')))
.toBeVisible()
.withTimeout(3000)
await new Promise((r) => setTimeout(r, 400))
// tap input to show keyboard
await element(by.id('sheet-keyboard-drag-input')).tap()
await new Promise((r) => setTimeout(r, 500))
await expect(element(by.id('sheet-keyboard-drag-kb-visible'))).toHaveText(
'Keyboard: visible'
)
await device.takeScreenshot('case4-before-drag')
// drag sheet down on handle (should dismiss keyboard)
await element(by.id('sheet-keyboard-drag-handle')).swipe('down', 'slow', 0.3)
await new Promise((r) => setTimeout(r, 600))
await device.takeScreenshot('case4-after-drag')
// keyboard should be dismissed
await expect(element(by.id('sheet-keyboard-drag-kb-visible'))).toHaveText(
'Keyboard: hidden'
)
// check events - should see keyboard dismiss
const eventsAttr = await element(by.id('sheet-keyboard-drag-events')).getAttributes()
console.log('Case 4 events:', (eventsAttr as any).text)
})
it('Case 5: switch between inputs keeps keyboard visible', async () => {
if (isAndroid()) return
// open sheet
await element(by.id('sheet-keyboard-drag-trigger')).tap()
await waitFor(element(by.id('sheet-keyboard-drag-frame')))
.toBeVisible()
.withTimeout(3000)
await new Promise((r) => setTimeout(r, 400))
// tap first input
await element(by.id('sheet-keyboard-drag-input')).tap()
await new Promise((r) => setTimeout(r, 400))
await expect(element(by.id('sheet-keyboard-drag-kb-visible'))).toHaveText(
'Keyboard: visible'
)
// tap second input
await element(by.id('sheet-keyboard-drag-input-2')).tap()
await new Promise((r) => setTimeout(r, 300))
// keyboard should still be visible
await expect(element(by.id('sheet-keyboard-drag-kb-visible'))).toHaveText(
'Keyboard: visible'
)
await device.takeScreenshot('case5-switched-inputs')
})
it('Case 6: close sheet dismisses keyboard', async () => {
if (isAndroid()) return
// open sheet
await element(by.id('sheet-keyboard-drag-trigger')).tap()
await waitFor(element(by.id('sheet-keyboard-drag-frame')))
.toBeVisible()
.withTimeout(5000)
await new Promise((r) => setTimeout(r, 600))
// tap input to show keyboard
await element(by.id('sheet-keyboard-drag-input')).tap()
await new Promise((r) => setTimeout(r, 500))
await expect(element(by.id('sheet-keyboard-drag-kb-visible'))).toHaveText(
'Keyboard: visible'
)
// close sheet
await element(by.id('sheet-keyboard-drag-close')).tap()
await new Promise((r) => setTimeout(r, 500))
// sheet should be closed
await waitFor(element(by.id('sheet-keyboard-drag-frame')))
.not.toBeVisible()
.withTimeout(2000)
// keyboard should be hidden (check outside sheet)
await expect(element(by.id('sheet-keyboard-drag-kb-visible'))).toHaveText(
'Keyboard: hidden'
)
})
})