comparison mrjunejune/test/theme_and_webp_test.js @ 263:ee04e4e69fed

Add functional JRPG frame and tools Add full-screen background_2 apertures, functional frame chrome, card-driven details, dual-window tools, live conversion workflows, and bounded cleanup for generated downloads. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <mrjunejune@users.noreply.github.com>
date Thu, 06 Aug 2026 11:31:30 -0700
parents 0f45474c1b1a
children 04fee26ecce0
comparison
equal deleted inserted replaced
262:0f45474c1b1a 263:ee04e4e69fed
181 !message.text().startsWith('Failed to load resource:')) { 181 !message.text().startsWith('Failed to load resource:')) {
182 errors.push(`console: ${message.text()}`); 182 errors.push(`console: ${message.text()}`);
183 } 183 }
184 }); 184 });
185 page.on('requestfailed', request => { 185 page.on('requestfailed', request => {
186 if (request.url().startsWith('blob:') &&
187 request.failure()?.errorText === 'net::ERR_ABORTED') {
188 return;
189 }
190 if (request.method() === 'DELETE' &&
191 request.url().includes('/api/download/') &&
192 request.failure()?.errorText === 'net::ERR_ABORTED') {
193 return;
194 }
195 if (request.method() === 'POST' &&
196 request.url().endsWith('/api/latex/render') &&
197 request.failure()?.errorText === 'net::ERR_ABORTED') {
198 return;
199 }
186 errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`); 200 errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`);
187 }); 201 });
188 page.on('response', response => { 202 page.on('response', response => {
189 if (response.status() === 404 && 203 if (response.status() === 404 &&
190 response.url().endsWith( 204 response.url().endsWith(
447 !message.text().startsWith('Failed to load resource:')) { 461 !message.text().startsWith('Failed to load resource:')) {
448 errors.push(`console: ${message.text()}`); 462 errors.push(`console: ${message.text()}`);
449 } 463 }
450 }); 464 });
451 page.on('requestfailed', request => { 465 page.on('requestfailed', request => {
466 if (request.url().startsWith('blob:') &&
467 request.failure()?.errorText === 'net::ERR_ABORTED') {
468 return;
469 }
470 if (request.method() === 'DELETE' &&
471 request.url().includes('/api/download/') &&
472 request.failure()?.errorText === 'net::ERR_ABORTED') {
473 return;
474 }
475 if (request.method() === 'POST' &&
476 request.url().endsWith('/api/latex/render') &&
477 request.failure()?.errorText === 'net::ERR_ABORTED') {
478 return;
479 }
452 errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`); 480 errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`);
453 }); 481 });
454 page.on('response', response => { 482 page.on('response', response => {
455 if (response.status() === 404 && 483 if (response.status() === 404 &&
456 response.url().endsWith( 484 response.url().endsWith(
485 .getBoundingClientRect(); 513 .getBoundingClientRect();
486 const composerTextarea = document.querySelector('#jrpg-message') 514 const composerTextarea = document.querySelector('#jrpg-message')
487 .getBoundingClientRect(); 515 .getBoundingClientRect();
488 const menu = document.querySelector('mjj-jrpg-menu').getBoundingClientRect(); 516 const menu = document.querySelector('mjj-jrpg-menu').getBoundingClientRect();
489 const character = document.querySelector('[data-character]'); 517 const character = document.querySelector('[data-character]');
518 const characterBox = character.getBoundingClientRect();
519 const sceneBarStyle = getComputedStyle(
520 document.querySelector('.jrpg-scene'),
521 '::before',
522 );
490 const messageScroller = document.querySelector('zen-message-scroller') 523 const messageScroller = document.querySelector('zen-message-scroller')
491 .getBoundingClientRect(); 524 .getBoundingClientRect();
492 const messageViewport = document.querySelector('.jrpg-message-viewport') 525 const messageViewport = document.querySelector('.jrpg-message-viewport')
493 .getBoundingClientRect(); 526 .getBoundingClientRect();
527 const workspace = document.querySelector('.jrpg-workspace')
528 .getBoundingClientRect();
529 const brand = document.querySelector('.jrpg-brand').getBoundingClientRect();
530 const frameControls = document.querySelector('.jrpg-frame-controls')
531 .getBoundingClientRect();
532 const frameTelemetry = document.querySelector('.jrpg-frame-telemetry')
533 .getBoundingClientRect();
534 const telemetryValues = [
535 ...document.querySelectorAll('.jrpg-frame-telemetry > *'),
536 ].map(value => value.getBoundingClientRect());
494 const shell = document.querySelector('.jrpg-shell').getBoundingClientRect(); 537 const shell = document.querySelector('.jrpg-shell').getBoundingClientRect();
495 const main = document.querySelector('.jrpg-main').getBoundingClientRect(); 538 const main = document.querySelector('.jrpg-main').getBoundingClientRect();
496 const colorProbe = document.createElement('span'); 539 const colorProbe = document.createElement('span');
497 colorProbe.style.background = 'var(--zenbu-sys-color-surface-page)'; 540 colorProbe.style.background = 'var(--zenbu-sys-color-surface-page)';
498 colorProbe.style.color = 541 colorProbe.style.color =
564 }; 607 };
565 colorProbe.remove(); 608 colorProbe.remove();
566 surfaceProbe.remove(); 609 surfaceProbe.remove();
567 sunkenProbe.remove(); 610 sunkenProbe.remove();
568 panelProbe.remove(); 611 panelProbe.remove();
612 const closeTo = (value, expected) => Math.abs(value - expected) < 0.01;
613 const frameAlignment = [
614 closeTo((scene.left - workspace.left) / workspace.width, 0.032),
615 closeTo((scene.top - workspace.top) / workspace.height, 0.094),
616 closeTo(scene.width / workspace.width, 0.548),
617 closeTo(scene.height / workspace.height, 0.57),
618 closeTo((utility.left - workspace.left) / workspace.width, 0.604),
619 closeTo(utility.width / workspace.width, 0.365),
620 closeTo((composer.top - workspace.top) / workspace.height, 0.72),
621 closeTo(composer.width / workspace.width, 0.682),
622 closeTo((menu.left - workspace.left) / workspace.width, 0.734),
623 closeTo(menu.width / workspace.width, 0.234),
624 ].every(Boolean);
625 const borderless = [
626 '.jrpg-shell',
627 '.jrpg-scene',
628 '.jrpg-utility',
629 'mjj-jrpg-chat',
630 'mjj-jrpg-composer',
631 'mjj-jrpg-menu',
632 '.jrpg-preview-panel',
633 '.jrpg-message',
634 '.jrpg-work-showcase a',
635 '.jrpg-composer textarea',
636 '.jrpg-composer button[type="submit"]',
637 'mjj-jrpg-menu button',
638 ].every(selector => {
639 const style = getComputedStyle(document.querySelector(selector));
640 return style.borderTopWidth === '0px' &&
641 style.borderRightWidth === '0px' &&
642 style.borderBottomWidth === '0px' &&
643 style.borderLeftWidth === '0px';
644 });
569 return { 645 return {
570 art: getComputedStyle(document.querySelector('.jrpg-scene')).backgroundImage, 646 art: getComputedStyle(document.querySelector('.jrpg-workspace'))
647 .backgroundImage,
648 borderless,
649 brandInSystemSlot:
650 closeTo((brand.left - workspace.left) / workspace.width, 0.054) &&
651 closeTo((brand.top - workspace.top) / workspace.height, 0.022) &&
652 document.querySelector('.jrpg-brand').parentElement.classList
653 .contains('jrpg-workspace'),
654 brandCoversSystemLabel:
655 brand.left <= workspace.left + workspace.width * 0.062 &&
656 brand.right >= workspace.left + workspace.width * 0.102 &&
657 brand.top <= workspace.top + workspace.height * 0.03 &&
658 brand.bottom >= workspace.top + workspace.height * 0.06,
571 characterLoaded: character.complete && character.naturalWidth > 0, 659 characterLoaded: character.complete && character.naturalWidth > 0,
572 colors, 660 colors,
573 composerBelowScene: composer.top >= scene.bottom, 661 composerBelowScene: composer.top >= scene.bottom,
574 composerTextareaFills: ( 662 composerTextareaFills: (
575 Math.abs(composerTextarea.left - composerField.left) < 1 && 663 Math.abs(composerTextarea.left - composerField.left) < 1 &&
598 Math.abs(shell.bottom - innerHeight) < 1 && 686 Math.abs(shell.bottom - innerHeight) < 1 &&
599 main.left === 0 && 687 main.left === 0 &&
600 main.top === 0 && 688 main.top === 0 &&
601 Math.abs(main.right - innerWidth) < 1 && 689 Math.abs(main.right - innerWidth) < 1 &&
602 Math.abs(main.bottom - innerHeight) < 1, 690 Math.abs(main.bottom - innerHeight) < 1,
691 frameAlignment,
692 frameChromeAligned:
693 closeTo((frameControls.left - workspace.left) / workspace.width, 0.866) &&
694 closeTo((frameTelemetry.left - workspace.left) / workspace.width, 0.085) &&
695 closeTo((frameTelemetry.top - workspace.top) / workspace.height, 0.928),
603 fonts: computedFonts, 696 fonts: computedFonts,
604 fontLoaded: [...document.fonts].some(font => 697 fontLoaded: [...document.fonts].some(font =>
605 font.family.replaceAll('"', '') === 'Pixel Mplus' && 698 font.family.replaceAll('"', '') === 'Pixel Mplus' &&
606 font.status === 'loaded' 699 font.status === 'loaded'
607 ), 700 ),
612 messageViewport.height > 192 705 messageViewport.height > 192
613 ), 706 ),
614 noOverflow: document.documentElement.scrollWidth <= innerWidth, 707 noOverflow: document.documentElement.scrollWidth <= innerWidth,
615 nonPixelText, 708 nonPixelText,
616 questLabelAbsent: !document.body.textContent.includes('QUEST 01'), 709 questLabelAbsent: !document.body.textContent.includes('QUEST 01'),
710 statsRemoved:
711 !document.querySelector('.jrpg-preview-stats') &&
712 !document.querySelector('[data-preview-type]'),
713 telemetryCovered:
714 document.querySelectorAll('.jrpg-frame-telemetry > *').length === 4 &&
715 document.querySelector('[data-frame-network]').textContent
716 .includes('ONLINE') &&
717 document.querySelector('[data-frame-uptime]').textContent
718 .match(/^\d{2}:\d{2}:\d{2}$/) &&
719 [...document.querySelectorAll('.jrpg-frame-telemetry > *')]
720 .every(item => !/TEMP:|USER:|STATUS:|UPTIME:/.test(item.textContent)),
721 telemetryValuesAligned:
722 closeTo(
723 (telemetryValues[0].left + telemetryValues[0].width / 2 -
724 frameTelemetry.left) / frameTelemetry.width,
725 0.12,
726 ) &&
727 closeTo(
728 (telemetryValues[1].left + telemetryValues[1].width / 2 -
729 frameTelemetry.left) / frameTelemetry.width,
730 0.325,
731 ) &&
732 closeTo(
733 (telemetryValues[2].left + telemetryValues[2].width / 2 -
734 frameTelemetry.left) / frameTelemetry.width,
735 0.605,
736 ) &&
737 closeTo(
738 (telemetryValues[3].left + telemetryValues[3].width / 2 -
739 frameTelemetry.left) / frameTelemetry.width,
740 0.88,
741 ),
742 shibaInFrontOfBar:
743 sceneBarStyle.backgroundImage.includes('bar-ink.webp') &&
744 Number(getComputedStyle(character.parentElement).zIndex) >
745 Number(sceneBarStyle.zIndex) &&
746 characterBox.bottom > scene.top + scene.height * 0.65,
747 terminalScrollbar: (
748 getComputedStyle(
749 document.querySelector('.jrpg-message-viewport'),
750 ).scrollbarWidth === 'thin' &&
751 getComputedStyle(
752 document.querySelector('.jrpg-message-viewport'),
753 ).scrollbarColor !== 'auto'
754 ),
617 theme: document.documentElement.dataset.zenTheme, 755 theme: document.documentElement.dataset.zenTheme,
618 utilityRightOfScene: utility.left >= scene.right, 756 utilityRightOfScene: utility.left >= scene.right,
757 workspaceFillsViewport:
758 Math.abs(workspace.left) < 1 &&
759 Math.abs(workspace.top) < 1 &&
760 Math.abs(workspace.right - innerWidth) < 1 &&
761 Math.abs(workspace.bottom - innerHeight) < 1,
619 }; 762 };
620 }); 763 });
621 assert.match(desktop.art, /bar-ink\.webp/); 764 assert.match(desktop.art, /background-frame-2\.webp/);
765 assert.equal(desktop.borderless, true);
766 assert.equal(desktop.brandInSystemSlot, true);
767 assert.equal(desktop.brandCoversSystemLabel, true);
622 assert.equal(desktop.characterLoaded, true); 768 assert.equal(desktop.characterLoaded, true);
623 assert.equal(desktop.colors.page, desktop.colors.canvas); 769 assert.equal(desktop.colors.page, desktop.colors.canvas);
624 assert.equal( 770 assert.equal(desktop.colors.conversation, 'rgba(0, 0, 0, 0)');
625 desktop.colors.conversation,
626 desktop.colors.conversationPanel,
627 );
628 assert.equal(desktop.colors.conversationBorder, desktop.colors.frame);
629 assert.equal(desktop.colors.conversationText, desktop.colors.text); 771 assert.equal(desktop.colors.conversationText, desktop.colors.text);
630 assert.equal(desktop.colors.menu, desktop.colors.sunken); 772 assert.equal(desktop.colors.menu, 'rgba(0, 0, 0, 0)');
631 assert.equal(desktop.colors.primaryText, desktop.colors.primary); 773 assert.equal(desktop.colors.primaryText, desktop.colors.primary);
632 assert.equal(desktop.colors.submit, desktop.colors.primary); 774 assert.equal(desktop.colors.submit, desktop.colors.primary);
633 assert.equal(desktop.colors.utility, desktop.colors.surface); 775 assert.equal(desktop.colors.utility, 'rgba(0, 0, 0, 0)');
634 assert.equal(desktop.composerBelowScene, true); 776 assert.equal(desktop.composerBelowScene, true);
635 assert.equal( 777 assert.equal(
636 desktop.composerTextareaFills, 778 desktop.composerTextareaFills,
637 true, 779 true,
638 JSON.stringify(desktop.composerTextareaGeometry), 780 JSON.stringify(desktop.composerTextareaGeometry),
639 ); 781 );
640 assert.equal(desktop.fillsViewport, true); 782 assert.equal(desktop.fillsViewport, true);
783 assert.equal(desktop.frameAlignment, true);
784 assert.equal(desktop.frameChromeAligned, true);
641 assert.ok(desktop.fonts.every(font => font === '"Pixel Mplus"')); 785 assert.ok(desktop.fonts.every(font => font === '"Pixel Mplus"'));
642 assert.equal(desktop.fontLoaded, true); 786 assert.equal(desktop.fontLoaded, true);
643 assert.equal(desktop.menuRightOfComposer, true); 787 assert.equal(desktop.menuRightOfComposer, true);
644 assert.equal(desktop.messageViewportFills, true); 788 assert.equal(desktop.messageViewportFills, true);
645 assert.equal(desktop.noOverflow, true); 789 assert.equal(desktop.noOverflow, true);
646 assert.deepEqual(desktop.nonPixelText, []); 790 assert.deepEqual(desktop.nonPixelText, []);
647 assert.equal(desktop.questLabelAbsent, true); 791 assert.equal(desktop.questLabelAbsent, true);
792 assert.equal(desktop.statsRemoved, true);
793 assert.equal(desktop.telemetryCovered, true);
794 assert.equal(desktop.telemetryValuesAligned, true);
795 assert.equal(desktop.shibaInFrontOfBar, true);
796 assert.equal(desktop.terminalScrollbar, true);
648 assert.equal(desktop.theme, 'cyberpunk'); 797 assert.equal(desktop.theme, 'cyberpunk');
649 assert.equal(desktop.utilityRightOfScene, true); 798 assert.equal(desktop.utilityRightOfScene, true);
799 assert.equal(desktop.workspaceFillsViewport, true);
650 await page.locator('.jrpg-brand a').focus(); 800 await page.locator('.jrpg-brand a').focus();
651 assert.equal( 801 assert.equal(
652 await page.locator('.jrpg-brand a').evaluate(element => 802 await page.locator('.jrpg-brand a').evaluate(element =>
653 getComputedStyle(element).outlineColor 803 getComputedStyle(element).outlineColor
654 ), 804 ),
663 ); 813 );
664 assert.equal( 814 assert.equal(
665 await page.locator('meta[name="theme-color"]').first().getAttribute('content'), 815 await page.locator('meta[name="theme-color"]').first().getAttribute('content'),
666 desktop.colors.canvas, 816 desktop.colors.canvas,
667 ); 817 );
668 assert.equal(await page.locator('[data-work-showcase] a').count(), 6); 818 assert.equal(
819 await page.locator('.jrpg-frame-controls :is(button, a)').count(),
820 3,
821 );
822 assert.equal(
823 await page.locator('.jrpg-frame-controls a').getAttribute('href'),
824 '/',
825 );
826 await page.getByRole('button', { name: 'Minimize interface' }).click();
827 assert.equal(
828 await page.locator('.jrpg-scene').evaluate(
829 scene => getComputedStyle(scene).visibility,
830 ),
831 'hidden',
832 );
833 await page.getByRole('button', { name: 'Restore interface' }).click();
834 assert.equal(
835 await page.locator('.jrpg-scene').evaluate(
836 scene => getComputedStyle(scene).visibility,
837 ),
838 'visible',
839 );
840 assert.equal(await page.locator('[data-work-showcase] a').count(), 7);
841 assert.equal(
842 await page.getByRole('button', { name: /Inspect/ }).count(),
843 0,
844 );
669 assert.match( 845 assert.match(
670 await page.locator('[data-work-showcase]').textContent(), 846 await page.locator('[data-work-showcase]').textContent(),
671 /Copilot Tasks/, 847 /Copilot Tasks/,
672 ); 848 );
673 assert.match( 849 assert.match(
699 assert.equal( 875 assert.equal(
700 await page.getByRole('button', { name: 'Tools', exact: true }) 876 await page.getByRole('button', { name: 'Tools', exact: true })
701 .getAttribute('aria-pressed'), 877 .getAttribute('aria-pressed'),
702 'true', 878 'true',
703 ); 879 );
704 assert.equal(await page.locator('[data-work-showcase] a').count(), 3); 880 await page.waitForFunction(() =>
705 881 document.querySelectorAll('[data-latest-tool]').length === 5
706 await page.getByRole('button', { name: /Inspect/ }).click(); 882 );
707 await page.locator('.jrpg-preview-dialog dialog[open]').waitFor(); 883 assert.match(
884 await page.locator('[data-work-showcase]').textContent(),
885 /Online LaTeX Editor/,
886 );
887 assert.match(
888 await page.locator('[data-work-showcase]').textContent(),
889 /Personal Notes/,
890 );
891 await page.locator('[data-latest-tool]').first().click();
892 await page.locator('[data-functional-tool="markdown"]').waitFor();
708 assert.equal( 893 assert.equal(
709 await page.locator('[data-dialog-title]').textContent(), 894 await page.locator('[data-dialog-title]').textContent(),
710 'Tools', 895 'MarkDown to HTML',
711 ); 896 );
897 assert.equal(
898 await page.locator('[data-preview-link]').getAttribute('href'),
899 '/tools/markdown_to_html',
900 );
901 await page.locator('[data-markdown-source]').fill('# Modal Markdown');
902 await page.waitForFunction(() =>
903 document.querySelector('[data-markdown-output] h1')?.textContent ===
904 'Modal Markdown'
905 );
906 assert.equal(await page.locator('.jrpg-tool-pane').count(), 2);
907 const toolWorkspace = await page.evaluate(() => {
908 const dialogElement = document.querySelector(
909 '.jrpg-detail-dialog[data-detail-mode="tools"]',
910 );
911 const dialog = dialogElement.getBoundingClientRect();
912 const windows = [...document.querySelectorAll('.jrpg-tool-window')]
913 .map(window => window.getBoundingClientRect());
914 const windowShadows = [...document.querySelectorAll('.jrpg-tool-window')]
915 .map(window => getComputedStyle(window).boxShadow);
916 const rail = getComputedStyle(
917 document.querySelector('.jrpg-tool-panes'),
918 '::before',
919 );
920 return {
921 dialogHeight: dialogElement.offsetHeight,
922 dialogWidth: dialogElement.offsetWidth,
923 fillsViewport: dialogElement.offsetWidth > innerWidth * 0.9 &&
924 dialogElement.offsetHeight > innerHeight * 0.9,
925 viewportHeight: innerHeight,
926 viewportWidth: innerWidth,
927 sideBySide: windows.length === 2 &&
928 windows[0].right < windows[1].left &&
929 Math.abs(windows[0].top - windows[1].top) < 1,
930 fullPageInHeader: Boolean(
931 document.querySelector('[data-preview-link]')
932 .closest('.jrpg-dialog-heading-actions'),
933 ),
934 footerHidden: document.querySelector('[data-dialog-actions]').hidden,
935 metalRail: rail.content !== 'none' &&
936 rail.backgroundImage.includes('bar-ink.webp'),
937 pixelIcons: document.querySelectorAll('.jrpg-tool-window zen-icon').length >= 2 &&
938 [...document.querySelectorAll('.jrpg-tool-window zen-icon svg')]
939 .every(icon =>
940 getComputedStyle(icon).shapeRendering.toLowerCase() === 'crispedges'
941 ),
942 separateGlows: windowShadows.length === 2 &&
943 windowShadows.every(shadow => shadow !== 'none'),
944 wideWindows: windows.every(window => window.width > dialog.width * 0.35),
945 };
946 });
947 assert.equal(
948 toolWorkspace.fillsViewport,
949 true,
950 JSON.stringify(toolWorkspace),
951 );
952 assert.equal(toolWorkspace.sideBySide, true);
953 assert.equal(toolWorkspace.fullPageInHeader, true);
954 assert.equal(toolWorkspace.footerHidden, true);
955 assert.equal(toolWorkspace.metalRail, true);
956 assert.equal(toolWorkspace.pixelIcons, true);
957 assert.equal(toolWorkspace.separateGlows, true);
958 assert.equal(toolWorkspace.wideWindows, true);
712 await page.getByRole('button', { 959 await page.getByRole('button', {
713 name: 'Close destination details', 960 name: 'Close destination details',
714 }).click(); 961 }).click();
715 await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' }); 962 await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });
716 963
964 await page.locator('[data-latest-tool]').first().click();
965 await page.locator('[data-functional-tool="markdown"]').waitFor();
966 await page.getByRole('button', {
967 name: 'Close destination details',
968 }).click();
969 await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });
970
971 await page.locator('[data-latest-tool]').nth(1).click();
972 await page.locator('[data-functional-tool="converter"]').waitFor();
973 assert.equal(
974 await page.locator('[data-converter-input]').count(),
975 2,
976 );
977 const converterFixture = path.join(
978 RUNFILES,
979 WORKSPACE,
980 'mrjunejune/test/shiba.webp',
981 );
982 assert.equal(fs.existsSync(converterFixture), true);
983 await page.locator('[data-converter-input="image"]').setInputFiles(
984 converterFixture,
985 );
986 const conversionResponse = page.waitForResponse(response =>
987 response.url().endsWith('/api/convert/image-to-webp') &&
988 response.request().method() === 'POST'
989 );
990 await page.locator('[data-convert-kind="image"]').click();
991 assert.equal((await conversionResponse).status(), 200);
992 await page.locator('[data-converter-download="image"]').waitFor();
993 const cleanupResponse = page.waitForResponse(response =>
994 response.url().includes('/api/download/') &&
995 response.request().method() === 'DELETE'
996 );
997 await page.getByRole('button', {
998 name: 'Close destination details',
999 }).click();
1000 assert.equal((await cleanupResponse).status(), 204);
1001 await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });
1002
1003 await page.locator('[data-latest-tool]').nth(1).click();
1004 await page.locator('[data-functional-tool="converter"]').waitFor();
1005 await page.locator('[data-converter-input="image"]').setInputFiles(
1006 converterFixture,
1007 );
1008 const lateConversionResponse = page.waitForResponse(response =>
1009 response.url().endsWith('/api/convert/image-to-webp') &&
1010 response.request().method() === 'POST'
1011 );
1012 const lateCleanupResponse = page.waitForResponse(response =>
1013 response.url().includes('/api/download/') &&
1014 response.request().method() === 'DELETE'
1015 );
1016 await page.locator('[data-convert-kind="image"]').click();
1017 await page.getByRole('button', {
1018 name: 'Close destination details',
1019 }).click();
1020 assert.equal((await lateConversionResponse).status(), 200);
1021 assert.equal((await lateCleanupResponse).status(), 204);
1022 await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });
1023
1024 await page.locator('[data-latest-tool]').nth(2).click();
1025 await page.locator('[data-functional-tool="hls"]').waitFor();
1026 assert.equal(
1027 await page.locator('[data-preview-link]').getAttribute('href'),
1028 '/tools/hls_player',
1029 );
1030 await page.locator('[data-hls-sample]').click();
1031 await page.waitForFunction(() =>
1032 ['ready', 'error'].includes(
1033 document.querySelector('[data-hls-status]')?.dataset.state,
1034 )
1035 );
1036 assert.ok(
1037 ['ready', 'error'].includes(
1038 await page.locator('[data-hls-status]').getAttribute('data-state'),
1039 ),
1040 );
1041 assert.match(
1042 await page.locator('[data-hls-status]').textContent(),
1043 /ready|unsupported|failed/i,
1044 );
1045 await page.getByRole('button', {
1046 name: 'Close destination details',
1047 }).click();
1048 await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });
1049
1050 await page.locator('[data-latest-tool]').nth(3).click();
1051 await page.locator('[data-functional-tool="latex"]').waitFor();
1052 await page.locator('[data-latex-compile]').click();
1053 await page.locator('[data-latex-source]').fill(
1054 '\\documentclass{article}\\begin{document}Updated modal PDF\\end{document}',
1055 );
1056 assert.equal(await page.locator('[data-latex-download]').isHidden(), true);
1057 await page.locator('[data-latex-compile]').click();
1058 await page.waitForFunction(() =>
1059 document.querySelector('[data-latex-status]')?.dataset.state === 'ready'
1060 );
1061 assert.match(
1062 await page.locator('[data-latex-preview]').getAttribute('src'),
1063 /^blob:/,
1064 );
1065 await page.getByRole('button', {
1066 name: 'Close destination details',
1067 }).click();
1068 await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });
1069
1070 await page.locator('[data-latest-tool]').nth(4).click();
1071 await page.waitForFunction(() =>
1072 document.querySelector('[data-tool-content] h1')?.textContent ===
1073 'Personal Notes'
1074 );
1075 assert.match(
1076 await page.locator('[data-tool-content]').textContent(),
1077 /private browser-based writing workspace/,
1078 );
1079 assert.equal(
1080 await page.locator('[data-preview-link]').getAttribute('href'),
1081 '/notes',
1082 );
1083 await page.getByRole('button', {
1084 name: 'Close destination details',
1085 }).click();
1086 await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });
1087
717 await page.getByRole('button', { name: 'Resume', exact: true }).click(); 1088 await page.getByRole('button', { name: 'Resume', exact: true }).click();
718 await page.getByRole('button', { name: /Inspect/ }).click(); 1089 await page.locator('[data-resume-modal]').click();
719 await page.locator('[data-resume-content] .sub-header').first().waitFor(); 1090 await page.locator('[data-resume-content] .sub-header').first().waitFor();
720 const resumeDossier = await page.locator('[data-resume-dossier]').evaluate( 1091 const resumeDossier = await page.locator('[data-resume-dossier]').evaluate(
721 dossier => ({ 1092 dossier => ({
722 fontFamily: getComputedStyle(dossier).fontFamily, 1093 fontFamily: getComputedStyle(dossier).fontFamily,
723 text: dossier.textContent, 1094 text: dossier.textContent,
767 await page.getByRole('button', { 1138 await page.getByRole('button', {
768 name: 'Close destination details', 1139 name: 'Close destination details',
769 }).click(); 1140 }).click();
770 await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' }); 1141 await page.locator('.jrpg-preview-dialog dialog').waitFor({ state: 'hidden' });
771 1142
772 await page.getByRole('button', { name: /Inspect/ }).click(); 1143 await page.locator('[data-blog-archive]').click();
773 await page.waitForFunction(() => 1144 await page.waitForFunction(() =>
774 document.querySelectorAll('[data-blog-entry]').length === 9 1145 document.querySelectorAll('[data-blog-entry]').length === 9
775 ); 1146 );
776 assert.equal(await page.locator('[data-blog-entry]').count(), 9); 1147 assert.equal(await page.locator('[data-blog-entry]').count(), 9);
777 await page.locator('[data-blog-entry]').nth(1).click(); 1148 await page.locator('[data-blog-entry]').nth(1).click();
884 await page.reload({ waitUntil: 'networkidle' }); 1255 await page.reload({ waitUntil: 'networkidle' });
885 await page.evaluate(() => document.fonts.ready); 1256 await page.evaluate(() => document.fonts.ready);
886 const controlledReload = await page.evaluate(() => { 1257 const controlledReload = await page.evaluate(() => {
887 const shell = document.querySelector('.jrpg-shell').getBoundingClientRect(); 1258 const shell = document.querySelector('.jrpg-shell').getBoundingClientRect();
888 return { 1259 return {
889 art: getComputedStyle(document.querySelector('.jrpg-scene')) 1260 art: getComputedStyle(document.querySelector('.jrpg-workspace'))
890 .backgroundImage, 1261 .backgroundImage,
891 fillsViewport: shell.left === 0 && 1262 fillsViewport: shell.left === 0 &&
892 shell.top === 0 && 1263 shell.top === 0 &&
893 Math.abs(shell.right - innerWidth) < 1 && 1264 Math.abs(shell.right - innerWidth) < 1 &&
894 Math.abs(shell.bottom - innerHeight) < 1, 1265 Math.abs(shell.bottom - innerHeight) < 1,
897 document.querySelector('.jrpg-brand h1'), 1268 document.querySelector('.jrpg-brand h1'),
898 ).fontFamily, 1269 ).fontFamily,
899 theme: document.documentElement.dataset.zenTheme, 1270 theme: document.documentElement.dataset.zenTheme,
900 }; 1271 };
901 }); 1272 });
902 assert.match(controlledReload.art, /bar-ink\.webp/); 1273 assert.match(controlledReload.art, /background-frame-2\.webp/);
903 assert.equal(controlledReload.fillsViewport, true); 1274 assert.equal(controlledReload.fillsViewport, true);
904 assert.equal(controlledReload.fontLoaded, true); 1275 assert.equal(controlledReload.fontLoaded, true);
905 assert.equal(controlledReload.fontFamily, '"Pixel Mplus"'); 1276 assert.equal(controlledReload.fontFamily, '"Pixel Mplus"');
906 assert.equal(controlledReload.theme, 'cyberpunk'); 1277 assert.equal(controlledReload.theme, 'cyberpunk');
907 assert.deepEqual(errors, []); 1278 assert.deepEqual(errors, []);
937 initialScroll: 0, 1308 initialScroll: 0,
938 noOverflow: true, 1309 noOverflow: true,
939 utilityBelowScene: true, 1310 utilityBelowScene: true,
940 }); 1311 });
941 await mobileContext.close(); 1312 await mobileContext.close();
1313
1314 const shortContext = await browser.newContext({
1315 colorScheme: 'dark',
1316 viewport: { width: 900, height: 500 },
1317 });
1318 const shortPage = await shortContext.newPage();
1319 await shortPage.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' });
1320 const shortLayout = await shortPage.evaluate(() => {
1321 const scene = document.querySelector('.jrpg-scene').getBoundingClientRect();
1322 const character = document.querySelector('mjj-jrpg-character')
1323 .getBoundingClientRect();
1324 const chat = document.querySelector('mjj-jrpg-chat')
1325 .getBoundingClientRect();
1326 return {
1327 characterInside: character.left >= scene.left &&
1328 character.right <= scene.right,
1329 chatInside: chat.left >= scene.left && chat.right <= scene.right,
1330 noSceneOverflow: document.querySelector('.jrpg-scene').scrollWidth <=
1331 document.querySelector('.jrpg-scene').clientWidth,
1332 };
1333 });
1334 assert.deepEqual(shortLayout, {
1335 characterInside: true,
1336 chatInside: true,
1337 noSceneOverflow: true,
1338 });
1339 await shortContext.close();
1340
1341 const forcedColorsContext = await browser.newContext({
1342 forcedColors: 'active',
1343 viewport: { width: 1280, height: 900 },
1344 });
1345 const forcedColorsPage = await forcedColorsContext.newPage();
1346 await forcedColorsPage.goto(`${baseUrl}/jrpg`, { waitUntil: 'networkidle' });
1347 assert.deepEqual(
1348 await forcedColorsPage.evaluate(() => ({
1349 sceneBar: getComputedStyle(
1350 document.querySelector('.jrpg-scene'),
1351 '::before',
1352 ).backgroundImage,
1353 workspace: getComputedStyle(
1354 document.querySelector('.jrpg-workspace'),
1355 ).backgroundImage,
1356 })),
1357 {
1358 sceneBar: 'none',
1359 workspace: 'none',
1360 },
1361 );
1362 await forcedColorsContext.close();
942 } 1363 }
943 1364
944 async function testHlsPlayer(browser, siteRoot) { 1365 async function testHlsPlayer(browser, siteRoot) {
945 const page = await browser.newPage(); 1366 const page = await browser.newPage();
946 const errors = []; 1367 const errors = [];
1259 await fetch(`${baseUrl}/sw.js`) 1680 await fetch(`${baseUrl}/sw.js`)
1260 ).text(); 1681 ).text();
1261 for (const source of [home, dogGame, manifest]) { 1682 for (const source of [home, dogGame, manifest]) {
1262 assert.doesNotMatch(source, /\.png(?:["')]|$)/i); 1683 assert.doesNotMatch(source, /\.png(?:["')]|$)/i);
1263 } 1684 }
1264 assert.match(serviceWorker, /v14-blog-browser/); 1685 assert.match(serviceWorker, /v30-card-driven-details/);
1265 assert.match( 1686 assert.match(
1266 await ( 1687 await (
1267 await fetch(`${baseUrl}/public/pwa-register.js`) 1688 await fetch(`${baseUrl}/public/pwa-register.js`)
1268 ).text(), 1689 ).text(),
1269 /register\('\/sw\.js', \{ scope: '\/' \}\)/, 1690 /register\('\/sw\.js', \{ scope: '\/' \}\)/,