import assert from 'node:assert/strict'; import {test} from 'node:test'; import {readFile} from 'node:fs/promises'; import {loadTypescript} from './support/load-typescript.mjs'; // Removing the carousel markup must fail before implementing it. test('entry replaces three permanent explanations with one manually controlled notice region',async()=>{ const html=await readFile(new URL('../index.html',import.meta.url),'utf8'); assert.match(html,/id="entry-notices"/); assert.match(html,/id="notice-prev"[^>]*aria-label="이전 안내"/); assert.match(html,/id="notice-next"[^>]*aria-label="다음 안내"/); assert.doesNotMatch(html,/

/); }); class El extends EventTarget { constructor(id){super();this.id=id;this.hidden=false;this.dataset={};this.attrs={};} setAttribute(k,v){this.attrs[k]=v;} fire(type,props={}){const e=new Event(type,{cancelable:true});Object.assign(e,props);this.dispatchEvent(e);return e;} closest(){return null;} } async function harness(desktop=false){ const {initEntryNotices}=await loadTypescript(new URL('../src/ui/EntryNotices.ts',import.meta.url)); const ids=['entry-notices','notice-prev','notice-next','notice-position','notice-status','photo-tip','privacy-tip','paste-tip']; const els=Object.fromEntries(ids.map(id=>[id,new El(id)])); els['photo-tip'].textContent='정면 사진이면 더 귀여워요';els['privacy-tip'].textContent='사진은 이 브라우저 안에서만 처리해요';els['paste-tip'].textContent='복사한 사진도 붙여넣을 수 있어요 Ctrl+V · ⌘V'; const media=new El('media');media.matches=desktop; const root={querySelector:s=>els[s.slice(1)],defaultView:{matchMedia:()=>media}}; const controller=new AbortController();initEntryNotices(root,controller.signal); return {els,media,controller,active:()=>ids.filter(id=>els[id].dataset.active==='true')}; } test('mobile has two notices, arrows loop, and initial render is silent',async()=>{ const h=await harness();assert.deepEqual(h.active(),['photo-tip']);assert.equal(h.els['paste-tip'].hidden,true);assert.equal(h.els['notice-position'].textContent,'1 / 2');assert.equal(h.els['notice-status'].textContent,undefined); h.els['notice-next'].fire('click');assert.deepEqual(h.active(),['privacy-tip']);assert.match(h.els['notice-status'].textContent,/2 \/ 2/); h.els['notice-next'].fire('click');assert.deepEqual(h.active(),['photo-tip']); h.els['notice-prev'].fire('click');assert.deepEqual(h.active(),['privacy-tip']); }); test('desktop adds paste notice and changing to touch-only removes it immediately',async()=>{ const h=await harness(true);h.els['notice-prev'].fire('click');assert.deepEqual(h.active(),['paste-tip']); h.media.matches=false;h.media.fire('change');assert.deepEqual(h.active(),['photo-tip']);assert.equal(h.els['paste-tip'].hidden,true);assert.equal(h.els['notice-status'].textContent,''); }); test('horizontal single-touch swipe advances; vertical, cancelled and multiple pointers do not',async()=>{ const h=await harness();const el=h.els['entry-notices']; const down=(id=1)=>el.fire('pointerdown',{pointerId:id,pointerType:'touch',isPrimary:true,clientX:160,clientY:20,targetElement:null}); const up=(x,y=20,id=1)=>el.fire('pointerup',{pointerId:id,clientX:x,clientY:y}); down();up(70);assert.deepEqual(h.active(),['privacy-tip']); down();up(150,100);assert.deepEqual(h.active(),['privacy-tip']); down();el.fire('pointercancel',{pointerId:1});up(70);assert.deepEqual(h.active(),['privacy-tip']); down();el.fire('pointerdown',{pointerId:2,pointerType:'touch',isPrimary:false,clientX:120,clientY:20});up(70);assert.deepEqual(h.active(),['privacy-tip']); }); test('abort removes interaction and media listeners',async()=>{ const h=await harness(true);h.controller.abort();h.els['notice-next'].fire('click');h.media.matches=false;h.media.fire('change');assert.deepEqual(h.active(),['photo-tip']);assert.equal(h.els['paste-tip'].hidden,false); }); test('notices follow the sample action and use app-font counters with a bottom mobile layout',async()=>{ const html=await readFile(new URL('../index.html',import.meta.url),'utf8'); const css=await readFile(new URL('../src/style.css',import.meta.url),'utf8'); assert.ok(html.indexOf('id="entry-samples"') < html.indexOf('id="entry-notices"')); assert.match(css,/#notice-position\s*\{[^}]*font-family:inherit/); assert.match(css,/@media\s*\(max-width:640px\)[\s\S]*#entry-notices\s*\{[^}]*margin-top:auto/); });