mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-08 16:39:42 +02:00
feat(tabs): COD-358 add resizable vertical session rail
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
/** Real Chromium visibility and click coverage for nested vertical session actions. */
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { resolve } from 'node:path';
|
||||
import { afterAll, beforeAll, describe, expect, it } from 'vitest';
|
||||
import { chromium, type Browser, type Page } from 'playwright';
|
||||
|
||||
const styles = readFileSync(resolve(import.meta.dirname, '../src/web/public/styles.css'), 'utf8');
|
||||
const controller = readFileSync(resolve(import.meta.dirname, '../src/web/public/tab-rail-resize.js'), 'utf8');
|
||||
|
||||
function fixture(surface: 'sidebar' | 'rail', active = false) {
|
||||
const root =
|
||||
surface === 'sidebar'
|
||||
? 'data-session-list="sidebar" data-sidebar="expanded" data-tab-orientation="horizontal"'
|
||||
: 'data-session-list="header" data-sidebar="expanded" data-tab-orientation="vertical"';
|
||||
const hostClass = surface === 'sidebar' ? 'session-sidebar' : 'tab-rail';
|
||||
return `<!doctype html><html ${root}><head><style>${styles}</style></head><body>
|
||||
<div class="${hostClass}"><div class="session-tabs">
|
||||
<div class="session-tab${active ? ' active' : ''}" tabindex="0">
|
||||
<span class="tab-info"><span class="tab-name-row"><span class="tab-name">session</span>
|
||||
<span class="tab-actions"><button class="tab-more" type="button">…</button></span>
|
||||
</span></span>
|
||||
</div>
|
||||
</div></div>
|
||||
</body></html>`;
|
||||
}
|
||||
|
||||
async function installActionMenuController(page: Page) {
|
||||
await page.addScriptTag({ content: 'class CodemanApp {}; window.CodemanApp = CodemanApp;' });
|
||||
await page.addScriptTag({ content: controller });
|
||||
await page.evaluate(() => {
|
||||
const app = new (window as any).CodemanApp();
|
||||
app.loadAppSettingsFromStorage = () => ({ showTabDetachButton: false });
|
||||
app.openSessionOptions = () => undefined;
|
||||
app.requestCloseSession = () => undefined;
|
||||
document
|
||||
.querySelector('.tab-more')
|
||||
?.addEventListener('click', (event) => app.openTabRailActionMenu(event, 'session-1'));
|
||||
(window as any).app = app;
|
||||
});
|
||||
}
|
||||
|
||||
async function actionState(page: Page) {
|
||||
return page.locator('.tab-more').evaluate((node) => {
|
||||
const style = getComputedStyle(node);
|
||||
return { visibility: style.visibility, pointerEvents: style.pointerEvents };
|
||||
});
|
||||
}
|
||||
|
||||
describe('expanded vertical session actions in Chromium', () => {
|
||||
let browser: Browser;
|
||||
let page: Page;
|
||||
|
||||
beforeAll(async () => {
|
||||
browser = await chromium.launch({ headless: true });
|
||||
page = await browser.newPage({ viewport: { width: 1280, height: 800 } });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await browser.close();
|
||||
});
|
||||
|
||||
for (const surface of ['sidebar', 'rail'] as const) {
|
||||
it(`${surface} hides inactive actions, reveals them contextually, and opens the menu`, async () => {
|
||||
await page.setContent(fixture(surface));
|
||||
await installActionMenuController(page);
|
||||
await page.mouse.move(1200, 760);
|
||||
const tab = page.locator('.session-tab');
|
||||
expect(await actionState(page)).toEqual({ visibility: 'hidden', pointerEvents: 'none' });
|
||||
await tab.hover();
|
||||
expect(await actionState(page)).toEqual({ visibility: 'visible', pointerEvents: 'auto' });
|
||||
await page.locator('.tab-more').click();
|
||||
expect(await page.locator('.tab-rail-action-menu').count()).toBe(1);
|
||||
|
||||
await page.setContent(fixture(surface));
|
||||
await installActionMenuController(page);
|
||||
await page.locator('.session-tab').focus();
|
||||
expect(await actionState(page)).toEqual({ visibility: 'visible', pointerEvents: 'auto' });
|
||||
|
||||
await page.setContent(fixture(surface, true));
|
||||
await installActionMenuController(page);
|
||||
expect(await actionState(page)).toEqual({ visibility: 'visible', pointerEvents: 'auto' });
|
||||
});
|
||||
}
|
||||
|
||||
it('keeps nested actions reachable for a coarse pointer', async () => {
|
||||
const context = await browser.newContext({ viewport: { width: 1280, height: 800 }, hasTouch: true });
|
||||
try {
|
||||
const touchPage = await context.newPage();
|
||||
for (const surface of ['sidebar', 'rail'] as const) {
|
||||
await touchPage.setContent(fixture(surface));
|
||||
expect(await actionState(touchPage)).toEqual({ visibility: 'visible', pointerEvents: 'auto' });
|
||||
}
|
||||
} finally {
|
||||
await context.close();
|
||||
}
|
||||
});
|
||||
|
||||
it('closes the real controller menu when viewport geometry changes', async () => {
|
||||
await page.setContent(fixture('rail', true));
|
||||
await installActionMenuController(page);
|
||||
await page.locator('.tab-more').click();
|
||||
expect(await page.locator('.tab-rail-action-menu').count()).toBe(1);
|
||||
await page.evaluate(() => window.dispatchEvent(new Event('resize')));
|
||||
expect(await page.locator('.tab-rail-action-menu').count()).toBe(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user