chore: add tests for data loading

This commit is contained in:
hypercross 2026-04-15 13:27:12 +08:00
parent 726856af35
commit 33095d5226
6 changed files with 216 additions and 2 deletions

View File

@ -0,0 +1,10 @@
type EffectDesertTable = readonly {
readonly id: string;
readonly name: string;
readonly description: string;
}[];
export type EffectDesert = EffectDesertTable[number];
declare const data: EffectDesertTable;
export default data;

View File

@ -1,7 +1,11 @@
import type { EnemyDesert } from './enemyDesert.csv';
type EncounterDesertTable = readonly {
readonly type: "minion" | "elite" | "event" | "shop" | "camp" | "curio";
readonly name: string;
readonly description: string;
readonly enemies: readonly [EnemyDesert, number];
readonly dialogue: string;
}[];
export type EncounterDesert = EncounterDesertTable[number];

View File

@ -0,0 +1,11 @@
type EnemyDesertTable = readonly {
readonly id: string;
readonly initHp: number;
readonly initBuffs: readonly [string, number];
readonly initialIntent: string;
}[];
export type EnemyDesert = EnemyDesertTable[number];
declare const data: EnemyDesertTable;
export default data;

View File

@ -0,0 +1,15 @@
import type { EnemyDesert } from './enemyDesert.csv';
import type { EffectDesert } from './effectDesert.csv';
type EnemyIntentDesertTable = readonly {
readonly enemy: EnemyDesert;
readonly intent: string;
readonly nextIntents: readonly string[];
readonly brokenIntent: readonly string[];
readonly effects: readonly ["self" | "opponent", EffectDesert, number];
}[];
export type EnemyIntentDesert = EnemyIntentDesertTable[number];
declare const data: EnemyIntentDesertTable;
export default data;

View File

@ -1,6 +1,16 @@
import heroItemFighter1Csv from './heroItemFighter1.csv';
import encounterDesertCsv from './encounterDesert.csv';
import enemyDesertCsv from './enemyDesert.csv';
import enemyIntentDesertCsv from './enemyIntentDesert.csv';
import effectDesertCsv from './effectDesert.csv';
export const heroItemFighter1Data = heroItemFighter1Csv;
export const encounterDesertData = encounterDesertCsv;
export const enemyDesertData = enemyDesertCsv;
export const enemyIntentDesertData = enemyIntentDesertCsv;
export const effectDesertData = effectDesertCsv;
export { default as encounterDesertCsv, type EncounterDesert } from './encounterDesert.csv';
export { default as enemyDesertCsv, type EnemyDesert } from './enemyDesert.csv';
export { default as enemyIntentDesertCsv, type EnemyIntentDesert } from './enemyIntentDesert.csv';
export { default as effectDesertCsv, type EffectDesert } from './effectDesert.csv';

View File

