File size: 7,757 Bytes
39371ea
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import { chromium } from 'playwright';
import assert from 'node:assert/strict';
import fs from 'node:fs/promises';

const url = process.argv[2] ?? 'http://127.0.0.1:4173/';
const result = { url, checks: [], errors: [] };
const browser = await chromium.launch({ executablePath: '/usr/bin/chromium', headless: true });
const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
page.setDefaultTimeout(15000);
page.on('pageerror', error => result.errors.push(error.message));
const ready = async () => { await page.waitForFunction(() => window.browserPi?.terminal); await page.evaluate(() => window.browserPi.ready); };
const divider = page.getByRole('separator', { name: 'Resize chat and workspace' });
const geometry = async () => {
  const size = await page.evaluate(() => {
  const rect = selector => document.querySelector(selector).getBoundingClientRect().toJSON();
  return { main: rect('main'), chat: rect('.conversation'), workspace: rect('.workspace'), handle: rect('#column-resizer'),
    desktop: matchMedia('(min-width: 851px)').matches,
    gutter: parseFloat(getComputedStyle(document.querySelector('main')).getPropertyValue('--content-gutter')),
    cols: window.browserPi.terminal.cols, overflow: document.documentElement.scrollWidth > innerWidth };
  });
  if (size.desktop) {
    closeTo(size.workspace.left, size.chat.right);
    closeTo(size.workspace.right + size.gutter, size.main.right);
  }
  return size;
};
const frames = () => page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))));
async function drag(delta, release = true) {
  const r = await divider.boundingBox();
  await page.mouse.move(r.x + r.width / 2, r.y + r.height / 2); await page.mouse.down();
  await page.mouse.move(r.x + r.width / 2 + delta, r.y + r.height / 2, { steps: 12 });
  if (release) await page.mouse.up();
  await frames();
}
const closeTo = (actual, expected) => assert(Math.abs(actual - expected) < 1, `${actual} vs ${expected}`);
try {
  await page.goto(url); await ready();
  assert(await divider.isVisible());
  const initial = await geometry();
  closeTo(initial.chat.width, initial.main.width / 1.83);
  assert(initial.handle.x >= initial.chat.right - 14.1 && initial.handle.right <= initial.workspace.left + .1);
  await page.locator('.xterm-helper-textarea').focus();
  await page.keyboard.type('Keep this draft'); await page.keyboard.press('Shift+Enter'); await page.keyboard.type('while resizing');
  await page.locator('#files button[title="/workspace/products.json"]').click();
  await page.locator('#editor').fill('{"unsaved": true}');
  await page.locator('#command').fill('echo "unsent command"');
  await drag(240);
  assert((await geometry()).chat.width > initial.chat.width + 230);
  await page.waitForFunction(cols => window.browserPi.terminal.cols > cols, initial.cols);
  assert.equal(await page.evaluate(() => window.browserPi.terminal.text), 'Keep this draft\nwhile resizing');
  assert.equal(await page.locator('#editor').inputValue(), '{"unsaved": true}');
  assert.equal(await page.locator('#command').inputValue(), 'echo "unsent command"');
  assert(await page.locator('#save').isEnabled());
  await page.waitForFunction(() => document.querySelector('#editor-highlight').clientWidth === document.querySelector('#editor').clientWidth);
  result.checks.push('Live resizing preserves Pi, file and shell drafts and resizes the terminal and syntax layer');

  await drag(-2000); closeTo((await geometry()).chat.width, 420);
  await divider.press('End'); await frames();
  let size = await geometry(); closeTo(size.main.width - size.chat.width, 370);
  assert.equal(await divider.getAttribute('aria-valuenow'), await divider.getAttribute('aria-valuemax'));
  await divider.press('ArrowLeft'); await frames(); closeTo((await geometry()).chat.width, size.chat.width - 16);
  await divider.press('Shift+ArrowLeft'); await frames(); closeTo((await geometry()).chat.width, size.chat.width - 64);
  await divider.press('Home'); await frames(); closeTo((await geometry()).chat.width, 420);
  await divider.press('Enter'); await frames(); closeTo((await geometry()).chat.width, initial.chat.width);
  result.checks.push('Width limits, keyboard nudges, Shift, Home/End, Enter reset and accessible values work');

  await drag(150, false); await divider.press('Escape'); await page.mouse.up(); await frames();
  closeTo((await geometry()).chat.width, initial.chat.width);
  assert(!await page.locator('html').evaluate(el => el.classList.contains('is-resizing-columns')));
  await drag(100, false); await divider.dispatchEvent('pointercancel', { pointerId: 1 }); await page.mouse.up(); await frames();
  closeTo((await geometry()).chat.width, initial.chat.width);
  await drag(170); const saved = (await geometry()).chat.width;
  await page.reload(); await ready(); await frames(); closeTo((await geometry()).chat.width, saved);
  await page.setViewportSize({ width: 851, height: 800 }); await frames();
  size = await geometry(); assert(size.chat.width >= 420 && size.main.width - size.chat.width >= 369.9 && !size.overflow);
  await page.setViewportSize({ width: 1440, height: 1000 }); await frames(); closeTo((await geometry()).chat.width, saved);
  result.checks.push('Cancelled drags roll back; saved ratio survives reload and returns after narrow-window clamping');

  await divider.dblclick(); await frames(); closeTo((await geometry()).chat.width, initial.chat.width);
  await page.setViewportSize({ width: 2400, height: 1000 }); await frames();
  size = await geometry(); assert.equal(size.main.width, 1800); assert(size.main.left > 0);
  await drag(100); closeTo((await geometry()).chat.width, size.chat.width + 100);
  await page.screenshot({ path: '../validation/column-resizer-wide.png', fullPage: true });
  await page.setViewportSize({ width: 1440, height: 1000 }); await frames(); await divider.dblclick(); await frames();
  await page.screenshot({ path: '../validation/column-resizer-desktop.png', fullPage: true });
  await drag(80, false); await page.setViewportSize({ width: 850, height: 800 }); await page.mouse.up(); await frames();
  assert(!await page.locator('html').evaluate(el => el.classList.contains('is-resizing-columns')));
  for (const width of [850,390,320]) {
    await page.setViewportSize({ width, height: 844 }); await frames();
    assert(await page.locator('#column-resizer').isHidden());
    size = await geometry(); assert(!size.overflow && size.workspace.top >= size.chat.bottom - 1);
  }
  await page.screenshot({ path: '../validation/column-resizer-mobile.png', fullPage: true });
  result.checks.push('Double-click resets; centered wide layouts drag accurately; mobile stays stacked and cancels desktop drags');

  const blocked = await browser.newPage({ viewport: { width: 1440, height: 1000 } });
  await blocked.addInitScript(() => { Storage.prototype.getItem = Storage.prototype.setItem = () => { throw new DOMException('Blocked', 'SecurityError'); }; });
  await blocked.goto(url); await blocked.waitForFunction(() => window.browserPi?.terminal);
  await blocked.getByRole('separator', { name: 'Resize chat and workspace' }).press('Home');
  closeTo(await blocked.locator('.conversation').evaluate(el => el.getBoundingClientRect().width), 420);
  await blocked.close(); result.checks.push('Resizing works with blocked browser storage');
  assert.deepEqual(result.errors, []); result.passed = true; console.log(JSON.stringify(result));
} catch (error) {
  result.failure = error.stack;
  await page.screenshot({ path: '../validation/column-resizer-failure.png', fullPage: true }); throw error;
} finally {
  await fs.writeFile('../validation/column-resizer.json', JSON.stringify(result, null, 2)); await browser.close();
}