82 lines
4.7 KiB
JavaScript
82 lines
4.7 KiB
JavaScript
// Optional real-browser regressions for fast typing. Motion stays enabled.
|
|
// Start npm start, then run CHROME=/path/to/chrome node test/responsiveness.mjs.
|
|
import assert from 'node:assert/strict';
|
|
import {createRequire} from 'node:module';
|
|
import {writeFile, mkdir} from 'node:fs/promises';
|
|
const require = createRequire(import.meta.url);
|
|
const {chromium} = require(process.env.CODEX_PRIMARY_RUNTIME_NODE_MODULES
|
|
? process.env.CODEX_PRIMARY_RUNTIME_NODE_MODULES + '/playwright' : 'playwright');
|
|
const base = (process.env.TYPY_BASE_URL || 'http://127.0.0.1:8787').replace(/\/$/, '');
|
|
const browser = await chromium.launch({executablePath:process.env.CHROME || chromium.executablePath(),headless:true,args:['--no-sandbox']});
|
|
const checks = [];
|
|
const errors = [];
|
|
try {
|
|
const page = await browser.newPage({viewport:{width:1440,height:1000},reducedMotion:'no-preference',ignoreHTTPSErrors:process.env.TYPY_IGNORE_HTTPS_ERRORS === '1'});
|
|
page.on('pageerror',error=>errors.push(error.message));
|
|
await page.goto(base + '/');
|
|
await page.locator('#word[data-word]').waitFor();
|
|
const idleMutations = await page.evaluate(async () => {
|
|
let changes = 0;
|
|
const observer = new MutationObserver(rows => { changes += rows.length; });
|
|
observer.observe(document.getElementById('play-panel'), {subtree:true,childList:true,attributes:true,characterData:true});
|
|
await new Promise(resolve => setTimeout(resolve, 400));
|
|
observer.disconnect();
|
|
return changes;
|
|
});
|
|
assert.equal(idleMutations,0,'The ready stage must not continuously redraw itself.');
|
|
checks.push({name:'Ready stage stays still',idleMutations});
|
|
|
|
async function paste(text, advance = 'space') {
|
|
await page.locator('[data-mode=endless]').click();
|
|
await page.locator('#settings-open').click();
|
|
await page.locator('#advance-setting').selectOption(advance);
|
|
await page.locator('#settings-done').click();
|
|
await page.locator('#source-open').click();
|
|
await page.locator('#tab-paste').click();
|
|
await page.locator('#paste-input').fill(text);
|
|
await page.locator('#source-paste button[type=submit]').click();
|
|
}
|
|
|
|
await paste('calm river');
|
|
await page.evaluate(() => { window.firstLetter = document.querySelector('#word span'); });
|
|
await page.keyboard.type('c');
|
|
assert.equal(await page.evaluate(() => window.firstLetter === document.querySelector('#word span')),true,'A key must update existing letters, not replace the word.');
|
|
assert.equal(await page.locator('#word .correct').count(),1);
|
|
await page.keyboard.type('alm ');
|
|
const transition = await page.evaluate(() => {
|
|
const word = document.getElementById('word'), style = getComputedStyle(word);
|
|
return {word:word.dataset.word,opacity:style.opacity,transform:style.transform,animations:word.getAnimations().length};
|
|
});
|
|
assert.deepEqual(transition,{word:'river',opacity:'1',transform:'none',animations:0},'The next word must be fully readable immediately, with normal motion settings.');
|
|
await page.keyboard.type('r');
|
|
assert.equal(await page.locator('#word .correct').count(),1);
|
|
checks.push({name:'Immediate stationary target and persistent letters',...transition});
|
|
|
|
for (const advance of ['space','letter']) {
|
|
await paste('a calm river moves through the city',advance);
|
|
await page.keyboard.type('a calm river moves through the city ',{delay:75});
|
|
assert.equal(await page.locator('#word').getAttribute('data-word'),'a');
|
|
assert.equal(await page.locator('#live-accuracy').textContent(),'100');
|
|
assert.equal(await page.locator('#word .incorrect').count(),0);
|
|
checks.push({name:'Normal spaced typing at 160 WPM input cadence',advance,accuracy:100});
|
|
}
|
|
|
|
for (const width of [390,360]) {
|
|
await page.setViewportSize({width,height:844});
|
|
await paste('a'.repeat(64) + ' next','letter');
|
|
const before = await page.evaluate(() => ({viewport:innerWidth,document:document.documentElement.scrollWidth}));
|
|
assert.ok(before.document <= before.viewport + 1);
|
|
await page.locator('#typing-input').fill('a'.repeat(64));
|
|
const after = await page.evaluate(() => ({word:document.getElementById('word').dataset.word,viewport:innerWidth,document:document.documentElement.scrollWidth,opacity:getComputedStyle(document.getElementById('word')).opacity}));
|
|
assert.equal(after.word,'next');
|
|
assert.equal(after.opacity,'1');
|
|
assert.ok(after.document <= after.viewport + 1);
|
|
checks.push({name:'Long phone word and instant transition fit',width,before,after});
|
|
}
|
|
assert.deepEqual(errors,[]);
|
|
const report = {checkedAt:new Date().toISOString(),base,motion:'no-preference',checks,errors};
|
|
await mkdir('evidence',{recursive:true});
|
|
await writeFile('evidence/responsiveness.json',JSON.stringify(report,null,2) + '\n');
|
|
console.log(JSON.stringify(report,null,2));
|
|
} finally { await browser.close(); }
|