Refactor and enhance ProjectSequencerPane tests
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
import { render, screen } from "@testing-library/react";
|
||||
import { fireEvent, render, screen } from "@testing-library/react";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import {
|
||||
createActorControlTargetRef,
|
||||
createProjectGlobalControlTargetRef,
|
||||
getControlTargetRefKey
|
||||
} from "../../src/controls/control-surface";
|
||||
@@ -14,34 +15,35 @@ import {
|
||||
import { createEmptyProjectSequenceLibrary } from "../../src/sequencer/project-sequences";
|
||||
|
||||
describe("ProjectSequencerPane", () => {
|
||||
it("renders project event placements without crashing when they have no inline effects", () => {
|
||||
const target = createProjectGlobalControlTargetRef();
|
||||
const routine = createProjectScheduleRoutine({
|
||||
id: "routine-project-events",
|
||||
title: "Project Events",
|
||||
target,
|
||||
startHour: 9,
|
||||
endHour: 17,
|
||||
priority: 0,
|
||||
effects: []
|
||||
});
|
||||
const scheduler = createEmptyProjectScheduler();
|
||||
scheduler.routines[routine.id] = routine;
|
||||
|
||||
render(
|
||||
function renderPane({
|
||||
scheduler = createEmptyProjectScheduler(),
|
||||
targetOptions,
|
||||
selectedRoutineId = null,
|
||||
onSetRoutineTarget = vi.fn(),
|
||||
onSetRoutineStartHour = vi.fn(),
|
||||
onSetRoutineEndHour = vi.fn()
|
||||
}: {
|
||||
scheduler?: ReturnType<typeof createEmptyProjectScheduler>;
|
||||
targetOptions: Array<{
|
||||
key: string;
|
||||
target: ReturnType<
|
||||
typeof createProjectGlobalControlTargetRef | typeof createActorControlTargetRef
|
||||
>;
|
||||
label: string;
|
||||
subtitle: string;
|
||||
groupLabel: string;
|
||||
defaults: Record<string, unknown>;
|
||||
}>;
|
||||
selectedRoutineId?: string | null;
|
||||
onSetRoutineTarget?: ReturnType<typeof vi.fn>;
|
||||
onSetRoutineStartHour?: ReturnType<typeof vi.fn>;
|
||||
onSetRoutineEndHour?: ReturnType<typeof vi.fn>;
|
||||
}) {
|
||||
return render(
|
||||
<ProjectSequencerPane
|
||||
mode="timeline"
|
||||
onSetMode={vi.fn()}
|
||||
targetOptions={[
|
||||
{
|
||||
key: getControlTargetRefKey(target),
|
||||
target,
|
||||
label: "Project Events",
|
||||
subtitle: "One-shot project events",
|
||||
groupLabel: "Project",
|
||||
defaults: {}
|
||||
}
|
||||
]}
|
||||
targetOptions={targetOptions}
|
||||
teleportTargetOptions={[]}
|
||||
sceneTransitionTargetOptions={[]}
|
||||
visibilityTargetOptions={[]}
|
||||
@@ -50,7 +52,7 @@ describe("ProjectSequencerPane", () => {
|
||||
scheduler={scheduler}
|
||||
sequences={createEmptyProjectSequenceLibrary()}
|
||||
dialogues={createEmptyProjectDialogueLibrary()}
|
||||
selectedRoutineId={routine.id}
|
||||
selectedRoutineId={selectedRoutineId}
|
||||
selectedSequenceId={null}
|
||||
onSelectRoutine={vi.fn()}
|
||||
onSelectSequence={vi.fn()}
|
||||
@@ -59,11 +61,11 @@ describe("ProjectSequencerPane", () => {
|
||||
onDeleteRoutine={vi.fn()}
|
||||
onDeleteSequence={vi.fn()}
|
||||
onClose={vi.fn()}
|
||||
onSetRoutineTarget={vi.fn()}
|
||||
onSetRoutineTarget={onSetRoutineTarget}
|
||||
onSetRoutineTitle={vi.fn()}
|
||||
onSetRoutineEnabled={vi.fn()}
|
||||
onSetRoutineStartHour={vi.fn()}
|
||||
onSetRoutineEndHour={vi.fn()}
|
||||
onSetRoutineStartHour={onSetRoutineStartHour}
|
||||
onSetRoutineEndHour={onSetRoutineEndHour}
|
||||
onSetRoutinePriority={vi.fn()}
|
||||
onSetRoutineSequenceId={vi.fn()}
|
||||
onSetRoutineEffectOption={vi.fn()}
|
||||
@@ -102,6 +104,36 @@ describe("ProjectSequencerPane", () => {
|
||||
onSetVisibilityStepMode={vi.fn()}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
it("renders project event placements without crashing when they have no inline effects", () => {
|
||||
const target = createProjectGlobalControlTargetRef();
|
||||
const routine = createProjectScheduleRoutine({
|
||||
id: "routine-project-events",
|
||||
title: "Project Events",
|
||||
target,
|
||||
startHour: 9,
|
||||
endHour: 17,
|
||||
priority: 0,
|
||||
effects: []
|
||||
});
|
||||
const scheduler = createEmptyProjectScheduler();
|
||||
scheduler.routines[routine.id] = routine;
|
||||
|
||||
renderPane({
|
||||
scheduler,
|
||||
targetOptions: [
|
||||
{
|
||||
key: getControlTargetRefKey(target),
|
||||
target,
|
||||
label: "Project Events",
|
||||
subtitle: "One-shot project events",
|
||||
groupLabel: "Project",
|
||||
defaults: {}
|
||||
}
|
||||
],
|
||||
selectedRoutineId: routine.id
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.getByText(/Project event placements run attached sequences only/i)
|
||||
@@ -109,4 +141,173 @@ describe("ProjectSequencerPane", () => {
|
||||
expect(screen.queryByText("Target")).toBeNull();
|
||||
expect(screen.queryByText("Legacy Day Filter")).toBeNull();
|
||||
});
|
||||
|
||||
it("moves sequence placements horizontally and between target rows", () => {
|
||||
const actorA = createActorControlTargetRef("actor-a");
|
||||
const actorB = createActorControlTargetRef("actor-b");
|
||||
const routine = createProjectScheduleRoutine({
|
||||
id: "routine-a",
|
||||
title: "Morning Patrol",
|
||||
target: actorA,
|
||||
startHour: 9,
|
||||
endHour: 10,
|
||||
priority: 0,
|
||||
effects: []
|
||||
});
|
||||
const scheduler = createEmptyProjectScheduler();
|
||||
scheduler.routines[routine.id] = routine;
|
||||
const onSetRoutineTarget = vi.fn();
|
||||
const onSetRoutineStartHour = vi.fn();
|
||||
const onSetRoutineEndHour = vi.fn();
|
||||
|
||||
const { container } = renderPane({
|
||||
scheduler,
|
||||
targetOptions: [
|
||||
{
|
||||
key: getControlTargetRefKey(actorA),
|
||||
target: actorA,
|
||||
label: "Guard A",
|
||||
subtitle: "NPC",
|
||||
groupLabel: "Actors",
|
||||
defaults: {
|
||||
actorAnimationClipNames: [],
|
||||
actorPathOptions: []
|
||||
}
|
||||
},
|
||||
{
|
||||
key: getControlTargetRefKey(actorB),
|
||||
target: actorB,
|
||||
label: "Guard B",
|
||||
subtitle: "NPC",
|
||||
groupLabel: "Actors",
|
||||
defaults: {
|
||||
actorAnimationClipNames: [],
|
||||
actorPathOptions: []
|
||||
}
|
||||
}
|
||||
],
|
||||
selectedRoutineId: routine.id,
|
||||
onSetRoutineTarget,
|
||||
onSetRoutineStartHour,
|
||||
onSetRoutineEndHour
|
||||
});
|
||||
|
||||
const tracks = container.querySelectorAll<HTMLElement>(
|
||||
"[data-sequencer-track='true']"
|
||||
);
|
||||
expect(tracks).toHaveLength(2);
|
||||
|
||||
Object.defineProperty(tracks[0], "getBoundingClientRect", {
|
||||
configurable: true,
|
||||
value: () => ({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1440,
|
||||
height: 64,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 1440,
|
||||
bottom: 64,
|
||||
toJSON: () => ({})
|
||||
})
|
||||
});
|
||||
|
||||
const originalElementFromPoint = document.elementFromPoint;
|
||||
document.elementFromPoint = vi.fn((x: number, y: number) =>
|
||||
y >= 100 ? tracks[1] : tracks[0]
|
||||
);
|
||||
|
||||
const block = screen.getByRole("button", { name: /morning patrol/i });
|
||||
|
||||
fireEvent.pointerDown(block, { button: 0, clientX: 540, clientY: 24 });
|
||||
fireEvent.pointerMove(window, { clientX: 600, clientY: 120 });
|
||||
fireEvent.pointerUp(window, { clientX: 600, clientY: 120 });
|
||||
|
||||
document.elementFromPoint = originalElementFromPoint;
|
||||
|
||||
expect(onSetRoutineTarget).toHaveBeenCalledWith(
|
||||
routine.id,
|
||||
getControlTargetRefKey(actorB)
|
||||
);
|
||||
expect(onSetRoutineStartHour).toHaveBeenCalledWith(routine.id, 10);
|
||||
expect(onSetRoutineEndHour).toHaveBeenCalledWith(routine.id, 11);
|
||||
});
|
||||
|
||||
it("resizes sequence placements from both edges with minute precision", () => {
|
||||
const actor = createActorControlTargetRef("actor-a");
|
||||
const routine = createProjectScheduleRoutine({
|
||||
id: "routine-a",
|
||||
title: "Morning Patrol",
|
||||
target: actor,
|
||||
startHour: 9,
|
||||
endHour: 10,
|
||||
priority: 0,
|
||||
effects: []
|
||||
});
|
||||
const scheduler = createEmptyProjectScheduler();
|
||||
scheduler.routines[routine.id] = routine;
|
||||
const onSetRoutineStartHour = vi.fn();
|
||||
const onSetRoutineEndHour = vi.fn();
|
||||
|
||||
const { container } = renderPane({
|
||||
scheduler,
|
||||
targetOptions: [
|
||||
{
|
||||
key: getControlTargetRefKey(actor),
|
||||
target: actor,
|
||||
label: "Guard A",
|
||||
subtitle: "NPC",
|
||||
groupLabel: "Actors",
|
||||
defaults: {
|
||||
actorAnimationClipNames: [],
|
||||
actorPathOptions: []
|
||||
}
|
||||
}
|
||||
],
|
||||
selectedRoutineId: routine.id,
|
||||
onSetRoutineStartHour,
|
||||
onSetRoutineEndHour
|
||||
});
|
||||
|
||||
const track = container.querySelector<HTMLElement>("[data-sequencer-track='true']");
|
||||
expect(track).not.toBeNull();
|
||||
|
||||
Object.defineProperty(track, "getBoundingClientRect", {
|
||||
configurable: true,
|
||||
value: () => ({
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 1440,
|
||||
height: 64,
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 1440,
|
||||
bottom: 64,
|
||||
toJSON: () => ({})
|
||||
})
|
||||
});
|
||||
|
||||
fireEvent.pointerDown(screen.getByLabelText("Resize start of Morning Patrol"), {
|
||||
button: 0,
|
||||
clientX: 540,
|
||||
clientY: 24
|
||||
});
|
||||
fireEvent.pointerMove(window, { clientX: 525, clientY: 24 });
|
||||
fireEvent.pointerUp(window, { clientX: 525, clientY: 24 });
|
||||
|
||||
expect(onSetRoutineStartHour).toHaveBeenLastCalledWith(routine.id, 8.75);
|
||||
|
||||
onSetRoutineStartHour.mockClear();
|
||||
onSetRoutineEndHour.mockClear();
|
||||
|
||||
fireEvent.pointerDown(screen.getByLabelText("Resize end of Morning Patrol"), {
|
||||
button: 0,
|
||||
clientX: 600,
|
||||
clientY: 24
|
||||
});
|
||||
fireEvent.pointerMove(window, { clientX: 630, clientY: 24 });
|
||||
fireEvent.pointerUp(window, { clientX: 630, clientY: 24 });
|
||||
|
||||
expect(onSetRoutineEndHour).toHaveBeenLastCalledWith(routine.id, 10.5);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user