This commit is contained in:
2025-09-19 00:40:19 -06:00
parent 75a9a44996
commit 8187a8f5ac
6 changed files with 170 additions and 6 deletions

View File

@@ -1,12 +1,10 @@
import { describe, it, expect } from 'vitest';
import Ajv, { type ErrorObject } from 'ajv';
import addFormats from 'ajv-formats';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
// Implementation placeholder import; will fail until implemented per tasks T016, T018
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-expect-error - module not implemented yet
import { exportToJson } from '../../src/export';
const __filename = fileURLToPath(import.meta.url);
@@ -20,7 +18,8 @@ const schemaPath = path.join(
describe('Contract: export JSON schema (T009)', () => {
it('exportToJson() output should validate against export.schema.json', async () => {
const schemaJson = JSON.parse(fs.readFileSync(schemaPath, 'utf-8'));
const ajv = new Ajv({ allErrors: true, strict: true });
const ajv = new Ajv({ allErrors: true, strict: true });
addFormats(ajv);
const validate = ajv.compile(schemaJson);
// Minimal call; actual implementation will read from DB/models

View File

@@ -0,0 +1,66 @@
import { describe, it, expect } from 'vitest';
import {
computeNetScore,
clamp01,
clampRange,
luminanceFromNetScore,
addEntryToDay,
updateEntryInDay,
removeEntryFromDay,
replaceEntriesForDay,
recomputeDayNetScore
} from '../../src/compute';
import type { DayTile, HabitEntry } from '../../src/models';
function day(entries: HabitEntry[] = []): DayTile {
return {
date: '2025-01-01',
mood: { hue: 180, intensity: 0.5 },
entries,
netScore: computeNetScore(entries)
};
}
describe('compute helpers', () => {
it('computes netScore correctly', () => {
const entries: HabitEntry[] = [
{ id: '1', type: 'positive', habitId: 'h1', label: 'A', weight: 1, timestamp: new Date().toISOString() },
{ id: '2', type: 'positive', habitId: 'h2', label: 'B', weight: 2, timestamp: new Date().toISOString() },
{ id: '3', type: 'negative', habitId: 'h3', label: 'C', weight: 1, timestamp: new Date().toISOString() }
];
expect(computeNetScore(entries)).toBe(1 + 2 - 1);
});
it('clamps values', () => {
expect(clampRange(5, 0, 3)).toBe(3);
expect(clampRange(-2, 0, 3)).toBe(0);
expect(clamp01(2)).toBe(1);
expect(clamp01(-1)).toBe(0);
});
it('maps luminance smoothly', () => {
const lo = luminanceFromNetScore(-100);
const hi = luminanceFromNetScore(100);
const mid = luminanceFromNetScore(0);
expect(lo).toBeGreaterThanOrEqual(0);
expect(hi).toBeLessThanOrEqual(1);
expect(hi).toBeGreaterThan(lo);
expect(Math.abs(mid - 0.5)).toBeLessThan(1e-6);
});
it('recomputes on CRUD operations', () => {
let d = day();
d = addEntryToDay(d, { id: 'e1', type: 'positive', habitId: 'h1', label: 'A', weight: 2, timestamp: new Date().toISOString() });
expect(d.netScore).toBe(2);
d = addEntryToDay(d, { id: 'e2', type: 'negative', habitId: 'h2', label: 'B', weight: 1, timestamp: new Date().toISOString() });
expect(d.netScore).toBe(1);
d = updateEntryInDay(d, { id: 'e2', type: 'negative', habitId: 'h2', label: 'B', weight: 2, timestamp: new Date().toISOString() });
expect(d.netScore).toBe(0);
d = removeEntryFromDay(d, 'e1');
expect(d.netScore).toBe(-2);
d = replaceEntriesForDay(d, []);
expect(d.netScore).toBe(0);
expect(recomputeDayNetScore(d)).toBe(0);
});
});