comparison mrjunejune/test/theme_and_webp_test.js @ 260:1f9877b637e9

Add Copilot-powered cyberpunk JRPG chat Integrate the production JRPG chat with Seobeo streaming, Deita persistence, and a Bazel-managed Copilot SDK and LiteLLM inference stack. Co-authored-by: Copilot <[email protected]>
author MrJuneJune <mrjunejune@users.noreply.github.com>
date Wed, 05 Aug 2026 09:19:41 -0700
parents 667156fcd3e3
children b401627fc49e
comparison
equal deleted inserted replaced
259:667156fcd3e3 260:1f9877b637e9
1 const assert = require('node:assert/strict'); 1 const assert = require('node:assert/strict');
2 const fs = require('node:fs'); 2 const fs = require('node:fs');
3 const net = require('node:net'); 3 const net = require('node:net');
4 const os = require('node:os');
4 const path = require('node:path'); 5 const path = require('node:path');
5 const { spawn } = require('node:child_process'); 6 const { spawn } = require('node:child_process');
6 7
7 const RUNFILES = process.env.JS_BINARY__RUNFILES; 8 const RUNFILES = process.env.JS_BINARY__RUNFILES;
8 const WORKSPACE = process.env.JS_BINARY__WORKSPACE; 9 const WORKSPACE = process.env.JS_BINARY__WORKSPACE;
127 await context.addInitScript(value => { 128 await context.addInitScript(value => {
128 localStorage.setItem('theme-preference', value); 129 localStorage.setItem('theme-preference', value);
129 }, theme); 130 }, theme);
130 const page = await context.newPage(); 131 const page = await context.newPage();
131 const errors = []; 132 const errors = [];
133 await page.route('**/api/conversations**', async route => {
134 const request = route.request();
135 const url = new URL(request.url());
136 if (request.method() === 'POST' &&
137 url.pathname === '/api/conversations') {
138 await route.fulfill({
139 status: 201,
140 contentType: 'application/json',
141 body: JSON.stringify({
142 id: '11111111-1111-4111-8111-111111111111',
143 }),
144 });
145 return;
146 }
147 if (request.method() === 'POST' && url.pathname.endsWith('/turns')) {
148 await route.fulfill({
149 status: 200,
150 contentType: 'text/event-stream',
151 body: [
152 'event: turn.accepted',
153 'data: {"request_id":"request-1"}',
154 '',
155 'event: assistant.delta',
156 'data: {"request_id":"request-1","delta":"Start with "}',
157 '',
158 'event: assistant.delta',
159 'data: {"request_id":"request-1","delta":"the tools."}',
160 '',
161 'event: assistant.completed',
162 'data: {"request_id":"request-1","content":"Start with the tools."}',
163 '',
164 'event: turn.done',
165 'data: {"request_id":"request-1","failed":false,"aborted":false}',
166 '',
167 '',
168 ].join('\n'),
169 });
170 return;
171 }
172 await route.fulfill({
173 status: 404,
174 contentType: 'application/json',
175 body: JSON.stringify({ error: { message: 'Not found' } }),
176 });
177 });
132 page.on('pageerror', error => errors.push(`pageerror: ${error.message}`)); 178 page.on('pageerror', error => errors.push(`pageerror: ${error.message}`));
133 page.on('console', message => { 179 page.on('console', message => {
134 if (message.type() === 'error') errors.push(`console: ${message.text()}`); 180 if (message.type() === 'error' &&
181 !message.text().startsWith('Failed to load resource:')) {
182 errors.push(`console: ${message.text()}`);
183 }
135 }); 184 });
136 page.on('requestfailed', request => { 185 page.on('requestfailed', request => {
137 errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`); 186 errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`);
138 }); 187 });
139 page.on('response', response => { 188 page.on('response', response => {
189 if (response.status() === 404 &&
190 response.url().endsWith(
191 '/api/conversations/00000000-0000-4000-8000-000000000000'
192 )) {
193 return;
194 }
140 if (response.status() >= 400) { 195 if (response.status() >= 400) {
141 errors.push(`response: ${response.status()} ${response.url()}`); 196 errors.push(`response: ${response.status()} ${response.url()}`);
142 } 197 }
143 }); 198 });
144 199
376 async function testJrpgPage(browser) { 431 async function testJrpgPage(browser) {
377 const context = await browser.newContext({ 432 const context = await browser.newContext({
378 colorScheme: 'light', 433 colorScheme: 'light',
379 viewport: { width: 1280, height: 900 }, 434 viewport: { width: 1280, height: 900 },
380 }); 435 });
436 await context.addInitScript(key => {
437 sessionStorage.setItem(
438 key,
439 '00000000-0000-4000-8000-000000000000',
440 );
441 }, 'mjj-jrpg-conversation-id');
381 const page = await context.newPage(); 442 const page = await context.newPage();
382 const errors = []; 443 const errors = [];
383 page.on('pageerror', error => errors.push(`pageerror: ${error.message}`)); 444 page.on('pageerror', error => errors.push(`pageerror: ${error.message}`));
384 page.on('console', message => { 445 page.on('console', message => {
385 if (message.type() === 'error') errors.push(`console: ${message.text()}`); 446 if (message.type() === 'error' &&
447 !message.text().startsWith('Failed to load resource:')) {
448 errors.push(`console: ${message.text()}`);
449 }
386 }); 450 });
387 page.on('requestfailed', request => { 451 page.on('requestfailed', request => {
388 errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`); 452 errors.push(`requestfailed: ${request.url()} ${request.failure()?.errorText || ''}`);
389 }); 453 });
390 page.on('response', response => { 454 page.on('response', response => {
455 if (response.status() === 404 &&
456 response.url().endsWith(
457 '/api/conversations/00000000-0000-4000-8000-000000000000'
458 )) {
459 return;
460 }
391 if (response.status() >= 400) { 461 if (response.status() >= 400) {
392 errors.push(`response: ${response.status()} ${response.url()}`); 462 errors.push(`response: ${response.status()} ${response.url()}`);
393 } 463 }
394 }); 464 });
395 465
433 const computedFonts = [ 503 const computedFonts = [
434 document.body, 504 document.body,
435 document.querySelector('.jrpg-brand h1'), 505 document.querySelector('.jrpg-brand h1'),
436 document.querySelector('.jrpg-message p'), 506 document.querySelector('.jrpg-message p'),
437 document.querySelector('#jrpg-message'), 507 document.querySelector('#jrpg-message'),
438 document.querySelector('.jrpg-composer button'), 508 document.querySelector('.jrpg-composer button[type="submit"]'),
439 ].map(element => getComputedStyle(element).fontFamily); 509 ].map(element => getComputedStyle(element).fontFamily);
440 const nonPixelText = [document.body, ...document.body.querySelectorAll('*')] 510 const nonPixelText = [document.body, ...document.body.querySelectorAll('*')]
441 .filter(element => [...element.childNodes].some(node => 511 .filter(element => [...element.childNodes].some(node =>
442 node.nodeType === Node.TEXT_NODE && node.textContent.trim() 512 node.nodeType === Node.TEXT_NODE && node.textContent.trim()
443 )) 513 ))
445 getComputedStyle(element).fontFamily !== '"Pixel Mplus"' 515 getComputedStyle(element).fontFamily !== '"Pixel Mplus"'
446 ) 516 )
447 .map(element => element.localName); 517 .map(element => element.localName);
448 const pageStyle = getComputedStyle(document.body); 518 const pageStyle = getComputedStyle(document.body);
449 const submitStyle = getComputedStyle( 519 const submitStyle = getComputedStyle(
450 document.querySelector('.jrpg-composer button'), 520 document.querySelector('.jrpg-composer button[type="submit"]'),
451 ); 521 );
452 const colors = { 522 const colors = {
453 canvas: probeStyle.backgroundColor, 523 canvas: probeStyle.backgroundColor,
454 conversation: getComputedStyle( 524 conversation: getComputedStyle(
455 document.querySelector('mjj-jrpg-chat'), 525 document.querySelector('mjj-jrpg-chat'),
535 await page.locator('.jrpg-brand a').evaluate(element => 605 await page.locator('.jrpg-brand a').evaluate(element =>
536 getComputedStyle(element).outlineColor 606 getComputedStyle(element).outlineColor
537 ), 607 ),
538 desktop.colors.focus, 608 desktop.colors.focus,
539 ); 609 );
540 await page.locator('.jrpg-composer button').focus(); 610 await page.locator('.jrpg-composer button[type="submit"]').focus();
541 assert.equal( 611 assert.equal(
542 await page.locator('.jrpg-composer button').evaluate(element => 612 await page.locator('.jrpg-composer button[type="submit"]').evaluate(element =>
543 getComputedStyle(element).outlineColor 613 getComputedStyle(element).outlineColor
544 ), 614 ),
545 desktop.colors.focus, 615 desktop.colors.focus,
546 ); 616 );
547 assert.equal( 617 assert.equal(
894 const serverBinary = path.join( 964 const serverBinary = path.join(
895 runfilesWorkspace, 965 runfilesWorkspace,
896 'mrjunejune/mrjunejune_server_debug', 966 'mrjunejune/mrjunejune_server_debug',
897 ); 967 );
898 const siteRoot = path.join(runfilesWorkspace, 'mrjunejune/src'); 968 const siteRoot = path.join(runfilesWorkspace, 'mrjunejune/src');
969 const fakeSidecar = path.join(
970 runfilesWorkspace,
971 'mrjunejune/test/inference_bridge_fake_sidecar',
972 );
973 const tempDirectory = fs.mkdtempSync(
974 path.join(os.tmpdir(), 'mrjunejune-theme-test-'),
975 );
899 const chromiumPath = path.resolve(process.env.CHROMIUM_PATH); 976 const chromiumPath = path.resolve(process.env.CHROMIUM_PATH);
900 const serverLogs = []; 977 const serverLogs = [];
901 let server; 978 let server;
902 let browser; 979 let browser;
903 980
935 ); 1012 );
936 } 1013 }
937 1014
938 server = spawn(serverBinary, [], { 1015 server = spawn(serverBinary, [], {
939 cwd: runfilesWorkspace, 1016 cwd: runfilesWorkspace,
940 env: { ...process.env, MRJUNEJUNE_PORT: port }, 1017 env: {
1018 ...process.env,
1019 MRJUNEJUNE_PORT: port,
1020 MRJUNEJUNE_DB_PATH: path.join(tempDirectory, 'site.db'),
1021 MRJUNEJUNE_INFERENCE_SIDECAR_PATH: fakeSidecar,
1022 MRJUNEJUNE_COPILOT_CLI_PATH: fakeSidecar,
1023 MRJUNEJUNE_ALLOW_ANONYMOUS_INFERENCE: '1',
1024 },
941 stdio: ['ignore', 'pipe', 'pipe'], 1025 stdio: ['ignore', 'pipe', 'pipe'],
942 }); 1026 });
943 server.stdout.on('data', chunk => serverLogs.push(chunk.toString())); 1027 server.stdout.on('data', chunk => serverLogs.push(chunk.toString()));
944 server.stderr.on('data', chunk => serverLogs.push(chunk.toString())); 1028 server.stderr.on('data', chunk => serverLogs.push(chunk.toString()));
945 await waitForServer(server, serverLogs); 1029 await waitForServer(server, serverLogs);
955 await fetch(`${baseUrl}/sw.js`) 1039 await fetch(`${baseUrl}/sw.js`)
956 ).text(); 1040 ).text();
957 for (const source of [home, dogGame, manifest]) { 1041 for (const source of [home, dogGame, manifest]) {
958 assert.doesNotMatch(source, /\.png(?:["')]|$)/i); 1042 assert.doesNotMatch(source, /\.png(?:["')]|$)/i);
959 } 1043 }
960 assert.match(serviceWorker, /v9-dev-page-split/); 1044 assert.match(serviceWorker, /v10-inference-chat/);
961 assert.match( 1045 assert.match(
962 await ( 1046 await (
963 await fetch(`${baseUrl}/public/pwa-register.js`) 1047 await fetch(`${baseUrl}/public/pwa-register.js`)
964 ).text(), 1048 ).text(),
965 /register\('\/sw\.js', \{ scope: '\/' \}\)/, 1049 /register\('\/sw\.js', \{ scope: '\/' \}\)/,
1025 await testJrpgPage(browser); 1109 await testJrpgPage(browser);
1026 await testHlsPlayer(browser, siteRoot); 1110 await testHlsPlayer(browser, siteRoot);
1027 } finally { 1111 } finally {
1028 if (browser) await browser.close(); 1112 if (browser) await browser.close();
1029 await stopProcess(server); 1113 await stopProcess(server);
1114 fs.rmSync(tempDirectory, { recursive: true, force: true });
1030 } 1115 }
1031 })().catch(error => { 1116 })().catch(error => {
1032 console.error(error.stack || error); 1117 console.error(error.stack || error);
1033 process.exitCode = 1; 1118 process.exitCode = 1;
1034 }); 1119 });