test(native-chat): make composition frames carry isComposing, not just inputType

This suite's comment claimed "Gating onChange on `isComposing` breaks here."
It did not. composeFrame() fired `input` with `inputType` but never set
`isComposing`, so a gate on `isComposing` passed all four tests untouched —
the suite asserted a discriminator it did not exercise.

Composition frames now carry both, so neither gate is exempt. Verified by
pointing the mutant at it: with an `isComposing` gate on the composer's
onChange, this suite now fails 3 of 4 (it passed 4 of 4 before), and the
ordinary-English arm correctly survives, since a composition gate should not
touch it. Production code is unchanged and stays gate-free; the mutation was
applied, measured, and reverted.

Found while excluding NativeChatView's question-card remount as the owner of
#12118 / STA-3219: the remount is real, but the preedit survives it precisely
because this write path has no composition gate.
This commit is contained in:
Neil
2026-08-06 16:28:02 -07:00
parent c0c59a8b65
commit fc5ec84965
@@ -190,7 +190,14 @@ function setInteractivePrompt(prompt: string | null, rendered: ReturnType<typeof
* `value` without this is what made the loss look real. */
function composeFrame(el: HTMLTextAreaElement, value: string, data: string): void {
fireEvent.compositionUpdate(el, { data })
fireEvent.input(el, { target: { value }, inputType: 'insertCompositionText', data })
// `isComposing` is set as well as `inputType`: a gate on either one is a plausible
// regression, and omitting it silently exempted the `isComposing` variant.
fireEvent.input(el, {
target: { value },
inputType: 'insertCompositionText',
data,
isComposing: true
})
}
describe('native chat question card vs an in-flight composition', () => {
@@ -257,7 +264,10 @@ describe('native chat question card vs an in-flight composition', () => {
// The mechanism, pinned directly: onChange fires on composition `input`
// frames, so the draft cache — not the unmounted DOM node — is what carries
// the preedit across the swap. Gating onChange on `isComposing` breaks here.
// the preedit across the swap. Gating onChange on either `isComposing` or
// `inputType` breaks here — the frames now carry both, so neither gate is
// exempt. Previously only `inputType` was set, so an `isComposing` gate
// passed this suite untouched despite the claim.
setInteractivePrompt(ASK_USER_QUESTION, rendered)
expect(readNativeChatDraftCache(PANE_KEY)).toBe('가')
})