feat: bootstrap walking skeleton

This commit is contained in:
ginnoir
2026-06-11 17:06:52 -05:00
commit ec8f857845
44 changed files with 6677 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
import { describe, expect, it } from 'vitest';
import { buildContent } from '../../content/schema';
import { createGameState, startAction } from '../../engine/game';
import { formatOfflineDuration, toView } from '../viewModel';
function testContent() {
return buildContent({
resources: [{ id: 'gold', name: 'Gold', startAmount: 4 }],
actions: [
{ id: 'forage', name: 'Forage', durationMs: 200, yields: { resourceId: 'gold', amount: 1 } },
],
});
}
describe('toView()', () => {
it('maps resources with their names and amounts', () => {
const content = testContent();
const view = toView(createGameState(content), content);
expect(view.resources).toEqual([{ id: 'gold', name: 'Gold', amount: 4 }]);
});
it('reports no progress and no action name when idle', () => {
const content = testContent();
const view = toView(createGameState(content), content);
expect(view.activeActionId).toBeNull();
expect(view.actionName).toBeNull();
expect(view.actionProgress).toBe(0);
});
it('reports the active action name and fractional progress', () => {
const content = testContent();
const state = createGameState(content);
startAction(state, content, 'forage');
state.actionElapsedMs = 50; // of 200ms
const view = toView(state, content);
expect(view.actionName).toBe('Forage');
expect(view.actionProgress).toBeCloseTo(0.25);
});
it('clamps progress to at most 1', () => {
const content = testContent();
const state = createGameState(content);
startAction(state, content, 'forage');
state.actionElapsedMs = 999;
expect(toView(state, content).actionProgress).toBe(1);
});
});
describe('formatOfflineDuration()', () => {
it('formats sub-minute durations in seconds', () => {
expect(formatOfflineDuration(0)).toBe('0s');
expect(formatOfflineDuration(45_000)).toBe('45s');
});
it('formats minutes with seconds', () => {
expect(formatOfflineDuration(90_000)).toBe('1m 30s');
expect(formatOfflineDuration(120_000)).toBe('2m');
});
it('formats hours with minutes', () => {
expect(formatOfflineDuration(3_660_000)).toBe('1h 1m');
expect(formatOfflineDuration(7_200_000)).toBe('2h');
});
});