|
|
|
@ -183,4 +183,130 @@ describe("QuestionSnapList keyboard interaction", () => { |
|
|
|
expect(input).toHaveFocus(); |
|
|
|
}); |
|
|
|
}); |
|
|
|
|
|
|
|
describe("touch isolation and first-tap focus reliability", () => { |
|
|
|
it("does not step question or blur active input when touch has jitter on input", () => { |
|
|
|
const onActiveIndexChange = vi.fn(); |
|
|
|
render( |
|
|
|
<QuestionSnapList onActiveIndexChange={onActiveIndexChange}> |
|
|
|
<div> |
|
|
|
<label htmlFor="name-input">Full Name</label> |
|
|
|
<input id="name-input" aria-label="Full Name" type="text" /> |
|
|
|
</div> |
|
|
|
<div> |
|
|
|
<input aria-label="Second Question" type="text" /> |
|
|
|
</div> |
|
|
|
</QuestionSnapList>, |
|
|
|
); |
|
|
|
|
|
|
|
const input = screen.getByRole("textbox", { name: "Full Name" }); |
|
|
|
input.focus(); |
|
|
|
expect(input).toHaveFocus(); |
|
|
|
|
|
|
|
// Finger touches input with 12px jitter/drift (typical coarse touch contact area shift)
|
|
|
|
fireEvent.touchStart(input, { |
|
|
|
touches: [{ clientY: 300 }], |
|
|
|
target: input, |
|
|
|
}); |
|
|
|
fireEvent.touchMove(input, { |
|
|
|
touches: [{ clientY: 288 }], |
|
|
|
target: input, |
|
|
|
}); |
|
|
|
fireEvent.touchEnd(input, { |
|
|
|
changedTouches: [{ clientY: 288 }], |
|
|
|
target: input, |
|
|
|
}); |
|
|
|
|
|
|
|
// Input should remain focused, and active question must remain index 0
|
|
|
|
expect(input).toHaveFocus(); |
|
|
|
expect(onActiveIndexChange).not.toHaveBeenCalledWith(1); |
|
|
|
}); |
|
|
|
|
|
|
|
it("does not engage drag or blur input when touched while snap animation is settling", () => { |
|
|
|
const onActiveIndexChange = vi.fn(); |
|
|
|
render( |
|
|
|
<QuestionSnapList onActiveIndexChange={onActiveIndexChange}> |
|
|
|
<div> |
|
|
|
<textarea aria-label="Bio description" /> |
|
|
|
</div> |
|
|
|
<div> |
|
|
|
<textarea aria-label="Second Bio" /> |
|
|
|
</div> |
|
|
|
</QuestionSnapList>, |
|
|
|
); |
|
|
|
|
|
|
|
const textarea = screen.getByRole("textbox", { |
|
|
|
name: "Bio description", |
|
|
|
}); |
|
|
|
textarea.focus(); |
|
|
|
|
|
|
|
// Simulate touching during animation or right as question is rendered
|
|
|
|
fireEvent.touchStart(textarea, { |
|
|
|
touches: [{ clientY: 400 }], |
|
|
|
target: textarea, |
|
|
|
}); |
|
|
|
fireEvent.touchEnd(textarea, { |
|
|
|
changedTouches: [{ clientY: 390 }], |
|
|
|
target: textarea, |
|
|
|
}); |
|
|
|
|
|
|
|
expect(textarea).toHaveFocus(); |
|
|
|
expect(onActiveIndexChange).not.toHaveBeenCalledWith(1); |
|
|
|
}); |
|
|
|
|
|
|
|
it("does not step question on buttons or labels", () => { |
|
|
|
const onActiveIndexChange = vi.fn(); |
|
|
|
render( |
|
|
|
<QuestionSnapList onActiveIndexChange={onActiveIndexChange}> |
|
|
|
<div> |
|
|
|
<button type="button">Select Option</button> |
|
|
|
</div> |
|
|
|
<div> |
|
|
|
<button type="button">Next Step</button> |
|
|
|
</div> |
|
|
|
</QuestionSnapList>, |
|
|
|
); |
|
|
|
|
|
|
|
const button = screen.getByRole("button", { name: "Select Option" }); |
|
|
|
|
|
|
|
fireEvent.touchStart(button, { |
|
|
|
touches: [{ clientY: 250 }], |
|
|
|
target: button, |
|
|
|
}); |
|
|
|
fireEvent.touchMove(button, { |
|
|
|
touches: [{ clientY: 235 }], |
|
|
|
target: button, |
|
|
|
}); |
|
|
|
fireEvent.touchEnd(button, { |
|
|
|
changedTouches: [{ clientY: 235 }], |
|
|
|
target: button, |
|
|
|
}); |
|
|
|
|
|
|
|
expect(onActiveIndexChange).not.toHaveBeenCalledWith(1); |
|
|
|
}); |
|
|
|
|
|
|
|
it("allows intentional vertical swipe on non-interactive question background", () => { |
|
|
|
const onActiveIndexChange = vi.fn(); |
|
|
|
render( |
|
|
|
<QuestionSnapList onActiveIndexChange={onActiveIndexChange}> |
|
|
|
<div data-testid="slide-1">Slide 1</div> |
|
|
|
<div data-testid="slide-2">Slide 2</div> |
|
|
|
</QuestionSnapList>, |
|
|
|
); |
|
|
|
|
|
|
|
const region = screen.getByRole("region", { name: "Questions" }); |
|
|
|
|
|
|
|
// Fast upward flick on background area (startY = 400, endY = 320 -> delta = 80 > TOUCH_MIN_DISTANCE)
|
|
|
|
fireEvent.touchStart(region, { |
|
|
|
touches: [{ clientY: 400 }], |
|
|
|
target: region, |
|
|
|
}); |
|
|
|
fireEvent.touchEnd(region, { |
|
|
|
changedTouches: [{ clientY: 320 }], |
|
|
|
target: region, |
|
|
|
}); |
|
|
|
|
|
|
|
expect(onActiveIndexChange).toHaveBeenCalledWith(1); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}); |