This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
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('/', `<!doctype html><html><head><meta charset="utf-8"></head><body><main id="case" style="width:720px"><div id="root"></div></main><script type="module" src="/@fs${fixturePath}"></script></body></html>`);
|
||||
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(() => {});
|
||||
}
|
||||
Reference in New Issue
Block a user