Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
e87dc6b
Create iOS browserstack test
ethan-james Jun 17, 2026
eccf583
Check activeElemnt in browser.execute
ethan-james Jun 19, 2026
83c35d5
Fix TransitionStyle error caused by radix-ui version bump
ethan-james Jun 18, 2026
eeb8621
Merge branch 'main' into 4394-bug-prevent-focus-on-non-cursor-thoughts
ethan-james Jun 19, 2026
6225800
Move tap position right
ethan-james Jun 19, 2026
560a62d
Increase lint:src old space size
ethan-james Jun 19, 2026
ddc823a
Nudge tap position right
ethan-james Jun 19, 2026
74d3dd0
Merge branch 'main' into 4394-bug-prevent-focus-on-non-cursor-thoughts
ethan-james Jun 19, 2026
465a826
Merge branch 'main' into 4394-bug-prevent-focus-on-non-cursor-thoughts
ethan-james Jun 22, 2026
d2a202c
Profile events in test
ethan-james Jun 23, 2026
363ac00
Add more logging to browserstack test
ethan-james Jun 23, 2026
866f831
Nudge tap position down
ethan-james Jun 23, 2026
721993f
Remove redundant event profiling
ethan-james Jun 23, 2026
25c17d8
Try to match touch event position in browserstack test
ethan-james Jun 23, 2026
dee9340
Tweak touch event position
ethan-james Jun 23, 2026
48794ae
Use gesture in test to trigger compatability events
ethan-james Jun 24, 2026
8050965
Nudge y position of gesture
ethan-james Jun 24, 2026
b24f377
Fix y position of gesture
ethan-james Jun 29, 2026
a2f9c78
Add pointerType option to tap helper
ethan-james Jun 29, 2026
0573ddc
Nudge x position of tap
ethan-james Jun 29, 2026
a982645
Add y:60 to tap call in new test
ethan-james Jun 29, 2026
e70fec8
Merge branch 'main' into 4394-bug-prevent-focus-on-non-cursor-thoughts
ethan-james Jun 29, 2026
ee3c627
Log document.activeElement
ethan-james Jun 30, 2026
022a221
Log document.activeElement.nodeType
ethan-james Jun 30, 2026
b045905
Wait until document.activeElement === document.body
ethan-james Jun 30, 2026
7b40830
Log more information about document.activeElement
ethan-james Jun 30, 2026
40fb43e
Nudge tap x position to the right
ethan-james Jun 30, 2026
9f68529
Nudge tap x position to the right
ethan-james Jun 30, 2026
9cac4b1
Nudge tap x position to the right
ethan-james Jun 30, 2026
4cc3ba5
Use x: 20 adjustment
ethan-james Jun 30, 2026
2605185
fix(e2e): clear cursor with native click and tap editable at x: 6
ethan-james Jul 3, 2026
6bb3987
test(e2e): faithful #4394 iOS caret focus regression test
ethan-james Jul 6, 2026
525b4bb
fix(e2e): extract BrowserStack capabilities to consts to satisfy tsc
ethan-james Jul 6, 2026
55324a4
fix(e2e): clear cursor with native click and tap editable at x: 6
ethan-james Jul 3, 2026
ee5ad61
Merge branch '4394-bug-prevent-focus-on-non-cursor-thoughts' of githu…
ethan-james Jul 7, 2026
f1926de
Merge branch 'main' into 4394-bug-prevent-focus-on-non-cursor-thoughts
ethan-james Jul 7, 2026
6ad5869
Merge branch 'main' into 4394-bug-prevent-focus-on-non-cursor-thoughts
ethan-james Jul 7, 2026
dd4ca37
test(ios): port latest #4394 caret focus regression test
ethan-james Jul 8, 2026
8d1d2f9
test(ios): run #4394 caret focus spec in the default iOS suite
ethan-james Jul 8, 2026
7e01d87
test(ios): move #4394 caret focus regression into caret.ts
ethan-james Jul 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions src/e2e/iOS/__tests__/caret.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,104 @@ describe('Caret', () => {
expect(selectionTextContent).toBe('new')
expect(childrenTexts).toEqual(['foo', 'bar'])
})

