292 lines
8.1 KiB
TypeScript
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');
|
|
});
|
|
});
|
|
});
|