import test from 'node:test' import assert from 'node:assert/strict' import {readFile} from 'node:fs/promises' import {JSDOM} from '../../learner/node_modules/jsdom/lib/api.js' let serial=0 async function page(run){ const html=await readFile(new URL('./index.html',import.meta.url),'utf8') const dom=new JSDOM(html,{url:'http://127.0.0.1:5184/'}) globalThis.window=dom.window;globalThis.document=dom.window.document dom.window.scrollTo=()=>{} dom.window.HTMLElement.prototype.scrollIntoView=()=>{} await import(`./app.mjs?test=${++serial}`) const $=id=>document.getElementById(id) try{await run($,dom.window)}finally{await new Promise(resolve=>setTimeout(resolve,120));dom.window.close()} } test('close preserves keyboard cursor: next arrow continues at the adjacent word',()=>page(($,w)=>{ const span=[...$('reader').children].find(n=>n.textContent==='workshop') span.click();assert.equal($('term').textContent,'workshop') $('close').click();assert.equal($('panel').hidden,true) $('reader').dispatchEvent(new w.KeyboardEvent('keydown',{key:'ArrowRight',bubbles:true})) assert.equal($('term').textContent,'before') })) test('native punctuation-only selection clears stale word and cannot save it',()=>page(async($,w)=>{ [...$('reader').children].find(n=>n.textContent==='curiosity').click() const dot=[...$('reader').children].find(n=>n.textContent==='.') const range=document.createRange();range.selectNodeContents(dot) w.getSelection().removeAllRanges();w.getSelection().addRange(range) document.dispatchEvent(new w.Event('selectionchange')) await new Promise(resolve=>setTimeout(resolve,130)) assert.equal($('panel').hidden,true) assert.equal($('reader').querySelectorAll('.selected').length,0) })) test('native selection maps DOM text through spans and expands to full token endpoints',()=>page(async($,w)=>{ const spans=[...$('reader').children] const start=spans.find(n=>n.textContent==='Mira'),end=spans.find(n=>n.textContent==='workshop') const range=document.createRange();range.setStart(start.firstChild,2);range.setEnd(end.firstChild,3) w.getSelection().removeAllRanges();w.getSelection().addRange(range) document.dispatchEvent(new w.Event('selectionchange')) await new Promise(resolve=>setTimeout(resolve,130)) assert.equal($('term').textContent,'Mira opened the workshop') const result=JSON.parse($('range-output').textContent) assert.equal($('reader').textContent.slice(...result.utf16),result.text) })) test('explicit save updates repeated words; unsaved edits are discarded on close',()=>page(($)=>{ const words=[...$('reader').children].filter(n=>n.textContent==='curiosity') words[0].click();$('meaning').value='虚构释义';$('save').click() assert.ok(words.every(n=>n.classList.contains('learning'))) $('meaning').value='未保存修改';$('close').click();words[1].click() assert.equal($('meaning').value,'虚构释义') $('next').click();assert.equal($('panel').hidden,true) }))