Files
stack/packages/webui/tests/w15-project-remote-browser.test.mjs

117 lines
10 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdirSync } from 'node:fs';
import { join } from 'node:path';
import { remoteFixture } from './helpers/w15-project-remote.mjs';
for (const width of [1440, 390]) test(`project remote fixture at ${width}px`, async t => {
const { b, mount, wait, errors, network } = await remoteFixture(t, width);
const shots = process.env.MOSAIC_UX_SHOTS;
if (shots) mkdirSync(shots, { recursive: true });
async function shot(name) {
if (!shots) return;
await b.screenshot(join(shots, `project-remote-${name}-${width}.png`));
const { contentSize } = await b.call('Page.getLayoutMetrics');
const { data } = await b.call('Page.captureScreenshot', { format: 'png', captureBeyondViewport: true, clip: { x: 0, y: 0, width: contentSize.width, height: contentSize.height, scale: 1 } });
const { writeFile } = await import('node:fs/promises');
await writeFile(join(shots, `project-remote-${name}-${width}-full.png`), Buffer.from(data, 'base64'));
}
async function input(value) {
await b.evaluate(`(()=>{const e=document.querySelector('#project-remote-url');e.value=${JSON.stringify(value)};e.dispatchEvent(new Event('input',{bubbles:true}));return true})()`);
}
async function submit() {
const rect = await b.evaluate(`(()=>{const r=document.querySelector('.project-remote button[type=submit]').getBoundingClientRect();return {x:r.x+r.width/2,y:r.y+r.height/2}})()`);
await b.call('Input.dispatchMouseEvent', { type: 'mousePressed', button: 'left', clickCount: 1, ...rect });
await b.call('Input.dispatchMouseEvent', { type: 'mouseReleased', button: 'left', clickCount: 1, ...rect });
}
await mount(); await wait(`document.querySelector('#project-remote-url')`);
for (const mode of ['light', 'dark']) {
await b.evaluate(`(()=>{const e=document.querySelector('#mode');e.value='${mode}';e.dispatchEvent(new Event('change',{bubbles:true}));return true})()`);
assert.equal(await b.evaluate(`document.documentElement.dataset.mode`), mode);
assert.equal(await b.evaluate(`getComputedStyle(document.querySelector('#project-remote-url')).visibility`), 'visible');
}
await b.evaluate(`(()=>{const e=document.querySelector('#mode');e.value='system';e.dispatchEvent(new Event('change',{bubbles:true}));return true})()`);
const ax = await b.call('Accessibility.getFullAXTree');
assert.ok(ax.nodes.some(n => n.role?.value === 'textbox' && n.name?.value === 'Existing HTTPS repository URL'));
assert.ok(ax.nodes.some(n => n.role?.value === 'button' && n.name?.value === 'Save'));
assert.equal(await b.evaluate(`document.querySelector('.project-remote button[type=submit]').getBoundingClientRect().height >= 44`), true);
await b.evaluate(`document.querySelector('#project-remote-url').focus(),true`);
await b.key('Tab'); assert.equal(await b.evaluate(`document.activeElement.textContent`), 'Save');
await b.key('Tab', 'Tab', 8); assert.equal(await b.evaluate(`document.activeElement.id`), 'project-remote-url');
assert.equal(await b.evaluate('document.documentElement.scrollWidth <= innerWidth'), true);
// Halving the CSS viewport models the reflow available at 200% browser zoom.
await b.viewport(width / 2, 500);
assert.equal(await b.evaluate(`document.querySelector('.project-remote').scrollWidth <= document.querySelector('.project-remote').clientWidth`), true);
await b.viewport(width, 1000);
await shot('remote-missing');
await b.key('Tab'); await b.key('Tab', 'Tab', 8); await b.key('Escape');
await input('https://git.example/owner/repo.git'); await shot('remote-editing');
await b.evaluate(`window.__mode='hold',true`); await submit();
await wait(`window.__writes.length===1 && !!window.__release`);
assert.equal(await b.evaluate(`document.querySelector('.project-remote button[type=submit]').disabled`), true);
await b.evaluate(`document.querySelector('.project-remote form').dispatchEvent(new Event('submit',{bubbles:true,cancelable:true})),window.__release(),true`);
await wait(`document.querySelector('.project-remote').textContent.includes('Configured, not verified')`);
assert.equal(await b.evaluate('window.__writes.length'), 1); await shot('remote-saved-not-published');
await b.evaluate(`window.__remote={project:'stack',remote:null,revision:'${'a'.repeat(64)}',firstSaveAllowed:true};window.__mode='stale';true`);
await mount(); await wait(`document.querySelector('#project-remote-url')`); await input('https://git.example/owner/repo.git'); await submit();
await wait(`document.querySelector('.project-remote').textContent.includes('setting changed')`); await shot('remote-stale');
assert.equal(await b.evaluate(`document.querySelector('#project-remote-url').value`), 'https://git.example/owner/repo.git');
await b.evaluate(`document.querySelector('.project-remote button[type=button]').click(),true`);
await wait(`document.querySelector('.project-remote').textContent.includes('Review your entry')`);
assert.equal(await b.evaluate(`document.querySelector('#project-remote-url').value`), 'https://git.example/owner/repo.git');
await input('https://name:[email protected]/owner/repo'); await submit();
await wait(`document.querySelector('.project-remote').textContent.includes('must not contain credentials')`);
assert.equal(await b.evaluate(`document.querySelector('#project-remote-url').value`), ''); await shot('remote-invalid');
await b.evaluate(`window.__mode='fail',true`); await input('https://git.example/owner/invalid'); await submit();
await wait(`document.querySelector('.project-remote').textContent.includes('Enter a valid HTTPS')`);
assert.equal(await b.evaluate(`document.querySelector('#project-remote-url').value`), 'https://git.example/owner/invalid');
await b.evaluate(`window.__mode='unknown',true`); await input('https://git.example/owner/unknown'); await submit();
await wait(`document.querySelector('.project-remote').textContent.includes('Save outcome is unknown')`);
assert.equal(await b.evaluate(`document.querySelector('.project-remote button[type=submit]').disabled`), true);
assert.equal(await b.evaluate(`document.querySelector('#project-remote-url').value`), 'https://git.example/owner/unknown');
await b.evaluate(`window.__writeEnabled=false,true`); await mount();
await wait(`document.querySelector('.project-remote').textContent.includes('read-only')`);
assert.equal(await b.evaluate(`document.querySelectorAll('.project-remote form').length`), 0);
await mount(null); await shot('remote-readonly-or-unlinked');
assert.equal(await b.evaluate('document.documentElement.scrollWidth <= innerWidth'), true);
await b.evaluate(`document.dispatchEvent(new Event('mosaic-signed-out')),true`);
assert.equal(await b.evaluate(`document.querySelector('.project-remote').textContent`), '');
assert.deepEqual(network, []); assert.deepEqual(errors, []);
});
for (const width of [1440, 390]) test(`real fixture saves the project remote at ${width}px`, async t => {
const { realRemoteFixture } = await import('./helpers/w15-project-remote-real.mjs');
const { browser } = await import('./browser.mjs');
const { readFileSync } = await import('node:fs');
const f = await realRemoteFixture(t), b = await browser({ machine: false });
t.after(() => b.close());
const errors = [], blocked = [];
b.on('Runtime.exceptionThrown', p => errors.push(p.exceptionDetails?.text));
b.on('Fetch.requestPaused', p => {
if (p.request.url.startsWith(f.base + '/')) b.call('Fetch.continueRequest', { requestId: p.requestId }).catch(() => {});
else { blocked.push(p.request.url); b.call('Fetch.failRequest', { requestId: p.requestId, errorReason: 'BlockedByClient' }).catch(() => {}); }
});
await b.call('Fetch.enable', { patterns: [{ urlPattern: '*' }] });
await b.viewport(width, 1000); await b.navigate(f.base + '/#/projects/10000000-0000-4000-8000-000000000001/settings');
await b.evaluate(`new Promise((resolve,reject)=>{const link=document.createElement('link');link.rel='stylesheet';link.href='/project-remote.css';document.head.append(link);const script=document.createElement('script');script.src='/project-remote.js';script.onload=resolve;script.onerror=reject;document.head.append(script)})`);
await b.evaluate(`(()=>{const main=document.querySelector('#main');for(const child of main.children)child.style.display='none';main.hidden=false;const h=document.createElement('h1');h.textContent='Project information';const slot=document.createElement('div');main.append(h,slot);MosaicProjectRemote.mount({container:slot,projectId:'10000000-0000-4000-8000-000000000001',repositoryKey:'stack'});return true})()`);
const wait = async expression => b.evaluate(`(async()=>{const until=Date.now()+10000;while(!(${expression})){if(Date.now()>until)throw Error('timed out');await new Promise(r=>setTimeout(r,20))}return true})()`);
await wait(`document.querySelector('#project-remote-url')`);
await b.evaluate(`(()=>{const e=document.querySelector('#project-remote-url');e.value='https://git.example/owner/repo.git';e.dispatchEvent(new Event('input',{bubbles:true}));return true})()`);
const rect = await b.evaluate(`(()=>{const r=document.querySelector('.project-remote button[type=submit]').getBoundingClientRect();return {x:r.x+r.width/2,y:r.y+r.height/2}})()`);
await b.call('Input.dispatchMouseEvent', { type: 'mousePressed', button: 'left', clickCount: 1, ...rect });
await b.call('Input.dispatchMouseEvent', { type: 'mouseReleased', button: 'left', clickCount: 1, ...rect });
await wait(`document.querySelector('.project-remote').textContent.includes('Configured, not verified')`);
assert.equal(JSON.parse(readFileSync(f.file)).projects.stack.remote, 'https://git.example/owner/repo.git');
assert.equal(await b.evaluate('document.documentElement.scrollWidth <= innerWidth'), true);
if (process.env.MOSAIC_UX_SHOTS) {
mkdirSync(process.env.MOSAIC_UX_SHOTS, { recursive: true });
await b.screenshot(join(process.env.MOSAIC_UX_SHOTS, `project-remote-real-saved-${width}.png`));
const { contentSize } = await b.call('Page.getLayoutMetrics');
const { data } = await b.call('Page.captureScreenshot', { format: 'png', captureBeyondViewport: true, clip: { x: 0, y: 0, width: contentSize.width, height: contentSize.height, scale: 1 } });
const { writeFile } = await import('node:fs/promises');
await writeFile(join(process.env.MOSAIC_UX_SHOTS, `project-remote-real-saved-${width}-full.png`), Buffer.from(data, 'base64'));
}
assert.deepEqual(blocked, []); assert.deepEqual(errors, []);
});