fix(clip): typed times belong to their value, and every mark on the player chrome can be seen
TimecodeField drops a draft when the value moves under it (another segment selected, a nudge), so leaving the field never commits it to another cut; it is invalid only once offered, with a line under it saying how to type a time. On the always-dark chrome media-scope points every focus ring at --media-mark and errors at the new --media-danger (a light theme's accent and danger were under 3:1 there). A track whose dragged handle unmounts ends the drag on the pointer's release instead of locking up; a remove refused while clips are queued leaves no focus to take later; each remove-cut button names its cut and is a 24px target. DESIGN.md records the media tokens; stale comments fixed.
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
// @vitest-environment jsdom
|
||||
//
|
||||
// The time field: invalid only once a time was offered, said in words under the field, and a draft
|
||||
// that never outlives the value it was typed over.
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { cleanup, fireEvent, render, screen } from "@testing-library/react";
|
||||
import { TimecodeField } from "./TimecodeField";
|
||||
|
||||
vi.mock("react-i18next", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("react-i18next")>();
|
||||
return { ...actual, useTranslation: () => ({ t: (k: string) => k }) };
|
||||
});
|
||||
|
||||
afterEach(cleanup);
|
||||
|
||||
const field = () => screen.getByRole("textbox") as HTMLInputElement;
|
||||
|
||||
describe("TimecodeField", () => {
|
||||
it("is not invalid while a time is still being typed, only once it is offered", () => {
|
||||
render(<TimecodeField aria-label="In" value={10} onCommit={() => {}} />);
|
||||
fireEvent.change(field(), { target: { value: "" } });
|
||||
fireEvent.change(field(), { target: { value: "1:" } });
|
||||
expect(field().getAttribute("aria-invalid")).toBeNull();
|
||||
fireEvent.blur(field());
|
||||
expect(field().getAttribute("aria-invalid")).toBe("true");
|
||||
});
|
||||
|
||||
it("says in words, under the field and linked to it, how a time is typed", () => {
|
||||
render(<TimecodeField aria-label="In" value={10} onCommit={() => {}} />);
|
||||
fireEvent.change(field(), { target: { value: "abc" } });
|
||||
fireEvent.keyDown(field(), { key: "Enter" });
|
||||
const id = field().getAttribute("aria-describedby");
|
||||
expect(id).toBeTruthy();
|
||||
expect(document.getElementById(id!)?.textContent).toBe("common.timecodeInvalid");
|
||||
fireEvent.change(field(), { target: { value: "0:12" } });
|
||||
fireEvent.keyDown(field(), { key: "Enter" });
|
||||
expect(field().getAttribute("aria-describedby")).toBeNull();
|
||||
});
|
||||
|
||||
it("drops a draft when the value moves under it, and never commits it to the new value", () => {
|
||||
const onCommit = vi.fn();
|
||||
const { rerender } = render(<TimecodeField aria-label="In" value={10} onCommit={onCommit} />);
|
||||
fireEvent.change(field(), { target: { value: "0:05" } });
|
||||
// Another segment selected under the same field (or a nudge): the field edits a new value now.
|
||||
rerender(<TimecodeField aria-label="In" value={40} onCommit={onCommit} />);
|
||||
expect(field().value).toBe("0:40.0");
|
||||
fireEvent.blur(field());
|
||||
expect(onCommit).not.toHaveBeenCalled();
|
||||
// An unreadable one goes too, and its mark with it.
|
||||
fireEvent.change(field(), { target: { value: "x" } });
|
||||
fireEvent.blur(field());
|
||||
expect(field().getAttribute("aria-invalid")).toBe("true");
|
||||
rerender(<TimecodeField aria-label="In" value={41} onCommit={onCommit} />);
|
||||
expect(field().value).toBe("0:41.0");
|
||||
expect(field().getAttribute("aria-invalid")).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user