Mercurial
diff mrjunejune/test/theme_and_webp_test.js @ 259:667156fcd3e3
Add dev-only cyberpunk JRPG page
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Wed, 05 Aug 2026 09:19:41 -0700 |
| parents | 60a876c4587a |
| children | 1f9877b637e9 |
line wrap: on
line diff
--- a/mrjunejune/test/theme_and_webp_test.js Wed Aug 05 05:25:40 2026 -0700 +++ b/mrjunejune/test/theme_and_webp_test.js Wed Aug 05 09:19:41 2026 -0700 @@ -373,6 +373,284 @@ await page.close(); } +async function testJrpgPage(browser) { + const context = await browser.newContext({ + colorScheme: 'light', + viewport: { width: 1280, height: 900 }, + }); + const page = await context.newPage(); + const errors = []; + page.on('pageerror', error => errors.push(`pageerror: ${error.message}`)); + page.on('console', message => { + if (message.type() === 'error') errors.push(`console: ${message.text()}`); + }); + page.on('requestfailed', request => { + errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`); + }); + page.on('response', response => { + if (response.status() >= 400) { + errors.push(`response: ${response.status()} ${response.url()}`); + } + }); + + await page.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' }); + await page.waitForFunction(() => + customElements.get('mjj-jrpg-composer') && + customElements.get('mjj-jrpg-menu') && + customElements.get('mjj-jrpg-chat') && + customElements.get('mjj-jrpg-preview') + ); + await page.evaluate(() => document.fonts.ready); + const desktop = await page.evaluate(() => { + const scene = document.querySelector('.jrpg-scene').getBoundingClientRect(); + const utility = document.querySelector('.jrpg-utility').getBoundingClientRect(); + const composer = document.querySelector('mjj-jrpg-composer') + .getBoundingClientRect(); + const menu = document.querySelector('mjj-jrpg-menu').getBoundingClientRect(); + const character = document.querySelector('[data-character]'); + const shell = document.querySelector('.jrpg-shell').getBoundingClientRect(); + const main = document.querySelector('.jrpg-main').getBoundingClientRect(); + const colorProbe = document.createElement('span'); + colorProbe.style.background = 'var(--zenbu-sys-color-surface-page)'; + colorProbe.style.color = + 'var(--zenbu-sys-color-action-primary-background)'; + colorProbe.style.borderColor = 'var(--zenbu-sys-color-focus-outer)'; + const surfaceProbe = document.createElement('span'); + surfaceProbe.style.background = + 'var(--zenbu-sys-color-surface-default)'; + surfaceProbe.style.color = 'var(--zenbu-sys-color-text-primary)'; + surfaceProbe.style.borderColor = 'var(--zenbu-sys-color-border-strong)'; + const sunkenProbe = document.createElement('span'); + sunkenProbe.style.background = + 'var(--zenbu-sys-color-surface-sunken)'; + const panelProbe = document.createElement('span'); + panelProbe.style.background = 'var(--mjj-jrpg-panel-strong)'; + document.body.append(colorProbe, surfaceProbe, sunkenProbe, panelProbe); + const probeStyle = getComputedStyle(colorProbe); + const surfaceProbeStyle = getComputedStyle(surfaceProbe); + const sunkenProbeStyle = getComputedStyle(sunkenProbe); + const panelProbeStyle = getComputedStyle(panelProbe); + const computedFonts = [ + document.body, + document.querySelector('.jrpg-brand h1'), + document.querySelector('.jrpg-message p'), + document.querySelector('#jrpg-message'), + document.querySelector('.jrpg-composer button'), + ].map(element => getComputedStyle(element).fontFamily); + const nonPixelText = [document.body, ...document.body.querySelectorAll('*')] + .filter(element => [...element.childNodes].some(node => + node.nodeType === Node.TEXT_NODE && node.textContent.trim() + )) + .filter(element => + getComputedStyle(element).fontFamily !== '"Pixel Mplus"' + ) + .map(element => element.localName); + const pageStyle = getComputedStyle(document.body); + const submitStyle = getComputedStyle( + document.querySelector('.jrpg-composer button'), + ); + const colors = { + canvas: probeStyle.backgroundColor, + conversation: getComputedStyle( + document.querySelector('mjj-jrpg-chat'), + ).backgroundColor, + conversationBorder: getComputedStyle( + document.querySelector('mjj-jrpg-chat'), + ).borderTopColor, + conversationPanel: panelProbeStyle.backgroundColor, + conversationText: getComputedStyle( + document.querySelector('.jrpg-message p'), + ).color, + focus: probeStyle.borderColor, + frame: surfaceProbeStyle.borderColor, + menu: getComputedStyle( + document.querySelector('mjj-jrpg-menu'), + ).backgroundColor, + page: pageStyle.backgroundColor, + primary: probeStyle.color, + primaryText: getComputedStyle( + document.querySelector('.jrpg-preview-panel h2'), + ).color, + sunken: sunkenProbeStyle.backgroundColor, + surface: surfaceProbeStyle.backgroundColor, + submit: submitStyle.backgroundColor, + text: surfaceProbeStyle.color, + utility: getComputedStyle( + document.querySelector('.jrpg-utility'), + ).backgroundColor, + }; + colorProbe.remove(); + surfaceProbe.remove(); + sunkenProbe.remove(); + panelProbe.remove(); + return { + art: getComputedStyle(document.querySelector('.jrpg-scene')).backgroundImage, + characterLoaded: character.complete && character.naturalWidth > 0, + colors, + composerBelowScene: composer.top >= scene.bottom, + fillsViewport: shell.left === 0 && + shell.top === 0 && + Math.abs(shell.right - innerWidth) < 1 && + Math.abs(shell.bottom - innerHeight) < 1 && + main.left === 0 && + main.top === 0 && + Math.abs(main.right - innerWidth) < 1 && + Math.abs(main.bottom - innerHeight) < 1, + fonts: computedFonts, + fontLoaded: [...document.fonts].some(font => + font.family.replaceAll('"', '') === 'Pixel Mplus' && + font.status === 'loaded' + ), + menuRightOfComposer: menu.left >= composer.right, + noOverflow: document.documentElement.scrollWidth <= innerWidth, + nonPixelText, + theme: document.documentElement.dataset.zenTheme, + utilityRightOfScene: utility.left >= scene.right, + }; + }); + assert.match(desktop.art, /bar-ink\.webp/); + assert.equal(desktop.characterLoaded, true); + assert.equal(desktop.colors.page, desktop.colors.canvas); + assert.equal( + desktop.colors.conversation, + desktop.colors.conversationPanel, + ); + assert.equal(desktop.colors.conversationBorder, desktop.colors.frame); + assert.equal(desktop.colors.conversationText, desktop.colors.text); + assert.equal(desktop.colors.menu, desktop.colors.sunken); + assert.equal(desktop.colors.primaryText, desktop.colors.primary); + assert.equal(desktop.colors.submit, desktop.colors.primary); + assert.equal(desktop.colors.utility, desktop.colors.surface); + assert.equal(desktop.composerBelowScene, true); + assert.equal(desktop.fillsViewport, true); + assert.ok(desktop.fonts.every(font => font === '"Pixel Mplus"')); + assert.equal(desktop.fontLoaded, true); + assert.equal(desktop.menuRightOfComposer, true); + assert.equal(desktop.noOverflow, true); + assert.deepEqual(desktop.nonPixelText, []); + assert.equal(desktop.theme, 'cyberpunk'); + assert.equal(desktop.utilityRightOfScene, true); + await page.locator('.jrpg-brand a').focus(); + assert.equal( + await page.locator('.jrpg-brand a').evaluate(element => + getComputedStyle(element).outlineColor + ), + desktop.colors.focus, + ); + await page.locator('.jrpg-composer button').focus(); + assert.equal( + await page.locator('.jrpg-composer button').evaluate(element => + getComputedStyle(element).outlineColor + ), + desktop.colors.focus, + ); + assert.equal( + await page.locator('meta[name="theme-color"]').first().getAttribute('content'), + desktop.colors.canvas, + ); + + await page.getByRole('button', { name: 'Tools', exact: true }).click(); + assert.equal( + await page.locator('[data-preview-title]').textContent(), + 'Tools', + ); + assert.equal( + await page.locator('[data-preview-link]').getAttribute('href'), + '/tools', + ); + assert.equal( + await page.getByRole('button', { name: 'Tools', exact: true }) + .getAttribute('aria-pressed'), + 'true', + ); + + await page.getByRole('button', { name: /Inspect/ }).click(); + await page.locator('.jrpg-preview-dialog dialog[open]').waitFor(); + assert.equal( + await page.locator('[data-dialog-title]').textContent(), + 'Tools', + ); + await page.getByRole('button', { + name: 'Close destination details', + }).click(); + + await page.locator('#jrpg-message').fill('Where should I begin?'); + await page.locator('#jrpg-message').press('Enter'); + await page.locator('.jrpg-message[data-speaker="June"]').last().waitFor(); + await page.getByRole('button', { name: 'Blogs', exact: true }).click(); + assert.equal( + await page.locator('.jrpg-message[data-speaker="June"] p').last() + .textContent(), + 'Where should I begin?', + ); + await page.waitForFunction(() => + document.querySelector('mjj-jrpg-character')?.getAttribute('state') === + 'default' && + document.querySelectorAll('.jrpg-message[data-speaker="Epi"]').length > 1 + ); + assert.equal(await page.locator('#jrpg-message').isEnabled(), true); + + await page.waitForFunction(() => navigator.serviceWorker?.controller); + await page.reload({ waitUntil: 'networkidle' }); + await page.evaluate(() => document.fonts.ready); + const controlledReload = await page.evaluate(() => { + const shell = document.querySelector('.jrpg-shell').getBoundingClientRect(); + return { + art: getComputedStyle(document.querySelector('.jrpg-scene')) + .backgroundImage, + fillsViewport: shell.left === 0 && + shell.top === 0 && + Math.abs(shell.right - innerWidth) < 1 && + Math.abs(shell.bottom - innerHeight) < 1, + fontLoaded: document.fonts.check('16px "Pixel Mplus"'), + fontFamily: getComputedStyle( + document.querySelector('.jrpg-brand h1'), + ).fontFamily, + theme: document.documentElement.dataset.zenTheme, + }; + }); + assert.match(controlledReload.art, /bar-ink\.webp/); + assert.equal(controlledReload.fillsViewport, true); + assert.equal(controlledReload.fontLoaded, true); + assert.equal(controlledReload.fontFamily, '"Pixel Mplus"'); + assert.equal(controlledReload.theme, 'cyberpunk'); + assert.deepEqual(errors, []); + await context.close(); + + const mobileContext = await browser.newContext({ + colorScheme: 'light', + viewport: { width: 360, height: 640 }, + }); + const mobilePage = await mobileContext.newPage(); + await mobilePage.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' }); + const mobile = await mobilePage.evaluate(() => { + const scene = document.querySelector('.jrpg-scene').getBoundingClientRect(); + const utility = document.querySelector('.jrpg-utility').getBoundingClientRect(); + const shell = document.querySelector('.jrpg-shell').getBoundingClientRect(); + const menuButtons = [...document.querySelectorAll( + 'mjj-jrpg-menu button[data-preview]', + )]; + return { + buttonsFit: menuButtons.every(button => + button.getBoundingClientRect().right <= innerWidth + ), + fillsWidth: shell.left === 0 && + Math.abs(shell.right - innerWidth) < 1, + initialScroll: scrollY, + noOverflow: document.documentElement.scrollWidth <= innerWidth, + utilityBelowScene: utility.top >= scene.bottom, + }; + }); + assert.deepEqual(mobile, { + buttonsFit: true, + fillsWidth: true, + initialScroll: 0, + noOverflow: true, + utilityBelowScene: true, + }); + await mobileContext.close(); +} + async function testHlsPlayer(browser, siteRoot) { const page = await browser.newPage(); const errors = []; @@ -615,7 +893,7 @@ const runfilesWorkspace = path.join(RUNFILES, WORKSPACE); const serverBinary = path.join( runfilesWorkspace, - 'mrjunejune/mrjunejune_server', + 'mrjunejune/mrjunejune_server_debug', ); const siteRoot = path.join(runfilesWorkspace, 'mrjunejune/src'); const chromiumPath = path.resolve(process.env.CHROMIUM_PATH); @@ -679,7 +957,7 @@ for (const source of [home, dogGame, manifest]) { assert.doesNotMatch(source, /\.png(?:["')]|$)/i); } - assert.match(serviceWorker, /v6-zenbu-headless/); + assert.match(serviceWorker, /v9-dev-page-split/); assert.match( await ( await fetch(`${baseUrl}/public/pwa-register.js`) @@ -687,6 +965,9 @@ /register\('\/sw\.js', \{ scope: '\/' \}\)/, ); assert.ok(serviceWorker.includes("startsWith('/tools/hls_player')")); + assert.ok(serviceWorker.includes("url.hostname === 'localhost'")); + assert.ok(serviceWorker.includes("url.hostname === '127.0.0.1'")); + assert.ok(!serviceWorker.includes("startsWith('/jrpg')")); for (const asset of [ 'sprite_shiba0.webp', @@ -741,6 +1022,7 @@ await testButtonScale(browser); await testDynamicButtonOwnership(browser); await testResumePrint(browser); + await testJrpgPage(browser); await testHlsPlayer(browser, siteRoot); } finally { if (browser) await browser.close();