@ -1,5 +1,11 @@
import { describe, it, expect } from 'vitest';
import { heroItemFighter1Data } from '@/samples/slay-the-spire-like/data';
import {
heroItemFighter1Data,
encounterDesertData,
enemyDesertData,
enemyIntentDesertData,
effectDesertData,
} from '@/samples/slay-the-spire-like/data';
describe('heroItemFighter1.csv import', () => {
it('should import data as an array', () => {
@ -8,7 +14,6 @@ describe('heroItemFighter1.csv import', () => {
});
it('should have expected number of items', () => {
// CSV has 24 data rows (excluding header and type rows)
expect(heroItemFighter1Data.length).toBe(24);
});
@ -71,3 +76,162 @@ describe('heroItemFighter1.csv import', () => {
expect(typeCounts['tool']).toBe(6);
});
});
describe('encounterDesert.csv import', () => {
it('should import data as an array', () => {
expect(Array.isArray(encounterDesertData)).toBe(true);
expect(encounterDesertData.length).toBeGreaterThan(0);
});
it('should have correct encounter type counts', () => {
const typeCounts = encounterDesertData.reduce((acc, e) => {
acc[e.type] = (acc[e.type] || 0) + 1;
return acc;
}, {} as Record<string, number>);
expect(typeCounts['minion']).toBe(10);
expect(typeCounts['elite']).toBe(4);
expect(typeCounts['shop']).toBe(2);
expect(typeCounts['camp']).toBe(2);
expect(typeCounts['curio']).toBe(8);
expect(typeCounts['event']).toBe(1);
});
it('should have enemies for combat encounters', () => {
for (const e of encounterDesertData) {
if (e.type === 'minion' || e.type === 'elite') {
expect(Array.isArray(e.enemies)).toBe(true);
expect(e.enemies.length).toBeGreaterThan(0);
for (const [enemyId, bonusHp] of e.enemies) {
expect(typeof enemyId).toBe('string');
expect(typeof bonusHp).toBe('number');
}
}
}
});
it('should have empty enemies for non-combat encounters', () => {
for (const e of encounterDesertData) {
if (e.type === 'shop' || e.type === 'camp') {
expect(e.enemies.length).toBe(0);
}
}
});
it('should have dialogue for curio and event encounters', () => {
for (const e of encounterDesertData) {
if (e.type === 'curio' || e.type === 'event') {
expect(e.dialogue).toBeTruthy();
expect(e.dialogue.startsWith('desert_')).toBe(true);
}
}
});
});
describe('effectDesert.csv import', () => {
it('should import data as an array', () => {
expect(Array.isArray(effectDesertData)).toBe(true);
expect(effectDesertData.length).toBeGreaterThan(0);
});
it('should have expected number of effects', () => {
expect(effectDesertData.length).toBe(19);
});
it('should have correct fields for each effect', () => {
for (const effect of effectDesertData) {
expect(effect).toHaveProperty('id');
expect(effect).toHaveProperty('name');
expect(effect).toHaveProperty('description');
}
});
it('should contain core effect types', () => {
const ids = effectDesertData.map(e => e.id);
expect(ids).toContain('attack');
expect(ids).toContain('defend');
expect(ids).toContain('spike');
expect(ids).toContain('venom');
});
});
describe('enemyDesert.csv import', () => {
it('should import data as an array', () => {
expect(Array.isArray(enemyDesertData)).toBe(true);
expect(enemyDesertData.length).toBeGreaterThan(0);
});
it('should have expected number of enemies', () => {
expect(enemyDesertData.length).toBe(14);
});
it('should have correct fields for each enemy', () => {
for (const enemy of enemyDesertData) {
expect(enemy).toHaveProperty('id');
expect(enemy).toHaveProperty('initHp');
expect(enemy).toHaveProperty('initBuffs');
expect(enemy).toHaveProperty('initialIntent');
expect(typeof enemy.initHp).toBe('number');
expect(typeof enemy.initialIntent).toBe('string');
}
});
it('should have valid HP ranges', () => {
for (const enemy of enemyDesertData) {
expect(enemy.initHp).toBeGreaterThan(0);
}
});
it('should have minions with lower HP than elites', () => {
const minionIds = ['仙人掌怪', '蛇', '木乃伊', '枪手', '风卷草', '秃鹫', '沙蝎', '幼沙虫', '蜥蜴', '沙匪'];
const eliteIds = ['风暴之灵', '骑马枪手', '沙虫王', '沙漠守卫'];
const byId = Object.fromEntries(enemyDesertData.map(e => [e.id, e]));
for (const id of minionIds) {
expect(byId[id].initHp).toBeLessThan(40);
}
for (const id of eliteIds) {
expect(byId[id].initHp).toBeGreaterThanOrEqual(40);
}
});
});
describe('enemyIntentDesert.csv import', () => {
it('should import data as an array', () => {
expect(Array.isArray(enemyIntentDesertData)).toBe(true);
expect(enemyIntentDesertData.length).toBeGreaterThan(0);
});
it('should have expected number of intent rows', () => {
expect(enemyIntentDesertData.length).toBe(41);
});
it('should have correct fields for each intent', () => {
for (const intent of enemyIntentDesertData) {
expect(intent).toHaveProperty('enemy');
expect(intent).toHaveProperty('intent');
expect(intent).toHaveProperty('nextIntents');
expect(intent).toHaveProperty('brokenIntent');
expect(intent).toHaveProperty('effects');
expect(intent.enemy).toHaveProperty('id');
expect(Array.isArray(intent.nextIntents)).toBe(true);
expect(Array.isArray(intent.brokenIntent)).toBe(true);
expect(Array.isArray(intent.effects)).toBe(true);
}
});
it('should have effects with target, effect ref, and value', () => {
for (const intent of enemyIntentDesertData) {
for (const [target, effectId, value] of intent.effects) {
expect(target === 'self' || target === 'opponent').toBe(true);
expect(typeof effectId).toBe('string');
expect(typeof value).toBe('number');
}
}
});
it('should cover all 14 enemies', () => {
const enemyIds = new Set(enemyIntentDesertData.map(i => typeof i.enemy === 'string' ? i.enemy : i.enemy.id));
expect(enemyIds.size).toBe(14);
});
});