mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-10-16 20:51:00 +02:00
fix: keep mobile fact columns readable during horizontal scrolling
This commit is contained in:
1 parent
c47a8b97bb
commit
268eb9e286
3 files changed
+112
-1
No files matched your search
@@ -194,3 +194,72 @@ test('mobile project page scrolls its final row above the bottom navigation', as
|
||||
}
|
||||
} finally { await browser.close(); }
|
||||
});
|
||||
|
||||
test('mobile facts keep badges and actions inside their columns while scrolling horizontally', async () => {
|
||||
const browser = await chromium.launch({ headless: true, ...(process.env.CHROME_PATH ? { executablePath: process.env.CHROME_PATH } : {}) });
|
||||
try {
|
||||
for (const width of [320, 393, 767]) {
|
||||
const context = await browser.newContext({viewport: {width, height: 900}, isMobile: true, hasTouch: true});
|
||||
try {
|
||||
const page = await context.newPage();
|
||||
await page.setContent(`<html><head><meta name="viewport" content="width=device-width, initial-scale=1"></head><body><div class="container"><header>CyberStrikeAI</header><div id="page-projects" class="page active"><div class="page-content projects-page-layout">${markup}</div></div></div></body></html>`);
|
||||
for (const file of ['style.css', 'mobile.css']) await page.addStyleTag({content: read(`web/static/css/${file}`)});
|
||||
await page.addScriptTag({content: source});
|
||||
const mobileSource = read('web/static/js/mobile-ui.js');
|
||||
const wrapTables = mobileSource.slice(mobileSource.indexOf(' function wrapTables('), mobileSource.indexOf(' /* ---------------------------------------------------------------------\n 11.'));
|
||||
await page.addScriptTag({content: `const isMobile = () => true;
|
||||
const $$ = (selector, root) => Array.from(root.querySelectorAll(selector));
|
||||
const el = (tag, cls) => Object.assign(document.createElement(tag), {className: cls});
|
||||
${wrapTables}
|
||||
wrapTables(document);`});
|
||||
await page.evaluate(() => {
|
||||
currentProjectId = 'fixture-project';
|
||||
document.querySelector('.projects-detail').classList.add('has-project');
|
||||
document.querySelector('#projects-detail-inner').hidden = false;
|
||||
window.apiFetch = async () => ({ok: true, json: async () => [
|
||||
{id:'note', fact_key:'note/automatic-continuation', category:'note', summary:'自动续接的项目记录 '.repeat(10), body:'详细记录', confidence:'tentative', updated_at:'2026-10-12T10:00:00Z'},
|
||||
{id:'finding', fact_key:'finding/8080/exposure', category:'finding', summary:'8080 / exposed service', body:'## Steps\n1. curl http://target:8080\n## Request\nGET /', confidence:'confirmed', updated_at:'2026-10-12T10:00:00Z', link_counts:{incoming:2,outgoing:10}},
|
||||
]});
|
||||
refreshProjectHeaderStats = () => {};
|
||||
window.showEditFactModal = () => { window.actionClicked = 'edit'; };
|
||||
window.viewProjectFactBody = () => { window.actionClicked = 'details'; };
|
||||
});
|
||||
for (const locale of ['zh-CN', 'en-US', 'ru-RU']) {
|
||||
await page.evaluate(dict => {
|
||||
window.t = key => key.split('.').reduce((v, k) => v?.[k], dict) || key;
|
||||
document.querySelectorAll('thead [data-i18n]').forEach(el => { el.textContent = window.t(el.dataset.i18n); });
|
||||
}, JSON.parse(read(`web/static/i18n/${locale}.json`)));
|
||||
await page.evaluate(() => loadProjectFacts());
|
||||
for (const theme of ['light', 'dark']) {
|
||||
const label = `${width}px ${locale} ${theme}`;
|
||||
await page.evaluate(theme => { document.documentElement.dataset.theme = theme; }, theme);
|
||||
const layout = await page.evaluate(() => {
|
||||
const table = document.querySelector('#project-panel-facts table');
|
||||
return {
|
||||
width:table.getBoundingClientRect().width,
|
||||
badCells: [...table.querySelectorAll('thead th, tbody td')].filter(cell => {
|
||||
const r = cell.getBoundingClientRect();
|
||||
return [...cell.children].some(child => {
|
||||
const c = child.getBoundingClientRect();
|
||||
return c.left < r.left - 1 || c.right > r.right + 1;
|
||||
});
|
||||
}).map(cell => cell.className || cell.textContent),
|
||||
sticky: getComputedStyle(table.querySelector('td.col-actions')).position,
|
||||
};
|
||||
});
|
||||
assert.ok(layout.width >= 1200, `${label}: eight columns are squeezed to the phone width`);
|
||||
assert.deepEqual(layout.badCells, [], `${label}: badges or actions escape their cells`);
|
||||
assert.notEqual(layout.sticky, 'sticky', `${label}: action column overlays fact data`);
|
||||
const scroller = page.locator('#project-panel-facts .projects-table-wrap');
|
||||
await scroller.evaluate(el => { el.scrollLeft = el.scrollWidth; });
|
||||
for (const [selector, action] of [['.projects-action-btn--edit', 'edit'], ['.projects-action-btn--view', 'details']]) {
|
||||
await page.locator(`#project-facts-tbody ${selector}`).first().tap({timeout: 3000});
|
||||
assert.equal(await page.evaluate(() => window.actionClicked), action, `${label}: ${action} is unreachable`);
|
||||
}
|
||||
await scroller.evaluate(el => { el.scrollLeft = 0; });
|
||||
}
|
||||
}
|
||||
} finally { await context.close(); }
|
||||
}
|
||||
} finally { await browser.close(); }
|
||||
});
|
||||
@@ -2162,6 +2162,48 @@ body.m-panel-open .chat-input-container {
|
||||
overscroll-behavior-y: auto;
|
||||
}
|
||||
|
||||
/* The project wrapper already owns scrolling; the generic mobile wrapper
|
||||
must not add a second scrollbar or negative gutters inside the card. */
|
||||
#page-projects .projects-table-wrap > .m-table-scroll {
|
||||
overflow: visible;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
background: none;
|
||||
}
|
||||
|
||||
/* Eight fact columns need readable widths. Keep one horizontal scroll area
|
||||
and let actions scroll with their row instead of covering other cells. */
|
||||
#page-projects #project-panel-facts .data-table--projects {
|
||||
width: 1440px;
|
||||
min-width: 1440px;
|
||||
}
|
||||
|
||||
#page-projects #project-panel-facts th:nth-child(1) { width: 160px; }
|
||||
#page-projects #project-panel-facts th:nth-child(2) { width: 120px; }
|
||||
#page-projects #project-panel-facts th:nth-child(3) { width: 280px; }
|
||||
#page-projects #project-panel-facts th:nth-child(4) { width: 100px; }
|
||||
#page-projects #project-panel-facts th:nth-child(5) { width: 120px; }
|
||||
#page-projects #project-panel-facts th:nth-child(6) { width: 140px; }
|
||||
#page-projects #project-panel-facts th:nth-child(7) { width: 160px; }
|
||||
#page-projects #project-panel-facts th:nth-child(8) { width: 360px; }
|
||||
|
||||
#page-projects #project-panel-facts .data-table--projects .col-actions {
|
||||
position: static;
|
||||
max-width: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
#page-projects #project-panel-facts .col-actions .projects-table-actions {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
#page-projects #project-panel-facts :is(.projects-category, .projects-fact-badge, .projects-confidence) {
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
#page-projects #project-panel-assets .data-table--projects {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<link rel="stylesheet" href="/static/css/c2.css">
|
||||
<link rel="stylesheet" href="/static/vendor/xterm.css">
|
||||
<!-- 移动端样式层:全部规则限定在 767.98px 以下,桌面端零影响 -->
|
||||
<link rel="stylesheet" href="/static/css/mobile.css?v=20261015-project-scroll-2">
|
||||
<link rel="stylesheet" href="/static/css/mobile.css?v=20261015-project-table-3">
|
||||
<script src="/static/js/router.js?v=20261009-retest-1"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
Reference in new issue
Block a user