chore: remove old tests

This commit is contained in:
hypercross 2026-04-17 10:20:04 +08:00
parent 1c238aec3a
commit 4deebf67c3
6 changed files with 4 additions and 1522 deletions

View File

@ -1,344 +0,0 @@
import { describe, it, expect } from 'vitest';
import {
applyDamage,
applyDefend,
applyBuff,
removeBuff,
updateBuffs,
canPlayCard,
playCard,
areAllEnemiesDead,
isPlayerDead,
getModifiedAttackDamage,
getModifiedDefendAmount,
} from '@/samples/slay-the-spire-like/combat/effects';
import type { CombatState, PlayerCombatState, EnemyState } from '@/samples/slay-the-spire-like/combat/types';
import {
createCombatState,
createEnemyInstance,
createPlayerCombatState,
drawCardsToHand,
} from '@/samples/slay-the-spire-like/combat/state';
import { createGridInventory, placeItem } from '@/samples/slay-the-spire-like/grid-inventory';
import type { GridInventory, InventoryItem } from '@/samples/slay-the-spire-like/grid-inventory';
import type { GameItemMeta, PlayerState } from '@/samples/slay-the-spire-like/progress/types';
import { IDENTITY_TRANSFORM } from '@/samples/slay-the-spire-like/utils/shape-collision';
import { parseShapeString } from '@/samples/slay-the-spire-like/utils/parse-shape';
import { enemyDesertData, encounterDesertData } from '@/samples/slay-the-spire-like/data';
import { Mulberry32RNG } from '@/utils/rng';
function createTestMeta(name: string, shapeStr: string): GameItemMeta {
const shape = parseShapeString(shapeStr);
return {
itemData: {
type: 'weapon',
name,
shape: shapeStr,
costType: 'energy',
costCount: 1,
targetType: 'single',
price: 10,
desc: '测试',
},
shape,
};
}
function createTestInventory(): GridInventory<GameItemMeta> {
const inv = createGridInventory<GameItemMeta>(6, 4);
const meta1 = createTestMeta('短刀', 'oe');
const item1: InventoryItem<GameItemMeta> = {
id: 'item-1',
shape: meta1.shape,
transform: { ...IDENTITY_TRANSFORM, offset: { x: 0, y: 0 } },
meta: meta1,
};
placeItem(inv, item1);
return inv;
}
function createTestCombatState(): CombatState {
const inv = createTestInventory();
const playerState: PlayerState = { maxHp: 50, currentHp: 50, gold: 0 };
const encounter = encounterDesertData.find(e => e.name === '仙人掌怪')!;
return createCombatState(playerState, inv, encounter);
}
function createSimpleRng() {
return new Mulberry32RNG(42);
}
describe('combat/effects', () => {
describe('applyDamage', () => {
it('should deal damage to player', () => {
const state = createTestCombatState();
applyDamage(state, 'player', 10);
expect(state.player.hp).toBe(40);
expect(state.player.damageTakenThisTurn).toBe(10);
expect(state.player.damagedThisTurn).toBe(true);
});
it('should deal damage to enemy', () => {
const state = createTestCombatState();
const enemyId = state.enemyOrder[0];
const enemy = state.enemies[enemyId];
const initialHp = enemy.hp;
applyDamage(state, enemyId, 5);
expect(enemy.hp).toBe(initialHp - 5);
});
it('should be absorbed by defend buff on player', () => {
const state = createTestCombatState();
state.player.buffs['defend'] = 3;
const result = applyDamage(state, 'player', 5);
expect(result.blockedByDefend).toBe(3);
expect(result.damageDealt).toBe(2);
expect(state.player.hp).toBe(48);
});
it('should be fully absorbed by defend buff', () => {
const state = createTestCombatState();
state.player.buffs['defend'] = 10;
applyDamage(state, 'player', 5);
expect(state.player.hp).toBe(50);
expect(state.player.buffs['defend']).toBe(5);
});
it('should be absorbed by defend buff on enemy', () => {
const state = createTestCombatState();
const enemyId = state.enemyOrder[0];
state.enemies[enemyId].buffs['defend'] = 4;
const result = applyDamage(state, enemyId, 6);
expect(result.blockedByDefend).toBe(4);
expect(result.damageDealt).toBe(2);
expect(state.enemies[enemyId].hp).toBe(state.enemies[enemyId].maxHp - 2);
});
it('should mark defend broken when defend fully consumed', () => {
const state = createTestCombatState();
const enemyId = state.enemyOrder[0];
state.enemies[enemyId].buffs['defend'] = 3;
applyDamage(state, enemyId, 5);
expect(state.enemies[enemyId].hadDefendBroken).toBe(true);
});
it('should kill enemy when HP reaches 0', () => {
const state = createTestCombatState();
const enemyId = state.enemyOrder[0];
applyDamage(state, enemyId, state.enemies[enemyId].maxHp);
expect(state.enemies[enemyId].isAlive).toBe(false);
expect(state.enemies[enemyId].hp).toBe(0);
});
it('should not deal negative damage', () => {
const state = createTestCombatState();
const result = applyDamage(state, 'player', -5);
expect(result.damageDealt).toBe(0);
expect(state.player.hp).toBe(50);
});
it('should apply damageReduce buff', () => {
const state = createTestCombatState();
state.player.buffs['damageReduce'] = 3;
applyDamage(state, 'player', 5);
expect(state.player.hp).toBe(48);
});
});
describe('applyDefend', () => {
it('should add defend stacks', () => {
const buffs: Record<string, number> = {};
applyDefend(buffs, 5);
expect(buffs['defend']).toBe(5);
});
it('should stack with existing defend', () => {
const buffs: Record<string, number> = { defend: 3 };
applyDefend(buffs, 4);
expect(buffs['defend']).toBe(7);
});
});
describe('applyBuff / removeBuff', () => {
it('should apply buff stacks', () => {
const buffs: Record<string, number> = {};
applyBuff(buffs, 'aim', 'lingering', 3);
expect(buffs['aim']).toBe(3);
});
it('should stack existing buffs', () => {
const buffs: Record<string, number> = { aim: 2 };
applyBuff(buffs, 'aim', 'lingering', 3);
expect(buffs['aim']).toBe(5);
});
it('should remove buff partially', () => {
const buffs: Record<string, number> = { aim: 5 };
const removed = removeBuff(buffs, 'aim', 3);
expect(removed).toBe(3);
expect(buffs['aim']).toBe(2);
});
it('should remove buff fully when stacks exceed current', () => {
const buffs: Record<string, number> = { aim: 2 };
const removed = removeBuff(buffs, 'aim', 10);
expect(removed).toBe(2);
expect(buffs['aim']).toBeUndefined();
});
});
describe('updateBuffs', () => {
it('should clear temporary buffs', () => {
const buffs: Record<string, number> = { damageReduce: 3, defendNext: 2 };
updateBuffs(buffs);
expect(buffs['damageReduce']).toBeUndefined();
expect(buffs['defendNext']).toBeUndefined();
});
it('should decrement lingering buffs', () => {
const buffs: Record<string, number> = { curse: 3, energyDrain: 1 };
updateBuffs(buffs);
expect(buffs['curse']).toBe(2);
expect(buffs['energyDrain']).toBeUndefined();
});
it('should not affect permanent or posture buffs', () => {
const buffs: Record<string, number> = { defend: 5, spike: 1 };
updateBuffs(buffs);
expect(buffs['defend']).toBe(5);
expect(buffs['spike']).toBe(1);
});
});
describe('canPlayCard', () => {
it('should allow playing card with enough energy', () => {
const state = createTestCombatState();
const cardId = state.player.deck.hand[0];
const result = canPlayCard(state, cardId);
expect(result.canPlay).toBe(true);
});
it('should reject card not in hand', () => {
const state = createTestCombatState();
const result = canPlayCard(state, 'nonexistent-card');
expect(result.canPlay).toBe(false);
});
it('should reject card with insufficient energy', () => {
const state = createTestCombatState();
state.player.energy = 0;
const cardId = state.player.deck.hand[0];
const card = state.player.deck.cards[cardId];
if (card?.itemData?.costType === 'energy' && card.itemData.costCount > 0) {
const result = canPlayCard(state, cardId);
expect(result.canPlay).toBe(false);
expect(result.reason).toBe('能量不足');
}
});
});
describe('playCard', () => {
it('should deduct energy cost when playing card', () => {
const state = createTestCombatState();
const cardId = state.player.deck.hand[0];
const card = state.player.deck.cards[cardId];
const initialEnergy = state.player.energy;
if (card?.itemData?.costType === 'energy') {
const ctx = { state, rng: createSimpleRng() };
const result = playCard(ctx, cardId);
if (result.success) {
expect(state.player.energy).toBe(initialEnergy - card.itemData.costCount);
}
}
});
it('should move card to discard pile after playing', () => {
const state = createTestCombatState();
const cardId = state.player.deck.hand[0];
const ctx = { state, rng: createSimpleRng() };
const result = playCard(ctx, cardId);
if (result.success) {
expect(state.player.deck.hand.includes(cardId)).toBe(false);
expect(state.player.deck.discardPile.includes(cardId) || state.player.deck.exhaustPile.includes(cardId)).toBe(true);
}
});
});
describe('areAllEnemiesDead / isPlayerDead', () => {
it('should detect all enemies dead', () => {
const state = createTestCombatState();
expect(areAllEnemiesDead(state)).toBe(false);
for (const enemyId of state.enemyOrder) {
state.enemies[enemyId].isAlive = false;
}
expect(areAllEnemiesDead(state)).toBe(true);
});
it('should detect player death', () => {
const state = createTestCombatState();
expect(isPlayerDead(state)).toBe(false);
state.player.hp = 0;
expect(isPlayerDead(state)).toBe(true);
});
});
describe('getModifiedAttackDamage / getModifiedDefendAmount', () => {
it('should return base damage with no item buffs', () => {
const state = createTestCombatState();
expect(getModifiedAttackDamage(state, 'some-card', 5)).toBe(5);
});
it('should return base defend with no item buffs', () => {
const state = createTestCombatState();
expect(getModifiedDefendAmount(state, 'some-card', 4)).toBe(4);
});
it('should add item buff attack damage', () => {
const state = createTestCombatState();
state.itemBuffs.push({
effectId: 'attackBuff',
stacks: 3,
timing: 'itemUntilPlayed',
sourceItemId: 'item-1',
targetItemId: 'item-1',
});
expect(getModifiedAttackDamage(state, 'some-card', 5)).toBe(5);
});
});
});

