You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

164 lines
4.5 KiB

import {
cleanup,
fireEvent,
render,
screen,
waitFor,
} from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import { updateQuestionKeyboardHeight } from "./question-viewport-coordinator";
import { QuestionSnapList } from "./question-snap-list";
vi.mock("@/translations/provider", () => ({
useI18n: () => ({ dictionary: {} }),
}));
describe("QuestionSnapList keyboard interaction", () => {
afterEach(() => {
cleanup();
document.body.classList.remove("question-keyboard-open");
document.documentElement.style.removeProperty("--question-lift-y");
});
it("does not treat pointer contact as input focus", () => {
render(
<QuestionSnapList>
<input aria-label="Question one" type="text" />
</QuestionSnapList>,
);
fireEvent.pointerDown(
screen.getByRole("textbox", { name: "Question one" }),
);
expect(document.body).not.toHaveClass("question-keyboard-open");
});
it("activates only on real focus and resets before changing questions", async () => {
render(
<QuestionSnapList>
<input aria-label="Question one" type="text" />
<input aria-label="Question two" type="text" />
</QuestionSnapList>,
);
const firstInput = screen.getByRole("textbox", { name: "Question one" });
const secondInput = document.querySelector<HTMLInputElement>(
'input[aria-label="Question two"]',
);
const content = firstInput.closest<HTMLElement>(".question-snap-content");
vi.spyOn(content as HTMLElement, "getBoundingClientRect").mockReturnValue({
top: 300,
bottom: 600,
left: 0,
right: 300,
width: 300,
height: 300,
x: 0,
y: 300,
toJSON: () => ({}),
});
firstInput.focus();
updateQuestionKeyboardHeight(300);
await waitFor(() => {
expect(document.body).toHaveClass("question-keyboard-open");
});
fireEvent.wheel(screen.getByRole("region", { name: "Questions" }), {
deltaY: 100,
});
await waitFor(() => {
expect(document.body).not.toHaveClass("question-keyboard-open");
expect(firstInput).not.toHaveFocus();
expect(secondInput).not.toHaveFocus();
expect(
document.documentElement.style.getPropertyValue(
"--question-lift-y",
),
).toBe("0px");
});
});
it("does not keep question keyboard state for a dialog input", async () => {
render(
<>
<QuestionSnapList>
<input aria-label="Question input" type="text" />
</QuestionSnapList>
<div role="dialog">
<input aria-label="Dialog search" type="text" />
</div>
</>,
);
const questionInput = screen.getByRole("textbox", {
name: "Question input",
});
const content = questionInput.closest<HTMLElement>(
".question-snap-content",
);
vi.spyOn(content as HTMLElement, "getBoundingClientRect").mockReturnValue({
top: 300,
bottom: 600,
left: 0,
right: 300,
width: 300,
height: 300,
x: 0,
y: 300,
toJSON: () => ({}),
});
questionInput.focus();
updateQuestionKeyboardHeight(300);
await waitFor(() => {
expect(document.body).toHaveClass("question-keyboard-open");
});
screen.getByRole("textbox", { name: "Dialog search" }).focus();
await waitFor(() => {
expect(document.body).not.toHaveClass("question-keyboard-open");
});
});
it("raises again when Flutter reopens the keyboard without another focusin", async () => {
render(
<QuestionSnapList>
<input aria-label="Persistent input" type="text" />
</QuestionSnapList>,
);
const input = screen.getByRole("textbox", { name: "Persistent input" });
const content = input.closest<HTMLElement>(".question-snap-content");
vi.spyOn(content as HTMLElement, "getBoundingClientRect").mockReturnValue({
top: 300,
bottom: 600,
left: 0,
right: 300,
width: 300,
height: 300,
x: 0,
y: 300,
toJSON: () => ({}),
});
input.focus();
updateQuestionKeyboardHeight(300);
await waitFor(() => expect(document.body).toHaveClass("question-keyboard-open"));
updateQuestionKeyboardHeight(0);
await waitFor(() => {
expect(document.body).not.toHaveClass("question-keyboard-open");
expect(input).toHaveFocus();
});
updateQuestionKeyboardHeight(300);
await waitFor(() => {
expect(document.body).toHaveClass("question-keyboard-open");
expect(input).toHaveFocus();
});
});
});