Replace project with current server release and Windows setup

Import the 2026-10-09 source release, Fastify/SQLite backend, restored source
assets, and analytics-enabled server build. Replace files from the previous
release while retaining the existing repository history.

Make builds portable across Windows and Linux, configure local startup,
normalize Windows static asset paths, and fix test fixture cleanup and file
URL handling. Exclude local environment settings, databases, dependencies,
validation output, and TypeScript build cache from version control.

Validation: npm ci, static and server builds, 106 arrival tests, and actual
local HTTP checks passed. The full suite passed 467 of 470 tests; three tests
still require QA fixtures omitted from the supplied release, documented in
README-서버실행.md.
This commit is contained in:
2026-10-09 16:43:35 +09:00
parent 71c8d3b7ff
commit e358e24436
642 changed files with 35202 additions and 17642 deletions
+27
View File
@@ -0,0 +1,27 @@
import assert from 'node:assert/strict';
import { test } from 'node:test';
import { loadTypescript } from './support/load-typescript.mjs';
const { initImagePaste } = await loadTypescript(new URL('../src/input/ImagePaste.ts', import.meta.url));
function harness() {
const handlers = new Map(), accepted=[]; let enabled=true;
const root={activeElement:null,addEventListener(type,cb,{signal}){handlers.set(type,cb);signal.addEventListener('abort',()=>handlers.delete(type));}};
const abort=new AbortController(); initImagePaste(root,abort.signal,()=>enabled,file=>{accepted.push(file);return true;});
const paste=(items=[],extra={})=>{let prevented=false; const event={defaultPrevented:false,target:null,clipboardData:{items,files:[]},preventDefault(){prevented=true;},...extra}; handlers.get('paste')?.(event);return prevented;};
return {root,abort,accepted,paste,disable(){enabled=false;}};
}
const photo={name:'paste.png',type:'image/png',size:1024};
const item=(file=photo)=>({kind:'file',type:file.type,getAsFile:()=>file});
test('explicit image paste accepts first image once and consumes only that paste',()=>{const h=harness();assert.equal(h.paste([item(),item()]),true);assert.deepEqual(h.accepted,[photo]);});
test('text, missing clipboard and invalid image items remain untouched',()=>{const h=harness();assert.equal(h.paste([{kind:'string',type:'text/plain'}]),false);assert.equal(h.paste([], {clipboardData:null}),false);assert.equal(h.paste([{kind:'file',type:'image/png',getAsFile:()=>null}]),false);assert.equal(h.accepted.length,0);});
test('editable fields and contenteditable keep native paste',()=>{for(const target of [{isContentEditable:true},{closest:()=>({})}]){const h=harness();assert.equal(h.paste([item()],{target}),false);assert.equal(h.accepted.length,0);} const h=harness();h.root.activeElement={closest:()=>({})};assert.equal(h.paste([item()]),false);});
test('blocked mode, previously handled event and teardown do not read images',()=>{const h=harness();h.disable();assert.equal(h.paste([item()]),false);const other=harness();assert.equal(other.paste([item()],{defaultPrevented:true}),false);other.abort.abort();assert.equal(other.paste([item()]),false);assert.equal(other.accepted.length,0);});
test('files-only clipboard fallback accepts image but not nonimages',()=>{const h=harness();assert.equal(h.paste([],{clipboardData:{files:[{type:'text/plain'},photo]}}),true);assert.deepEqual(h.accepted,[photo]);});
test('open dialogs and editable composed paths leave paste untouched',()=>{const h=harness();h.root.querySelector=()=>({open:true});assert.equal(h.paste([item()]),false);h.root.querySelector=()=>null;assert.equal(h.paste([item()],{composedPath:()=>[{isContentEditable:true}]}),false);assert.equal(h.accepted.length,0);});
test('JPEG after text and unusable image is accepted without reading HTML',()=>{const h=harness(),jpg={name:'dog.jpg',type:'image/jpeg',size:20};assert.equal(h.paste([{kind:'string',type:'text/html',getAsString(){throw Error('not allowed');}},{kind:'file',type:'image/png',getAsFile:()=>null},item(jpg)]),true);assert.deepEqual(h.accepted,[jpg]);});
test('rejected image does not consume native paste',()=>{let callback;const abort=new AbortController();const root={activeElement:null,addEventListener(_,fn){callback=fn;}};initImagePaste(root,abort.signal,()=>true,()=>false);let prevented=false;callback({target:null,clipboardData:{items:[item()]},preventDefault(){prevented=true;}});assert.equal(prevented,false);});
test('keyboard shortcut hint is part of the desktop-only manual notices', async()=>{
const {readFile}=await import('node:fs/promises');const source=await readFile(new URL('../src/ui/EntryNotices.ts',import.meta.url),'utf8');
assert.match(source,/\(hover: hover\) and \(pointer: fine\)/);
assert.match(source,/desktop\?\.matches \? 3 : 2/);
});