View File

@ -1,261 +0,0 @@
import { describe, it, expect } from 'vitest';
import { createGameHost, GameHost } from '@/core/game-host';
import { createGameContext, createGameCommandRegistry } from '@/core/game';
import type { CombatState, CombatGameContext } from '@/samples/slay-the-spire-like/combat/types';
import { createCombatState } from '@/samples/slay-the-spire-like/combat/state';
import { runCombat } from '@/samples/slay-the-spire-like/combat/procedure';
import { prompts } from '@/samples/slay-the-spire-like/combat/prompts';
import { createGridInventory, placeItem } from '@/samples/slay-the-spire-like/grid-inventory';
import type { GridInventory, InventoryItem } from '@/samples/slay-the-spire-like/grid-inventory';
import type { GameItemMeta, PlayerState } from '@/samples/slay-the-spire-like/progress/types';
import { IDENTITY_TRANSFORM } from '@/samples/slay-the-spire-like/utils/shape-collision';
import { parseShapeString } from '@/samples/slay-the-spire-like/utils/parse-shape';
import { encounterDesertData, enemyDesertData } from '@/samples/slay-the-spire-like/data';
function createTestMeta(name: string, shapeStr: string): GameItemMeta {
const shape = parseShapeString(shapeStr);
return {
itemData: {
type: 'weapon',
name,
shape: shapeStr,
costType: 'energy',
costCount: 1,
targetType: 'single',
price: 10,
desc: '测试',
},
shape,
};
}
function createTestInventory(): GridInventory<GameItemMeta> {
const inv = createGridInventory<GameItemMeta>(6, 4);
const meta1 = createTestMeta('短刀', 'oe');
const item1: InventoryItem<GameItemMeta> = {
id: 'item-1',
shape: meta1.shape,
transform: { ...IDENTITY_TRANSFORM, offset: { x: 0, y: 0 } },
meta: meta1,
};
placeItem(inv, item1);
return inv;
}
function createTestCombatState(): CombatState {
const inv = createTestInventory();
const playerState: PlayerState = { maxHp: 50, currentHp: 50, gold: 0 };
const encounter = encounterDesertData.find(e => e.name === '仙人掌怪')!;
return createCombatState(playerState, inv, encounter);
}
function waitForPrompt(host: GameHost<CombatState>): Promise<void> {
return new Promise((resolve) => {
const check = () => {
if (host.activePromptSchema.value !== null) {
resolve();
} else {
setTimeout(check, 10);
}
};
check();
});
}
describe('combat/procedure', () => {
describe('runCombat with GameHost', () => {
it('should start combat and prompt for player action', async () => {
const registry = createGameCommandRegistry<CombatState>();
const initialState = createTestCombatState();
const host = new GameHost(
registry,
() => createTestCombatState(),
async (ctx) => {
return await runCombat(ctx);
},
);
const combatPromise = host.start(42);
await waitForPrompt(host);
expect(host.activePromptSchema.value).not.toBeNull();
expect(host.activePromptSchema.value?.name).toBe('play-card');
host._context._commands._cancel();
try { await combatPromise; } catch {}
});
it('should accept play-card input', async () => {
const registry = createGameCommandRegistry<CombatState>();
const host = new GameHost(
registry,
() => createTestCombatState(),
async (ctx) => {
return await runCombat(ctx);
},
);
const combatPromise = host.start(42);
await waitForPrompt(host);
const state = host.state.value;
const cardId = state.player.deck.hand[0];
const error = host.tryAnswerPrompt(prompts.playCard, cardId, state.enemyOrder[0]);
expect(error).toBeNull();
host._context._commands._cancel();
try { await combatPromise; } catch {}
});
it('should reject invalid card play', async () => {
const registry = createGameCommandRegistry<CombatState>();
const host = new GameHost(
registry,
() => createTestCombatState(),
async (ctx) => {
return await runCombat(ctx);
},
);
const combatPromise = host.start(42);
await waitForPrompt(host);
const error = host.tryAnswerPrompt(prompts.playCard, 'nonexistent-card');
expect(error).not.toBeNull();
host._context._commands._cancel();
try { await combatPromise; } catch {}
});
it('should transition to end-turn after playing cards', async () => {
const registry = createGameCommandRegistry<CombatState>();
const host = new GameHost(
registry,
() => createTestCombatState(),
async (ctx) => {
return await runCombat(ctx);
},
);
const combatPromise = host.start(42);
await waitForPrompt(host);
const state = host.state.value;
const cardId = state.player.deck.hand[0];
host.tryAnswerPrompt(prompts.playCard, cardId, state.enemyOrder[0]);
await waitForPrompt(host);
expect(host.activePromptSchema.value).not.toBeNull();
host._context._commands._cancel();
try { await combatPromise; } catch {}
});
it('should accept end-turn', async () => {
const registry = createGameCommandRegistry<CombatState>();
const host = new GameHost(
registry,
() => createTestCombatState(),
async (ctx) => {
return await runCombat(ctx);
},
);
const combatPromise = host.start(42);
await waitForPrompt(host);
const error = host.tryAnswerPrompt(prompts.endTurn);
expect(error).toBeNull();
host._context._commands._cancel();
try { await combatPromise; } catch {}
});
});
describe('combat outcome', () => {
it('should return victory when all enemies are dead', async () => {
const registry = createGameCommandRegistry<CombatState>();
const host = new GameHost(
registry,
() => {
const state = createTestCombatState();
for (const enemyId of state.enemyOrder) {
state.enemies[enemyId].hp = 1;
}
return state;
},
async (ctx) => {
return await runCombat(ctx);
},
);
const combatPromise = host.start(42);
await waitForPrompt(host);
let iterations = 0;
while (host.status.value === 'running' && iterations < 100) {
const state = host.state.value;
if (host.activePromptSchema.value?.name === 'play-card') {
const cardId = state.player.deck.hand[0];
if (cardId) {
const targetId = state.enemyOrder.find(id => state.enemies[id].isAlive);
host.tryAnswerPrompt(prompts.playCard, cardId, targetId);
}
} else if (host.activePromptSchema.value?.name === 'end-turn') {
host.tryAnswerPrompt(prompts.endTurn);
}
await new Promise(r => setTimeout(r, 10));
iterations++;
}
if (host.status.value === 'running') {
host._context._commands._cancel();
try { await combatPromise; } catch {}
}
});
});
describe('combat state transitions', () => {
it('should track turn number across turns', async () => {
const registry = createGameCommandRegistry<CombatState>();
const host = new GameHost(
registry,
() => createTestCombatState(),
async (ctx) => {
return await runCombat(ctx);
},
);
const combatPromise = host.start(42);
await waitForPrompt(host);
host.tryAnswerPrompt(prompts.endTurn);
await waitForPrompt(host);
host._context._commands._cancel();
try { await combatPromise; } catch {}
});
it('should reset energy at start of player turn', async () => {
const registry = createGameCommandRegistry<CombatState>();
const host = new GameHost(
registry,
() => createTestCombatState(),
async (ctx) => {
return await runCombat(ctx);
},
);
const combatPromise = host.start(42);
await waitForPrompt(host);
const state = host.state.value;
expect(state.player.energy).toBe(state.player.maxEnergy);
host._context._commands._cancel();
try { await combatPromise; } catch {}
});
});
});