/**
* Regression test for #4394. Tapping ~4px past the right edge of a non-cursor thought must not open the
* virtual keyboard. Safari's touch-adjustment heuristic retargets the synthesized mouse cascade onto the
* nearby editable while the `touchstart`/`touchend` land on the thought-annotation overlay, so the
* editable's `onTouchEnd` never runs to `preventDefault`. Before the fix, the retargeted `mouseup` ran
* `onMouseUp` with a stale, never-reset `offsetRef.current`, calling `setCaretOffset` -> `selection.set`,
* which programmatically focused the editable and opened the keyboard even though `onMouseDown` had
* preventDefaulted the native focus. This branch prevents focus on non-cursor thoughts, so the keyboard
* stays down.
*/
it('Focus is prevented after clearing the cursor', async () => {
await newThought('Hello')

const editable = await waitForEditable('Hello')
await hideKeyboardByTappingDone()
await browser.pause(800)
await browser.execute(() => window.scrollTo(0, 0))
await browser.pause(400)

const rect = await getElementRectByScreen(editable)

// Prime with a real native tap on the thought's center + keyboard dismissal. This is required for
// the synthetic edge-touch below to reliably win the timing race that a real finger triggers on
// its own (see comment on the tap). A native tap (not a webview element.click()) is used because
// on real devices only a native touch focuses the editable and opens the keyboard.
const webviewContext = (await browser.getContext()) as string
await browser.switchContext('NATIVE_APP')
await browser.performActions([
{
type: 'pointer',
id: 'finger1',
parameters: { pointerType: 'touch' },
actions: [
{
type: 'pointerMove',
duration: 0,
x: Math.round(rect.x + rect.width / 2),
y: Math.round(rect.y + rect.height / 2),
origin: 'viewport',
},
{ type: 'pointerDown', button: 0 },
{ type: 'pause', duration: 60 },
{ type: 'pointerUp', button: 0 },
],
},
])
await browser.switchContext(webviewContext)
await browser.pause(600)
await hideKeyboardByTappingDone()
await browser.pause(400)

// Cursor Back (swipe right) to set the cursor to null, so that "Hello" becomes a non-cursor thought.
await gesture('r', {
xStart: rect.x + 5,
yStart: rect.y + rect.height / 2,
segmentLength: rect.width,
})
await waitUntil(async () => !(await getEditingText()))

// Tap just past the right edge of the thought text, vertically centered, using a finger-sized
// contact area (width/height/pressure). Safari's touch-adjustment heuristic uses the contact radius
// to retarget the synthesized mouse events onto the nearby editable, but the `touchstart`/`touchend`
// land on the thought-annotation overlay - so the editable's `onTouchEnd` never runs to
// `preventDefault`. Without the fix the retargeted `mouseup` would focus the editable via a stale
// offset and open the keyboard; this branch prevents focus on non-cursor thoughts. #4394.
const tapX = Math.round(rect.x + rect.width + 4)
const tapY = Math.round(rect.y + rect.height / 2)
await browser.switchContext('NATIVE_APP')
await browser.performActions([
{
type: 'pointer',
id: 'finger1',
parameters: { pointerType: 'touch' },
actions: [
{
type: 'pointerMove',
duration: 0,
x: tapX,
y: tapY,
origin: 'viewport',
width: 40,
height: 40,
pressure: 0.9,
},
{ type: 'pointerDown', button: 0, width: 40, height: 40, pressure: 0.9 },
{ type: 'pause', duration: 90 },
{ type: 'pointerUp', button: 0 },
],
},
])
await browser.switchContext(webviewContext)
await browser.pause(600)

const keyboard = await isKeyboardShown()

// A non-cursor thought must not open the virtual keyboard. The stale-offset focus path that opened it
// before is gone, so focus is correctly prevented and the keyboard stays down.
expect(keyboard).toBe(false)
})
})
6 changes: 4 additions & 2 deletions src/e2e/iOS/helpers/tap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import type { Element } from 'webdriverio'
interface Options {
// Where in the horizontal line (inside) of the target node should be tapped
horizontalTapLine?: 'left' | 'right'
// Pointer type to use for the tap action. Defaults to 'mouse'.
pointerType?: 'mouse' | 'touch'
// Specify specific node on editable to tap. Overrides horizontalClickLine
offset?: number
// Number of pixels of x offset to add to the tap coordinates
Expand All @@ -21,7 +23,7 @@ interface Options {
*/
const tap = async (
nodeHandle: Element,
{ horizontalTapLine = 'left', offset, x = 0, y = 0, releaseDelayMs = 100 }: Options = {},
{ horizontalTapLine = 'left', offset, x = 0, y = 0, pointerType = 'mouse', releaseDelayMs = 100 }: Options = {},
) => {
// Ensure element exists and has an elementId
const exists = await nodeHandle.isExisting()
Expand Down Expand Up @@ -98,7 +100,7 @@ const tap = async (
{
type: 'pointer',
id: 'pointer1',
parameters: { pointerType: 'mouse' },
parameters: { pointerType },
actions: [
{
type: 'pointerMove',
Expand Down
Loading