feat: add visual green board activation presets
Publish GitHub Pages output / publish (push) Successful in 31s

This commit is contained in:
2026-09-21 10:24:36 +09:00
parent 4c049b0860
commit 3b1c1aeb6f
4 changed files with 101 additions and 13 deletions
@@ -0,0 +1,39 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { activateGreenNodePreset, type GreenNodePreset } from './board-green-presets.ts';
const summary = JSON.parse(readFileSync(new URL('../data/boards-summary.json', import.meta.url), 'utf8'));
const expected: Record<GreenNodePreset, string[]> = {
3: ['G-001', 'G-002', 'G-005'],
5: ['G-001', 'G-002', 'G-003', 'G-004', 'G-005'],
7: ['G-001', 'G-002', 'G-003', 'G-004', 'G-005', 'G-006', 'G-007']
};
for (const count of [3, 5, 7] as const) {
test(`${count}칸은 모든 멤버의 지정 초록 노드만 활성화한다`, () => {
const result = activateGreenNodePreset({}, summary.members, summary.models, count);
assert.equal(Object.keys(result).length, summary.members.length);
for (const member of summary.members) {
assert.deepEqual(result[member.id].toSorted(), expected[count], member.id);
}
});
}
test('7칸 적용 후 3칸·5칸을 반복 적용해도 기존 활성 상태를 보존하며 원본을 변경하지 않는다', () => {
const memberId = summary.members[0].id;
const original = { [memberId]: ['B-001', 'Y-001', 'G-001', 'G-008'], other: ['R-001'] };
const snapshot = structuredClone(original);
const seven = activateGreenNodePreset(original, summary.members, summary.models, 7);
const three = activateGreenNodePreset(seven, summary.members, summary.models, 3);
const five = activateGreenNodePreset(three, summary.members, summary.models, 5);
assert.deepEqual(original, snapshot);
assert.deepEqual(five, seven);
assert.deepEqual(five[memberId].toSorted(), ['B-001', ...expected[7], 'G-008', 'Y-001']);
assert.deepEqual(five.other, ['R-001']);
});
test('선택 좌표에 다른 색 노드가 있으면 활성화하지 않는다', () => {
const models = { model: [{ groupId: 'B-001', x: 0, y: -1 }, { groupId: 'Y-001', x: -1, y: -2 }] };
assert.deepEqual(activateGreenNodePreset({}, [{ id: 'a', positionGroupId: 'model' }], models, 7), { a: [] });
});
+29
View File
@@ -0,0 +1,29 @@
export type GreenNodePreset = 3 | 5 | 7;
type BoardPosition = { groupId: string; x: number; y: number };
type BoardMember = { id: string; positionGroupId: string };
// Ordered so the first 3, 5 or 7 coordinates define each preset and its preview.
export const greenNodePresetPositions = [
{ x: 0, y: -1 }, { x: 0, y: -2 }, { x: 0, y: -3 },
{ x: -1, y: -2 }, { x: 1, y: -2 },
{ x: 0, y: -4 }, { x: 0, y: -5 }
] as const;
/** Add the central green branch without resetting any existing board progress. */
export function activateGreenNodePreset(
memberBoards: Readonly<Record<string, readonly string[]>>,
members: readonly BoardMember[],
models: Readonly<Record<string, readonly BoardPosition[]>>,
count: GreenNodePreset
): Record<string, string[]> {
const result = Object.fromEntries(Object.entries(memberBoards).map(([id, nodes]) => [id, [...nodes]]));
const coordinates = greenNodePresetPositions.slice(0, count);
for (const member of members) {
const nodes = (models[member.positionGroupId] ?? []).filter(({ groupId, x, y }) =>
groupId.startsWith('G-') && coordinates.some((position) => position.x === x && position.y === y)
);
result[member.id] = [...new Set([...(result[member.id] ?? []), ...nodes.map((node) => node.groupId)])];
}
return result;
}