111 lines
4.0 KiB
TypeScript
111 lines
4.0 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import initialView from "../../fixtures/player-view/initial.json";
|
|
import type { ForkBranchRequest, PlayerView, TurnRequest } from "@contracts";
|
|
|
|
import { forkDemoBranch, submitDemoTurn } from "./turnAdapter";
|
|
|
|
function request(intent: TurnRequest["intent"], input: string): TurnRequest {
|
|
return {
|
|
storyId: initialView.storyId,
|
|
branchId: initialView.branchId,
|
|
expectedNodeId: initialView.nodeId,
|
|
actionId: `test_${intent}`,
|
|
intent,
|
|
input
|
|
};
|
|
}
|
|
|
|
describe("local turn adapter", () => {
|
|
it("preserves literal player input without inventing additional player speech", async () => {
|
|
const literalInput = "我把手电筒放在长椅上。";
|
|
const result = await submitDemoTurn(
|
|
request("speak_or_act", literalInput),
|
|
initialView as PlayerView
|
|
);
|
|
const playerBeats = result.playerView.beats.filter((beat) => beat.speaker === "你");
|
|
|
|
expect(playerBeats).toHaveLength(1);
|
|
expect(playerBeats[0]?.text).toBe(literalInput);
|
|
});
|
|
|
|
it("accepts an empty Continue intent without creating a player beat", async () => {
|
|
const result = await submitDemoTurn(request("continue", ""), initialView as PlayerView);
|
|
|
|
expect(result.committedNodeId).toBe("node_002");
|
|
expect(result.playerView.beats.some((beat) => beat.speaker === "你")).toBe(false);
|
|
expect(result.playerView.beats.at(-1)?.speaker).toBe("娜娜");
|
|
});
|
|
|
|
it("mirrors a fork without moving or deleting the source history", async () => {
|
|
const advanced = await submitDemoTurn(
|
|
request("speak_or_act", "我先去看看站台。"),
|
|
initialView as PlayerView
|
|
);
|
|
const forkRequest: ForkBranchRequest = {
|
|
storyId: advanced.playerView.storyId,
|
|
currentBranchId: advanced.playerView.branchId,
|
|
expectedCurrentNodeId: advanced.playerView.nodeId,
|
|
sourceNodeId: initialView.nodeId,
|
|
actionId: "fork_adapter_test"
|
|
};
|
|
|
|
const forked = await forkDemoBranch(forkRequest, advanced.playerView);
|
|
|
|
expect(forked.branchId).toBe("branch_fork_adapter_test");
|
|
expect(forked.playerView.nodeId).toBe(initialView.nodeId);
|
|
expect(forked.playerView.branchId).toBe(forked.branchId);
|
|
expect(forked.playerView.history).toHaveLength(1);
|
|
expect(forked.playerView.history[0]?.isCurrent).toBe(true);
|
|
expect(advanced.playerView.branchId).toBe(initialView.branchId);
|
|
expect(advanced.playerView.nodeId).toBe("node_002");
|
|
});
|
|
|
|
it("plays the promise, hidden-search clue, tunnel, and return vertical slice", async () => {
|
|
const promise = await submitDemoTurn(
|
|
{
|
|
...request("speak_or_act", "我答应你,天亮前一定回来。"),
|
|
actionId: "slice_promise"
|
|
},
|
|
initialView as PlayerView
|
|
);
|
|
expect(promise.playerView.promises[0]?.status).toBe("accepted");
|
|
|
|
const clue = await submitDemoTurn(
|
|
{
|
|
...request("continue", ""),
|
|
branchId: promise.playerView.branchId,
|
|
expectedNodeId: promise.playerView.nodeId,
|
|
actionId: "slice_clue"
|
|
},
|
|
promise.playerView
|
|
);
|
|
expect(clue.playerView.knowledge.at(-1)?.title).toBe("封锁隧道的检修门");
|
|
expect(clue.playerView.inventory.at(-1)?.name).toBe("半张旧车票");
|
|
|
|
const tunnel = await submitDemoTurn(
|
|
{
|
|
...request("continue", ""),
|
|
branchId: clue.playerView.branchId,
|
|
expectedNodeId: clue.playerView.nodeId,
|
|
actionId: "slice_tunnel"
|
|
},
|
|
clue.playerView
|
|
);
|
|
expect(tunnel.playerView.history.at(-1)?.label).toBe("进入封锁隧道");
|
|
|
|
const returned = await submitDemoTurn(
|
|
{
|
|
...request("continue", ""),
|
|
branchId: tunnel.playerView.branchId,
|
|
expectedNodeId: tunnel.playerView.nodeId,
|
|
actionId: "slice_return"
|
|
},
|
|
tunnel.playerView
|
|
);
|
|
expect(returned.playerView.promises[0]?.status).toBe("fulfilled");
|
|
expect(returned.playerView.canContinue).toBe(false);
|
|
expect(returned.playerView.beats.at(-1)?.text).toContain("你回来了");
|
|
});
|
|
});
|