View File

@ -1,303 +0,0 @@
import { describe, it, expect } from 'vitest';
import {
createCombatState,
createEnemyInstance,
createPlayerCombatState,
drawCardsToHand,
addFatigueCards,
discardHand,
discardCard,
exhaustCard,
getEnemyCurrentIntent,
advanceEnemyIntent,
getEffectTiming,
getEffectData,
INITIAL_HAND_SIZE,
DEFAULT_MAX_ENERGY,
FATIGUE_CARDS_PER_SHUFFLE,
} from '@/samples/slay-the-spire-like/combat/state';
import { createGridInventory, placeItem } from '@/samples/slay-the-spire-like/grid-inventory';
import type { GridInventory, InventoryItem } from '@/samples/slay-the-spire-like/grid-inventory';
import type { GameItemMeta, PlayerState } from '@/samples/slay-the-spire-like/progress/types';
import { IDENTITY_TRANSFORM } from '@/samples/slay-the-spire-like/utils/shape-collision';
import { parseShapeString } from '@/samples/slay-the-spire-like/utils/parse-shape';
import { encounterDesertData, enemyDesertData, effectDesertData } from '@/samples/slay-the-spire-like/data';
function createTestMeta(name: string, shapeStr: string): GameItemMeta {
const shape = parseShapeString(shapeStr);
return {
itemData: {
type: 'weapon',
name,
shape: shapeStr,
costType: 'energy',
costCount: 1,
targetType: 'single',
price: 10,
desc: '测试物品',
},
shape,
};
}
function createTestInventory(): GridInventory<GameItemMeta> {
const inv = createGridInventory<GameItemMeta>(6, 4);
const meta1 = createTestMeta('短刀', 'oe');
const item1: InventoryItem<GameItemMeta> = {
id: 'item-1',
shape: meta1.shape,
transform: { ...IDENTITY_TRANSFORM, offset: { x: 0, y: 0 } },
meta: meta1,
};
placeItem(inv, item1);
return inv;
}
function createTestPlayerState(): PlayerState {
return { maxHp: 50, currentHp: 50, gold: 0 };
}
describe('combat/state', () => {
describe('constants', () => {
it('should have correct default values', () => {
expect(INITIAL_HAND_SIZE).toBe(5);
expect(DEFAULT_MAX_ENERGY).toBe(3);
expect(FATIGUE_CARDS_PER_SHUFFLE).toBe(2);
});
});
describe('createEnemyInstance', () => {
it('should create enemy from desert data', () => {
const cactusData = enemyDesertData.find(e => e.id === '仙人掌怪')!;
const enemy = createEnemyInstance('仙人掌怪', cactusData, 0, { value: 0 });
expect(enemy.templateId).toBe('仙人掌怪');
expect(enemy.hp).toBe(cactusData.initHp);
expect(enemy.maxHp).toBe(cactusData.initHp);
expect(enemy.isAlive).toBe(true);
expect(enemy.hadDefendBroken).toBe(false);
});
it('should apply bonus HP', () => {
const cactusData = enemyDesertData.find(e => e.id === '仙人掌怪')!;
const enemy = createEnemyInstance('仙人掌怪', cactusData, 5, { value: 0 });
expect(enemy.hp).toBe(cactusData.initHp + 5);
expect(enemy.maxHp).toBe(cactusData.initHp + 5);
});
it('should initialize buffs from template', () => {
const cactusData = enemyDesertData.find(e => e.id === '仙人掌怪')!;
const enemy = createEnemyInstance('仙人掌怪', cactusData, 0, { value: 0 });
expect(enemy.buffs['spike']).toBe(1);
});
it('should set initial intent', () => {
const cactusData = enemyDesertData.find(e => e.id === '仙人掌怪')!;
const enemy = createEnemyInstance('仙人掌怪', cactusData, 0, { value: 0 });
expect(enemy.currentIntentId).toBe(cactusData.initialIntent);
});
it('should generate unique IDs', () => {
const cactusData = enemyDesertData.find(e => e.id === '仙人掌怪')!;
const e1 = createEnemyInstance('仙人掌怪', cactusData, 0, { value: 0 });
const e2 = createEnemyInstance('仙人掌怪', cactusData, 0, { value: 0 });
expect(e1.id).not.toBe(e2.id);
});
});
describe('createPlayerCombatState', () => {
it('should create player state from run state and inventory', () => {
const inv = createTestInventory();
const playerState = createTestPlayerState();
const combatPlayer = createPlayerCombatState(playerState, inv);
expect(combatPlayer.hp).toBe(50);
expect(combatPlayer.maxHp).toBe(50);
expect(combatPlayer.energy).toBe(DEFAULT_MAX_ENERGY);
expect(combatPlayer.maxEnergy).toBe(DEFAULT_MAX_ENERGY);
expect(Object.keys(combatPlayer.buffs).length).toBe(0);
expect(combatPlayer.damagedThisTurn).toBe(false);
expect(combatPlayer.cardsDiscardedThisTurn).toBe(0);
});
it('should generate deck from inventory', () => {
const inv = createTestInventory();
const playerState = createTestPlayerState();
const combatPlayer = createPlayerCombatState(playerState, inv);
expect(Object.keys(combatPlayer.deck.cards).length).toBeGreaterThan(0);
expect(combatPlayer.deck.drawPile.length).toBeGreaterThan(0);
});
});
describe('createCombatState', () => {
it('should create full combat state from encounter', () => {
const inv = createTestInventory();
const playerState = createTestPlayerState();
const encounter = encounterDesertData.find(e => e.name === '仙人掌怪')!;
const combat = createCombatState(playerState, inv, encounter);
expect(combat.phase).toBe('playerTurn');
expect(combat.turnNumber).toBe(1);
expect(combat.result).toBeNull();
expect(combat.loot).toEqual([]);
expect(combat.fatigueAddedCount).toBe(0);
});
it('should create enemies from encounter data', () => {
const inv = createTestInventory();
const playerState = createTestPlayerState();
const encounter = encounterDesertData.find(e => e.name === '仙人掌怪')!;
const combat = createCombatState(playerState, inv, encounter);
expect(combat.enemyOrder.length).toBeGreaterThan(0);
for (const enemyId of combat.enemyOrder) {
expect(combat.enemies[enemyId].isAlive).toBe(true);
}
});
it('should draw initial hand', () => {
const inv = createTestInventory();
const playerState = createTestPlayerState();
const encounter = encounterDesertData.find(e => e.name === '仙人掌怪')!;
const combat = createCombatState(playerState, inv, encounter);
expect(combat.player.deck.hand.length).toBe(INITIAL_HAND_SIZE);
});
});
describe('drawCardsToHand', () => {
it('should draw cards from draw pile to hand', () => {
const inv = createTestInventory();
const playerState = createTestPlayerState();
const combatPlayer = createPlayerCombatState(playerState, inv);
const initialDrawPile = combatPlayer.deck.drawPile.length;
const initialHand = combatPlayer.deck.hand.length;
drawCardsToHand(combatPlayer.deck, 3);
expect(combatPlayer.deck.hand.length).toBe(initialHand + 3);
expect(combatPlayer.deck.drawPile.length).toBe(initialDrawPile - 3);
});
});
describe('addFatigueCards', () => {
it('should add fatigue cards to draw pile', () => {
const inv = createTestInventory();
const playerState = createTestPlayerState();
const combatPlayer = createPlayerCombatState(playerState, inv);
const initialCount = Object.keys(combatPlayer.deck.cards).length;
const fatigueCounter = { value: 0 };
addFatigueCards(combatPlayer.deck, FATIGUE_CARDS_PER_SHUFFLE, fatigueCounter);
expect(Object.keys(combatPlayer.deck.cards).length).toBe(initialCount + FATIGUE_CARDS_PER_SHUFFLE);
expect(fatigueCounter.value).toBe(FATIGUE_CARDS_PER_SHUFFLE);
});
it('should create fatigue cards with correct properties', () => {
const inv = createTestInventory();
const playerState = createTestPlayerState();
const combatPlayer = createPlayerCombatState(playerState, inv);
addFatigueCards(combatPlayer.deck, 1, { value: 0 });
const fatigueCard = combatPlayer.deck.cards['fatigue-1'];
expect(fatigueCard).toBeDefined();
expect(fatigueCard.displayName).toBe('疲劳');
expect(fatigueCard.sourceItemId).toBeNull();
expect(fatigueCard.itemData).toBeNull();
});
});
describe('discardHand', () => {
it('should move all hand cards to discard pile', () => {
const inv = createTestInventory();
const playerState = createTestPlayerState();
const combatPlayer = createPlayerCombatState(playerState, inv);
drawCardsToHand(combatPlayer.deck, 3);
const handCount = combatPlayer.deck.hand.length;
discardHand(combatPlayer.deck);
expect(combatPlayer.deck.hand).toEqual([]);
expect(combatPlayer.deck.discardPile.length).toBe(handCount);
});
});
describe('discardCard / exhaustCard', () => {
it('should move a card from hand to discard pile', () => {
const inv = createTestInventory();
const playerState = createTestPlayerState();
const combatPlayer = createPlayerCombatState(playerState, inv);
drawCardsToHand(combatPlayer.deck, 3);
const cardId = combatPlayer.deck.hand[0];
discardCard(combatPlayer.deck, cardId);
expect(combatPlayer.deck.hand.includes(cardId)).toBe(false);
expect(combatPlayer.deck.discardPile.includes(cardId)).toBe(true);
});
it('should move a card from hand to exhaust pile', () => {
const inv = createTestInventory();
const playerState = createTestPlayerState();
const combatPlayer = createPlayerCombatState(playerState, inv);
drawCardsToHand(combatPlayer.deck, 3);
const cardId = combatPlayer.deck.hand[0];
exhaustCard(combatPlayer.deck, cardId);
expect(combatPlayer.deck.hand.includes(cardId)).toBe(false);
expect(combatPlayer.deck.exhaustPile.includes(cardId)).toBe(true);
});
});
describe('enemy intent', () => {
it('should get current intent', () => {
const cactusData = enemyDesertData.find(e => e.id === '仙人掌怪')!;
const enemy = createEnemyInstance('仙人掌怪', cactusData, 0, { value: 0 });
const intent = getEnemyCurrentIntent(enemy);
expect(intent).toBeDefined();
expect(intent!.id).toBe('boost');
});
it('should advance intent after action', () => {
const cactusData = enemyDesertData.find(e => e.id === '仙人掌怪')!;
const enemy = createEnemyInstance('仙人掌怪', cactusData, 0, { value: 0 });
const originalIntent = enemy.currentIntentId;
advanceEnemyIntent(enemy);
expect(enemy.currentIntentId).toBeDefined();
});
});
describe('getEffectTiming / getEffectData', () => {
it('should return timing for known effects', () => {
expect(getEffectTiming('attack')).toBe('instant');
expect(getEffectTiming('defend')).toBe('posture');
expect(getEffectTiming('spike')).toBe('permanent');
expect(getEffectTiming('energyDrain')).toBe('lingering');
});
it('should return undefined for unknown effects', () => {
expect(getEffectTiming('nonexistent')).toBeUndefined();
});
it('should return effect data for known effects', () => {
const data = getEffectData('attack');
expect(data).toBeDefined();
expect(data!.id).toBe('attack');
expect(data!.name).toBe('攻击');
});
});
});

