feat: save analysis results and compare growth snapshots
Publish GitHub Pages output / publish (push) Successful in 46s

This commit is contained in:
2026-09-06 10:39:06 +09:00
parent b484daf956
commit 476a25caef
9 changed files with 510 additions and 8 deletions
+46
View File
@@ -0,0 +1,46 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { compactResult, currentGrowthSettings, snapshotKey, type AnalysisSettings, type GrowthSnapshot } from '../models/analysis-history.ts';
import { parseAnalysisArchive } from '../storage/analysis-history.ts';
import type { ExactOptimizationResult } from './types.ts';
const growth: GrowthSnapshot = { collection: { a: { owned: true, awakening: 2 } }, memberBoards: { a: ['B1', 'G1'] }, boardCards: {}, memoryCount: 10, memberPowerUpPercent: 1 };
test('동일한 성장 상태는 입력 순서와 무관하게 공유하고 성장 차이는 구분한다', () => {
const reordered = { ...growth, memberBoards: { a: ['G1', 'B1'] } };
assert.equal(snapshotKey(growth), snapshotKey(reordered));
assert.notEqual(snapshotKey(growth), snapshotKey({ ...growth, memoryCount: 11 }));
});
test('현재 성장 비교는 저장 조건을 유지하고 보유 개화만 교체한다', () => {
const settings: AnalysisSettings = { songId: 'song', difficulty: 'expert', cardIds: ['a'], awakenings: [0], allBlueNodes: [true], generationBonusOverrides: [900], leaderCharacterId: 'a', leaderOutfitCardId: 'a' };
const current = currentGrowthSettings(settings, growth);
assert.deepEqual(current.awakenings, [2]); assert.deepEqual(settings.awakenings, [0]);
assert.deepEqual(current.generationBonusOverrides, [900]); assert.deepEqual(current.allBlueNodes, [true]);
assert.throws(() => currentGrowthSettings(settings, { ...growth, collection: {} }), /보유/);
});
test('기록은 노트별 배열을 제외하고 원본과 독립적으로 보존한다', () => {
const source = { notes: [{ timeSeconds: 1 }], scoreBySecond: [2], order: ['a'], expectedScore: 123 } as unknown as ExactOptimizationResult;
const saved = compactResult(source);
source.order = ['b'];
assert.deepEqual(saved.order, ['a']); assert.equal(saved.expectedScore, 123);
assert.equal('notes' in saved, false); assert.equal('scoreBySecond' in saved, false);
});
test('기록 백업은 잘못된 구조와 누락된 성장 참조를 거부한다', () => {
assert.throws(() => parseAnalysisArchive('{}'));
assert.throws(() => parseAnalysisArchive(JSON.stringify({ schemaVersion: 1, snapshots: [], records: [{ settings: {}, result: {}, snapshotId: 'missing' }] })));
assert.deepEqual(parseAnalysisArchive('{"schemaVersion":1,"snapshots":[],"records":[]}').records, []);
});
test('성장 상태를 공유하는 기록 백업을 손실 없이 읽고 잘못된 개화를 거부한다', () => {
const ids = ['a', 'b', 'c', 'd', 'e'];
const record = {
id: 'record', name: '기록', title: '곡', analyzedAt: '2026-09-06T00:00:00Z', snapshotId: 'snapshot', overallPower: 1000, durationSeconds: 120,
settings: { songId: 'song', difficulty: 'expert', cardIds: ids, awakenings: [0, 1, 2, 3, 4], leaderCharacterId: 'a', leaderOutfitCardId: 'a' },
result: { expectedScore: 100, theoreticalMaxScore: 120, baselineScore: 50, perfectNoteScore: 10, order: ids, cooldownReductions: Object.fromEntries(ids.map((id) => [id, 0])) }
};
const archive = { schemaVersion: 1, snapshots: [{ id: 'snapshot', growth }], records: [record, { ...record, id: 'second' }] };
const parsed = parseAnalysisArchive(JSON.stringify(archive));
assert.equal(parsed.records.length, 2); assert.equal(parsed.snapshots.length, 1);
assert.deepEqual(parsed.snapshots[0].growth, growth);
record.settings.awakenings[0] = 8;
assert.throws(() => parseAnalysisArchive(JSON.stringify(archive)));
});