Added quality of life changes, improved animations, added basic tutorials (need to add more pages), MVP is robust and only needs sound effects and music pretty much, besides a lot of user testing.
|
After Width: | Height: | Size: 302 B |
@ -0,0 +1,36 @@ |
|||||||
|
<script lang="ts"> |
||||||
|
interface Props { |
||||||
|
class?: string; |
||||||
|
} |
||||||
|
|
||||||
|
let { class: className = "" }: Props = $props(); |
||||||
|
</script> |
||||||
|
|
||||||
|
<svg |
||||||
|
class="text-kv-yellow {className}" |
||||||
|
viewBox="0 0 128 128" |
||||||
|
fill="currentColor" |
||||||
|
xmlns="http://www.w3.org/2000/svg" |
||||||
|
> |
||||||
|
<path |
||||||
|
fill-rule="evenodd" |
||||||
|
clip-rule="evenodd" |
||||||
|
d="M8.05957 1.5L11.3781 12.5V115.5L8.05957 126.5H45.9855L42.667 115.5V73.6719L82.0151 126.5H119.941L67.3188 59L115.674 1.50006H77.7485L42.667 50.5001V12.5L45.9855 1.50006L8.05957 1.5Z" |
||||||
|
/> |
||||||
|
</svg> |
||||||
|
|
||||||
|
<style> |
||||||
|
svg { |
||||||
|
animation: spin-k 2s cubic-bezier(0.67, -0.42, 0.1, 1.29) infinite; |
||||||
|
filter: drop-shadow(6px 6px 4px rgba(0, 0, 0, 0.5)); |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes spin-k { |
||||||
|
from { |
||||||
|
transform: rotate(0deg); |
||||||
|
} |
||||||
|
to { |
||||||
|
transform: rotate(360deg); |
||||||
|
} |
||||||
|
} |
||||||
|
</style> |
||||||
@ -0,0 +1,172 @@ |
|||||||
|
<script lang="ts"> |
||||||
|
import * as m from "$lib/paraglide/messages"; |
||||||
|
|
||||||
|
export interface TutorialSlide { |
||||||
|
image: string; |
||||||
|
text: string; |
||||||
|
} |
||||||
|
|
||||||
|
interface Props { |
||||||
|
open: boolean; |
||||||
|
title: string; |
||||||
|
slides: TutorialSlide[]; |
||||||
|
onclose?: () => void; |
||||||
|
} |
||||||
|
|
||||||
|
let { open = $bindable(), title, slides, onclose }: Props = $props(); |
||||||
|
|
||||||
|
let currentIndex = $state(0); |
||||||
|
|
||||||
|
function close() { |
||||||
|
open = false; |
||||||
|
currentIndex = 0; |
||||||
|
onclose?.(); |
||||||
|
} |
||||||
|
|
||||||
|
function prev() { |
||||||
|
if (currentIndex > 0) { |
||||||
|
currentIndex--; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function next() { |
||||||
|
if (currentIndex < slides.length - 1) { |
||||||
|
currentIndex++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function handleKeydown(e: KeyboardEvent) { |
||||||
|
if (!open) return; |
||||||
|
if (e.key === "Escape") close(); |
||||||
|
if (e.key === "ArrowLeft") prev(); |
||||||
|
if (e.key === "ArrowRight") next(); |
||||||
|
} |
||||||
|
|
||||||
|
// Reset index when modal opens |
||||||
|
$effect(() => { |
||||||
|
if (open) { |
||||||
|
currentIndex = 0; |
||||||
|
} |
||||||
|
}); |
||||||
|
</script> |
||||||
|
|
||||||
|
<svelte:window onkeydown={handleKeydown} /> |
||||||
|
|
||||||
|
{#if open} |
||||||
|
<!-- Backdrop --> |
||||||
|
<div |
||||||
|
class="fixed inset-0 bg-kv-background/50 z-50 flex items-center justify-center p-4" |
||||||
|
onclick={close} |
||||||
|
onkeydown={(e) => e.key === "Enter" && close()} |
||||||
|
role="button" |
||||||
|
tabindex="-1" |
||||||
|
aria-label="Close modal" |
||||||
|
> |
||||||
|
<!-- Modal --> |
||||||
|
<div |
||||||
|
class="bg-kv-blue border-[16px] border-kv-black w-full max-w-5xl max-h-[90vh] flex flex-col gap-6 md:gap-8 p-4 md:p-8 overflow-y-auto" |
||||||
|
role="dialog" |
||||||
|
aria-modal="true" |
||||||
|
tabindex="-1" |
||||||
|
onclick={(e) => e.stopPropagation()} |
||||||
|
onkeydown={(e) => e.stopPropagation()} |
||||||
|
> |
||||||
|
<!-- Header --> |
||||||
|
<div class="flex items-start justify-between gap-4"> |
||||||
|
<h2 |
||||||
|
class="font-kv-body text-xl md:text-3xl text-kv-white uppercase kv-shadow-text" |
||||||
|
> |
||||||
|
{title} |
||||||
|
</h2> |
||||||
|
<button |
||||||
|
class="text-kv-yellow hover:opacity-80 transition-opacity cursor-pointer bg-transparent border-none p-0" |
||||||
|
onclick={close} |
||||||
|
aria-label="Close" |
||||||
|
> |
||||||
|
<svg |
||||||
|
class="w-6 h-6 md:w-8 md:h-8" |
||||||
|
viewBox="0 0 24 24" |
||||||
|
fill="currentColor" |
||||||
|
> |
||||||
|
<path |
||||||
|
d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" |
||||||
|
/> |
||||||
|
</svg> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- Image Carousel --> |
||||||
|
{#if slides.length > 0} |
||||||
|
<div class="flex items-center gap-2 md:gap-4"> |
||||||
|
<!-- Previous Button --> |
||||||
|
<button |
||||||
|
class="text-kv-yellow hover:opacity-80 transition-opacity cursor-pointer bg-transparent border-none p-0 disabled:opacity-30 disabled:cursor-not-allowed shrink-0" |
||||||
|
onclick={prev} |
||||||
|
disabled={currentIndex === 0} |
||||||
|
aria-label="Previous" |
||||||
|
> |
||||||
|
<svg |
||||||
|
class="w-8 h-8 md:w-12 md:h-12" |
||||||
|
viewBox="0 0 48 48" |
||||||
|
fill="currentColor" |
||||||
|
> |
||||||
|
<path |
||||||
|
d="M29.5334 40L13.5334 24L29.5334 8L33.2668 11.7333L21.0001 24L33.2668 36.2667L29.5334 40Z" |
||||||
|
/> |
||||||
|
</svg> |
||||||
|
</button> |
||||||
|
|
||||||
|
<!-- Image --> |
||||||
|
<div |
||||||
|
class="flex-1 aspect-video bg-kv-black/30 overflow-hidden" |
||||||
|
> |
||||||
|
<img |
||||||
|
src={slides[currentIndex].image} |
||||||
|
alt="Tutorial step {currentIndex + 1}" |
||||||
|
class="w-full h-full object-contain" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- Next Button --> |
||||||
|
<button |
||||||
|
class="text-kv-yellow hover:opacity-80 transition-opacity cursor-pointer bg-transparent border-none p-0 disabled:opacity-30 disabled:cursor-not-allowed shrink-0" |
||||||
|
onclick={next} |
||||||
|
disabled={currentIndex === slides.length - 1} |
||||||
|
aria-label="Next" |
||||||
|
> |
||||||
|
<svg |
||||||
|
class="w-8 h-8 md:w-12 md:h-12" |
||||||
|
viewBox="0 0 48 48" |
||||||
|
fill="currentColor" |
||||||
|
> |
||||||
|
<path |
||||||
|
d="M18.4666 8L34.4666 24L18.4666 40L14.7332 36.2667L26.9999 24L14.7332 11.7333L18.4666 8Z" |
||||||
|
/> |
||||||
|
</svg> |
||||||
|
</button> |
||||||
|
</div> |
||||||
|
|
||||||
|
<!-- Text Description --> |
||||||
|
<p |
||||||
|
class="font-kv-body text-sm md:text-base text-kv-white uppercase kv-shadow-text text-left whitespace-pre-line" |
||||||
|
> |
||||||
|
{slides[currentIndex].text} |
||||||
|
</p> |
||||||
|
|
||||||
|
<!-- Page Indicator --> |
||||||
|
<div class="flex justify-center gap-2"> |
||||||
|
{#each slides as _, i} |
||||||
|
<button |
||||||
|
class="w-3 h-3 rounded-full border-2 border-kv-yellow transition-colors cursor-pointer {i === |
||||||
|
currentIndex |
||||||
|
? 'bg-kv-yellow' |
||||||
|
: 'bg-transparent'}" |
||||||
|
onclick={() => (currentIndex = i)} |
||||||
|
aria-label="Go to slide {i + 1}" |
||||||
|
></button> |
||||||
|
{/each} |
||||||
|
</div> |
||||||
|
{/if} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
{/if} |
||||||
@ -0,0 +1,209 @@ |
|||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest'; |
||||||
|
import type { KuldvillakGame } from '$lib/types/kuldvillak'; |
||||||
|
import { DEFAULT_SETTINGS, DEFAULT_STATE } from '$lib/types/kuldvillak'; |
||||||
|
|
||||||
|
// Mock localStorage
|
||||||
|
const localStorageMock = (() => { |
||||||
|
let store: Record<string, string> = {}; |
||||||
|
return { |
||||||
|
getItem: vi.fn((key: string) => store[key] ?? null), |
||||||
|
setItem: vi.fn((key: string, value: string) => { store[key] = value; }), |
||||||
|
removeItem: vi.fn((key: string) => { delete store[key]; }), |
||||||
|
clear: vi.fn(() => { store = {}; }), |
||||||
|
get length() { return Object.keys(store).length; }, |
||||||
|
key: vi.fn((i: number) => Object.keys(store)[i] ?? null), |
||||||
|
}; |
||||||
|
})(); |
||||||
|
|
||||||
|
Object.defineProperty(global, 'localStorage', { value: localStorageMock }); |
||||||
|
|
||||||
|
// Import after mocking localStorage
|
||||||
|
import { |
||||||
|
getKuldvillakGamesList, |
||||||
|
getAllKuldvillakGames, |
||||||
|
loadKuldvillakGame, |
||||||
|
saveKuldvillakGame, |
||||||
|
deleteKuldvillakGame, |
||||||
|
duplicateKuldvillakGame, |
||||||
|
setActiveKuldvillakGame, |
||||||
|
getActiveKuldvillakGameId, |
||||||
|
} from './persistence'; |
||||||
|
|
||||||
|
// Helper to create a test game
|
||||||
|
function createTestGame(overrides: Partial<KuldvillakGame> = {}): KuldvillakGame { |
||||||
|
return { |
||||||
|
id: 'test-game-id', |
||||||
|
name: 'Test Game', |
||||||
|
createdAt: '2024-01-01T00:00:00.000Z', |
||||||
|
updatedAt: '2024-01-01T00:00:00.000Z', |
||||||
|
settings: { ...DEFAULT_SETTINGS }, |
||||||
|
teams: [ |
||||||
|
{ id: 'team-1', name: 'Team 1', score: 0 }, |
||||||
|
{ id: 'team-2', name: 'Team 2', score: 0 }, |
||||||
|
], |
||||||
|
rounds: [{ |
||||||
|
id: 'round-1', |
||||||
|
name: 'Round 1', |
||||||
|
pointMultiplier: 1, |
||||||
|
categories: [{ |
||||||
|
id: 'cat-1', |
||||||
|
name: 'Category 1', |
||||||
|
questions: [{ |
||||||
|
id: 'q-1', |
||||||
|
question: 'Test Question', |
||||||
|
answer: 'Test Answer', |
||||||
|
points: 100, |
||||||
|
isDailyDouble: false, |
||||||
|
isRevealed: false, |
||||||
|
}], |
||||||
|
}], |
||||||
|
}], |
||||||
|
finalRound: null, |
||||||
|
state: { ...DEFAULT_STATE }, |
||||||
|
...overrides, |
||||||
|
}; |
||||||
|
} |
||||||
|
|
||||||
|
describe('Persistence', () => { |
||||||
|
beforeEach(() => { |
||||||
|
localStorageMock.clear(); |
||||||
|
vi.clearAllMocks(); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('saveKuldvillakGame', () => { |
||||||
|
it('should save a new game', () => { |
||||||
|
const game = createTestGame(); |
||||||
|
const result = saveKuldvillakGame(game); |
||||||
|
|
||||||
|
expect(result).toBe(true); |
||||||
|
expect(localStorageMock.setItem).toHaveBeenCalled(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should update existing game', () => { |
||||||
|
const game = createTestGame(); |
||||||
|
saveKuldvillakGame(game); |
||||||
|
|
||||||
|
game.name = 'Updated Name'; |
||||||
|
saveKuldvillakGame(game); |
||||||
|
|
||||||
|
const games = getAllKuldvillakGames(); |
||||||
|
expect(games).toHaveLength(1); |
||||||
|
expect(games[0].name).toBe('Updated Name'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should update the updatedAt timestamp', () => { |
||||||
|
const game = createTestGame(); |
||||||
|
const originalUpdatedAt = game.updatedAt; |
||||||
|
|
||||||
|
// Wait a tiny bit to ensure different timestamp
|
||||||
|
saveKuldvillakGame(game); |
||||||
|
|
||||||
|
const games = getAllKuldvillakGames(); |
||||||
|
expect(games[0].updatedAt).not.toBe(originalUpdatedAt); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('loadKuldvillakGame', () => { |
||||||
|
it('should return null for non-existent game', () => { |
||||||
|
const result = loadKuldvillakGame('non-existent-id'); |
||||||
|
expect(result).toBeNull(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should load existing game', () => { |
||||||
|
const game = createTestGame(); |
||||||
|
saveKuldvillakGame(game); |
||||||
|
|
||||||
|
const loaded = loadKuldvillakGame(game.id); |
||||||
|
expect(loaded).not.toBeNull(); |
||||||
|
expect(loaded?.name).toBe(game.name); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('deleteKuldvillakGame', () => { |
||||||
|
it('should delete a game', () => { |
||||||
|
const game = createTestGame(); |
||||||
|
saveKuldvillakGame(game); |
||||||
|
|
||||||
|
const result = deleteKuldvillakGame(game.id); |
||||||
|
|
||||||
|
expect(result).toBe(true); |
||||||
|
expect(getAllKuldvillakGames()).toHaveLength(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should handle deleting non-existent game', () => { |
||||||
|
const result = deleteKuldvillakGame('non-existent'); |
||||||
|
expect(result).toBe(true); // Should not fail
|
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('duplicateKuldvillakGame', () => { |
||||||
|
it('should create a duplicate with new ID', () => { |
||||||
|
const original = createTestGame(); |
||||||
|
saveKuldvillakGame(original); |
||||||
|
|
||||||
|
const duplicate = duplicateKuldvillakGame(original.id); |
||||||
|
|
||||||
|
expect(duplicate).not.toBeNull(); |
||||||
|
expect(duplicate?.id).not.toBe(original.id); |
||||||
|
expect(duplicate?.name).toBe(`${original.name} (Copy)`); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should reset game state in duplicate', () => { |
||||||
|
const original = createTestGame(); |
||||||
|
original.state.phase = 'board'; |
||||||
|
original.teams[0].score = 500; |
||||||
|
saveKuldvillakGame(original); |
||||||
|
|
||||||
|
const duplicate = duplicateKuldvillakGame(original.id); |
||||||
|
|
||||||
|
expect(duplicate?.state.phase).toBe('lobby'); |
||||||
|
expect(duplicate?.teams[0].score).toBe(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should reset revealed questions in duplicate', () => { |
||||||
|
const original = createTestGame(); |
||||||
|
original.rounds[0].categories[0].questions[0].isRevealed = true; |
||||||
|
saveKuldvillakGame(original); |
||||||
|
|
||||||
|
const duplicate = duplicateKuldvillakGame(original.id); |
||||||
|
|
||||||
|
expect(duplicate?.rounds[0].categories[0].questions[0].isRevealed).toBe(false); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return null for non-existent game', () => { |
||||||
|
const result = duplicateKuldvillakGame('non-existent'); |
||||||
|
expect(result).toBeNull(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('getKuldvillakGamesList', () => { |
||||||
|
it('should return empty array when no games', () => { |
||||||
|
const result = getKuldvillakGamesList(); |
||||||
|
expect(result).toEqual([]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should return metadata for all games', () => { |
||||||
|
saveKuldvillakGame(createTestGame({ id: 'game-1', name: 'Game 1' })); |
||||||
|
saveKuldvillakGame(createTestGame({ id: 'game-2', name: 'Game 2' })); |
||||||
|
|
||||||
|
const list = getKuldvillakGamesList(); |
||||||
|
|
||||||
|
expect(list).toHaveLength(2); |
||||||
|
expect(list[0].teamCount).toBe(2); |
||||||
|
expect(list[0].roundCount).toBe(1); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('Active Game', () => { |
||||||
|
it('should set and get active game ID', () => { |
||||||
|
setActiveKuldvillakGame('test-id'); |
||||||
|
expect(getActiveKuldvillakGameId()).toBe('test-id'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should clear active game when set to null', () => { |
||||||
|
setActiveKuldvillakGame('test-id'); |
||||||
|
setActiveKuldvillakGame(null); |
||||||
|
expect(getActiveKuldvillakGameId()).toBeNull(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
@ -0,0 +1,145 @@ |
|||||||
|
import { describe, it, expect } from 'vitest'; |
||||||
|
import { DEFAULT_SETTINGS, DEFAULT_STATE } from './kuldvillak'; |
||||||
|
import type { Team, Round, Question, GameSettings } from './kuldvillak'; |
||||||
|
|
||||||
|
describe('Kuldvillak Types', () => { |
||||||
|
describe('DEFAULT_SETTINGS', () => { |
||||||
|
it('should have correct number of rounds', () => { |
||||||
|
expect(DEFAULT_SETTINGS.numberOfRounds).toBe(2); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should have 5 point values', () => { |
||||||
|
expect(DEFAULT_SETTINGS.pointValues).toHaveLength(5); |
||||||
|
expect(DEFAULT_SETTINGS.pointValues).toEqual([10, 20, 30, 40, 50]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should have 6 categories per round', () => { |
||||||
|
expect(DEFAULT_SETTINGS.categoriesPerRound).toBe(6); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should have 5 questions per category', () => { |
||||||
|
expect(DEFAULT_SETTINGS.questionsPerCategory).toBe(5); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should have daily doubles configuration', () => { |
||||||
|
expect(DEFAULT_SETTINGS.dailyDoublesPerRound).toEqual([1, 2]); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should enable final round by default', () => { |
||||||
|
expect(DEFAULT_SETTINGS.enableFinalRound).toBe(true); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should allow negative scores by default', () => { |
||||||
|
expect(DEFAULT_SETTINGS.allowNegativeScores).toBe(true); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should have 6 max teams', () => { |
||||||
|
expect(DEFAULT_SETTINGS.maxTeams).toBe(6); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should have default timer of 5 seconds', () => { |
||||||
|
expect(DEFAULT_SETTINGS.defaultTimerSeconds).toBe(5); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should have answer reveal of 5 seconds', () => { |
||||||
|
expect(DEFAULT_SETTINGS.answerRevealSeconds).toBe(5); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('DEFAULT_STATE', () => { |
||||||
|
it('should start in lobby phase', () => { |
||||||
|
expect(DEFAULT_STATE.phase).toBe('lobby'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should start at round index 0', () => { |
||||||
|
expect(DEFAULT_STATE.currentRoundIndex).toBe(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should have no active team', () => { |
||||||
|
expect(DEFAULT_STATE.activeTeamId).toBeNull(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should have empty final round data', () => { |
||||||
|
expect(DEFAULT_STATE.finalWagers).toEqual({}); |
||||||
|
expect(DEFAULT_STATE.finalAnswers).toEqual({}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
describe('Type Validation Helpers', () => { |
||||||
|
it('should validate team structure', () => { |
||||||
|
const validTeam: Team = { |
||||||
|
id: 'team-1', |
||||||
|
name: 'Test Team', |
||||||
|
score: 100, |
||||||
|
}; |
||||||
|
|
||||||
|
expect(validTeam.id).toBeDefined(); |
||||||
|
expect(validTeam.name).toBeDefined(); |
||||||
|
expect(typeof validTeam.score).toBe('number'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should validate question structure', () => { |
||||||
|
const validQuestion: Question = { |
||||||
|
id: 'q-1', |
||||||
|
question: 'What is 2+2?', |
||||||
|
answer: 'What is 4?', |
||||||
|
points: 100, |
||||||
|
isDailyDouble: false, |
||||||
|
isRevealed: false, |
||||||
|
}; |
||||||
|
|
||||||
|
expect(validQuestion.id).toBeDefined(); |
||||||
|
expect(validQuestion.question).toBeDefined(); |
||||||
|
expect(validQuestion.answer).toBeDefined(); |
||||||
|
expect(typeof validQuestion.points).toBe('number'); |
||||||
|
expect(typeof validQuestion.isDailyDouble).toBe('boolean'); |
||||||
|
expect(typeof validQuestion.isRevealed).toBe('boolean'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should validate optional imageUrl in question', () => { |
||||||
|
const questionWithImage: Question = { |
||||||
|
id: 'q-1', |
||||||
|
question: 'What is shown?', |
||||||
|
answer: 'What is a cat?', |
||||||
|
points: 200, |
||||||
|
isDailyDouble: false, |
||||||
|
isRevealed: false, |
||||||
|
imageUrl: 'https://example.com/image.jpg', |
||||||
|
}; |
||||||
|
|
||||||
|
expect(questionWithImage.imageUrl).toBeDefined(); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should calculate point values correctly', () => { |
||||||
|
const baseValue = 10; |
||||||
|
const multiplier = 2; |
||||||
|
const expectedValues = [20, 40, 60, 80, 100]; |
||||||
|
|
||||||
|
const calculatedValues = DEFAULT_SETTINGS.pointValues.map( |
||||||
|
(v) => v * multiplier |
||||||
|
); |
||||||
|
|
||||||
|
expect(calculatedValues).toEqual(expectedValues); |
||||||
|
}); |
||||||
|
|
||||||
|
it('should validate game phases', () => { |
||||||
|
const validPhases = [ |
||||||
|
'lobby', |
||||||
|
'intro', |
||||||
|
'intro-categories', |
||||||
|
'board', |
||||||
|
'question', |
||||||
|
'daily-double', |
||||||
|
'final-intro', |
||||||
|
'final-category', |
||||||
|
'final-wager', |
||||||
|
'final-question', |
||||||
|
'final-reveal', |
||||||
|
'final-scores', |
||||||
|
'finished', |
||||||
|
]; |
||||||
|
|
||||||
|
expect(validPhases).toContain(DEFAULT_STATE.phase); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}); |
||||||
|
After Width: | Height: | Size: 355 B |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 223 KiB |
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 253 KiB |
|
After Width: | Height: | Size: 1.5 MiB |
|
After Width: | Height: | Size: 233 KiB |
|
After Width: | Height: | Size: 233 KiB |
|
After Width: | Height: | Size: 233 KiB |
|
After Width: | Height: | Size: 233 KiB |
|
After Width: | Height: | Size: 201 KiB |
|
After Width: | Height: | Size: 253 KiB |
@ -0,0 +1,13 @@ |
|||||||
|
import { defineConfig } from 'vitest/config'; |
||||||
|
|
||||||
|
export default defineConfig({ |
||||||
|
test: { |
||||||
|
include: ['src/**/*.{test,spec}.{js,ts}'], |
||||||
|
environment: 'jsdom', |
||||||
|
globals: true, |
||||||
|
alias: { |
||||||
|
'$lib': '/src/lib', |
||||||
|
'$app': '/src/app', |
||||||
|
}, |
||||||
|
}, |
||||||
|
}); |
||||||