lotr-sut/sut/frontend/test/store/questStore.test.ts
Fellowship Scholar f6a5823439 init commit
2026-03-29 20:07:56 +00:00

292 lines
8.1 KiB
TypeScript

/**
* Tests for Quest Store (Zustand)
* TDD: Testing state mutations, selectors, and computed values
*/
import { describe, it, expect, beforeEach } from 'vitest';
import { useQuestStore } from '@/store/questStore';
import { Quest } from '@/types';
// Mock quest data
const mockQuestNone: Quest = {
id: '1',
title: 'Test Quest 1',
description: 'A test quest',
status: 'not_yet_begun',
quest_type: 'The Journey',
priority: 'Standard',
is_dark_magic: false,
assigned_to: 'user1',
assignee_name: 'Frodo',
location_id: 'loc1',
location_name: 'Bag End',
character_quote: 'Test quote',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
completed_at: null,
};
const mockQuestInProgress: Quest = {
...mockQuestNone,
id: '2',
status: 'the_road_goes_ever_on',
};
const mockQuestCompleted: Quest = {
...mockQuestNone,
id: '3',
status: 'it_is_done',
completed_at: new Date().toISOString(),
};
const mockDarkMagicQuest: Quest = {
...mockQuestNone,
id: '4',
is_dark_magic: true,
};
describe('useQuestStore', () => {
beforeEach(() => {
useQuestStore.setState({
quests: [],
members: [],
locations: [],
statusFilter: null,
typeFilter: null,
priorityFilter: null,
locationFilter: null,
searchQuery: '',
});
});
describe('Mutations', () => {
it('should add a quest', () => {
useQuestStore.getState().addQuest(mockQuestNone);
const quests = useQuestStore.getState().quests;
expect(quests).toHaveLength(1);
expect(quests[0].id).toBe('1');
});
it('should update a quest', () => {
useQuestStore.getState().addQuest(mockQuestNone);
useQuestStore.getState().updateQuest('1', { title: 'Updated Title' });
const quest = useQuestStore.getState().quests[0];
expect(quest.title).toBe('Updated Title');
});
it('should delete a quest', () => {
useQuestStore.getState().addQuest(mockQuestNone);
useQuestStore.getState().deleteQuest('1');
expect(useQuestStore.getState().quests).toHaveLength(0);
});
});
describe('Selectors - getQuestsByStatus', () => {
beforeEach(() => {
useQuestStore.getState().setQuests([
mockQuestNone,
mockQuestInProgress,
mockQuestCompleted,
]);
});
it('should filter quests by status', () => {
const completed = useQuestStore.getState().getQuestsByStatus('it_is_done');
expect(completed).toHaveLength(1);
expect(completed[0].id).toBe('3');
});
it('should return empty array for no matches', () => {
const blocked = useQuestStore.getState().getQuestsByStatus('the_shadow_falls');
expect(blocked).toHaveLength(0);
});
});
describe('Selectors - getDarkMagicQuests', () => {
beforeEach(() => {
useQuestStore.getState().setQuests([
mockQuestNone,
mockQuestInProgress,
mockDarkMagicQuest,
]);
});
it('should return only dark magic quests', () => {
const darkMagic = useQuestStore.getState().getDarkMagicQuests();
expect(darkMagic).toHaveLength(1);
expect(darkMagic[0].is_dark_magic).toBe(true);
});
});
describe('Selectors - getQuestStats', () => {
beforeEach(() => {
useQuestStore.getState().setQuests([
mockQuestNone,
mockQuestInProgress,
mockQuestCompleted,
]);
});
it('should calculate quest statistics', () => {
const stats = useQuestStore.getState().getQuestStats();
expect(stats.total).toBe(3);
expect(stats.notBegun).toBe(1);
expect(stats.inProgress).toBe(1);
expect(stats.completed).toBe(1);
expect(stats.blocked).toBe(0);
});
});
describe('Selectors - getCompletionRate', () => {
it('should calculate completion percentage', () => {
useQuestStore.getState().setQuests([
mockQuestNone,
mockQuestCompleted,
]);
const rate = useQuestStore.getState().getCompletionRate();
expect(rate).toBe(50);
});
it('should return 0 for empty quests', () => {
const rate = useQuestStore.getState().getCompletionRate();
expect(rate).toBe(0);
});
it('should return 100 for all completed', () => {
useQuestStore.getState().setQuests([
mockQuestCompleted,
{ ...mockQuestCompleted, id: '5' },
]);
const rate = useQuestStore.getState().getCompletionRate();
expect(rate).toBe(100);
});
});
describe('Filter actions', () => {
it('should set status filter', () => {
useQuestStore.getState().setStatusFilter('it_is_done');
expect(useQuestStore.getState().statusFilter).toBe('it_is_done');
});
it('should set search query', () => {
useQuestStore.getState().setSearchQuery('test');
expect(useQuestStore.getState().searchQuery).toBe('test');
});
it('should clear all filters', () => {
useQuestStore.getState().setStatusFilter('it_is_done');
useQuestStore.getState().setSearchQuery('test');
useQuestStore.getState().clearFilters();
expect(useQuestStore.getState().statusFilter).toBeNull();
expect(useQuestStore.getState().searchQuery).toBe('');
});
});
describe('Filtered quests', () => {
beforeEach(() => {
useQuestStore.getState().setQuests([
mockQuestNone,
mockQuestInProgress,
mockQuestCompleted,
]);
});
it('should filter by status', () => {
useQuestStore.getState().setStatusFilter('it_is_done');
const filtered = useQuestStore.getState().getFilteredQuests();
expect(filtered).toHaveLength(1);
expect(filtered[0].id).toBe('3');
});
it('should filter by search query', () => {
useQuestStore.getState().setSearchQuery('Test Quest 1');
const filtered = useQuestStore.getState().getFilteredQuests();
expect(filtered).toHaveLength(1);
expect(filtered[0].id).toBe('1');
});
it('should apply multiple filters', () => {
useQuestStore.getState().setStatusFilter('it_is_done');
useQuestStore.getState().setSearchQuery('1');
const filtered = useQuestStore.getState().getFilteredQuests();
// Quest 3 is completed but doesn't match search "1"
expect(filtered).toHaveLength(0);
});
});
describe('getQuestsByUser', () => {
beforeEach(() => {
useQuestStore.getState().setQuests([
mockQuestNone,
{ ...mockQuestInProgress, assigned_to: 'user2' },
]);
});
it('should return quests assigned to user', () => {
const userQuests = useQuestStore.getState().getQuestsByUser('user1');
expect(userQuests).toHaveLength(1);
expect(userQuests[0].assigned_to).toBe('user1');
});
it('should return empty for user with no quests', () => {
const userQuests = useQuestStore.getState().getQuestsByUser('unknown');
expect(userQuests).toHaveLength(0);
});
});
describe('getLocationStats', () => {
beforeEach(() => {
useQuestStore.getState().setQuests([
mockQuestNone,
{ ...mockQuestInProgress, location_id: 'loc1' },
{ ...mockQuestCompleted, location_id: 'loc2' },
]);
});
it('should count quests by location', () => {
const stats = useQuestStore.getState().getLocationStats();
expect(stats['loc1']).toBe(2);
expect(stats['loc2']).toBe(1);
});
});
describe('getActiveMembers', () => {
it('should return members with assigned quests', () => {
useQuestStore.getState().setMembers([
{ id: 'user1', name: 'Frodo', race: 'Hobbit', role: 'Ring-bearer', status: 'active', description: '' },
{ id: 'user2', name: 'Sam', race: 'Hobbit', role: 'Companion', status: 'active', description: '' },
{ id: 'user3', name: 'Gandalf', race: 'Wizard', role: 'Guide', status: 'active', description: '' },
]);
useQuestStore.getState().setQuests([
mockQuestNone,
{ ...mockQuestInProgress, assigned_to: 'user2' },
]);
const active = useQuestStore.getState().getActiveMembers();
expect(active).toHaveLength(2);
expect(active.map((m) => m.id)).toContain('user1');
expect(active.map((m) => m.id)).toContain('user2');
});
});
});