Files
Mana-Loop/src/lib/game/__tests__/discipline-reactivate-bug.test.ts
T
n8n-gitea c89d8fd2d8
Build and Publish Mana Loop Docker Image / build-and-publish (push) Successful in 1m28s
refactor: make activate() read mana state from stores directly instead of requiring UI to pass gameState bag
2026-06-09 10:14:20 +02:00

134 lines
6.0 KiB
TypeScript

import { describe, it, expect, beforeEach } from 'vitest';
import { useDisciplineStore } from '../stores/discipline-slice';
import { useManaStore } from '../stores/manaStore';
function resetDisciplineStore() {
useDisciplineStore.setState({
disciplines: {},
activeIds: [],
concurrentLimit: 1,
totalXP: 0,
processedPerks: [],
practicingCallbacks: null,
});
}
describe('DisciplineStore — reactivate after deactivate', () => {
beforeEach(resetDisciplineStore);
it('should reactivate a raw discipline after deactivate when rawMana is sufficient', () => {
// Set up mana store with sufficient raw mana
useManaStore.setState({ rawMana: 1000, elements: {} });
// First activation succeeds (reads rawMana from mana store)
useDisciplineStore.getState().activate('raw-mastery');
expect(useDisciplineStore.getState().activeIds).toContain('raw-mastery');
// Deactivate
useDisciplineStore.getState().deactivate('raw-mastery');
expect(useDisciplineStore.getState().activeIds).not.toContain('raw-mastery');
expect(useDisciplineStore.getState().disciplines['raw-mastery'].paused).toBe(true);
// Reactivate — reads rawMana=1000 from mana store, should succeed
useDisciplineStore.getState().activate('raw-mastery');
expect(useDisciplineStore.getState().activeIds).toContain('raw-mastery');
expect(useDisciplineStore.getState().disciplines['raw-mastery'].paused).toBe(false);
});
it('should NOT reactivate a raw discipline when rawMana is insufficient', () => {
// Activate with sufficient mana
useManaStore.setState({ rawMana: 1000, elements: {} });
useDisciplineStore.getState().activate('raw-mastery');
expect(useDisciplineStore.getState().activeIds).toContain('raw-mastery');
// Build up XP via ticks
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
const xp = useDisciplineStore.getState().disciplines['raw-mastery'].xp;
expect(xp).toBeGreaterThan(0);
// Deactivate
useDisciplineStore.getState().deactivate('raw-mastery');
expect(useDisciplineStore.getState().activeIds).not.toContain('raw-mastery');
// Set rawMana to 0 — reactivation should fail
useManaStore.setState({ rawMana: 0, elements: {} });
useDisciplineStore.getState().activate('raw-mastery');
expect(useDisciplineStore.getState().activeIds).not.toContain('raw-mastery');
});
it('should reactivate a discipline with elements after deactivating it', () => {
useManaStore.setState({
rawMana: 0,
elements: { fire: { unlocked: true, current: 100, max: 100, baseMax: 100 } },
});
useDisciplineStore.getState().activate('attune-fire');
expect(useDisciplineStore.getState().activeIds).toContain('attune-fire');
useDisciplineStore.getState().deactivate('attune-fire');
expect(useDisciplineStore.getState().activeIds).not.toContain('attune-fire');
// Reactivate — reads elements from mana store
useDisciplineStore.getState().activate('attune-fire');
expect(useDisciplineStore.getState().activeIds).toContain('attune-fire');
expect(useDisciplineStore.getState().disciplines['attune-fire'].paused).toBe(false);
});
it('should reactivate after processTick auto-pauses due to no mana', () => {
useManaStore.setState({ rawMana: 100, elements: {} });
useDisciplineStore.getState().activate('raw-mastery');
expect(useDisciplineStore.getState().activeIds).toContain('raw-mastery');
// Tick with no mana — discipline auto-pauses
useDisciplineStore.getState().processTick({ rawMana: 0, elements: {} });
expect(useDisciplineStore.getState().activeIds).not.toContain('raw-mastery');
expect(useDisciplineStore.getState().disciplines['raw-mastery'].paused).toBe(true);
// Restore mana and reactivate
useManaStore.setState({ rawMana: 1000, elements: {} });
useDisciplineStore.getState().activate('raw-mastery');
expect(useDisciplineStore.getState().activeIds).toContain('raw-mastery');
expect(useDisciplineStore.getState().disciplines['raw-mastery'].paused).toBe(false);
});
it('should preserve XP when deactivating and reactivating', () => {
useManaStore.setState({ rawMana: 1000, elements: {} });
useDisciplineStore.getState().activate('raw-mastery');
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
expect(useDisciplineStore.getState().disciplines['raw-mastery'].xp).toBe(3);
useDisciplineStore.getState().deactivate('raw-mastery');
expect(useDisciplineStore.getState().disciplines['raw-mastery'].xp).toBe(3);
expect(useDisciplineStore.getState().disciplines['raw-mastery'].paused).toBe(true);
useDisciplineStore.getState().activate('raw-mastery');
expect(useDisciplineStore.getState().activeIds).toContain('raw-mastery');
expect(useDisciplineStore.getState().disciplines['raw-mastery'].xp).toBe(3);
useDisciplineStore.getState().processTick({ rawMana: 1000, elements: {} });
expect(useDisciplineStore.getState().disciplines['raw-mastery'].xp).toBe(4);
});
it('should use gameState overrides when explicitly provided (for test control)', () => {
// Even though mana store has no mana, explicit override should work
useManaStore.setState({ rawMana: 0, elements: {} });
// First activation: no state yet, so canProceed returns true (optimistic)
useDisciplineStore.getState().activate('raw-mastery');
expect(useDisciplineStore.getState().activeIds).toContain('raw-mastery');
useDisciplineStore.getState().deactivate('raw-mastery');
// Reactivate with explicit override — bypasses store reads
useDisciplineStore.getState().activate('raw-mastery', {
rawMana: 1000,
elements: {},
signedPacts: [],
});
expect(useDisciplineStore.getState().activeIds).toContain('raw-mastery');
});
});