View File

@ -1,254 +0,0 @@
import { describe, it, expect } from 'vitest';
import {
createCombatTriggerRegistry,
dispatchTrigger,
dispatchAttackedTrigger,
dispatchDamageTrigger,
dispatchOutgoingDamageTrigger,
dispatchIncomingDamageTrigger,
} from '@/samples/slay-the-spire-like/combat/triggers';
import type { TriggerContext, CombatTriggerRegistry } from '@/samples/slay-the-spire-like/combat/triggers';
import type { CombatState } from '@/samples/slay-the-spire-like/combat/types';
import { createCombatState } from '@/samples/slay-the-spire-like/combat/state';
import { createGridInventory, placeItem } from '@/samples/slay-the-spire-like/grid-inventory';
import type { GridInventory, InventoryItem } from '@/samples/slay-the-spire-like/grid-inventory';
import type { GameItemMeta, PlayerState } from '@/samples/slay-the-spire-like/progress/types';
import { IDENTITY_TRANSFORM } from '@/samples/slay-the-spire-like/utils/shape-collision';
import { parseShapeString } from '@/samples/slay-the-spire-like/utils/parse-shape';
import { encounterDesertData, enemyDesertData } from '@/samples/slay-the-spire-like/data';
import { Mulberry32RNG } from '@/utils/rng';
function createTestMeta(name: string, shapeStr: string): GameItemMeta {
const shape = parseShapeString(shapeStr);
return {
itemData: {
type: 'weapon',
name,
shape: shapeStr,
costType: 'energy',
costCount: 1,
targetType: 'single',
price: 10,
desc: '测试',
},
shape,
};
}
function createTestInventory(): GridInventory<GameItemMeta> {
const inv = createGridInventory<GameItemMeta>(6, 4);
const meta1 = createTestMeta('短刀', 'oe');
const item1: InventoryItem<GameItemMeta> = {
id: 'item-1',
shape: meta1.shape,
transform: { ...IDENTITY_TRANSFORM, offset: { x: 0, y: 0 } },
meta: meta1,
};
placeItem(inv, item1);
return inv;
}
function createTestCombatState(): CombatState {
const inv = createTestInventory();
const playerState: PlayerState = { maxHp: 50, currentHp: 50, gold: 0 };
const encounter = encounterDesertData.find(e => e.name === '仙人掌怪')!;
return createCombatState(playerState, inv, encounter);
}
function createTestTriggerCtx(state: CombatState): TriggerContext {
return { state, rng: new Mulberry32RNG(42) };
}
describe('combat/triggers', () => {
describe('createCombatTriggerRegistry', () => {
it('should create registry with desert zone triggers', () => {
const registry = createCombatTriggerRegistry();
expect(registry['spike']).toBeDefined();
expect(registry['aim']).toBeDefined();
expect(registry['charge']).toBeDefined();
expect(registry['roll']).toBeDefined();
expect(registry['tailSting']).toBeDefined();
expect(registry['energyDrain']).toBeDefined();
expect(registry['molt']).toBeDefined();
expect(registry['storm']).toBeDefined();
expect(registry['vultureEye']).toBeDefined();
expect(registry['venom']).toBeDefined();
expect(registry['static']).toBeDefined();
expect(registry['curse']).toBeDefined();
expect(registry['discard']).toBeDefined();
});
});
describe('spike trigger', () => {
it('should deal damage to attacker when enemy is attacked', () => {
const state = createTestCombatState();
const registry = createCombatTriggerRegistry();
const ctx = createTestTriggerCtx(state);
const enemyId = state.enemyOrder[0];
state.enemies[enemyId].buffs['spike'] = 2;
const initialPlayerHp = state.player.hp;
dispatchAttackedTrigger(ctx, 'player', enemyId, 5, registry);
expect(state.player.hp).toBeLessThan(initialPlayerHp);
});
});
describe('aim trigger', () => {
it('should double outgoing damage with aim stacks', () => {
const state = createTestCombatState();
const registry = createCombatTriggerRegistry();
const ctx = createTestTriggerCtx(state);
const enemyId = state.enemyOrder[0];
state.enemies[enemyId].buffs['aim'] = 3;
const modified = dispatchOutgoingDamageTrigger(ctx, enemyId, 5, registry);
expect(modified).toBe(10);
});
it('should not double damage with 0 aim stacks', () => {
const state = createTestCombatState();
const registry = createCombatTriggerRegistry();
const ctx = createTestTriggerCtx(state);
const enemyId = state.enemyOrder[0];
state.enemies[enemyId].buffs['aim'] = 0;
const modified = dispatchOutgoingDamageTrigger(ctx, enemyId, 5, registry);
expect(modified).toBe(5);
});
it('should lose aim stacks on damage', () => {
const state = createTestCombatState();
const registry = createCombatTriggerRegistry();
const ctx = createTestTriggerCtx(state);
const enemyId = state.enemyOrder[0];
state.enemies[enemyId].buffs['aim'] = 5;
dispatchDamageTrigger(ctx, enemyId, 3, registry);
expect(state.enemies[enemyId].buffs['aim']).toBe(2);
});
});
describe('charge trigger', () => {
it('should double outgoing and incoming damage', () => {
const state = createTestCombatState();
const registry = createCombatTriggerRegistry();
const ctx = createTestTriggerCtx(state);
const enemyId = state.enemyOrder[0];
state.enemies[enemyId].buffs['charge'] = 2;
const outDmg = dispatchOutgoingDamageTrigger(ctx, enemyId, 6, registry);
expect(outDmg).toBe(12);
const inDmg = dispatchIncomingDamageTrigger(ctx, enemyId, 6, registry);
expect(inDmg).toBe(12);
});
it('should lose charge stacks on damage', () => {
const state = createTestCombatState();
const registry = createCombatTriggerRegistry();
const ctx = createTestTriggerCtx(state);
const enemyId = state.enemyOrder[0];
state.enemies[enemyId].buffs['charge'] = 5;
dispatchDamageTrigger(ctx, enemyId, 3, registry);
expect(state.enemies[enemyId].buffs['charge']).toBe(2);
});
});
describe('roll trigger', () => {
it('should increase damage when roll >= 10', () => {
const state = createTestCombatState();
const registry = createCombatTriggerRegistry();
const ctx = createTestTriggerCtx(state);
const enemyId = state.enemyOrder[0];
state.enemies[enemyId].buffs['roll'] = 20;
const modified = dispatchOutgoingDamageTrigger(ctx, enemyId, 5, registry);
expect(modified).toBe(7);
expect(state.enemies[enemyId].buffs['roll']).toBe(0);
});
it('should not modify damage when roll < 10', () => {
const state = createTestCombatState();
const registry = createCombatTriggerRegistry();
const ctx = createTestTriggerCtx(state);
const enemyId = state.enemyOrder[0];
state.enemies[enemyId].buffs['roll'] = 5;
const modified = dispatchOutgoingDamageTrigger(ctx, enemyId, 5, registry);
expect(modified).toBe(5);
});
});
describe('tailSting trigger', () => {
it('should deal damage to player at turn end', () => {
const state = createTestCombatState();
const registry = createCombatTriggerRegistry();
const ctx = createTestTriggerCtx(state);
const enemyId = state.enemyOrder[0];
state.enemies[enemyId].buffs['tailSting'] = 3;
const initialHp = state.player.hp;
dispatchTrigger(ctx, 'onTurnEnd', enemyId, registry);
expect(state.player.hp).toBeLessThan(initialHp);
});
});
describe('static trigger', () => {
it('should increase incoming damage to player', () => {
const state = createTestCombatState();
const registry = createCombatTriggerRegistry();
const ctx = createTestTriggerCtx(state);
state.player.buffs['static'] = 2;
const modified = dispatchIncomingDamageTrigger(ctx, 'player', 5, registry);
expect(modified).toBe(7);
});
});
describe('molt trigger', () => {
it('should cause enemy to flee when molt stacks >= maxHp', () => {
const state = createTestCombatState();
const registry = createCombatTriggerRegistry();
const ctx = createTestTriggerCtx(state);
const enemyId = state.enemyOrder[0];
state.enemies[enemyId].buffs['molt'] = state.enemies[enemyId].maxHp;
dispatchDamageTrigger(ctx, enemyId, 1, registry);
expect(state.enemies[enemyId].isAlive).toBe(false);
expect(state.result).toBe('fled');
});
it('should not cause flee when molt stacks < maxHp', () => {
const state = createTestCombatState();
const registry = createCombatTriggerRegistry();
const ctx = createTestTriggerCtx(state);
const enemyId = state.enemyOrder[0];
state.enemies[enemyId].buffs['molt'] = 1;
dispatchDamageTrigger(ctx, enemyId, 1, registry);
expect(state.enemies[enemyId].isAlive).toBe(true);
});
});
describe('dispatchTrigger with missing handler', () => {
it('should be a no-op for unknown buff', () => {
const state = createTestCombatState();
const registry = createCombatTriggerRegistry();
const ctx = createTestTriggerCtx(state);
const enemyId = state.enemyOrder[0];
state.enemies[enemyId].buffs['nonexistentBuff'] = 5;
expect(() => {
dispatchTrigger(ctx, 'onTurnStart', enemyId, registry);
}).not.toThrow();
});
});
});

