// End-to-end check of edits made outside the app: they are never silently overwritten. const { app, BrowserWindow } = require('electron'); const path = require('path'); const os = require('os'); const fs = require('fs'); const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ink-e2e-conflict-')); const vault = path.join(tmp, 'Vault'); fs.mkdirSync(vault); fs.writeFileSync(path.join(vault, 'Note.md'), 'Original line.\n'); app.setPath('userData', tmp); fs.writeFileSync(path.join(tmp, 'settings.json'), JSON.stringify({ vault, lastPage: 'Note', bounds: { width: 1200, height: 800 } })); require('../electron/main.cjs'); const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); const failures = []; const check = (name, ok, extra = '') => { console.log(`${ok ? 'PASS' : 'FAIL'} ${name}${extra ? ' — ' + extra : ''}`); if (!ok) failures.push(name); }; app.whenReady().then(async () => { let win; for (let i = 0; i < 80 && !(win = BrowserWindow.getAllWindows()[0]); i++) await sleep(100); const wc = win.webContents; if (wc.isLoading()) await new Promise((r) => wc.once('did-finish-load', r)); const js = (code) => wc.executeJavaScript(`(async () => { ${code} })()`); const note = path.join(vault, 'Note.md'); const versions = () => { const dir = path.join(vault, '.notes', 'history', 'Note'); return fs.existsSync(dir) ? fs.readdirSync(dir).map((f) => fs.readFileSync(path.join(dir, f), 'utf8')) : []; }; const toastText = () => js(`const t = document.querySelector('.toast'); return t && t.classList.contains('show') ? t.textContent : ''`); const typeAtEnd = async (text) => { await js(`const c = document.querySelector('.block .c'); c.focus(); const r = document.createRange(); r.selectNodeContents(c); r.collapse(false); const s = getSelection(); s.removeAllRanges(); s.addRange(r)`); await wc.insertText(text); }; try { await sleep(1800); // an ordinary edit: no fuss await typeAtEnd(' One.'); await sleep(1500); check('an ordinary edit saves with no warning', /Original line\. One\./.test(fs.readFileSync(note, 'utf8')) && !/outside Ink/.test(await toastText()), JSON.stringify(await toastText())); // changed on disk while this window has unsaved edits await typeAtEnd(' Mine.'); fs.writeFileSync(note, 'Written by another editor.\n'); // within the save delay await sleep(2200); const now = fs.readFileSync(note, 'utf8'); check('your edit is saved', /Mine\./.test(now), JSON.stringify(now)); check('...the other version is kept in version history', versions().some((v) => /Written by another editor\./.test(v)), JSON.stringify(versions().map((v) => v.slice(0, 40)))); check('...and you are told', /outside Ink/.test(await toastText()) && /Show versions/.test(await toastText()), JSON.stringify(await toastText())); await js(`document.querySelector('.toast .toast-btn').click()`); await sleep(700); check('"Show versions" opens the history', !!(await js(`return document.querySelector('[aria-label="Version history"], .panel')`))); await js(`window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' }))`); await sleep(400); // changed on disk with nothing unsaved: the window just follows fs.writeFileSync(note, 'Fresh from elsewhere.\n'); await sleep(1800); check('with nothing unsaved the page follows the file', /Fresh from elsewhere/.test(await js(`return document.querySelector('.block .c')?.textContent || ''`))); await typeAtEnd(' Then me.'); await sleep(1500); check('...and editing on from there raises no warning', /Fresh from elsewhere\. Then me\./.test(fs.readFileSync(note, 'utf8')) && !/outside Ink/.test(await toastText().then((t) => (/Updated from disk/.test(t) ? '' : t))), JSON.stringify(await toastText())); } catch (err) { check('harness', false, err.stack); } fs.rmSync(tmp, { recursive: true, force: true }); console.log(failures.length ? `\n${failures.length} check(s) failed` : '\nconflict e2e passed!'); app.exit(failures.length ? 1 : 0); });