test: unit and end-to-end suites
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
27fafa0bcd
commit
9b00e21c44
49 files changed
+6694
-1
No files matched your search
@@ -0,0 +1,105 @@
|
||||
// End-to-end check of a toggle's body: arrows in and out of it, Enter / Shift+Enter, and selecting across title and body.
|
||||
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-toggle-'));
|
||||
const vault = path.join(tmp, 'Vault');
|
||||
fs.mkdirSync(vault);
|
||||
fs.writeFileSync(path.join(vault, 'Note.md'), 'Before the toggle.\n\n<details>\n<summary>Title</summary>\n\nBody text\n\n</details>\n\nAfter the toggle.\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 key = async (keyCode, modifiers = []) => { wc.sendInputEvent({ type: 'keyDown', keyCode, modifiers }); wc.sendInputEvent({ type: 'keyUp', keyCode, modifiers }); await sleep(250); };
|
||||
const type = async (t) => { for (const ch of t) wc.sendInputEvent({ type: 'char', keyCode: ch }); await sleep(250); };
|
||||
// which field has the caret: 'head', 'body', or the text of another block's field
|
||||
const where = () => js(`const n = getSelection().focusNode; const f = (n?.nodeType === 1 ? n : n?.parentElement)?.closest('.c, .c2');
|
||||
if (!f) return null; if (f.closest('.tg')) return f.classList.contains('c2') ? 'body' : 'head'; return f.textContent;`);
|
||||
const blockTypes = () => js(`return [...document.querySelectorAll('.page .block')].map(b => b.querySelector('.tg') ? 'toggle' : (b.querySelector('.c')?.textContent ?? '?'))`);
|
||||
const rect = (sel) => js(`const r = document.querySelector('${sel}').getBoundingClientRect(); return { x: Math.round(r.x), y: Math.round(r.y + r.height / 2), w: Math.round(r.width), b: Math.round(r.bottom) }`);
|
||||
|
||||
try {
|
||||
for (let i = 0; i < 60 && !(await js(`return !!document.querySelector('.tg')`)); i++) await sleep(150);
|
||||
// open the toggle
|
||||
await js(`document.querySelector('.tg-chev').click()`);
|
||||
await sleep(500);
|
||||
|
||||
// caret at the end of the first paragraph, then down: title, body, next block
|
||||
await js(`const c = document.querySelector('.block .c'); c.focus(); const r = document.createRange(); r.selectNodeContents(c); r.collapse(false); getSelection().removeAllRanges(); getSelection().addRange(r);`);
|
||||
await key('Down');
|
||||
check('Down from the block above enters the toggle title', (await where()) === 'head', await where());
|
||||
await key('End');
|
||||
await key('Down');
|
||||
check('Down at the end of the title enters the body', (await where()) === 'body', await where());
|
||||
await key('End');
|
||||
await key('Down');
|
||||
check('Down at the end of the body leaves the toggle', (await where()) === 'After the toggle.', await where());
|
||||
await key('Home');
|
||||
await key('Up');
|
||||
check('Up from the block below enters the body', (await where()) === 'body', await where());
|
||||
await key('Home');
|
||||
await key('Up');
|
||||
check('Up at the start of the body goes to the title', (await where()) === 'head', await where());
|
||||
|
||||
// Shift+Enter: a new line in the body; Enter: a new block after the toggle
|
||||
await js(`const c = document.querySelector('.c2'); c.focus(); const r = document.createRange(); r.selectNodeContents(c); r.collapse(false); getSelection().removeAllRanges(); getSelection().addRange(r);`);
|
||||
await key('Enter', ['shift']);
|
||||
await type('second');
|
||||
const body = await js(`return document.querySelector('.c2').innerText`);
|
||||
check('Shift+Enter makes a new line inside the body', /Body text\s*\nsecond/.test(body), JSON.stringify(body));
|
||||
await key('Enter');
|
||||
await type('new');
|
||||
const types = await blockTypes();
|
||||
check('Enter in the body starts a new block after the toggle', JSON.stringify(types) === JSON.stringify(['Before the toggle.', 'toggle', 'new', 'After the toggle.']), JSON.stringify(types));
|
||||
check('...with the caret in it', (await where()) === 'new', await where());
|
||||
const body2 = await js(`return document.querySelector('.c2').innerText.trim()`);
|
||||
check('...and the body keeps its text', /^Body text\s*\nsecond$/.test(body2), JSON.stringify(body2));
|
||||
|
||||
// mouse: drag from the title into the body
|
||||
const head = await rect('.tg .c'), b2 = await rect('.c2');
|
||||
wc.sendInputEvent({ type: 'mouseMove', x: head.x + 3, y: head.y });
|
||||
wc.sendInputEvent({ type: 'mouseDown', x: head.x + 3, y: head.y, button: 'left', clickCount: 1 });
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
wc.sendInputEvent({ type: 'mouseMove', x: Math.round(head.x + 3 + (b2.x + 40 - head.x - 3) * i / 10), y: Math.round(head.y + (b2.b - 6 - head.y) * i / 10), button: 'left' });
|
||||
await sleep(25);
|
||||
}
|
||||
wc.sendInputEvent({ type: 'mouseUp', x: b2.x + 40, y: b2.b - 6, button: 'left', clickCount: 1 });
|
||||
await sleep(300);
|
||||
const picked = await js(`const s = getSelection(); return s.rangeCount ? s.getRangeAt(0).toString() : ''`);
|
||||
check('Dragging from the title into the body selects both', /itle/.test(picked) && /Body/.test(picked), JSON.stringify(picked));
|
||||
|
||||
// Shift+Down from the title extends into the body
|
||||
await js(`const c = document.querySelector('.tg .c'); c.focus(); const r = document.createRange(); r.setStart(c.firstChild, 0); r.collapse(true); getSelection().removeAllRanges(); getSelection().addRange(r);`);
|
||||
await key('Down', ['shift']);
|
||||
await key('Down', ['shift']);
|
||||
const picked2 = await js(`const s = getSelection(); return s.rangeCount ? s.getRangeAt(0).toString() : ''`);
|
||||
check('Shift+Down from the title extends into the body', /Title/.test(picked2) && /Body/.test(picked2), JSON.stringify(picked2));
|
||||
|
||||
// Backspace over a title-to-body selection: the title keeps what was before, the body what was after
|
||||
await js(`const h = document.querySelector('.tg .c'), b = document.querySelector('.c2'); const t = [...b.childNodes].find(n => n.nodeType === 3 && n.textContent.includes('Body'));
|
||||
getSelection().setBaseAndExtent(h.firstChild, 2, t, 5);`);
|
||||
await key('Backspace');
|
||||
const after = await js(`return [document.querySelector('.tg .c').textContent, document.querySelector('.c2').innerText.trim()]`);
|
||||
check('Backspace over title + body removes just the selection', after[0] === 'Ti' && /^text\s*\nsecond$/.test(after[1]), JSON.stringify(after));
|
||||
} catch (err) {
|
||||
check('harness', false, err.stack);
|
||||
}
|
||||
fs.rmSync(tmp, { recursive: true, force: true });
|
||||
console.log(failures.length ? `\n${failures.length} check(s) failed` : '\ntoggle e2e passed!');
|
||||
app.exit(failures.length ? 1 : 0);
|
||||
});
|
||||
Reference in new issue
Block a user