aether-shards/test/managers/NarrativeManager.test.js

251 lines
6.8 KiB
JavaScript

import { expect } from "@esm-bundle/chai";
import sinon from "sinon";
import { narrativeManager, NarrativeManager } from "../../src/managers/NarrativeManager.js";
describe("Manager: NarrativeManager", () => {
let manager;
beforeEach(() => {
// Create a fresh instance for testing
manager = new NarrativeManager();
});
it("CoA 1: Should initialize with empty state", () => {
expect(manager.currentSequence).to.be.null;
expect(manager.currentNode).to.be.null;
expect(manager.history).to.be.instanceof(Set);
expect(manager.history.size).to.equal(0);
});
it("CoA 2: startSequence should load sequence and set first node", () => {
const sequenceData = {
id: "TEST_SEQUENCE",
nodes: [
{ id: "1", type: "DIALOGUE", text: "Hello", next: "2" },
{ id: "2", type: "DIALOGUE", text: "World", next: "END" },
],
};
const updateSpy = sinon.spy(manager, "broadcastUpdate");
manager.startSequence(sequenceData);
expect(manager.currentSequence).to.equal(sequenceData);
expect(manager.currentNode.id).to.equal("1");
expect(manager.history.has("TEST_SEQUENCE")).to.be.true;
expect(updateSpy.called).to.be.true;
});
it("CoA 3: startSequence should handle invalid data gracefully", () => {
const consoleError = sinon.stub(console, "error");
manager.startSequence(null);
expect(manager.currentSequence).to.be.null;
manager.startSequence({});
expect(manager.currentSequence).to.be.null;
consoleError.restore();
});
it("CoA 4: next should advance to next node in sequence", () => {
const sequenceData = {
id: "TEST_SEQUENCE",
nodes: [
{ id: "1", type: "DIALOGUE", text: "First", next: "2" },
{ id: "2", type: "DIALOGUE", text: "Second", next: "END" },
],
};
manager.startSequence(sequenceData);
expect(manager.currentNode.id).to.equal("1");
manager.next();
expect(manager.currentNode.id).to.equal("2");
});
it("CoA 5: next should end sequence when reaching END", () => {
const endSpy = sinon.spy(manager, "endSequence");
const sequenceData = {
id: "TEST_SEQUENCE",
nodes: [{ id: "1", type: "DIALOGUE", text: "Last", next: "END" }],
};
manager.startSequence(sequenceData);
manager.next();
expect(endSpy.called).to.be.true;
expect(manager.currentSequence).to.be.null;
});
it("CoA 6: next should not advance on CHOICE nodes", () => {
const consoleWarn = sinon.stub(console, "warn");
const sequenceData = {
id: "TEST_SEQUENCE",
nodes: [
{
id: "1",
type: "CHOICE",
text: "Choose",
choices: [{ text: "Option 1", next: "2" }],
},
],
};
manager.startSequence(sequenceData);
manager.next();
expect(consoleWarn.called).to.be.true;
expect(manager.currentNode.id).to.equal("1");
consoleWarn.restore();
});
it("CoA 7: makeChoice should advance based on choice selection", () => {
const sequenceData = {
id: "TEST_SEQUENCE",
nodes: [
{
id: "1",
type: "CHOICE",
choices: [
{ text: "Option A", next: "2" },
{ text: "Option B", next: "3" },
],
},
{ id: "2", type: "DIALOGUE", text: "Result A", next: "END" },
{ id: "3", type: "DIALOGUE", text: "Result B", next: "END" },
],
};
manager.startSequence(sequenceData);
manager.makeChoice(1); // Select Option B
expect(manager.currentNode.id).to.equal("3");
});
it("CoA 8: makeChoice should dispatch trigger events", () => {
const triggerSpy = sinon.spy();
manager.addEventListener("narrative-trigger", triggerSpy);
const sequenceData = {
id: "TEST_SEQUENCE",
nodes: [
{
id: "1",
type: "CHOICE",
choices: [
{
text: "Option",
next: "2",
trigger: { action: "GAIN_REPUTATION", value: 10 },
},
],
},
{ id: "2", type: "DIALOGUE", next: "END" },
],
};
manager.startSequence(sequenceData);
manager.makeChoice(0);
expect(triggerSpy.called).to.be.true;
expect(triggerSpy.firstCall.args[0].detail.action).to.deep.equal({
action: "GAIN_REPUTATION",
value: 10,
});
});
it("CoA 9: _advanceToNode should process node triggers", () => {
const triggerSpy = sinon.spy();
manager.addEventListener("narrative-trigger", triggerSpy);
const sequenceData = {
id: "TEST_SEQUENCE",
nodes: [
{ id: "1", type: "DIALOGUE", next: "2" },
{
id: "2",
type: "DIALOGUE",
next: "END",
trigger: { action: "UNLOCK_MISSION" },
},
],
};
manager.startSequence(sequenceData);
manager.next();
expect(triggerSpy.called).to.be.true;
});
it("CoA 10: _advanceToNode should auto-advance ACTION nodes", () => {
const sequenceData = {
id: "TEST_SEQUENCE",
nodes: [
{ id: "1", type: "DIALOGUE", next: "2" },
{ id: "2", type: "ACTION", next: "3", trigger: { action: "DO_SOMETHING" } },
{ id: "3", type: "DIALOGUE", text: "After action", next: "END" },
],
};
manager.startSequence(sequenceData);
manager.next(); // Should skip ACTION node and go to 3
expect(manager.currentNode.id).to.equal("3");
});
it("CoA 11: endSequence should dispatch narrative-end event", () => {
const endSpy = sinon.spy();
manager.addEventListener("narrative-end", endSpy);
const sequenceData = {
id: "TEST_SEQUENCE",
nodes: [{ id: "1", type: "DIALOGUE", next: "END" }],
};
manager.startSequence(sequenceData);
manager.endSequence();
expect(endSpy.called).to.be.true;
expect(manager.currentSequence).to.be.null;
expect(manager.currentNode).to.be.null;
});
it("CoA 12: broadcastUpdate should dispatch narrative-update event", () => {
const updateSpy = sinon.spy();
manager.addEventListener("narrative-update", updateSpy);
const sequenceData = {
id: "TEST_SEQUENCE",
nodes: [{ id: "1", type: "DIALOGUE", text: "Test", next: "END" }],
};
manager.startSequence(sequenceData);
expect(updateSpy.called).to.be.true;
const eventDetail = updateSpy.firstCall.args[0].detail;
expect(eventDetail.node).to.equal(manager.currentNode);
expect(eventDetail.active).to.be.true;
});
it("CoA 13: _advanceToNode should handle missing nodes gracefully", () => {
const consoleError = sinon.stub(console, "error");
const endSpy = sinon.spy(manager, "endSequence");
const sequenceData = {
id: "TEST_SEQUENCE",
nodes: [{ id: "1", type: "DIALOGUE", next: "MISSING_NODE" }],
};
manager.startSequence(sequenceData);
manager.next();
expect(consoleError.called).to.be.true;
expect(endSpy.called).to.be.true;
consoleError.restore();
});
});