Mercurial
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 } |