110 lines
7.2 KiB
JavaScript
110 lines
7.2 KiB
JavaScript
// End-to-end: daily calendar and stepping, tabs, tag manager, typed properties, rich paste, web clip, imports.
|
|
const { app, BrowserWindow, ipcMain } = require('electron');
|
|
const path = require('path');
|
|
const os = require('os');
|
|
const fs = require('fs');
|
|
|
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ink-e2e-flow-'));
|
|
const vault = path.join(tmp, 'Vault');
|
|
fs.mkdirSync(path.join(vault, 'Daily'), { recursive: true });
|
|
fs.writeFileSync(path.join(vault, 'Daily.md'), '');
|
|
fs.writeFileSync(path.join(vault, 'Daily', '2020-01-01.md'), '---\ncreated: 2020-01-01\n---\n\nfirst\n');
|
|
fs.writeFileSync(path.join(vault, 'Daily', '2020-01-05.md'), '---\ncreated: 2020-01-05\n---\n\nsecond\n');
|
|
fs.writeFileSync(path.join(vault, 'A.md'), '---\ntags: [alpha, beta]\ndone: false\nrating: 4\n---\n\nPage A\n');
|
|
fs.writeFileSync(path.join(vault, 'B.md'), '---\ntags: [beta]\n---\n\nPage B\n');
|
|
app.setPath('userData', tmp);
|
|
fs.writeFileSync(path.join(tmp, 'settings.json'), JSON.stringify({ vault, lastPage: 'A', theme: 'dark', bounds: { width: 1200, height: 800 } }));
|
|
const lp = require('../electron/linkpreview.cjs');
|
|
lp.hooks.lookup = async () => [{ address: '93.184.216.34' }];
|
|
lp.hooks.fetch = async () => new Response('<html><head><title>Hello Web</title></head><body><nav>menu</nav><article><h1>Hello Web</h1><p>Some <b>bold</b> text and a <a href="/x">link</a>.</p><ul><li>one</li><li>two</li></ul></article></body></html>', { headers: { 'content-type': 'text/html' } });
|
|
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);
|
|
};
|
|
const read = (f) => fs.readFileSync(path.join(vault, f), 'utf8');
|
|
|
|
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 click = (sel) => js(`document.querySelector(${JSON.stringify(sel)}).click()`);
|
|
const rowClick = (id, mods = '') => js(`document.querySelector('.tree .row[data-id=${JSON.stringify(id)}]').dispatchEvent(new MouseEvent('click', { bubbles: true, ${mods} }))`);
|
|
try {
|
|
await sleep(2200);
|
|
|
|
// properties: typed by value
|
|
check('a "true/false" property is a checkbox, a number a number', (await js(`return document.querySelectorAll('.props .db-pv').length`)) === 2);
|
|
await js(`[...document.querySelectorAll('.props .chip-add')].find((b) => b.textContent.includes('Add property')).click()`);
|
|
await sleep(300);
|
|
const chips = await js(`return [...document.querySelectorAll('.type-chip')].map((b) => b.textContent.trim())`);
|
|
check('Add property offers types', chips.includes('Date') && chips.includes('Checkbox'), chips.join(','));
|
|
await js(`[...document.querySelectorAll('.type-chip')].find((b) => b.textContent.includes('Date')).click()`);
|
|
await sleep(200);
|
|
await js(`const i = document.querySelector('.custom-pk[placeholder$="property name"]'); i.value = 'due'; i.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }))`);
|
|
await sleep(1500);
|
|
check('a new Date property is written to the front matter', /due: \d{4}-\d{2}-\d{2}/.test(read('A.md')), read('A.md'));
|
|
|
|
// tags: merge and remove
|
|
await js(`document.querySelector('.sidebar button[aria-label="Manage tags"]').click()`);
|
|
await sleep(500);
|
|
check('the tag manager lists tags with counts', (await js(`return [...document.querySelectorAll('.tr .tr-t')].map((e) => e.textContent.replace(/\\s+/g, ' ').trim()).join('|')`)) === '#alpha1 page|#beta2 pages');
|
|
await js(`[...document.querySelectorAll('.tr')].find((r) => r.textContent.includes('#alpha')).querySelector('.btn').click()`);
|
|
await sleep(200);
|
|
await js(`const i = document.querySelector('.tg-edit'); i.value = 'beta'; i.dispatchEvent(new Event('input', { bubbles: true }))`);
|
|
await sleep(200);
|
|
await js(`[...document.querySelectorAll('.tr .btn')].find((b) => b.textContent.includes('Merge')).click()`);
|
|
await sleep(1200);
|
|
check('renaming into an existing tag merges without duplicates', /tags: \[beta\]/.test(read('A.md')), read('A.md').split('\n').slice(0, 5).join(' '));
|
|
await js(`[...document.querySelectorAll('.tr')].find((r) => r.textContent.includes('#beta')).querySelector('.btn.danger').click()`);
|
|
await sleep(200);
|
|
await js(`[...document.querySelectorAll('.tr')].find((r) => r.textContent.includes('#beta')).querySelector('.btn.danger').click()`);
|
|
await sleep(1200);
|
|
check('removing a tag takes it off every page', !/tags:/.test(read('A.md')) && !/tags:/.test(read('B.md')));
|
|
await js(`document.querySelector('.tasks-panel .btn-icon').click()`);
|
|
await sleep(400);
|
|
|
|
// daily: calendar + stepping
|
|
await js(`document.querySelector('.tree .row[data-id="Daily"]').dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }))`);
|
|
await sleep(500);
|
|
await rowClick('Daily/2020-01-01');
|
|
await sleep(500);
|
|
check('a daily note shows the day buttons', (await js(`return !!document.querySelector('.day-nav')`)) === true);
|
|
await js(`document.querySelector('.day-nav button[aria-label="Next daily note"]').click()`);
|
|
await sleep(700);
|
|
check('"next" goes to the next existing daily note', (await js(`return document.querySelector('.tree .row.active')?.dataset.id`)) === 'Daily/2020-01-05');
|
|
await js(`document.querySelector('.day-nav button[aria-label="Pick a day"]').click()`);
|
|
await sleep(400);
|
|
check('the calendar marks days with notes', (await js(`return document.querySelectorAll('.cal-d.has').length`)) === 2);
|
|
await js(`[...document.querySelectorAll('.cal-d')].find((b) => b.textContent.trim() === '3').click()`);
|
|
await sleep(1500);
|
|
check('picking an empty day creates its note', fs.existsSync(path.join(vault, 'Daily', '2020-01-03.md')));
|
|
|
|
// rich paste
|
|
await rowClick('B');
|
|
await sleep(600);
|
|
await js(`document.querySelector('.block .c').focus()`);
|
|
await js(`const dt = new DataTransfer(); dt.setData('text/html', '<h2>Title</h2><ul><li>one</li><li>two</li></ul><p>a <b>bold</b> <a href="https://x.test">link</a></p>'); dt.setData('text/plain', 'Title one two a bold link');
|
|
document.querySelector('.block .c').dispatchEvent(new ClipboardEvent('paste', { clipboardData: dt, bubbles: true, cancelable: true }))`);
|
|
await sleep(1500);
|
|
await sleep(2000);
|
|
const b = read('B.md');
|
|
check('pasting web content keeps headings, lists, bold and links', /## Title/.test(b) && /- one/.test(b) && /\*\*bold\*\*/.test(b) && /\[link\]\(https:\/\/x\.test\/?\)/.test(b), b);
|
|
|
|
// web clip
|
|
await ipcMain._invokeHandlers.get('web:clip')({}, 'https://example.test/post').then(async (page) => {
|
|
check('the clipper fetches a page', /Hello Web/.test(page.html));
|
|
});
|
|
} catch (err) {
|
|
check('harness', false, err.stack);
|
|
}
|
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
console.log(failures.length ? `\n${failures.length} check(s) failed` : '\nworkflow e2e passed!');
|
|
app.exit(failures.length ? 1 : 0);
|
|
});
|