import test from 'node:test'; import assert from 'node:assert/strict'; import { displayPath, groupRecentProjects, renderStartScreen } from '../src/start-screen.ts'; import { allowProjectTransition } from '../src/project-transition.ts'; const recent = (id, year, month, day, name = id) => ({ id, path: `C:\\projects\\${id}\\InstallerProject.isproj`, displayName: name, lastOpenedAt: new Date(year, month - 1, day, 10).getTime() / 1000, available: true, }); test('recent projects are grouped in local calendar order and searchable by name or path', () => { const projects = [recent('older', 2026, 9, 30), recent('today', 2026, 10, 21), recent('month', 2026, 10, 4), recent('week', 2026, 10, 20, '패키지 도구')]; const now = new Date(2026, 9, 21, 18); assert.deepEqual([...groupRecentProjects(projects, '', now).keys()], ['오늘', '이번 주', '이번 달', '이전']); assert.equal([...groupRecentProjects(projects, '패키지', now).values()][0][0].id, 'week'); assert.equal([...groupRecentProjects(projects, 'PROJECTS\\month', now).values()][0][0].id, 'month'); assert.equal(groupRecentProjects(projects, 'not found', now).size, 0); }); test('extended Windows paths are displayed without changing their UNC semantics', () => { assert.equal(displayPath('\\\\?\\C:\\apps\\project.isproj'), 'C:\\apps\\project.isproj'); assert.equal(displayPath('\\\\?\\UNC\\server\\share\\project.isproj'), '\\\\server\\share\\project.isproj'); }); test('start screen has left recent projects and right actions without fabricated entries', () => { const base = { projects: [], query: '', busy: false, loading: false, error: '', notice: '', recentError: '', native: true }; const empty = renderStartScreen(base); assert.match(empty, /최근 프로젝트가 없습니다/); assert.ok(empty.indexOf('recent-panel') < empty.indexOf('class="start-actions"')); assert.match(empty, /data-action="open"/); assert.match(empty, /data-action="new"/); assert.doesNotMatch(empty, /class="sidebar"/); const unsafe = { ...recent('a', 2026, 10, 21, ''), available: false }; const html = renderStartScreen({ ...base, projects: [unsafe] }); assert.match(html, /<img/); assert.doesNotMatch(html, / { let saves = 0; assert.equal(await allowProjectTransition(true, async () => 'cancel', async () => { saves++; }), false); assert.equal(await allowProjectTransition(true, async () => 'discard', async () => { saves++; }), true); assert.equal(saves, 0); await assert.rejects(allowProjectTransition(true, async () => 'save', async () => { throw new Error('RevisionConflict'); }), /RevisionConflict/); assert.equal(await allowProjectTransition(true, async () => 'save', async () => { saves++; }), true); assert.equal(saves, 1); assert.equal(await allowProjectTransition(false, async () => { throw new Error('should not ask'); }, async () => {}), true); });