comparison 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
comparison
equal deleted inserted replaced
258:60a876c4587a 259:667156fcd3e3
371 false, 371 false,
372 ); 372 );
373 await page.close(); 373 await page.close();
374 } 374 }
375 375
376 async function testJrpgPage(browser) {
377 const context = await browser.newContext({
378 colorScheme: 'light',
379 viewport: { width: 1280, height: 900 },
380 });
381 const page = await context.newPage();
382 const errors = [];
383 page.on('pageerror', error => errors.push(`pageerror: ${error.message}`));
384 page.on('console', message => {
385 if (message.type() === 'error') errors.push(`console: ${message.text()}`);
386 });
387 page.on('requestfailed', request => {
388 errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`);
389 });
390 page.on('response', response => {
391 if (response.status() >= 400) {
392 errors.push(`response: ${response.status()} ${response.url()}`);
393 }
394 });
395
396 await page.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' });
397 await page.waitForFunction(() =>
398 customElements.get('mjj-jrpg-composer') &&
399 customElements.get('mjj-jrpg-menu') &&
400 customElements.get('mjj-jrpg-chat') &&
401 customElements.get('mjj-jrpg-preview')
402 );
403 await page.evaluate(() => document.fonts.ready);
404 const desktop = await page.evaluate(() => {
405 const scene = document.querySelector('.jrpg-scene').getBoundingClientRect();
406 const utility = document.querySelector('.jrpg-utility').getBoundingClientRect();
407 const composer = document.querySelector('mjj-jrpg-composer')
408 .getBoundingClientRect();
409 const menu = document.querySelector('mjj-jrpg-menu').getBoundingClientRect();
410 const character = document.querySelector('[data-character]');
411 const shell = document.querySelector('.jrpg-shell').getBoundingClientRect();
412 const main = document.querySelector('.jrpg-main').getBoundingClientRect();
413 const colorProbe = document.createElement('span');
414 colorProbe.style.background = 'var(--zenbu-sys-color-surface-page)';
415 colorProbe.style.color =
416 'var(--zenbu-sys-color-action-primary-background)';
417 colorProbe.style.borderColor = 'var(--zenbu-sys-color-focus-outer)';
418 const surfaceProbe = document.createElement('span');
419 surfaceProbe.style.background =
420 'var(--zenbu-sys-color-surface-default)';
421 surfaceProbe.style.color = 'var(--zenbu-sys-color-text-primary)';
422 surfaceProbe.style.borderColor = 'var(--zenbu-sys-color-border-strong)';
423 const sunkenProbe = document.createElement('span');
424 sunkenProbe.style.background =
425 'var(--zenbu-sys-color-surface-sunken)';
426 const panelProbe = document.createElement('span');
427 panelProbe.style.background = 'var(--mjj-jrpg-panel-strong)';
428 document.body.append(colorProbe, surfaceProbe, sunkenProbe, panelProbe);
429 const probeStyle = getComputedStyle(colorProbe);
430 const surfaceProbeStyle = getComputedStyle(surfaceProbe);
431 const sunkenProbeStyle = getComputedStyle(sunkenProbe);
432 const panelProbeStyle = getComputedStyle(panelProbe);
433 const computedFonts = [
434 document.body,
435 document.querySelector('.jrpg-brand h1'),
436 document.querySelector('.jrpg-message p'),
437 document.querySelector('#jrpg-message'),
438 document.querySelector('.jrpg-composer button'),
439 ].map(element => getComputedStyle(element).fontFamily);
440 const nonPixelText = [document.body, ...document.body.querySelectorAll('*')]
441 .filter(element => [...element.childNodes].some(node =>
442 node.nodeType === Node.TEXT_NODE && node.textContent.trim()
443 ))
444 .filter(element =>
445 getComputedStyle(element).fontFamily !== '"Pixel Mplus"'
446 )
447 .map(element => element.localName);
448 const pageStyle = getComputedStyle(document.body);
449 const submitStyle = getComputedStyle(
450 document.querySelector('.jrpg-composer button'),
451 );
452 const colors = {
453 canvas: probeStyle.backgroundColor,
454 conversation: getComputedStyle(
455 document.querySelector('mjj-jrpg-chat'),
456 ).backgroundColor,
457 conversationBorder: getComputedStyle(
458 document.querySelector('mjj-jrpg-chat'),
459 ).borderTopColor,
460 conversationPanel: panelProbeStyle.backgroundColor,
461 conversationText: getComputedStyle(
462 document.querySelector('.jrpg-message p'),
463 ).color,
464 focus: probeStyle.borderColor,
465 frame: surfaceProbeStyle.borderColor,
466 menu: getComputedStyle(
467 document.querySelector('mjj-jrpg-menu'),
468 ).backgroundColor,
469 page: pageStyle.backgroundColor,
470 primary: probeStyle.color,
471 primaryText: getComputedStyle(
472 document.querySelector('.jrpg-preview-panel h2'),
473 ).color,
474 sunken: sunkenProbeStyle.backgroundColor,
475 surface: surfaceProbeStyle.backgroundColor,
476 submit: submitStyle.backgroundColor,
477 text: surfaceProbeStyle.color,
478 utility: getComputedStyle(
479 document.querySelector('.jrpg-utility'),
480 ).backgroundColor,
481 };
482 colorProbe.remove();
483 surfaceProbe.remove();
484 sunkenProbe.remove();
485 panelProbe.remove();
486 return {
487 art: getComputedStyle(document.querySelector('.jrpg-scene')).backgroundImage,
488 characterLoaded: character.complete && character.naturalWidth > 0,
489 colors,
490 composerBelowScene: composer.top >= scene.bottom,
491 fillsViewport: shell.left === 0 &&
492 shell.top === 0 &&
493 Math.abs(shell.right - innerWidth) < 1 &&
494 Math.abs(shell.bottom - innerHeight) < 1 &&
495 main.left === 0 &&
496 main.top === 0 &&
497 Math.abs(main.right - innerWidth) < 1 &&
498 Math.abs(main.bottom - innerHeight) < 1,
499 fonts: computedFonts,
500 fontLoaded: [...document.fonts].some(font =>
501 font.family.replaceAll('"', '') === 'Pixel Mplus' &&
502 font.status === 'loaded'
503 ),
504 menuRightOfComposer: menu.left >= composer.right,
505 noOverflow: document.documentElement.scrollWidth <= innerWidth,
506 nonPixelText,
507 theme: document.documentElement.dataset.zenTheme,
508 utilityRightOfScene: utility.left >= scene.right,
509 };
510 });
511 assert.match(desktop.art, /bar-ink\.webp/);
512 assert.equal(desktop.characterLoaded, true);
513 assert.equal(desktop.colors.page, desktop.colors.canvas);
514 assert.equal(
515 desktop.colors.conversation,
516 desktop.colors.conversationPanel,
517 );
518 assert.equal(desktop.colors.conversationBorder, desktop.colors.frame);
519 assert.equal(desktop.colors.conversationText, desktop.colors.text);
520 assert.equal(desktop.colors.menu, desktop.colors.sunken);
521 assert.equal(desktop.colors.primaryText, desktop.colors.primary);
522 assert.equal(desktop.colors.submit, desktop.colors.primary);
523 assert.equal(desktop.colors.utility, desktop.colors.surface);
524 assert.equal(desktop.composerBelowScene, true);
525 assert.equal(desktop.fillsViewport, true);
526 assert.ok(desktop.fonts.every(font => font === '"Pixel Mplus"'));
527 assert.equal(desktop.fontLoaded, true);
528 assert.equal(desktop.menuRightOfComposer, true);
529 assert.equal(desktop.noOverflow, true);
530 assert.deepEqual(desktop.nonPixelText, []);
531 assert.equal(desktop.theme, 'cyberpunk');
532 assert.equal(desktop.utilityRightOfScene, true);
533 await page.locator('.jrpg-brand a').focus();
534 assert.equal(
535 await page.locator('.jrpg-brand a').evaluate(element =>
536 getComputedStyle(element).outlineColor
537 ),
538 desktop.colors.focus,
539 );
540 await page.locator('.jrpg-composer button').focus();
541 assert.equal(
542 await page.locator('.jrpg-composer button').evaluate(element =>
543 getComputedStyle(element).outlineColor
544 ),
545 desktop.colors.focus,
546 );
547 assert.equal(
548 await page.locator('meta[name="theme-color"]').first().getAttribute('content'),
549 desktop.colors.canvas,
550 );
551
552 await page.getByRole('button', { name: 'Tools', exact: true }).click();
553 assert.equal(
554 await page.locator('[data-preview-title]').textContent(),
555 'Tools',
556 );
557 assert.equal(
558 await page.locator('[data-preview-link]').getAttribute('href'),
559 '/tools',
560 );
561 assert.equal(
562 await page.getByRole('button', { name: 'Tools', exact: true })
563 .getAttribute('aria-pressed'),
564 'true',
565 );
566
567 await page.getByRole('button', { name: /Inspect/ }).click();
568 await page.locator('.jrpg-preview-dialog dialog[open]').waitFor();
569 assert.equal(
570 await page.locator('[data-dialog-title]').textContent(),
571 'Tools',
572 );
573 await page.getByRole('button', {
574 name: 'Close destination details',
575 }).click();
576
577 await page.locator('#jrpg-message').fill('Where should I begin?');
578 await page.locator('#jrpg-message').press('Enter');
579 await page.locator('.jrpg-message[data-speaker="June"]').last().waitFor();
580 await page.getByRole('button', { name: 'Blogs', exact: true }).click();
581 assert.equal(
582 await page.locator('.jrpg-message[data-speaker="June"] p').last()
583 .textContent(),
584 'Where should I begin?',
585 );
586 await page.waitForFunction(() =>
587 document.querySelector('mjj-jrpg-character')?.getAttribute('state') ===
588 'default' &&
589 document.querySelectorAll('.jrpg-message[data-speaker="Epi"]').length > 1
590 );
591 assert.equal(await page.locator('#jrpg-message').isEnabled(), true);
592
593 await page.waitForFunction(() => navigator.serviceWorker?.controller);
594 await page.reload({ waitUntil: 'networkidle' });
595 await page.evaluate(() => document.fonts.ready);
596 const controlledReload = await page.evaluate(() => {
597 const shell = document.querySelector('.jrpg-shell').getBoundingClientRect();
598 return {
599 art: getComputedStyle(document.querySelector('.jrpg-scene'))
600 .backgroundImage,
601 fillsViewport: shell.left === 0 &&
602 shell.top === 0 &&
603 Math.abs(shell.right - innerWidth) < 1 &&
604 Math.abs(shell.bottom - innerHeight) < 1,
605 fontLoaded: document.fonts.check('16px "Pixel Mplus"'),
606 fontFamily: getComputedStyle(
607 document.querySelector('.jrpg-brand h1'),
608 ).fontFamily,
609 theme: document.documentElement.dataset.zenTheme,
610 };
611 });
612 assert.match(controlledReload.art, /bar-ink\.webp/);
613 assert.equal(controlledReload.fillsViewport, true);
614 assert.equal(controlledReload.fontLoaded, true);
615 assert.equal(controlledReload.fontFamily, '"Pixel Mplus"');
616 assert.equal(controlledReload.theme, 'cyberpunk');
617 assert.deepEqual(errors, []);
618 await context.close();
619
620 const mobileContext = await browser.newContext({
621 colorScheme: 'light',
622 viewport: { width: 360, height: 640 },
623 });
624 const mobilePage = await mobileContext.newPage();
625 await mobilePage.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' });
626 const mobile = await mobilePage.evaluate(() => {
627 const scene = document.querySelector('.jrpg-scene').getBoundingClientRect();
628 const utility = document.querySelector('.jrpg-utility').getBoundingClientRect();
629 const shell = document.querySelector('.jrpg-shell').getBoundingClientRect();
630 const menuButtons = [...document.querySelectorAll(
631 'mjj-jrpg-menu button[data-preview]',
632 )];
633 return {
634 buttonsFit: menuButtons.every(button =>
635 button.getBoundingClientRect().right <= innerWidth
636 ),
637 fillsWidth: shell.left === 0 &&
638 Math.abs(shell.right - innerWidth) < 1,
639 initialScroll: scrollY,
640 noOverflow: document.documentElement.scrollWidth <= innerWidth,
641 utilityBelowScene: utility.top >= scene.bottom,
642 };
643 });
644 assert.deepEqual(mobile, {
645 buttonsFit: true,
646 fillsWidth: true,
647 initialScroll: 0,
648 noOverflow: true,
649 utilityBelowScene: true,
650 });
651 await mobileContext.close();
652 }
653
376 async function testHlsPlayer(browser, siteRoot) { 654 async function testHlsPlayer(browser, siteRoot) {
377 const page = await browser.newPage(); 655 const page = await browser.newPage();
378 const errors = []; 656 const errors = [];
379 const mediaRequests = []; 657 const mediaRequests = [];
380 let testingExpectedFailure = false; 658 let testingExpectedFailure = false;
613 assert.ok(RUNFILES); 891 assert.ok(RUNFILES);
614 assert.ok(WORKSPACE); 892 assert.ok(WORKSPACE);
615 const runfilesWorkspace = path.join(RUNFILES, WORKSPACE); 893 const runfilesWorkspace = path.join(RUNFILES, WORKSPACE);
616 const serverBinary = path.join( 894 const serverBinary = path.join(
617 runfilesWorkspace, 895 runfilesWorkspace,
618 'mrjunejune/mrjunejune_server', 896 'mrjunejune/mrjunejune_server_debug',
619 ); 897 );
620 const siteRoot = path.join(runfilesWorkspace, 'mrjunejune/src'); 898 const siteRoot = path.join(runfilesWorkspace, 'mrjunejune/src');
621 const chromiumPath = path.resolve(process.env.CHROMIUM_PATH); 899 const chromiumPath = path.resolve(process.env.CHROMIUM_PATH);
622 const serverLogs = []; 900 const serverLogs = [];
623 let server; 901 let server;
677 await fetch(`${baseUrl}/sw.js`) 955 await fetch(`${baseUrl}/sw.js`)
678 ).text(); 956 ).text();
679 for (const source of [home, dogGame, manifest]) { 957 for (const source of [home, dogGame, manifest]) {
680 assert.doesNotMatch(source, /\.png(?:["')]|$)/i); 958 assert.doesNotMatch(source, /\.png(?:["')]|$)/i);
681 } 959 }
682 assert.match(serviceWorker, /v6-zenbu-headless/); 960 assert.match(serviceWorker, /v9-dev-page-split/);
683 assert.match( 961 assert.match(
684 await ( 962 await (
685 await fetch(`${baseUrl}/public/pwa-register.js`) 963 await fetch(`${baseUrl}/public/pwa-register.js`)
686 ).text(), 964 ).text(),
687 /register\('\/sw\.js', \{ scope: '\/' \}\)/, 965 /register\('\/sw\.js', \{ scope: '\/' \}\)/,
688 ); 966 );
689 assert.ok(serviceWorker.includes("startsWith('/tools/hls_player')")); 967 assert.ok(serviceWorker.includes("startsWith('/tools/hls_player')"));
968 assert.ok(serviceWorker.includes("url.hostname === 'localhost'"));
969 assert.ok(serviceWorker.includes("url.hostname === '127.0.0.1'"));
970 assert.ok(!serviceWorker.includes("startsWith('/jrpg')"));
690 971
691 for (const asset of [ 972 for (const asset of [
692 'sprite_shiba0.webp', 973 'sprite_shiba0.webp',
693 'dog-treat.webp', 974 'dog-treat.webp',
694 'start-large.webp', 975 'start-large.webp',
739 await testThemeCycle(browser); 1020 await testThemeCycle(browser);
740 await testPlainField(browser); 1021 await testPlainField(browser);
741 await testButtonScale(browser); 1022 await testButtonScale(browser);
742 await testDynamicButtonOwnership(browser); 1023 await testDynamicButtonOwnership(browser);
743 await testResumePrint(browser); 1024 await testResumePrint(browser);
1025 await testJrpgPage(browser);
744 await testHlsPlayer(browser, siteRoot); 1026 await testHlsPlayer(browser, siteRoot);
745 } finally { 1027 } finally {
746 if (browser) await browser.close(); 1028 if (browser) await browser.close();
747 await stopProcess(server); 1029 await stopProcess(server);
748 } 1030 }