203 lines
7.2 KiB
TypeScript
203 lines
7.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
buildCustomWorldAnchorPackFromIntent,
|
|
buildCustomWorldCreatorIntentDisplayText,
|
|
buildCustomWorldCreatorIntentFoundationText,
|
|
buildPendingClarifications,
|
|
createEmptyCustomWorldCreatorIntent,
|
|
evaluateCustomWorldCreatorIntentReadiness,
|
|
mergeCustomWorldCreatorIntent,
|
|
normalizeCustomWorldCreatorIntent,
|
|
} from './customWorldCreatorIntent';
|
|
|
|
describe('customWorldCreatorIntent', () => {
|
|
it('builds a readable summary from creator intent cards', () => {
|
|
const intent = {
|
|
...createEmptyCustomWorldCreatorIntent('card'),
|
|
worldHook: '一个会被灵潮反复改写地形的边境世界。',
|
|
themeKeywords: ['边境', '灵潮'],
|
|
toneDirectives: ['紧张', '潮湿'],
|
|
playerPremise: '玩家是带着旧名单回来的前巡夜人。',
|
|
coreConflicts: ['旧案名单再次出现'],
|
|
keyCharacters: [
|
|
{
|
|
id: 'character-1',
|
|
name: '沈砺',
|
|
role: '灰炬向导',
|
|
publicMask: '看起来只是熟路的带路人',
|
|
hiddenHook: '他一直在追查撤离线失控真相',
|
|
relationToPlayer: '会先怀疑玩家身份',
|
|
notes: '',
|
|
locked: true,
|
|
},
|
|
],
|
|
};
|
|
|
|
const summary = buildCustomWorldCreatorIntentDisplayText(intent);
|
|
|
|
expect(summary).toContain(
|
|
'世界一句话:一个会被灵潮反复改写地形的边境世界。',
|
|
);
|
|
expect(summary).toContain('主题关键词:边境、灵潮');
|
|
expect(summary).toContain('关键角色:沈砺 / 灰炬向导');
|
|
});
|
|
|
|
it('builds six-anchor foundation text from structured creator intent', () => {
|
|
const intent = {
|
|
...createEmptyCustomWorldCreatorIntent('card'),
|
|
worldHook: '一个会被灵潮反复改写地形的边境世界。',
|
|
themeKeywords: ['边境', '灵潮'],
|
|
toneDirectives: ['紧张', '潮湿'],
|
|
playerPremise: '玩家是带着旧名单回来的前巡夜人。',
|
|
openingSituation: '返乡第一夜,封锁线外出现了本不该存在的灯火。',
|
|
coreConflicts: ['旧案名单再次出现'],
|
|
keyCharacters: [
|
|
{
|
|
id: 'character-1',
|
|
name: '沈砺',
|
|
role: '灰炬向导',
|
|
publicMask: '看起来只是熟路的带路人',
|
|
hiddenHook: '他一直在追查撤离线失控真相',
|
|
relationToPlayer: '会先怀疑玩家身份',
|
|
notes: '',
|
|
locked: true,
|
|
},
|
|
],
|
|
iconicElements: ['会逆向蔓延的潮雾'],
|
|
};
|
|
|
|
const foundationText = buildCustomWorldCreatorIntentFoundationText(intent);
|
|
|
|
expect(foundationText).toContain(
|
|
'世界一句话:一个会被灵潮反复改写地形的边境世界。',
|
|
);
|
|
expect(foundationText).toContain('玩家开局:玩家是带着旧名单回来的前巡夜人。');
|
|
expect(foundationText).toContain('主题气质:边境、灵潮 / 紧张、潮湿');
|
|
expect(foundationText).toContain('关键关系:沈砺 · 灰炬向导');
|
|
expect(foundationText).toContain('标志元素:会逆向蔓延的潮雾');
|
|
});
|
|
|
|
it('builds anchor pack from creator intent and keeps locked ids', () => {
|
|
const intent = {
|
|
...createEmptyCustomWorldCreatorIntent('card'),
|
|
worldHook: '边境世界',
|
|
coreConflicts: ['裂潮失控'],
|
|
keyFactions: [
|
|
{
|
|
id: 'faction-1',
|
|
name: '巡边司',
|
|
publicGoal: '维持边境秩序',
|
|
tension: '正在被旧案拖入裂潮',
|
|
notes: '',
|
|
locked: true,
|
|
},
|
|
],
|
|
keyLandmarks: [
|
|
{
|
|
id: 'landmark-1',
|
|
name: '断桥旧哨',
|
|
purpose: '边境咽喉',
|
|
mood: '压迫',
|
|
secret: '封桥旧令来源不明',
|
|
locked: true,
|
|
},
|
|
],
|
|
};
|
|
|
|
const anchorPack = buildCustomWorldAnchorPackFromIntent(intent);
|
|
|
|
expect(anchorPack?.keyConflictSummaries).toEqual(['裂潮失控']);
|
|
expect(anchorPack?.keyFactionSummaries[0]).toContain('巡边司');
|
|
expect(anchorPack?.lockedAnchorIds).toEqual(
|
|
expect.arrayContaining(['faction-1', 'landmark-1']),
|
|
);
|
|
});
|
|
|
|
it('normalizes sparse creator intent payloads', () => {
|
|
const intent = normalizeCustomWorldCreatorIntent({
|
|
sourceMode: 'card',
|
|
worldHook: '雾海边城',
|
|
themeKeywords: ['雾海', '旧案'],
|
|
keyCharacters: [
|
|
{
|
|
name: '梁砺',
|
|
role: '断桥巡守',
|
|
},
|
|
],
|
|
});
|
|
|
|
expect(intent?.sourceMode).toBe('card');
|
|
expect(intent?.keyCharacters[0]?.name).toBe('梁砺');
|
|
expect(intent?.keyCharacters[0]?.id).toBeTruthy();
|
|
});
|
|
|
|
it('merges creator intent patches without dropping unrelated anchors', () => {
|
|
const baseIntent = {
|
|
...createEmptyCustomWorldCreatorIntent('freeform'),
|
|
worldHook: '潮雾会改写地形的列岛世界。',
|
|
playerPremise: '玩家是失职返乡的守灯人。',
|
|
};
|
|
|
|
const merged = mergeCustomWorldCreatorIntent(baseIntent, {
|
|
coreConflicts: ['守灯会与沉船商盟争夺航道解释权'],
|
|
toneDirectives: ['冷峻'],
|
|
});
|
|
if (!merged) {
|
|
throw new Error('expected merged creator intent');
|
|
}
|
|
|
|
expect(merged.worldHook).toBe('潮雾会改写地形的列岛世界。');
|
|
expect(merged.playerPremise).toBe('玩家是失职返乡的守灯人。');
|
|
expect(merged.coreConflicts).toEqual(['守灯会与沉船商盟争夺航道解释权']);
|
|
expect(merged.toneDirectives).toEqual(['冷峻']);
|
|
});
|
|
|
|
it('replaces array anchors when a patch marks explicit rewrite fields', () => {
|
|
const merged = mergeCustomWorldCreatorIntent(
|
|
{
|
|
...createEmptyCustomWorldCreatorIntent('freeform'),
|
|
themeKeywords: ['海岛', '旧案'],
|
|
coreConflicts: ['守灯会与沉船商盟争夺航道解释权'],
|
|
},
|
|
{
|
|
themeKeywords: ['宫廷', '悬疑'],
|
|
coreConflicts: ['王庭继承人与旧灯塔盟约对抗'],
|
|
replaceFields: ['themeKeywords', 'coreConflicts'],
|
|
},
|
|
);
|
|
if (!merged) {
|
|
throw new Error('expected merged creator intent');
|
|
}
|
|
|
|
expect(merged.themeKeywords).toEqual(['宫廷', '悬疑']);
|
|
expect(merged.coreConflicts).toEqual(['王庭继承人与旧灯塔盟约对抗']);
|
|
});
|
|
|
|
it('evaluates readiness and limits clarifications to top gaps', () => {
|
|
const readiness = evaluateCustomWorldCreatorIntentReadiness({
|
|
...createEmptyCustomWorldCreatorIntent('freeform'),
|
|
worldHook: '一个被潮雾切开的列岛世界。',
|
|
themeKeywords: ['海岛'],
|
|
toneDirectives: ['冷峻'],
|
|
coreConflicts: ['旧灯塔正在被沉船商盟接管'],
|
|
});
|
|
const clarifications = buildPendingClarifications(
|
|
{
|
|
...createEmptyCustomWorldCreatorIntent('freeform'),
|
|
worldHook: '一个被潮雾切开的列岛世界。',
|
|
themeKeywords: ['海岛'],
|
|
toneDirectives: ['冷峻'],
|
|
coreConflicts: ['旧灯塔正在被沉船商盟接管'],
|
|
},
|
|
readiness,
|
|
);
|
|
|
|
expect(readiness.isReady).toBe(false);
|
|
expect(readiness.completedKeys).toContain('world_hook');
|
|
expect(readiness.missingKeys).toContain('player_premise');
|
|
expect(clarifications).toHaveLength(3);
|
|
expect(clarifications[0]?.targetKey).toBe('player_premise');
|
|
});
|
|
});
|