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.
186 lines
4.9 KiB
186 lines
4.9 KiB
import {
|
|
cleanup,
|
|
fireEvent,
|
|
render,
|
|
screen,
|
|
waitFor,
|
|
} from "@testing-library/react";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { QuestionSnapList } from "./question-snap-list";
|
|
import {
|
|
computeQuestionLift,
|
|
updateQuestionKeyboardHeight,
|
|
} from "./question-viewport-coordinator";
|
|
|
|
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("computes a stable occlusion lift without overshoot", () => {
|
|
expect(
|
|
computeQuestionLift({
|
|
baseTop: 300,
|
|
baseBottom: 600,
|
|
visibleTop: 100,
|
|
visibleBottom: 500,
|
|
}),
|
|
).toBe(150);
|
|
expect(
|
|
computeQuestionLift({
|
|
baseTop: 300,
|
|
baseBottom: 600,
|
|
visibleTop: 100,
|
|
visibleBottom: 800,
|
|
}),
|
|
).toBe(0);
|
|
});
|
|
|
|
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();
|
|
});
|
|
});
|
|
});
|