import { describe, expect, it } from 'vitest';
import type { DialogueGraph } from '@rpg/engine';
import { analyzeGraph, checkGraph, type GraphRefs } from '../dialogueRules';
const refs = (): GraphRefs => ({
flags: new Set(['met_elder', 'quest_bells_taken']),
vars: new Set(['flowers']),
items: new Set(['cloth']),
customs: new Set(['plant_flowers']),
strings: new Set(['line.key'])
});
describe('checkGraph — ссылки и структура', () => {
it('чистый граф — ошибок нет', () => {
const g: DialogueGraph = {
start: 'a',
nodes: { a: { text: 'Привет', next: 'b' }, b: { text: 'Пока' } }
};
expect(checkGraph('g', g, refs()).filter((i) => i.severity === 'error')).toEqual([]);
});
it('битые ссылки: флаг, вар, предмет, custom, textKey, next, start', () => {
const g: DialogueGraph = {
start: 'a',
nodes: {
a: {
text: 'x',
when: ['нет_такого'],
whenVars: [{ key: 'нет_вара', op: 'ge', value: 1 }],
hasItem: ['salt'],
do: [
{ kind: 'giveItem', id: 'нет_предмета' },
{ kind: 'custom', id: 'нет_эффекта' }
],
textKey: 'нет.строки',
next: 'в_никуда'
}
}
};
const ids = checkGraph('g', g, refs()).map((i) => i.id);
expect(ids).toContain('flag-unknown');
expect(ids).toContain('var-unknown');
expect(ids).toContain('item-unknown');
expect(ids).toContain('do-item-unknown');
expect(ids).toContain('do-custom-unknown');
expect(ids).toContain('string-unknown');
expect(ids).toContain('dialogue-next');
// битый start
const ids2 = checkGraph('g', { start: 'нет', nodes: {} }, refs()).map((i) => i.id);
expect(ids2).toContain('dialogue-start');
});
it('правильные ссылки проходят, упоминания пишутся в used-множества', () => {
const usedFlags = new Set<string>();
const usedVars = new Set<string>();
const g: DialogueGraph = {
start: 'a',
nodes: {
a: {
when: ['met_elder'],
whenVars: [{ key: 'flowers', op: 'ge', value: 3 }],
setFlags: ['quest_bells_taken'],
do: [{ kind: 'giveItem', id: 'cloth' }, { kind: 'custom', id: 'plant_flowers' }],
text: 'ok'
}
}
};
expect(checkGraph('g', g, { ...refs(), usedFlags, usedVars })).toEqual([]);
expect([...usedFlags]).toEqual(['met_elder', 'quest_bells_taken']);
expect([...usedVars]).toEqual(['flowers']);
});
});
describe('analyzeGraph — достижимость', () => {
it('сироты и тихие концы', () => {
const g: DialogueGraph = {
start: 'a',
nodes: {
a: { text: 'ok', next: 'silent' },
lost: { text: 'никто не ссылается' },
silent: { setFlags: ['met_elder'] } // нет next и текста — молча кончается
}
};
const a = analyzeGraph(g);
expect(a.reachable).toEqual(['a', 'silent']);
expect(a.orphans).toEqual(['lost']);
expect(a.silentEnds).toEqual(['silent']);
});
it('цикл без текста — error, цикл с текстом — не цикл-ошибка', () => {
const g: DialogueGraph = {
start: 'a',
nodes: {
a: { text: 'Слово', next: 'hub' },
hub: { next: 'hub' } // действие само в себя
}
};
const a = analyzeGraph(g);
expect(a.textlessCycles).toEqual([['hub']]);
expect(a.silentEnds).toEqual([]);
});
it('выборы считаются рёбрами: цель выбора достижима', () => {
const g: DialogueGraph = {
start: 'q',
nodes: {
q: { text: '?', choices: [{ text: 'Да', next: 'yes' }] },
yes: { text: 'Да' }
}
};
const a = analyzeGraph(g);
expect(a.orphans).toEqual([]);
expect(a.reachable).toEqual(['q', 'yes']);
});
});