import assert from 'node:assert/strict';
import { once } from 'node:events';
import { createServer as createHttpServer } from 'node:http';
import { realpathSync } from 'node:fs';
import { unlink, writeFile } from 'node:fs/promises';
import { createRequire } from 'node:module';
import { dirname, join } from 'node:path';
import { fileURLToPath, pathToFileURL } from 'node:url';
const repoRoot = dirname(dirname(fileURLToPath(import.meta.url)));
const uiRoot = join(repoRoot, 'ui');
process.env.BROWSERSLIST_IGNORE_OLD_DATA = 'true';
process.chdir(uiRoot);
const requireFromUi = createRequire(join(uiRoot, 'package.json'));
const { createServer: createViteServer } = await import(pathToFileURL(requireFromUi.resolve('vite')).href);
const playwrightModule = await import(pathToFileURL(requireFromUi.resolve('playwright')).href);
const { chromium } = playwrightModule.chromium ? playwrightModule : playwrightModule.default;
const fixturePath = `/tmp/task-files-toolbar-${process.pid}.js`;
await writeFile(fixturePath, `
import React from 'react';
import { createRoot } from 'react-dom/client';
import '${uiRoot}/src/index.css';
import i18n from '${uiRoot}/src/i18n/index.ts';
import { FileBrowser } from '${uiRoot}/src/components/files/FileBrowser.tsx';
const entries = [
{ name: 'report.pdf', path: 'report.pdf', kind: 'file', size: 10, modifiedAt: '2026-08-01T00:00:00Z' },
{ name: 'data.csv', path: 'data.csv', kind: 'file', size: 10, modifiedAt: '2026-08-01T00:00:00Z' },
];
const management = {
canManage: true, writableSection: true, selected: new Set(),
toggleSelect() {}, toggleSelectAll() {}, upload() {}, remove() {}, download() {},
isUploading: false, isDeleting: false, isDownloading: false, message: null,
};
const root = createRoot(document.getElementById('root'));
window.__renderToolbar = (managed) => root.render(React.createElement(FileBrowser, {
section: 'output', currentPath: '', entries, pathSegments: [], taskId: 42,
onSectionChange(section) { window.__selectedSection = section; },
onNavigate() {}, onPreview() {}, onRefresh() {},
management: managed ? management : undefined,
}));
window.__setToolbarLanguage = (language) => i18n.changeLanguage(language);
window.__renderToolbar(true);
`);
const vite = await createViteServer({
root: uiRoot,
appType: 'custom',
logLevel: 'error',
server: {
middlewareMode: true,
hmr: false,
fs: { allow: [repoRoot, '/tmp', realpathSync(join(uiRoot, 'node_modules'))] },
},
});
const server = createHttpServer(async (req, res) => {
if (req.url === '/') {
const html = await vite.transformIndexHtml('/', `
`);
res.setHeader('content-type', 'text/html; charset=utf-8');
res.end(html);
return;
}
vite.middlewares(req, res);
});
server.listen(0, '127.0.0.1');
await once(server, 'listening');
const address = server.address();
if (!address || typeof address === 'string') throw new Error('Fixture server did not bind TCP');
let browser;
try {
browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] });
const page = await browser.newPage({ viewport: { width: 900, height: 700 } });
await page.goto(`http://127.0.0.1:${address.port}/`);
await page.getByTestId('task-file-origin-toggle').waitFor();
for (const language of ['ja', 'en']) {
await page.evaluate(async lang => { await window.__setToolbarLanguage(lang); }, language);
for (const managed of [true, false]) {
await page.evaluate(value => window.__renderToolbar(value), managed);
await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))));
await page.evaluate(() => document.fonts.ready);
for (const width of [699, 700, 701, 720, 721, 740, 620, 461, 460, 400, 320]) {
await page.locator('#case').evaluate((element, value) => { element.style.width = `${value}px`; }, width);
await page.evaluate(() => new Promise(resolve => requestAnimationFrame(() => requestAnimationFrame(resolve))));
const layout = await page.evaluate(() => {
const rect = element => {
const value = element.getBoundingClientRect();
return { left: value.left, top: value.top, right: value.right, bottom: value.bottom, width: value.width, height: value.height };
};
const tabs = document.querySelector('[data-testid="task-file-sections"]');
const actions = document.querySelector('.file-toolbar-actions');
if (!tabs || !actions) throw new Error('Toolbar regions missing');
const tabRect = rect(tabs);
const actionRect = rect(actions);
const buttons = [...tabs.querySelectorAll('button')].map(button => {
const buttonRect = rect(button);
const center = { x: buttonRect.left + buttonRect.width / 2, y: buttonRect.top + buttonRect.height / 2 };
return {
id: button.dataset.testid,
rect: buttonRect,
hit: document.elementFromPoint(center.x, center.y)?.closest('[data-testid]')?.getAttribute('data-testid') ?? null,
inside: buttonRect.left >= tabRect.left && buttonRect.right <= tabRect.right,
};
});
return { tabs: tabRect, actions: actionRect, buttons };
});
const context = `${language}, managed=${managed}, width=${width}`;
if (width <= 720) {
assert.ok(layout.actions.top >= layout.tabs.bottom, `actions must occupy a separate row: ${context}`);
} else {
assert.ok(layout.actions.top < layout.tabs.bottom, `wide toolbar must remain one row: ${context}`);
}
for (const button of layout.buttons) {
assert.ok(button.inside, `${button.id} is clipped outside the section region: ${context}; ${JSON.stringify({ button: button.rect, tabs: layout.tabs })}`);
assert.equal(button.hit, button.id, `${button.id} is not the hit target: ${context}`);
await page.evaluate(() => { window.__selectedSection = null; });
await page.getByTestId(button.id).click();
assert.equal(await page.evaluate(() => window.__selectedSection), button.id.replace('task-file-section-', ''), `${button.id} click did not select: ${context}`);
}
const origin = page.getByTestId('task-file-origin-toggle');
await origin.click();
assert.equal(await origin.getAttribute('aria-pressed'), 'true', `origin toggle did not activate: ${context}`);
await origin.click();
assert.equal(await origin.getAttribute('aria-pressed'), 'false', `origin toggle did not reset: ${context}`);
}
}
}
console.log('task-files-toolbar: 44 width/language/permission cases passed');
} finally {
await browser?.close();
await vite.close();
server.close();
await unlink(fixturePath).catch(() => {});
}