diff hg-web/e2e/app_e2e_test.js @ 224:3007ef5fc0ed hg-web

[hg-web] Simplify visuals and preview static files
author MrJuneJune <me@mrjunejune.com>
date Sun, 02 Aug 2026 13:51:11 -0700
parents 0e7b9464248d
children 70de0c80d093
line wrap: on
line diff
--- a/hg-web/e2e/app_e2e_test.js	Sun Aug 02 10:07:40 2026 -0700
+++ b/hg-web/e2e/app_e2e_test.js	Sun Aug 02 13:51:11 2026 -0700
@@ -73,6 +73,17 @@
   );
   fs.writeFileSync(path.join(root, 'docs', 'README.md'), '# Documentation\n\nNested README.\n');
   fs.writeFileSync(path.join(root, 'src', 'main.c'), 'int main(void) { return 0; }\n');
+  fs.writeFileSync(
+    path.join(root, 'pixel.png'),
+    Buffer.from(
+      'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Wl2nWQAAAAASUVORK5CYII=',
+      'base64',
+    ),
+  );
+  fs.writeFileSync(path.join(root, 'movie.mp4'), Buffer.from('00000018667479706d703432', 'hex'));
+  fs.writeFileSync(path.join(root, 'sound.mp3'), Buffer.from('ID3'));
+  fs.writeFileSync(path.join(root, 'document.pdf'), Buffer.from('%PDF-1.4\n%%EOF\n'));
+  fs.writeFileSync(path.join(root, 'archive.bin'), Buffer.from([0, 1, 2, 3]));
   run('hg', ['--repository', root, 'add'], options);
   run('hg', ['--repository', root, 'commit', '-u', 'Test User <[email protected]>', '-m', 'Initial fixture'], options);
   const firstNode = run('hg', ['--repository', root, 'log', '-r', '.', '-T', '{node}'], options);
@@ -178,6 +189,9 @@
       assert.equal(response.status, 200, `${pathname} static asset`);
       assert.ok((await response.arrayBuffer()).byteLength > 0, `${pathname} is non-empty`);
     }
+    for (const pathname of ['/hg-web-background.jpg', '/pencil_texture.png']) {
+      assert.equal((await fetch(`${BASE_URL}${pathname}`)).status, 404);
+    }
 
     const rootList = await assertJson('/api/repo/list');
     assert.ok(rootList.directories.some(entry => entry.basename === 'docs'));
@@ -190,6 +204,28 @@
     assert.equal(fileResponse.status, 200);
     assert.match(await fileResponse.text(), /int main/);
 
+    for (const [filename, contentType, disposition] of [
+      ['pixel.png', 'image/png', 'inline'],
+      ['movie.mp4', 'video/mp4', 'inline'],
+      ['sound.mp3', 'audio/mpeg', 'inline'],
+      ['document.pdf', 'application/pdf', 'inline'],
+      ['archive.bin', 'application/octet-stream', 'attachment'],
+    ]) {
+      const response = await fetch(
+        `${BASE_URL}/api/repo/file?path=${encodeURIComponent(filename)}`,
+      );
+      assert.equal(response.status, 200, `${filename} response`);
+      assert.match(response.headers.get('content-type') || '', new RegExp(`^${contentType}`));
+      assert.equal(response.headers.get('content-disposition'), disposition);
+      assert.equal(response.headers.get('x-content-type-options'), 'nosniff');
+      assert.ok((await response.arrayBuffer()).byteLength > 0);
+    }
+    const binaryResponse = await fetch(`${BASE_URL}/api/repo/file?path=archive.bin`);
+    assert.deepEqual(
+      Buffer.from(await binaryResponse.arrayBuffer()),
+      Buffer.from([0, 1, 2, 3]),
+    );
+
     const readmeResponse = await fetch(`${BASE_URL}/api/repo/readme?path=docs`);
     assert.equal(readmeResponse.status, 200);
     assert.match(await readmeResponse.text(), /Nested README/);
@@ -234,6 +270,12 @@
       await page.getByText('Recent Commits').waitFor();
       await page.getByText('Repository Files').waitFor();
       assert.equal(await page.evaluate(() => window.__hgWebXss), undefined);
+      assert.equal(
+        await page.locator('.graph-container').evaluate(
+          element => getComputedStyle(element).backgroundImage,
+        ),
+        'none',
+      );
       await page.locator('.theme-toggle').click();
     });
 
@@ -268,6 +310,21 @@
       assert.equal(await page.getByText('Documentation').count(), 0);
       assert.equal(delayedReadmeRequested, true);
 
+      await page.getByRole('link', { name: 'pixel.png' }).click();
+      const image = page.locator('.static-file-image');
+      await image.waitFor();
+      await image.evaluate(element => {
+        const imageElement = element;
+        if (imageElement.complete) return;
+        return new Promise((resolve, reject) => {
+          imageElement.addEventListener('load', resolve, { once: true });
+          imageElement.addEventListener('error', reject, { once: true });
+        });
+      });
+      assert.equal(await image.evaluate(element => element.naturalWidth), 1);
+      await page.getByRole('dialog', { name: 'Preview pixel.png' }).waitFor();
+      await page.keyboard.press('Escape');
+
       await page.getByRole('link', { name: 'src' }).click();
       await page.getByRole('link', { name: 'main.c' }).click();
       await page.getByText('int main(void)').waitFor();
@@ -298,6 +355,12 @@
       await page.locator('.changeset-files code').filter({ hasText: 'new-file.txt' }).waitFor();
       await page.getByText('added', { exact: true }).waitFor();
       await page.getByRole('region', { name: 'Changeset diff' }).waitFor();
+      assert.equal(
+        await page.locator('.changeset-paper').evaluate(
+          element => getComputedStyle(element).backgroundImage,
+        ),
+        'none',
+      );
       await page.getByRole('button', { name: firstNode.slice(0, 12) }).click();
       await page.waitForFunction(
         expectedPath => window.location.pathname === expectedPath,