press() dispatches a node's handler whatever link target that node carries, so a native screen test cannot observe the host rule that a link target pre-empts the handler. A test over a screen broken by that rule passes.
Where
packages/stx/src/native/testing.ts:697
const press = (node: NativeNode, event = 'onPress', nativeEvent: Record<string, unknown> = {}) => {
if (!node) throw new Error(`nothing to ${event}`)
if (!node.events[event]) throw new Error(`${node.type} has no ${event}`)
send('EVENT', { handlerId: node.events[event], nativeEvent })
}The host rule it is missing
A native host registers a link target for any Link, TouchableOpacity, TouchableHighlight or Pressable with a non-empty href or to and not disabled. On a tap it opens that target and returns, so the authored handler never runs. Both platforms do this:
- iOS:
packages/ios/templates/CraftNativeScreen.swift:11406inviewPressed - Android:
packages/android/templates/MainActivityNative.kt.template:6104insetOnClickListener
Filed against the host as craft-native/craft#442, where the behaviour itself is the question. Whatever that resolves to, the kit should model it rather than model nothing, because today it models the one thing the host never does.
What that cost
stacksjs/hq.training#392. HQ's consent gate listed its legal documents as rows carrying both an href and an onPress, and only the handler opened them. The screen was broken on a device for every user, and this test passed over it the whole time:
phone.press(phone.find(byTest('agree-read-terms'))!)
await until(() => pages.length === 3)
expect(pages).toEqual([...]) // the browser opened each document
expect(phone.opened).toEqual([]) // and nothing navigatedThe assertions are the right ones. expect(phone.opened).toEqual([]) is exactly the thing that was false on the device. It passed because press went straight to the handler, which is the code path the host had taken away. The bug was then found by hand on a simulator, and the regression test that catches it has to read node.props.href instead of pressing, which tests the authoring rather than the behaviour.
What good would look like
press on a node that carries a link target should do what the host does: record the navigation in opened and not dispatch the handler. Then the test above fails on the broken screen and passes on the fixed one, with no change to the test.
Worth keeping the predicate in one place rather than restating it, since it has three parts that must agree with the host: the node types, a non-empty href or to, and disabled. If craft-native/craft#442 lands on dispatching the handler instead, this becomes the same change in the other direction, and the kit should follow it rather than keep its own answer.
Found through @stacksjs/stx 0.2.x with Craft 0.0.420, iOS 26.5 simulator.