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 { describe, expect, it, vi } from "vitest";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
createActorControlTargetRef,
|
||||||
createProjectGlobalControlTargetRef,
|
createProjectGlobalControlTargetRef,
|
||||||
getControlTargetRefKey
|
getControlTargetRefKey
|
||||||
} from "../../src/controls/control-surface";
|
} from "../../src/controls/control-surface";
|
||||||
@@ -14,34 +15,35 @@ import {
|
|||||||
import { createEmptyProjectSequenceLibrary } from "../../src/sequencer/project-sequences";
|
import { createEmptyProjectSequenceLibrary } from "../../src/sequencer/project-sequences";
|
||||||
|
|
||||||
describe("ProjectSequencerPane", () => {
|
describe("ProjectSequencerPane", () => {
|
||||||
it("renders project event placements without crashing when they have no inline effects", () => {
|
function renderPane({
|
||||||
const target = createProjectGlobalControlTargetRef();
|
scheduler = createEmptyProjectScheduler(),
|
||||||
const routine = createProjectScheduleRoutine({
|
targetOptions,
|
||||||
id: "routine-project-events",
|
selectedRoutineId = null,
|
||||||
title: "Project Events",
|
onSetRoutineTarget = vi.fn(),
|
||||||
target,
|
onSetRoutineStartHour = vi.fn(),
|
||||||
startHour: 9,
|
onSetRoutineEndHour = vi.fn()
|
||||||
endHour: 17,
|
}: {
|
||||||
priority: 0,
|
scheduler?: ReturnType<typeof createEmptyProjectScheduler>;
|
||||||
effects: []
|
targetOptions: Array<{
|
||||||
});
|
key: string;
|
||||||
const scheduler = createEmptyProjectScheduler();
|
target: ReturnType<
|
||||||
scheduler.routines[routine.id] = routine;
|
typeof createProjectGlobalControlTargetRef | typeof createActorControlTargetRef
|
||||||
|
>;
|
||||||
render(
|
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
|
<ProjectSequencerPane
|
||||||
mode="timeline"
|
mode="timeline"
|
||||||
onSetMode={vi.fn()}
|
onSetMode={vi.fn()}
|
||||||
targetOptions={[
|
targetOptions={targetOptions}
|
||||||
{
|
|
||||||
key: getControlTargetRefKey(target),
|
|
||||||
target,
|
|
||||||
label: "Project Events",
|
|
||||||
subtitle: "One-shot project events",
|
|
||||||
groupLabel: "Project",
|
|
||||||
defaults: {}
|
|
||||||
}
|
|
||||||
]}
|
|
||||||
teleportTargetOptions={[]}
|
teleportTargetOptions={[]}
|
||||||
sceneTransitionTargetOptions={[]}
|
sceneTransitionTargetOptions={[]}
|
||||||
visibilityTargetOptions={[]}
|
visibilityTargetOptions={[]}
|
||||||
@@ -50,7 +52,7 @@ describe("ProjectSequencerPane", () => {
|
|||||||
scheduler={scheduler}
|
scheduler={scheduler}
|
||||||
sequences={createEmptyProjectSequenceLibrary()}
|
sequences={createEmptyProjectSequenceLibrary()}
|
||||||
dialogues={createEmptyProjectDialogueLibrary()}
|
dialogues={createEmptyProjectDialogueLibrary()}
|
||||||
selectedRoutineId={routine.id}
|
selectedRoutineId={selectedRoutineId}
|
||||||
selectedSequenceId={null}
|
selectedSequenceId={null}
|
||||||
onSelectRoutine={vi.fn()}
|
onSelectRoutine={vi.fn()}
|
||||||
onSelectSequence={vi.fn()}
|
onSelectSequence={vi.fn()}
|
||||||
@@ -59,11 +61,11 @@ describe("ProjectSequencerPane", () => {
|
|||||||
onDeleteRoutine={vi.fn()}
|
onDeleteRoutine={vi.fn()}
|
||||||
onDeleteSequence={vi.fn()}
|
onDeleteSequence={vi.fn()}
|
||||||
onClose={vi.fn()}
|
onClose={vi.fn()}
|
||||||
onSetRoutineTarget={vi.fn()}
|
onSetRoutineTarget={onSetRoutineTarget}
|
||||||
onSetRoutineTitle={vi.fn()}
|
onSetRoutineTitle={vi.fn()}
|
||||||
onSetRoutineEnabled={vi.fn()}
|
onSetRoutineEnabled={vi.fn()}
|
||||||
onSetRoutineStartHour={vi.fn()}
|
onSetRoutineStartHour={onSetRoutineStartHour}
|
||||||
onSetRoutineEndHour={vi.fn()}
|
onSetRoutineEndHour={onSetRoutineEndHour}
|
||||||
onSetRoutinePriority={vi.fn()}
|
onSetRoutinePriority={vi.fn()}
|
||||||
onSetRoutineSequenceId={vi.fn()}
|
onSetRoutineSequenceId={vi.fn()}
|
||||||
onSetRoutineEffectOption={vi.fn()}
|
onSetRoutineEffectOption={vi.fn()}
|
||||||
@@ -102,6 +104,36 @@ describe("ProjectSequencerPane", () => {
|
|||||||
onSetVisibilityStepMode={vi.fn()}
|
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(
|
expect(
|
||||||
screen.getByText(/Project event placements run attached sequences only/i)
|
screen.getByText(/Project event placements run attached sequences only/i)
|
||||||
@@ -109,4 +141,173 @@ describe("ProjectSequencerPane", () => {
|
|||||||
expect(screen.queryByText("Target")).toBeNull();
|
expect(screen.queryByText("Target")).toBeNull();
|
||||||
expect(screen.queryByText("Legacy Day Filter")).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