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:
@@ -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/);
|
||||
});
|
||||
Reference in New Issue
Block a user