Add the shared Rust engine, Tauri UI and CLI, registry authoring, developer signing, branded start screen and .isproj persistence. Remove the unused project ID. Validation: frontend build and tests, Rust workspace tests, native build and real Windows SDK package generation passed. Closes #2 Closes #3 Closes #4 Closes #5 Closes #6 Closes #7
50 lines
3.0 KiB
JavaScript
50 lines
3.0 KiB
JavaScript
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, '<img src=x onerror=alert(1)>'), available: false };
|
|
const html = renderStartScreen({ ...base, projects: [unsafe] });
|
|
assert.match(html, /<img/);
|
|
assert.doesNotMatch(html, /<img src=x/);
|
|
assert.match(html, /파일을 찾을 수 없습니다/);
|
|
});
|
|
|
|
test('cancel keeps edits, discard does not save, and a save failure prevents closing', async () => {
|
|
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);
|
|
});
|