146 lines
9.1 KiB
JavaScript
146 lines
9.1 KiB
JavaScript
// End-to-end check of picture covers: upload from the file dialog, drop onto the cover, remove, and moving with the page.
|
|
const { app, BrowserWindow, dialog } = require('electron');
|
|
const path = require('path');
|
|
const os = require('os');
|
|
const fs = require('fs');
|
|
|
|
const PNG = Buffer.from('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==', 'base64');
|
|
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'ink-e2e-cover-'));
|
|
const vault = path.join(tmp, 'Vault');
|
|
fs.mkdirSync(path.join(vault, 'Folder'), { recursive: true });
|
|
fs.writeFileSync(path.join(vault, 'Note.md'), 'Some text.\n');
|
|
fs.writeFileSync(path.join(vault, 'Folder', 'Home.md'), 'Home.\n');
|
|
fs.mkdirSync(path.join(vault, 'Gal'));
|
|
fs.writeFileSync(path.join(vault, 'Gal.md'), '---\nink-database: basic\n---\n\n%% ink-database\n{"properties":[],"views":[{"id":"v1","name":"Gallery","type":"gallery"}]}\n%%\n');
|
|
fs.writeFileSync(path.join(vault, 'Gal', 'Item.md'), '---\ncover: first.png\n---\n');
|
|
fs.writeFileSync(path.join(vault, 'Gal', 'first.png'), PNG);
|
|
const outside = path.join(tmp, 'banner.png');
|
|
fs.writeFileSync(outside, PNG);
|
|
app.setPath('userData', tmp);
|
|
fs.writeFileSync(path.join(tmp, 'settings.json'), JSON.stringify({ vault, lastPage: 'Note', bounds: { width: 1300, height: 800 } }));
|
|
dialog.showOpenDialog = async () => ({ canceled: false, filePaths: [outside] });
|
|
// web pictures: no real network (and the address check is told these hosts are public)
|
|
const lp = require('../electron/linkpreview.cjs');
|
|
const asked = [];
|
|
lp.hooks.lookup = async () => [{ address: '93.184.216.34' }];
|
|
lp.hooks.fetch = async (url) => {
|
|
asked.push(url);
|
|
if (/\/page$/.test(url)) return new Response('<html></html>', { headers: { 'content-type': 'text/html' } });
|
|
return new Response(PNG, { headers: { 'content-type': 'image/png' } });
|
|
};
|
|
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 centerOf = (sel) => js(`const e = ${sel}; if (!e) return null; const r = e.getBoundingClientRect(); return [Math.round(r.x + r.width / 2), Math.round(r.y + r.height / 2)]`);
|
|
const click = async (sel) => {
|
|
const c = await centerOf(sel);
|
|
if (!c) throw new Error('not found: ' + sel);
|
|
wc.sendInputEvent({ type: 'mouseMove', x: c[0], y: c[1] });
|
|
wc.sendInputEvent({ type: 'mouseDown', x: c[0], y: c[1], button: 'left', clickCount: 1 });
|
|
wc.sendInputEvent({ type: 'mouseUp', x: c[0], y: c[1], button: 'left', clickCount: 1 });
|
|
await sleep(450);
|
|
};
|
|
const fm = (f) => fs.readFileSync(path.join(vault, f), 'utf8');
|
|
const loaded = () => js(`const i = document.querySelector('.cover .cover-img'); return !!i && i.complete && i.naturalWidth > 0`);
|
|
|
|
try {
|
|
await sleep(1800);
|
|
check('a page starts with a gradient cover', await js(`return !document.querySelector('.cover .cover-img') && !document.querySelector('.cover.photo')`));
|
|
|
|
await click(`document.querySelector('.cover-btn')`);
|
|
check('the cover picker offers an upload', !!(await js(`return [...document.querySelectorAll('.cover-picker .c-up')].find((b) => b.textContent.includes('Upload'))`)));
|
|
await click(`[...document.querySelectorAll('.cover-picker .c-up')].find((b) => b.textContent.includes('Upload'))`);
|
|
await sleep(900);
|
|
check('uploading copies the picture next to the page', fs.existsSync(path.join(vault, 'banner.png')));
|
|
check('...and the page\'s front matter points at it', /cover: banner\.png/.test(fm('Note.md')), JSON.stringify(fm('Note.md')));
|
|
check('...and it shows as the cover', await loaded() && await js(`return document.querySelector('.cover').classList.contains('photo')`));
|
|
|
|
// a dropped picture
|
|
await js(`
|
|
const dt = new DataTransfer();
|
|
const bytes = Uint8Array.from(atob(${JSON.stringify(PNG.toString('base64'))}), (c) => c.charCodeAt(0));
|
|
dt.items.add(new File([bytes], 'dropped.png', { type: 'image/png' }));
|
|
const el = document.querySelector('.cover');
|
|
el.dispatchEvent(new DragEvent('dragover', { bubbles: true, cancelable: true, dataTransfer: dt }));
|
|
el.dispatchEvent(new DragEvent('drop', { bubbles: true, cancelable: true, dataTransfer: dt }));
|
|
`);
|
|
await sleep(1200);
|
|
check('dropping a picture on the cover replaces it', /cover: dropped\.png/.test(fm('Note.md')) && fs.existsSync(path.join(vault, 'dropped.png')), fm('Note.md'));
|
|
|
|
// it goes with the page
|
|
await js(`await window.vault.flush?.()`);
|
|
await sleep(800);
|
|
const moved = await js(`return await window.vault.move('Note', 'Folder')`);
|
|
await sleep(800);
|
|
check('moving the page moves its cover picture too', moved === 'Folder/Note' && fs.existsSync(path.join(vault, 'Folder', 'dropped.png')) && !fs.existsSync(path.join(vault, 'dropped.png')), String(moved));
|
|
check('...and the front matter still names it', /cover: dropped\.png/.test(fm('Folder/Note.md')));
|
|
|
|
// back to a gradient
|
|
await js(`document.querySelector('.row[data-id="Folder"]')?.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowRight', bubbles: true }))`); // folders draw their rows when first opened
|
|
await sleep(400);
|
|
await js(`document.querySelector('.row[data-id="Folder/Note"], .row[data-id="Folder"]')?.click()`);
|
|
await sleep(600);
|
|
await js(`await new Promise((r) => setTimeout(r, 200))`);
|
|
await js(`const row = document.querySelector('.row[data-id="Folder/Note"]'); if (row) row.click();`);
|
|
await sleep(1000);
|
|
await click(`document.querySelector('.cover-btn')`);
|
|
await click(`[...document.querySelectorAll('.cover-picker .c-up')].find((b) => b.textContent.includes('Remove'))`);
|
|
await sleep(1000);
|
|
check('"Remove the image" goes back to a gradient', !/cover:/.test(fm('Folder/Note.md')) && !(await js(`return !!document.querySelector('.cover .cover-img')`)), fm('Folder/Note.md'));
|
|
|
|
// a picture from a web address
|
|
await js(`document.querySelector('.row[data-id="Folder/Note"]').click()`);
|
|
await sleep(900);
|
|
await click(`document.querySelector('.cover-btn')`);
|
|
await click(`[...document.querySelectorAll('.cover-picker .c-up')].find((b) => b.textContent.includes('link'))`);
|
|
check('the picker offers "From a link"', !!(await js(`return document.querySelector('.cover-picker .c-url')`)));
|
|
const typeUrl = (v) => js(`const i = document.querySelector('.cover-picker .c-url'); i.value = ${JSON.stringify(v)}; i.dispatchEvent(new Event('input', { bubbles: true }))`);
|
|
await typeUrl('http://127.0.0.1/secret.png');
|
|
await click(`document.querySelector('.cover-picker .c-go')`);
|
|
await sleep(600);
|
|
check('an address on this machine is refused, with a message', /public web address/.test(await js(`return document.querySelector('.cover-picker .c-problem')?.textContent || ''`)));
|
|
check('...without anything being fetched', asked.length === 0, JSON.stringify(asked));
|
|
await typeUrl('https://example.com/blog/page');
|
|
await click(`document.querySelector('.cover-picker .c-go')`);
|
|
await sleep(600);
|
|
check('an address that is not a picture says so', /not a picture/.test(await js(`return document.querySelector('.cover-picker .c-problem')?.textContent || ''`)));
|
|
await typeUrl('https://example.com/img/lipstick.png?w=800');
|
|
await click(`document.querySelector('.cover-picker .c-go')`);
|
|
await sleep(1200);
|
|
check('a picture address is downloaded next to the page', fs.existsSync(path.join(vault, 'Folder', 'lipstick.png')), JSON.stringify(fs.readdirSync(path.join(vault, 'Folder'))));
|
|
check('...and becomes the cover', /cover: lipstick\.png/.test(fm('Folder/Note.md')) && await loaded());
|
|
check('...and the picker closes', !(await js(`return !!document.querySelector('.cover-picker')`)));
|
|
|
|
// a changed cover shows in the database gallery at once, not after a restart
|
|
await js(`document.querySelector('.row[data-id="Gal"]').click()`);
|
|
await sleep(1200);
|
|
const src = () => js(`return document.querySelector('.db-gcover img')?.getAttribute('src')`);
|
|
check('a gallery card shows the record\'s cover picture', /src=first\.png/.test(await src()), await src());
|
|
await js(`document.querySelector('.db-gcard').click()`);
|
|
await sleep(1000);
|
|
await click(`document.querySelector('.cover-btn')`);
|
|
await click(`[...document.querySelectorAll('.cover-picker .c-up')].find((b) => b.textContent.includes('Upload'))`);
|
|
await sleep(1200);
|
|
await js(`document.querySelector('.crumb').click()`);
|
|
await sleep(1200);
|
|
check('...and shows the new one right after it is changed', /src=banner\.png/.test(await src()), await src());
|
|
} catch (err) {
|
|
check('harness', false, err.stack);
|
|
}
|
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
console.log(failures.length ? `\n${failures.length} check(s) failed` : '\ncover e2e passed!');
|
|
app.exit(failures.length ? 1 : 0);
|
|
});
|