fix(clip): a time's error line sits in the flow, the remove button off the cut, and every player surface rings amber
- useTimecodeField holds a time field's draft for its caller, which places TimecodeError (role alert) in its own flow under the fields' row: it no longer lies over the crop row taking its clicks, runs out of the editor's card, or joins the field's <label> name. - The editor's remove-cut buttons sit in a row under the track at their cut (trackLeft), off the cut's grab area, where a press drags it; RangeTrack's adornment slot is gone. - media-focus re-points --focus to the media amber on the Plex player, the YouTube stage and every bg-media-chrome surface; a contract test holds each such class string to media-focus or media-scope, the field's focus edge to the focus token, and --color-focus to the inline theme. - An upload's quota headline says the converted file does not fit, not "Storage full". - DESIGN.md: the tokens paragraph says what is re-pointed where; the decision row measures all three colours on the chrome.
This commit is contained in:
@@ -137,6 +137,32 @@ describe("VideoEditor — a typed time and another segment", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// An unreadable time is said on a line under the segment's row: outside the field's label, so the
|
||||
// field's name stays "Out" and the line is read once, as its description.
|
||||
describe("VideoEditor — an unreadable time", () => {
|
||||
it("is said under the row, outside the field's label", () => {
|
||||
vi.spyOn(api, "downloadStoryboard").mockResolvedValue({ available: false } as never);
|
||||
render(
|
||||
<QueryClientProvider client={new QueryClient()}>
|
||||
<VideoEditor job={job} onClose={() => {}} />
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
const video = document.querySelector("video")!;
|
||||
Object.defineProperty(video, "currentTime", { value: 50, configurable: true, writable: true });
|
||||
fireEvent.timeUpdate(video);
|
||||
fireEvent.click(screen.getByText("editor.splitHere"));
|
||||
fireEvent.pointerDown(screen.getAllByTitle("editor.keptSeg")[0]!);
|
||||
const out = screen.getByRole("textbox", { name: "editor.out" });
|
||||
fireEvent.change(out, { target: { value: "abc" } });
|
||||
fireEvent.keyDown(out, { key: "Enter" });
|
||||
const line = screen.getByRole("alert");
|
||||
expect(line.textContent).toBe("common.timecodeInvalid");
|
||||
expect(line.closest("label")).toBeNull();
|
||||
expect(screen.getByRole("textbox", { name: "editor.out" })).toBe(out);
|
||||
expect(out.getAttribute("aria-describedby")).toBe(line.id);
|
||||
});
|
||||
});
|
||||
|
||||
// The cuts sit on the shared time track: each is a slider the keyboard steps, and a step stops a
|
||||
// tenth short of the next cut, so no segment ever closes up.
|
||||
describe("VideoEditor — a cut on the keyboard", () => {
|
||||
@@ -235,11 +261,14 @@ describe("VideoEditor — a cut's remove button", () => {
|
||||
expect(document.activeElement).toBe(splitButton);
|
||||
});
|
||||
|
||||
it("sits at the cut's foot, under the next segment's keep toggle at its head", () => {
|
||||
it("sits under the track at its cut, off the cut's grab area; the keep toggle sits over it", () => {
|
||||
split();
|
||||
const remove = screen.getByRole("button", { name: "editor.removeCut" });
|
||||
expect(remove.className).toMatch(/\bbottom-0\b/);
|
||||
const track = screen.getByRole("group", { name: "editor.timeline" });
|
||||
// A press anywhere on the cut drags it; the X that deletes it (no undo) is below the track.
|
||||
expect(track.contains(remove)).toBe(false);
|
||||
const cut = screen.getByRole("slider", { name: "editor.cutN" });
|
||||
expect(remove.closest<HTMLElement>("[style]")?.style.left).toBe(cut.style.left);
|
||||
const toggles = within(track).getAllByRole("button", { pressed: true });
|
||||
for (const toggle of toggles) expect(toggle.className).toMatch(/\bz-menu\b/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user