View File

@ -1,311 +1,8 @@
import { describe, it, expect } from 'vitest';
import {
heroItemFighter1Data,
encounterDesertData,
enemyDesertData,
enemyIntentDesertData,
effectDesertData,
statusCardDesertData,
} from '@/samples/slay-the-spire-like/data';
import data from '@/samples/slay-the-spire-like/data';
describe('heroItemFighter1.csv import', () => {
it('should import data as an array', () => {
expect(Array.isArray(heroItemFighter1Data)).toBe(true);
expect(heroItemFighter1Data.length).toBeGreaterThan(0);
});
it('should have expected number of items', () => {
expect(heroItemFighter1Data.length).toBe(24);
});
it('should have correct fields for each item', () => {
for (const item of heroItemFighter1Data) {
expect(item).toHaveProperty('type');
expect(item).toHaveProperty('name');
expect(item).toHaveProperty('shape');
expect(item).toHaveProperty('costType');
expect(item).toHaveProperty('costCount');
expect(item).toHaveProperty('targetType');
expect(item).toHaveProperty('desc');
expect(item).toHaveProperty('effects');
}
});
it('should parse costCount as number', () => {
for (const item of heroItemFighter1Data) {
expect(typeof item.costCount).toBe('number');
}
});
it('should contain expected items by name', () => {
const names = heroItemFighter1Data.map(item => item.name);
expect(names).toContain('剑');
expect(names).toContain('盾');
expect(names).toContain('绷带');
expect(names).toContain('火把');
});
it('should have valid type values', () => {
const validTypes = ['weapon', 'armor', 'consumable', 'tool'];
for (const item of heroItemFighter1Data) {
expect(validTypes).toContain(item.type);
}
});
it('should have valid costType values', () => {
const validCostTypes = ['energy', 'uses'];
for (const item of heroItemFighter1Data) {
expect(validCostTypes).toContain(item.costType);
}
});
it('should have valid targetType values', () => {
const validTargetTypes = ['single', 'none'];
for (const item of heroItemFighter1Data) {
expect(validTargetTypes).toContain(item.targetType);
}
});
it('should have correct item counts by type', () => {
const typeCounts = heroItemFighter1Data.reduce((acc, item) => {
acc[item.type] = (acc[item.type] || 0) + 1;
return acc;
}, {} as Record<string, number>);
expect(typeCounts['weapon']).toBe(6);
expect(typeCounts['armor']).toBe(6);
expect(typeCounts['consumable']).toBe(6);
expect(typeCounts['tool']).toBe(6);
});
it('should have effects with target, effect ref, and value', () => {
for (const item of heroItemFighter1Data) {
expect(Array.isArray(item.effects)).toBe(true);
for (const [target, effect, value] of item.effects) {
expect(target === 'self' || target === 'target' || target === 'all' || target === 'random').toBe(true);
expect(typeof effect === 'string' || typeof effect === 'object').toBe(true);
expect(typeof value).toBe('number');
}
}
});
});
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 [enemy, bonusHp] of e.enemies) {
expect(typeof enemy === 'string' || typeof enemy === 'object').toBe(true);
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(35);
});
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');
expect(effect).toHaveProperty('timing');
}
});
it('should have valid timing values', () => {
const validTimings = ['instant', 'temporary', 'lingering', 'permanent', 'posture', 'card', 'cardDraw', 'cardHand', 'item', 'itemUntilPlayed'];
for (const effect of effectDesertData) {
expect(validTimings).toContain(effect.timing);
}
});
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');
expect(ids).toContain('draw');
expect(ids).toContain('removeWound');
expect(ids).toContain('gainEnergy');
});
});
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('id');
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, effect, value] of intent.effects) {
expect(target === 'self' || target === 'player' || target === 'team').toBe(true);
expect(typeof effect === 'string' || typeof effect === 'object').toBe(true);
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);
});
});
describe('statusCardDesert.csv import', () => {
it('should import data as an array', () => {
expect(Array.isArray(statusCardDesertData)).toBe(true);
expect(statusCardDesertData.length).toBeGreaterThan(0);
});
it('should have expected number of status cards', () => {
expect(statusCardDesertData.length).toBe(6);
});
it('should have correct fields for each status card', () => {
for (const card of statusCardDesertData) {
expect(card).toHaveProperty('id');
expect(card).toHaveProperty('name');
expect(card).toHaveProperty('desc');
expect(card).toHaveProperty('unplayable');
expect(card).toHaveProperty('effects');
expect(typeof card.id).toBe('string');
expect(typeof card.name).toBe('string');
expect(typeof card.desc).toBe('string');
expect(typeof card.unplayable).toBe('boolean');
}
});
it('should have all cards unplayable', () => {
for (const card of statusCardDesertData) {
expect(card.unplayable).toBe(true);
}
});
it('should have effects with target, effect ref, and value', () => {
for (const card of statusCardDesertData) {
expect(Array.isArray(card.effects)).toBe(true);
for (const [target, effect, value] of card.effects) {
expect(target).toBe('self');
expect(typeof effect === 'string' || typeof effect === 'object').toBe(true);
expect(typeof value).toBe('number');
}
}
});
it('should contain expected status cards by id', () => {
const ids = statusCardDesertData.map(c => c.id);
expect(ids).toContain('wound');
expect(ids).toContain('venom');
expect(ids).toContain('curse');
expect(ids).toContain('static');
describe('data import', () => {
it('should import properly', () => {
expect(data.desert.effects).toBeDefined();
});
});

View File

@ -1,53 +0,0 @@
import { describe, it, expect } from 'vitest';
import encounters from '@/samples/slay-the-spire-like/dialogue/encounters/encounters.yarnproject';
describe('encounters.yarnproject import', () => {
it('should load the yarnproject with expected project metadata', () => {
expect(encounters.project.projectName).toBe('encounters');
expect(encounters.project.baseLanguage).toBe('en');
expect(encounters.project.authorName).toContain('hyper');
expect(encounters.project.projectFileVersion).toBe(4);
});
it('should have sourceFiles configured', () => {
expect(encounters.project.sourceFiles).toContain('**/*.yarn');
});
it('should have a valid baseDir', () => {
expect(typeof encounters.baseDir).toBe('string');
expect(encounters.baseDir.length).toBeGreaterThan(0);
});
it('should compile nodes from .yarn files', () => {
const nodeTitles = Object.keys(encounters.program.nodes);
expect(nodeTitles.length).toBeGreaterThan(0);
});
it('should contain expected nodes from story.yarn', () => {
const nodeTitles = Object.keys(encounters.program.nodes);
expect(nodeTitles).toContain('Start');
expect(nodeTitles).toContain('Scene1');
expect(nodeTitles).toContain('Scene2');
expect(nodeTitles).toContain('Scene3');
expect(nodeTitles).toContain('Scene4');
});
it('should have instructions in each node', () => {
for (const [title, node] of Object.entries(encounters.program.nodes)) {
if ('instructions' in node && Array.isArray((node as any).instructions)) {
expect((node as any).instructions.length).toBeGreaterThan(0);
}
}
});
it('should have Start node with jump instruction to Scene1', () => {
const startNode = encounters.program.nodes['Start'];
if ('instructions' in startNode) {
const jumpInstruction = startNode.instructions.find(
(instr) => instr.op === 'jump',
);
expect(jumpInstruction).toBeDefined();
expect(jumpInstruction!.target).toBe('Scene1');
}
});
});