731 lines
46 KiB
JavaScript
731 lines
46 KiB
JavaScript
import test from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
|
|
const ROOT = path.resolve(process.cwd(), "web/storyforge-web-v4");
|
|
const HTML = fs.readFileSync(path.join(ROOT, "index.html"), "utf8");
|
|
const APP = fs.readFileSync(path.join(ROOT, "assets/app.js"), "utf8");
|
|
const CSS = fs.readFileSync(path.join(ROOT, "assets/styles.css"), "utf8");
|
|
|
|
function extractBetween(source, startToken, endToken) {
|
|
const start = source.indexOf(startToken);
|
|
assert.notEqual(start, -1, `Missing token: ${startToken}`);
|
|
const end = source.indexOf(endToken, start);
|
|
assert.notEqual(end, -1, `Missing token: ${endToken}`);
|
|
return source.slice(start, end);
|
|
}
|
|
|
|
test("settings navigation and screen are real routes", () => {
|
|
assert.match(HTML, /data-screen-target="settings"/);
|
|
assert.match(HTML, /data-screen="settings"/);
|
|
assert.match(APP, /function renderSettingsScreen\(/);
|
|
assert.match(APP, /screenMap\.settings\.innerHTML = renderSettingsScreen\(\);/);
|
|
assert.match(APP, /window\.addEventListener\("hashchange"/);
|
|
});
|
|
|
|
test("mobile shell includes a native-like header, drawer toggle, and bottom tab bar", () => {
|
|
assert.match(HTML, /<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover"/);
|
|
assert.match(HTML, /class="mobile-shell-bar"/);
|
|
assert.match(HTML, /class="mobile-workspace-strip"/);
|
|
assert.match(HTML, /data-role="mobile-workspace-project"/);
|
|
assert.match(HTML, /data-role="mobile-workspace-platforms"/);
|
|
assert.match(HTML, /data-action="open-mobile-sidebar"/);
|
|
assert.match(HTML, /class="mobile-tabbar"/);
|
|
assert.match(HTML, /class="mobile-sidebar-backdrop"/);
|
|
assert.match(HTML, /data-screen-target="dashboard"[\s\S]*mobile-tabbar-item/);
|
|
assert.match(HTML, /data-screen-target="intake"[\s\S]*mobile-tabbar-item/);
|
|
assert.match(HTML, /data-screen-target="discovery"[\s\S]*mobile-tabbar-item/);
|
|
assert.match(HTML, /data-screen-target="production"[\s\S]*mobile-tabbar-item/);
|
|
assert.match(HTML, /data-screen-target="playbook"[\s\S]*mobile-tabbar-item/);
|
|
});
|
|
|
|
test("mobile shell styling uses safe-area padding, drawer navigation, and fixed bottom navigation", () => {
|
|
assert.match(CSS, /padding-top:\s*max\(12px,\s*env\(safe-area-inset-top\)\)/);
|
|
assert.match(CSS, /\.mobile-shell-bar\s*\{[\s\S]*position:\s*sticky/);
|
|
assert.match(CSS, /\.mobile-tabbar\s*\{[\s\S]*position:\s*fixed/);
|
|
assert.match(CSS, /\.mobile-sidebar-backdrop\s*\{[\s\S]*position:\s*fixed/);
|
|
assert.match(CSS, /\.mobile-sidebar-backdrop\s*\{[\s\S]*z-index:\s*46/);
|
|
assert.match(CSS, /\.mobile-sidebar-open\s+\.sidebar\s*\{[\s\S]*transform:\s*translateX\(0\)/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.sidebar\s*\{[\s\S]*z-index:\s*47/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.mobile-sidebar-open \.mobile-tabbar,[\s\S]*\.mobile-sidebar-open \.mobile-shell-bar\s*\{[\s\S]*pointer-events:\s*none/);
|
|
assert.match(CSS, /\.content\s*\{[\s\S]*padding-bottom:\s*calc\(110px \+ env\(safe-area-inset-bottom\)\)/);
|
|
assert.match(CSS, /\.oneliner-fab\s*\{[\s\S]*bottom:\s*calc\(96px \+ env\(safe-area-inset-bottom\)\)/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.mobile-workspace-strip\s*\{[\s\S]*display:\s*grid/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.workspace-switch span\s*\{[\s\S]*display:\s*none/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.auth-status\s*\{[\s\S]*display:\s*none/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.oneliner-fab-text\s*\{[\s\S]*display:\s*none/);
|
|
});
|
|
|
|
test("mobile shell javascript syncs drawer state and active labels with the current screen", () => {
|
|
const shell = extractBetween(APP, "function renderAuthUi()", "function openAuthModal()");
|
|
const clicks = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
|
|
|
|
assert.match(APP, /function setMobileSidebarOpen\(next\)/);
|
|
assert.match(APP, /function getScreenLabel\(screenId = appState\.screen\)/);
|
|
assert.match(APP, /function syncMobileShell\(\)/);
|
|
assert.match(APP, /const mobileWorkspaceProject = document\.querySelector\('\[data-role="mobile-workspace-project"\]'\)/);
|
|
assert.match(APP, /const mobileWorkspacePlatforms = document\.querySelector\('\[data-role="mobile-workspace-platforms"\]'\)/);
|
|
assert.match(shell, /syncMobileShell\(\);/);
|
|
assert.match(APP, /setMobileSidebarOpen\(false\);[\s\S]*appState\.screen = resolvedId;/);
|
|
assert.match(clicks, /name === "open-mobile-sidebar"/);
|
|
assert.match(clicks, /name === "close-mobile-sidebar"/);
|
|
assert.match(clicks, /action\.closest\("\.sidebar"\)/);
|
|
});
|
|
|
|
test("mobile workspace strip stays available for project and platform switching", () => {
|
|
const topbar = extractBetween(APP, "function renderTopbar()", "function syncRoleGatedNav()");
|
|
|
|
assert.match(topbar, /data-role="mobile-workspace-project"/);
|
|
assert.match(topbar, /data-role="mobile-workspace-platforms"/);
|
|
assert.match(topbar, /mobileWorkspaceProject\.dataset\.action = "open-dashboard-project-switcher"/);
|
|
assert.match(topbar, /data-action="select-platform"/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.mobile-workspace-strip\s*\{[\s\S]*display:\s*grid/);
|
|
});
|
|
|
|
test("mobile layout turns screen actions and page tabs into native-like horizontal rails", () => {
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.action-row\s*\{[\s\S]*flex-wrap:\s*nowrap/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.action-row\s*\{[\s\S]*overflow-x:\s*auto/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.page-detail-tabs\s*\{[\s\S]*overflow-x:\s*auto/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.page-detail-tabs\s*\{[\s\S]*scroll-snap-type:\s*x proximity/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.page-detail-tabs \.tab\s*\{[\s\S]*flex:\s*0 0 auto/);
|
|
});
|
|
|
|
test("mobile shell removes duplicated desktop topbar and collapses the main agent fab", () => {
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.topbar\s*\{[\s\S]*display:\s*none/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.oneliner-fab\s*\{[\s\S]*width:\s*52px/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.oneliner-fab\s*\{[\s\S]*justify-content:\s*center/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.oneliner-fab-text\s*\{[\s\S]*display:\s*none/);
|
|
});
|
|
|
|
test("mobile action sheets and oneliner runtime behave like bottom sheets", () => {
|
|
assert.match(APP, /class="sheet-handle"/);
|
|
assert.match(APP, /document\.body\.classList\.add\("sheet-open", "auth-sheet-open"\)/);
|
|
assert.match(APP, /document\.body\.classList\.remove\("sheet-open", "auth-sheet-open"\)/);
|
|
assert.match(APP, /document\.body\.classList\.add\("sheet-open", "action-sheet-open"\)/);
|
|
assert.match(APP, /document\.body\.classList\.remove\("sheet-open", "action-sheet-open"\)/);
|
|
assert.match(APP, /document\.body\.classList\.add\("sheet-open", "oneliner-open"\)/);
|
|
assert.match(APP, /document\.body\.classList\.remove\("sheet-open", "oneliner-open"\)/);
|
|
assert.match(CSS, /\.sheet-handle\s*\{/);
|
|
assert.match(CSS, /\.sheet-open \.mobile-tabbar\s*\{[\s\S]*opacity:\s*0/);
|
|
assert.match(CSS, /\.oneliner-open \.oneliner-fab\s*\{[\s\S]*opacity:\s*0/);
|
|
assert.match(CSS, /body\.sheet-open,\s*body\.mobile-sidebar-open\s*\{[\s\S]*overflow:\s*hidden/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.auth-modal,\s*[\s\S]*\.action-modal,\s*[\s\S]*\.oneliner-panel\s*\{[\s\S]*border-radius:\s*24px 24px 0 0/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.auth-actions\s*\{[\s\S]*position:\s*sticky/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.oneliner-composer\s*\{[\s\S]*position:\s*sticky/);
|
|
});
|
|
|
|
test("opening OneLiner clears the transient loading state after the panel is hydrated", () => {
|
|
const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
|
|
assert.match(actions, /name === "open-oneliner"[\s\S]*setBusy\(true,\s*"正在打开 OneLiner\.\.\."\)/);
|
|
assert.match(actions, /name === "open-oneliner"[\s\S]*finally \{[\s\S]*setBusy\(false,\s*""\);[\s\S]*renderAll\(\);[\s\S]*\}/);
|
|
});
|
|
|
|
test("project creation and switching use in-app sheets instead of browser prompts", () => {
|
|
const createProject = extractBetween(APP, "async function createProject()", "function openPreferredModelAction()");
|
|
const projectSwitcher = extractBetween(APP, "function openDashboardProjectSwitcher()", "function openDashboardActionReasonAction(");
|
|
assert.match(createProject, /openActionModal\(\{/);
|
|
assert.doesNotMatch(createProject, /window\.prompt/);
|
|
assert.doesNotMatch(createProject, /alert\(/);
|
|
assert.match(projectSwitcher, /type: "html"/);
|
|
assert.match(projectSwitcher, /最近任务/);
|
|
assert.match(projectSwitcher, /切换到这个项目/);
|
|
});
|
|
|
|
test("mobile project sheets support direct project picking and zoom-safe form controls", () => {
|
|
const projectSwitcher = extractBetween(APP, "function openDashboardProjectSwitcher()", "function openDashboardActionReasonAction(");
|
|
const applySelectedProject = extractBetween(APP, "async function applySelectedProject(projectId = \"\")", "function openDashboardProjectSwitcher()");
|
|
const createProject = extractBetween(APP, "async function createProject()", "function openPreferredModelAction()");
|
|
assert.match(APP, /async function applySelectedProject\(projectId = ""\)/);
|
|
assert.match(projectSwitcher, /data-project-choice=/);
|
|
assert.match(projectSwitcher, /onOpen:\s*\(/);
|
|
assert.match(projectSwitcher, /select\.value = nextProjectId/);
|
|
assert.match(projectSwitcher, /window\.matchMedia\?\.\("\(max-width: 760px\)"\)\?\.matches/);
|
|
assert.match(projectSwitcher, /submit\.hidden = true/);
|
|
assert.match(projectSwitcher, /closeActionModal\(\);/);
|
|
assert.match(projectSwitcher, /await applySelectedProject\(nextProjectId\);/);
|
|
assert.match(applySelectedProject, /loadStorageStatus\(appState\.selectedProjectId \|\| ""\)/);
|
|
assert.match(applySelectedProject, /loadAgentControlSurfaces\(appState\.selectedProjectId \|\| ""\)/);
|
|
assert.match(createProject, /onOpen:\s*\(/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.field-stack input,\s*[\s\S]*\.field-stack textarea,\s*[\s\S]*\.field-stack select\s*\{[\s\S]*min-height:\s*46px/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.field-stack input,\s*[\s\S]*\.field-stack textarea,\s*[\s\S]*\.field-stack select\s*\{[\s\S]*font-size:\s*16px/);
|
|
});
|
|
|
|
test("mobile touch targets raise tappable buttons, tabs, and action tags closer to native sizes", () => {
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.btn,\s*[\s\S]*\.tab,\s*[\s\S]*\.tag\.clickable-tag\s*\{[\s\S]*min-height:\s*44px/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.btn,\s*[\s\S]*\.tab,\s*[\s\S]*\.tag\.clickable-tag\s*\{[\s\S]*display:\s*inline-flex/);
|
|
});
|
|
|
|
test("mobile compact summaries scroll horizontally instead of stacking into a second row", () => {
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.compact-summary-row\s*\{[\s\S]*flex-wrap:\s*nowrap/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.compact-summary-row\s*\{[\s\S]*overflow-x:\s*auto/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.compact-summary-row::-webkit-scrollbar\s*\{[\s\S]*display:\s*none/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.compact-summary-row \.tag\s*\{[\s\S]*flex:\s*0 0 auto/);
|
|
});
|
|
|
|
test("action tags render interactive controls as buttons instead of passive spans", () => {
|
|
const actionTagSource = extractBetween(APP, "function actionTag(", "function renderPipelineButton(");
|
|
assert.match(actionTagSource, /if \(targetAction\) \{/);
|
|
assert.match(actionTagSource, /<button/);
|
|
assert.match(actionTagSource, /type="button"/);
|
|
assert.match(actionTagSource, /<span/);
|
|
});
|
|
|
|
test("mobile screen heads split the first action into a primary rail and keep the rest in a secondary strip", () => {
|
|
assert.match(APP, /function splitPrimaryAction\(actionsHtml\)/);
|
|
assert.match(APP, /class="action-row-primary"/);
|
|
assert.match(APP, /class="action-row-secondary"/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.action-row\s*\{[\s\S]*display:\s*grid/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.action-row-primary\s+\.btn\s*\{[\s\S]*width:\s*100%/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.action-row-secondary\s*\{[\s\S]*overflow-x:\s*auto/);
|
|
});
|
|
|
|
test("mobile screens with a focus card collapse the secondary action rail", () => {
|
|
assert.match(APP, /const hasMobileFocusCard = body\.includes\("mobile-flow-focus-card"\)/);
|
|
assert.match(APP, /screen-head-has-mobile-focus/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.screen-head\.screen-head-has-mobile-focus \.action-row-secondary\s*\{[\s\S]*display:\s*none/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.screen-head\.screen-head-has-mobile-focus p\s*\{[\s\S]*-webkit-line-clamp:\s*1/);
|
|
});
|
|
|
|
test("mobile bottom navigation stays highlighted for grouped related screens", () => {
|
|
assert.match(APP, /function getMobileTabGroup\(screenId = appState\.screen\)/);
|
|
assert.match(APP, /tracking:\s*"discovery"/);
|
|
assert.match(APP, /review:\s*"production"/);
|
|
assert.match(APP, /strategy:\s*"playbook"/);
|
|
assert.match(APP, /credits:\s*"dashboard"/);
|
|
assert.match(APP, /button\.classList\.toggle\("is-active", active \|\| mobileGroupActive\)/);
|
|
});
|
|
|
|
test("detail tab buttons expose the active state for touch navigation", () => {
|
|
const detailTabs = extractBetween(APP, "function renderDetailTabs(stateKey, tabs) {", "function renderDiscoveryOverviewSection(");
|
|
assert.match(detailTabs, /aria-pressed="\$\{tab\.value === active \? "true" : "false"\}"/);
|
|
});
|
|
|
|
test("strategy navigation and screen are real routes", () => {
|
|
assert.match(HTML, /data-screen-target="strategy"/);
|
|
assert.match(HTML, /data-screen="strategy"/);
|
|
assert.match(APP, /function renderStrategyScreen\(/);
|
|
assert.match(APP, /screenMap\.strategy\.innerHTML = renderStrategyScreen\(\);/);
|
|
});
|
|
|
|
test("strategy screen exposes a compact mobile summary for current governance state", () => {
|
|
const source = extractBetween(APP, "function renderStrategyScreen()", "function renderCreditsScreen()");
|
|
assert.match(source, /mobile-only compact-summary-row/);
|
|
assert.match(source, /mobile-only mobile-flow-focus-card/);
|
|
assert.match(source, /当前策略任务/);
|
|
assert.match(source, /当前生效/);
|
|
assert.match(source, /平台策略/);
|
|
});
|
|
|
|
test("automation screen stays user-facing and excludes admin-only panels", () => {
|
|
const source = extractBetween(APP, "function renderAutomationScreen()", "function renderOwnedScreen()");
|
|
assert.doesNotMatch(source, /renderTenantQuotaPanel\(/);
|
|
assert.doesNotMatch(source, /renderOneLinerActionRegistryPanel\(/);
|
|
assert.doesNotMatch(source, /renderAdminOpsPanel\(/);
|
|
assert.match(source, /renderDetailTabs\("automationDetailTab"/);
|
|
});
|
|
|
|
test("agent screen excludes quota and registry panels and uses page tabs", () => {
|
|
const source = extractBetween(APP, "function renderPlaybookScreen()", "function renderProductionScreen()");
|
|
assert.doesNotMatch(source, /renderTenantQuotaPanel\(/);
|
|
assert.doesNotMatch(source, /renderOneLinerActionRegistryPanel\(/);
|
|
assert.match(source, /renderDetailTabs\("playbookDetailTab"/);
|
|
assert.match(source, /mobile-only compact-summary-row/);
|
|
assert.match(source, /mobile-only mobile-flow-focus-card/);
|
|
assert.match(source, /当前 Agent 任务/);
|
|
assert.match(source, /renderGovernanceSummaryCard\(/);
|
|
assert.match(source, /open-user-global-policy/);
|
|
assert.match(source, /open-user-platform-policy/);
|
|
assert.match(source, /active_admin_override_notice/);
|
|
});
|
|
|
|
test("discovery, production, and admin screens use page tabs for heavy content", () => {
|
|
const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()");
|
|
const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()");
|
|
const admin = extractBetween(APP, "function renderAdminWorkbenchScreen()", "function renderDashboardScreen()");
|
|
const strategy = extractBetween(APP, "function renderStrategyScreen()", "function renderCreditsScreen()");
|
|
|
|
assert.match(discovery, /renderDetailTabs\("discoveryDetailTab"/);
|
|
assert.match(production, /renderDetailTabs\("productionDetailTab"/);
|
|
assert.match(admin, /renderDetailTabs\("adminWorkbenchTab"/);
|
|
assert.match(admin, /renderAdminGovernanceSummaryPanel\(/);
|
|
assert.match(admin, /覆盖与审计/);
|
|
assert.match(strategy, /renderDetailTabs\("strategyDetailTab"/);
|
|
assert.match(strategy, /renderPolicyAuditFeed\(/);
|
|
});
|
|
|
|
test("governance and quota panels use real empty-state language instead of backend-sync placeholders", () => {
|
|
const actionRegistry = extractBetween(APP, "function renderOneLinerActionRegistryPanel()", "function renderTenantQuotaPanel()");
|
|
const tenantQuota = extractBetween(APP, "function renderTenantQuotaPanel()", "function policyScopeTagLabel(");
|
|
const platformAgents = extractBetween(APP, "function renderPlatformAgentPanel()", "function renderTrackingScreen()");
|
|
|
|
assert.doesNotMatch(actionRegistry, /等 <code>\/v2\/oneliner\/action-registry<\/code> 可用后/);
|
|
assert.match(actionRegistry, /当前项目还没有单独动作配置/);
|
|
|
|
assert.doesNotMatch(tenantQuota, /等 live collector 同步 `\/v2\/tenant\/quota` 和 `\/v2\/tenant\/usage` 后/);
|
|
assert.match(tenantQuota, /当前项目还没有额度配置/);
|
|
assert.match(tenantQuota, /data-action="open-tenant-quota"/);
|
|
|
|
assert.doesNotMatch(platformAgents, /等 live collector 同步 `\/v2\/platform-agents` 后/);
|
|
assert.match(platformAgents, /当前项目还没有平台 Agent 配置/);
|
|
assert.match(platformAgents, /open-platform-agent-profile/);
|
|
});
|
|
|
|
test("quota and review screens foreground live next-step guidance", () => {
|
|
const tenantQuota = extractBetween(APP, "function renderTenantQuotaPanel()", "function policyScopeTagLabel(");
|
|
const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()");
|
|
|
|
assert.match(tenantQuota, /先处理存储超限|先恢复额度保护|先补项目额度策略|先检查本周期消耗|先跑出第一条计量/);
|
|
assert.match(tenantQuota, /主要消耗/);
|
|
assert.match(tenantQuota, /周期 /);
|
|
assert.match(tenantQuota, /计量 /);
|
|
|
|
assert.match(review, /先把最近完成任务写成复盘|先回看高频结论|先跑出第一条可复盘任务/);
|
|
assert.match(review, /高频结论/);
|
|
assert.match(review, /已发布/);
|
|
});
|
|
|
|
test("discovery and production screens expose compact mobile flow summaries", () => {
|
|
const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()");
|
|
const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()");
|
|
|
|
assert.match(discovery, /mobile-only compact-summary-row/);
|
|
assert.match(discovery, /当前对标/);
|
|
assert.match(discovery, /已接入/);
|
|
assert.match(production, /mobile-only compact-summary-row/);
|
|
assert.match(production, /处理中/);
|
|
assert.match(production, /失败/);
|
|
});
|
|
|
|
test("discovery and production screens expose mobile focus cards with next-step actions", () => {
|
|
const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()");
|
|
const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()");
|
|
const cssMobile = extractBetween(CSS, "@media (max-width: 760px) {", "@media (max-width: 560px) {");
|
|
|
|
assert.match(discovery, /mobile-only mobile-flow-focus-card/);
|
|
assert.match(discovery, /class="filters mobile-priority-filters"/);
|
|
assert.match(discovery, /下一步先做/);
|
|
assert.match(discovery, /导入当前对标/);
|
|
assert.match(discovery, /查相似/);
|
|
assert.match(production, /mobile-only mobile-flow-focus-card/);
|
|
assert.match(APP, /function renderProductionMobileTaskDeck\(/);
|
|
assert.match(APP, /mobile-only production-mobile-task-deck/);
|
|
assert.match(APP, /当前要先处理/);
|
|
assert.match(production, /当前工作流/);
|
|
assert.match(production, /批量恢复/);
|
|
assert.match(cssMobile, /\.mobile-flow-focus-card/);
|
|
assert.match(cssMobile, /\.production-mobile-task-deck/);
|
|
});
|
|
|
|
test("mobile discovery and production simplify duplicated top-level actions", () => {
|
|
const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()");
|
|
const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()");
|
|
|
|
assert.match(APP, /function isMobileViewport\(\)/);
|
|
assert.match(discovery, /const isMobileUi = isMobileViewport\(\);/);
|
|
assert.match(discovery, /const discoveryActionsHtml = isMobileUi/);
|
|
assert.match(discovery, /button\("导入主页", "open-import-homepage"\)/);
|
|
assert.match(discovery, /button\("存对标", "open-benchmark-link"/);
|
|
assert.match(production, /const isMobileUi = isMobileViewport\(\);/);
|
|
assert.match(production, /const productionActionsHtml = isMobileUi/);
|
|
assert.match(production, /button\("交给主 Agent", "handoff-to-main-agent"/);
|
|
assert.match(production, /button\("去复盘", "goto-review", "primary"\)/);
|
|
});
|
|
|
|
test("mobile discovery prioritizes the selected-account task flow before the scrollable account list", () => {
|
|
const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()");
|
|
assert.match(discovery, /mobile-discovery-priority/);
|
|
assert.match(discovery, /mobile-discovery-selected-summary/);
|
|
assert.match(discovery, /mobile-account-list/);
|
|
assert.match(discovery, /mobile-discovery-priority[\s\S]*mobile-account-list/);
|
|
});
|
|
|
|
test("mobile heavy screens mark redundant desktop metric blocks for compact hiding", () => {
|
|
const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()");
|
|
const tracking = extractBetween(APP, "function renderTrackingScreen()", "function renderAutomationScreen()");
|
|
const automation = extractBetween(APP, "function renderAutomationScreen()", "function renderOwnedScreen()");
|
|
const owned = extractBetween(APP, "function renderOwnedScreen()", "function renderPlaybookScreen()");
|
|
const projects = extractBetween(APP, "function renderProjectsScreen()", "function getActiveDetailTab(");
|
|
const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()");
|
|
const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()");
|
|
const playbook = extractBetween(APP, "function renderPlaybookScreen()", "function renderProductionScreen()");
|
|
const strategy = extractBetween(APP, "function renderStrategyScreen()", "function renderCreditsScreen()");
|
|
const cssMobile = extractBetween(CSS, "@media (max-width: 760px) {", "@media (max-width: 560px) {");
|
|
|
|
assert.match(discovery, /discovery-selected-hero mobile-secondary-card/);
|
|
assert.match(tracking, /hero-card mobile-secondary-card/);
|
|
assert.match(automation, /hero-card mobile-secondary-card/);
|
|
assert.match(owned, /hero-card mobile-secondary-card/);
|
|
assert.match(projects, /hero-card mobile-secondary-card/);
|
|
assert.match(production, /production-queue-grid/);
|
|
assert.match(review, /hero-card mobile-secondary-card/);
|
|
assert.match(playbook, /hero-card mobile-secondary-card/);
|
|
assert.match(strategy, /hero-card mobile-secondary-card/);
|
|
assert.match(cssMobile, /\.discovery-selected-hero \.mini-grid/);
|
|
assert.match(cssMobile, /\.production-queue-grid/);
|
|
assert.match(cssMobile, /\.mobile-priority-filters \.filter:nth-child\(n\+3\)/);
|
|
assert.match(cssMobile, /\.mobile-secondary-card/);
|
|
});
|
|
|
|
test("remaining mobile workbench screens expose focus cards and compact summaries", () => {
|
|
const projects = extractBetween(APP, "function renderProjectsScreen()", "function getActiveDetailTab(");
|
|
const tracking = extractBetween(APP, "function renderTrackingScreen()", "function renderAutomationScreen()");
|
|
const automation = extractBetween(APP, "function renderAutomationScreen()", "function renderOwnedScreen()");
|
|
const owned = extractBetween(APP, "function renderOwnedScreen()", "function renderPlaybookScreen()");
|
|
const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()");
|
|
const credits = extractBetween(APP, "function renderCreditsScreen()", "function renderSettingsScreen()");
|
|
const settings = extractBetween(APP, "function renderSettingsScreen()", "function renderTopbar()");
|
|
|
|
assert.match(projects, /mobile-only mobile-flow-focus-card/);
|
|
assert.match(projects, /当前项目任务/);
|
|
assert.match(projects, /mobile-only compact-summary-row/);
|
|
assert.match(projects, /当前项目/);
|
|
|
|
assert.match(tracking, /mobile-only mobile-flow-focus-card/);
|
|
assert.match(tracking, /当前跟踪任务/);
|
|
assert.match(tracking, /mobile-only compact-summary-row/);
|
|
assert.match(tracking, /日报/);
|
|
|
|
assert.match(automation, /mobile-only mobile-flow-focus-card/);
|
|
assert.match(automation, /当前自动流程任务/);
|
|
assert.match(automation, /mobile-only compact-summary-row/);
|
|
assert.match(automation, /AI 视频/);
|
|
|
|
assert.match(owned, /mobile-only mobile-flow-focus-card/);
|
|
assert.match(owned, /当前账号任务/);
|
|
assert.match(owned, /mobile-only compact-summary-row/);
|
|
assert.match(owned, /当前项目/);
|
|
|
|
assert.match(review, /mobile-only mobile-flow-focus-card/);
|
|
assert.match(review, /当前复盘任务/);
|
|
assert.match(review, /mobile-only compact-summary-row/);
|
|
assert.match(review, /已保存/);
|
|
|
|
assert.match(credits, /mobile-only mobile-flow-focus-card/);
|
|
assert.match(credits, /当前额度任务/);
|
|
assert.match(credits, /mobile-only compact-summary-row/);
|
|
assert.match(credits, /预算/);
|
|
|
|
assert.match(settings, /mobile-only mobile-flow-focus-card/);
|
|
assert.match(settings, /当前设置任务/);
|
|
assert.match(settings, /mobile-only compact-summary-row/);
|
|
assert.match(settings, /已自动连接/);
|
|
});
|
|
|
|
test("projects screen uses an adaptive project grid instead of a fixed three-column squeeze", () => {
|
|
const projects = extractBetween(APP, "function renderProjectsScreen()", "function getActiveDetailTab(");
|
|
assert.match(projects, /project-status-grid/);
|
|
assert.match(CSS, /\.project-status-grid\s*\{[\s\S]*repeat\(auto-fit,\s*minmax\(260px,\s*1fr\)\)/);
|
|
assert.match(CSS, /\.entity-card\.pad\s*\{[\s\S]*padding:\s*15px/);
|
|
});
|
|
|
|
test("mobile typography clamps subtitles and dense card copy on small screens", () => {
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.panel-subtitle\s*\{[\s\S]*-webkit-line-clamp:\s*2/);
|
|
assert.match(CSS, /@media \(max-width: 760px\)[\s\S]*\.task-item p,[\s\S]*\.review-card p\s*\{[\s\S]*-webkit-line-clamp:\s*3/);
|
|
});
|
|
|
|
test("dashboard and project screens distinguish auto-connecting from truly disconnected", () => {
|
|
const dashboard = extractBetween(APP, "function renderDashboardScreen()", "function renderProjectsScreen()");
|
|
const projects = extractBetween(APP, "function renderProjectsScreen()", "function getActiveDetailTab(");
|
|
const helper = extractBetween(APP, "function isAutoConnectionPending()", "async function refreshFromAuthModal()");
|
|
|
|
assert.match(APP, /function isAutoConnectionPending\(\)/);
|
|
assert.match(APP, /function renderAutoConnectingScreen\(/);
|
|
assert.match(helper, /正在自动连接工作区/);
|
|
assert.match(dashboard, /isAutoConnectionPending\(\)/);
|
|
assert.match(dashboard, /renderAutoConnectingScreen\("项目总台"/);
|
|
assert.match(projects, /isAutoConnectionPending\(\)/);
|
|
assert.match(projects, /renderAutoConnectingScreen\("我的项目"/);
|
|
});
|
|
|
|
test("ensureAutoSession re-renders immediately when auto-connect starts", () => {
|
|
const source = extractBetween(APP, "async function ensureAutoSession(options = {})", "async function refreshFromAuthModal()");
|
|
assert.match(source, /appState\.autoConnectAttempted = true;/);
|
|
assert.match(source, /renderAll\(\);/);
|
|
});
|
|
|
|
test("bootstrap does not trust a stored session from a different backend", () => {
|
|
const helpers = extractBetween(APP, "function loadStoredSession()", "function compareDateDesc(");
|
|
const ensure = extractBetween(APP, "async function ensureAutoSession(options = {})", "async function refreshFromAuthModal()");
|
|
const bootstrap = extractBetween(APP, "async function bootstrap()", "async function markTrackingDigestRead()");
|
|
|
|
assert.match(helpers, /function normalizeBackendUrlValue\(/);
|
|
assert.match(helpers, /function hasSessionBackendMismatch\(/);
|
|
assert.match(ensure, /const backendMismatch = hasSessionBackendMismatch\(backendUrl\);/);
|
|
assert.match(ensure, /persistSession\(null\);/);
|
|
assert.match(bootstrap, /const backendMismatch = hasSessionBackendMismatch\(\);/);
|
|
assert.match(bootstrap, /await ensureAutoSession\(\{ force: backendMismatch \}\);/);
|
|
});
|
|
|
|
test("bootstrap only loads live recorder runtime endpoints when the integration is reachable", () => {
|
|
const bootstrap = extractBetween(APP, "async function bootstrap()", "async function markTrackingDigestRead()");
|
|
assert.match(bootstrap, /const liveRecorderIntegration = integrationHealth\?\.live_recorder \|\| null/);
|
|
assert.match(bootstrap, /const canLoadLiveRecorderRuntime = Boolean\(liveRecorderIntegration\?\.reachable\)/);
|
|
assert.match(bootstrap, /supportsLiveRecorderStatus && canLoadLiveRecorderRuntime/);
|
|
assert.match(bootstrap, /supportsLiveRecorderFiles && canLoadLiveRecorderRuntime/);
|
|
assert.match(bootstrap, /supportsLiveRecorderHealth && canLoadLiveRecorderRuntime/);
|
|
});
|
|
|
|
test("oneliner submit failures stay inside the app instead of using a browser alert", () => {
|
|
assert.doesNotMatch(APP, /alert\("OneLiner 调度失败:/);
|
|
assert.match(APP, /presentActionFailure\(error,\s*"OneLiner 调度失败"\)/);
|
|
});
|
|
|
|
test("agent control surfaces load governance endpoints for user and admin summaries", () => {
|
|
const source = extractBetween(APP, "async function loadAgentControlSurfaces(projectId = \"\")", "async function loadOneLinerMessages(sessionId)");
|
|
assert.match(source, /\/v2\/oneliner\/governance\/effective/);
|
|
assert.match(source, /\/v2\/oneliner\/runs/);
|
|
assert.match(APP, /function hydrateSelectedOneLinerRun\(\)/);
|
|
assert.match(APP, /\/v2\/oneliner\/runs\/\$\{encodeURIComponent\(runId\)\}/);
|
|
assert.match(source, /\/v2\/oneliner\/governance\/user\/global/);
|
|
assert.match(source, /\/v2\/oneliner\/governance\/user\/platforms\/\$\{encodeURIComponent\(governancePlatform\)\}/);
|
|
assert.match(source, /\/v2\/admin\/oneliner\/governance\/system\/main-agent/);
|
|
assert.match(source, /\/v2\/admin\/oneliner\/governance\/system\/platforms\/\$\{encodeURIComponent\(item\.value\)\}/);
|
|
assert.match(source, /\/v2\/admin\/oneliner\/governance\/directory/);
|
|
assert.match(source, /\/v2\/admin\/oneliner\/governance\/overrides/);
|
|
assert.match(source, /Object\.prototype\.hasOwnProperty\.call\(existingTarget, "targetProjectId"\)/);
|
|
assert.match(source, /const requestedProjectId = hasExistingProjectTarget/);
|
|
assert.match(source, /const targetProjectId = requestedProjectId === ""/);
|
|
});
|
|
|
|
test("oneliner panel includes a dedicated runtime header for agent runs", () => {
|
|
const source = extractBetween(APP, "function ensureOneLinerUi()", "function renderOneLinerSessionTabs()");
|
|
const runtime = extractBetween(APP, "function renderOneLinerRunsHtml()", "function renderOneLinerMessagesHtml()");
|
|
assert.match(source, /data-role="oneliner-runs"/);
|
|
assert.match(runtime, /confirm-oneliner-run/);
|
|
assert.match(runtime, /cancel-oneliner-run/);
|
|
assert.match(runtime, /当前计划/);
|
|
assert.match(runtime, /recommended_preview_action/);
|
|
assert.match(runtime, /renderOneLinerExecutionPayloadHtml\(currentRun\.result\)/);
|
|
assert.match(runtime, /open-oneliner-run-result/);
|
|
assert.match(runtime, /recommended_action/);
|
|
});
|
|
|
|
test("confirm-oneliner-run opens a dedicated confirmation sheet before execution", () => {
|
|
const confirmSheet = extractBetween(APP, "function openConfirmOneLinerRunAction(runId = \"\")", "async function loadPlatformAccount(");
|
|
const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
|
|
|
|
assert.match(confirmSheet, /确认主 Agent 执行计划/);
|
|
assert.match(confirmSheet, /当前计划/);
|
|
assert.match(confirmSheet, /预计落点/);
|
|
assert.match(confirmSheet, /submitLabel: "确认执行"/);
|
|
assert.match(confirmSheet, /reason/);
|
|
assert.match(confirmSheet, /confirmOneLinerRun\(run\.id,\s*values\.reason/);
|
|
assert.match(actions, /openConfirmOneLinerRunAction\(action\.dataset\.runId \|\| ""\)/);
|
|
});
|
|
|
|
test("oneliner meta and action handlers expose governance entry points", () => {
|
|
const meta = extractBetween(APP, "function renderOneLinerUi()", "function openOneLinerPanel()");
|
|
const messages = extractBetween(APP, "function renderOneLinerMessagesHtml()", "function renderOneLinerUi()");
|
|
const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
|
|
assert.match(meta, /open-user-global-policy/);
|
|
assert.match(meta, /renderOneLinerRunsHtml\(\)/);
|
|
assert.match(meta, /policyScopeTagLabel/);
|
|
assert.match(messages, /active_admin_override_notice/);
|
|
assert.match(actions, /name === "open-user-global-policy"/);
|
|
assert.match(actions, /name === "open-system-main-policy"/);
|
|
assert.match(actions, /name === "handoff-to-main-agent"/);
|
|
assert.match(actions, /name === "confirm-oneliner-run"/);
|
|
assert.match(actions, /name === "open-oneliner-run-result"/);
|
|
});
|
|
|
|
test("oneliner runtime remembers completed runs exactly once after hydration", () => {
|
|
const hydrate = extractBetween(APP, "async function hydrateSelectedOneLinerRun()", "async function loadAgentControlSurfaces(projectId = \"\")");
|
|
const state = extractBetween(APP, "const appState = {", "};\n\nlet PLATFORM_RUNTIME");
|
|
assert.match(state, /lastCompletedOnelinerRunId/);
|
|
assert.match(hydrate, /detail\.run_status === "done"/);
|
|
assert.match(hydrate, /appState\.lastCompletedOnelinerRunId !== detail\.id/);
|
|
assert.match(hydrate, /rememberAction\("主 Agent 已完成本轮"/);
|
|
});
|
|
|
|
test("system governance saves refresh control surfaces after persisting", () => {
|
|
const profile = extractBetween(APP, "function openOneLinerProfileAction()", "function parsePolicyJsonField(rawValue, label = \"策略 JSON\")");
|
|
const userGlobal = extractBetween(APP, "function openUserGlobalPolicyAction()", "function openUserPlatformPolicyAction(platform)");
|
|
const userPlatform = extractBetween(APP, "function openUserPlatformPolicyAction(platform)", "function openSystemMainPolicyAction()");
|
|
const main = extractBetween(APP, "function openSystemMainPolicyAction()", "function openSystemPlatformPolicyAction(platform)");
|
|
const platform = extractBetween(APP, "function openSystemPlatformPolicyAction(platform)", "function openPlatformAgentProfileAction(platform)");
|
|
|
|
assert.match(profile, /appState\.onelinerProfile = saved;/);
|
|
assert.match(profile, /await loadAgentControlSurfaces\(project\.id\);/);
|
|
|
|
assert.match(userGlobal, /appState\.userGlobalPolicy = saved;/);
|
|
assert.match(userGlobal, /await loadAgentControlSurfaces\(project\.id\);/);
|
|
|
|
assert.match(userPlatform, /appState\.userCurrentPlatformPolicy = saved;/);
|
|
assert.match(userPlatform, /await loadAgentControlSurfaces\(project\.id\);/);
|
|
|
|
assert.match(main, /const projectId = getOneLinerProjectId\(\);/);
|
|
assert.match(main, /appState\.adminSystemMainPolicy = saved;/);
|
|
assert.match(main, /await loadAgentControlSurfaces\(projectId\);/);
|
|
|
|
assert.match(platform, /const projectId = getOneLinerProjectId\(\);/);
|
|
assert.match(platform, /appState\.adminSystemPlatformPolicies = safeArray\(appState\.adminSystemPlatformPolicies\)/);
|
|
assert.match(platform, /await loadAgentControlSurfaces\(projectId\);/);
|
|
});
|
|
|
|
test("governance UI exposes admin override target picker and history rollback entrypoints", () => {
|
|
const admin = extractBetween(APP, "function renderAdminGovernanceSummaryPanel()", "function renderPlatformAgentPanel()");
|
|
const targetPicker = extractBetween(APP, "async function openAdminOverrideTargetAction()", "function openAdminOverridePolicyAction()");
|
|
const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
|
|
|
|
assert.match(admin, /open-admin-override-target/);
|
|
assert.match(admin, /open-admin-override-policy/);
|
|
assert.match(admin, /open-admin-override-history/);
|
|
assert.match(admin, /open-system-main-policy-history/);
|
|
assert.match(admin, /open-system-platform-policy-history/);
|
|
assert.match(APP, /function syncAdminOverrideProjectOptions\(/);
|
|
assert.match(targetPicker, /onOpen: \(\) => \{/);
|
|
assert.match(targetPicker, /userSelect\.addEventListener\("change"/);
|
|
assert.match(targetPicker, /syncAdminOverrideProjectOptions\(userSelect\.value, ""\)/);
|
|
|
|
assert.match(actions, /name === "open-admin-override-target"/);
|
|
assert.match(actions, /name === "open-admin-override-policy"/);
|
|
assert.match(actions, /name === "open-admin-override-history"/);
|
|
assert.match(actions, /name === "open-system-main-policy-history"/);
|
|
assert.match(actions, /name === "open-system-platform-policy-history"/);
|
|
});
|
|
|
|
test("user governance UI exposes personal history and rollback entrypoints", () => {
|
|
const playbook = extractBetween(APP, "function renderPlaybookScreen()", "function renderProductionScreen()");
|
|
const strategy = extractBetween(APP, "function renderStrategyScreen()", "function renderCreditsScreen()");
|
|
const automation = extractBetween(APP, "function renderAutomationScreen()", "function renderOwnedScreen()");
|
|
const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
|
|
|
|
assert.match(playbook, /open-user-global-policy-history/);
|
|
assert.match(playbook, /open-user-platform-policy-history/);
|
|
assert.match(playbook, /handoff-to-main-agent/);
|
|
assert.match(playbook, /playbook-main-agent-handoff/);
|
|
assert.match(strategy, /active_admin_override_notice/);
|
|
assert.match(strategy, /管理员覆盖生效中/);
|
|
assert.match(strategy, /handoff-to-main-agent/);
|
|
assert.match(strategy, /strategy-main-agent-handoff/);
|
|
assert.match(automation, /handoff-to-main-agent/);
|
|
assert.match(automation, /automation-main-agent-handoff/);
|
|
|
|
assert.match(actions, /name === "open-user-global-policy-history"/);
|
|
assert.match(actions, /name === "open-user-platform-policy-history"/);
|
|
});
|
|
|
|
test("main agent result rendering offers a direct route back into the recommended screen", () => {
|
|
const execution = extractBetween(APP, "function renderOneLinerExecutionPayloadHtml(payload)", "function parseOneLinerActionPayloadValue(value)");
|
|
const lastAction = extractBetween(APP, "function renderLastActionCard()", "function getJobRecoveryCategory(job)");
|
|
assert.match(execution, /recommended_action/);
|
|
assert.match(execution, /result_sections/);
|
|
assert.match(execution, /当前焦点/);
|
|
assert.match(execution, /detail-grid/);
|
|
assert.match(execution, /data-action="\$\{escapeHtml\(payload\.recommended_action\.action\)\}"/);
|
|
assert.match(lastAction, /open-oneliner-run-result/);
|
|
assert.match(lastAction, /recommended_action/);
|
|
});
|
|
|
|
test("main agent route actions keep landing context and destination screens render a notice", () => {
|
|
const execution = extractBetween(APP, "function renderOneLinerExecutionPayloadHtml(payload)", "function parseOneLinerActionPayloadValue(value)");
|
|
const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
|
|
const landingActions = extractBetween(APP, "function renderMainAgentLandingQuickActions(screenKey)", "function renderMainAgentLandingNotice(screenKey)");
|
|
const strategy = extractBetween(APP, "function renderStrategyScreen()", "function renderCreditsScreen()");
|
|
const landing = extractBetween(APP, "function renderMainAgentLandingNotice(screenKey)", "function renderEmptyState(title, description)");
|
|
const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()");
|
|
assert.match(execution, /data-main-agent-run-id/);
|
|
assert.match(actions, /captureMainAgentLandingContext\(action,\s*"goto-production"/);
|
|
assert.match(actions, /captureMainAgentLandingContext\(action,\s*"goto-strategy"/);
|
|
assert.match(actions, /name === "dismiss-main-agent-landing"/);
|
|
assert.match(landingActions, /open-user-global-policy/);
|
|
assert.match(landingActions, /refresh-tracking/);
|
|
assert.match(landing, /result_sections/);
|
|
assert.match(landing, /renderMainAgentLandingQuickActions\(screenKey\)/);
|
|
assert.match(landing, /detail-grid/);
|
|
assert.match(strategy, /renderMainAgentLandingNotice\("strategy"\)/);
|
|
assert.match(production, /renderMainAgentLandingNotice\("production"\)/);
|
|
});
|
|
|
|
test("main agent landing notices expose a compact mobile follow-up strip", () => {
|
|
const landing = extractBetween(APP, "function renderMainAgentLandingNotice(screenKey)", "function renderEmptyState(title, description)");
|
|
assert.match(landing, /mobile-only compact-summary-row/);
|
|
assert.match(landing, /mobile-only mobile-flow-focus-card/);
|
|
assert.match(landing, /主 Agent 结果/);
|
|
assert.match(landing, /继续处理/);
|
|
});
|
|
|
|
test("key workbench screens expose contextual handoff-to-main-agent actions", () => {
|
|
const discovery = extractBetween(APP, "function renderDiscoveryScreen()", "function renderTrackingScreen()");
|
|
const tracking = extractBetween(APP, "function renderTrackingScreen()", "function renderAutomationScreen()");
|
|
const projects = extractBetween(APP, "function renderProjectsScreen()", "function getActiveDetailTab(");
|
|
const production = extractBetween(APP, "function renderProductionScreen()", "function renderReviewScreen()");
|
|
const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()");
|
|
|
|
assert.match(discovery, /buildMainAgentHandoffAttrs\(\{/);
|
|
assert.match(discovery, /button\("交给主 Agent", "handoff-to-main-agent"/);
|
|
assert.match(discovery, /sourceScreen: "discovery"/);
|
|
|
|
assert.match(tracking, /buildMainAgentHandoffAttrs\(\{/);
|
|
assert.match(tracking, /button\("交给主 Agent", "handoff-to-main-agent"/);
|
|
assert.match(tracking, /sourceScreen: "tracking"/);
|
|
|
|
assert.match(projects, /buildMainAgentHandoffAttrs\(\{/);
|
|
assert.match(projects, /button\("交给主 Agent", "handoff-to-main-agent"/);
|
|
assert.match(projects, /sourceScreen: "intake"/);
|
|
|
|
assert.match(production, /buildMainAgentHandoffAttrs\(\{/);
|
|
assert.match(production, /button\("交给主 Agent", "handoff-to-main-agent"/);
|
|
assert.match(production, /sourceScreen: "production"/);
|
|
|
|
assert.match(review, /buildMainAgentHandoffAttrs\(\{/);
|
|
assert.match(review, /button\("交给主 Agent", "handoff-to-main-agent"/);
|
|
assert.match(review, /sourceScreen: "review"/);
|
|
});
|
|
|
|
test("oneliner runtime shows grouped run health summary above the current run card", () => {
|
|
const runtime = extractBetween(APP, "function renderOneLinerRunsHtml()", "function renderOneLinerMessagesHtml()");
|
|
assert.match(runtime, /近期运行概况/);
|
|
assert.match(runtime, /待确认/);
|
|
assert.match(runtime, /执行中/);
|
|
assert.match(runtime, /已完成/);
|
|
assert.match(runtime, /异常/);
|
|
assert.match(runtime, /最近完成/);
|
|
assert.match(runtime, /recentCompletedRuns/);
|
|
assert.match(runtime, /select-oneliner-run-filter/);
|
|
assert.match(runtime, /重点运行/);
|
|
assert.match(runtime, /已完成/);
|
|
assert.match(runtime, /异常运行/);
|
|
assert.match(runtime, /全部/);
|
|
assert.match(runtime, /problemRunCount/);
|
|
assert.match(runtime, /safeArray\(runs\)\.filter\(\(item\) => item\.run_status === "needs_confirmation"\)\.length/);
|
|
});
|
|
|
|
test("oneliner runtime exposes a compact mobile task strip for the current run", () => {
|
|
const runtime = extractBetween(APP, "function renderOneLinerRunsHtml()", "function renderOneLinerMessagesHtml()");
|
|
assert.match(runtime, /mobile-only compact-summary-row/);
|
|
assert.match(runtime, /mobile-only mobile-flow-focus-card/);
|
|
assert.match(runtime, /当前运行/);
|
|
assert.match(runtime, /继续这个任务/);
|
|
});
|
|
|
|
test("opening a main agent run result keeps that run selected in the floating runtime", () => {
|
|
const resultAction = extractBetween(APP, "function openCurrentOneLinerRunResultAction(runId = \"\")", "function openConfirmOneLinerRunAction(runId = \"\")");
|
|
assert.match(resultAction, /appState\.selectedOnelinerRunId = currentRun\.id/);
|
|
assert.match(resultAction, /appState\.onelinerRunFilter = currentRun\.run_status === "done" \? "done" : appState\.onelinerRunFilter/);
|
|
});
|
|
|
|
test("oneliner runtime exposes retry for retryable runs and wires the action handler", () => {
|
|
const runtime = extractBetween(APP, "function renderOneLinerRunsHtml()", "function renderOneLinerMessagesHtml()");
|
|
const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
|
|
assert.match(runtime, /retry-oneliner-run/);
|
|
assert.match(actions, /name === "retry-oneliner-run"/);
|
|
assert.match(actions, /await retryOneLinerRun\(action\.dataset\.runId \|\| "", "user requested retry"\)/);
|
|
});
|
|
|
|
test("oneliner panel auto-polls active runs while the floating panel stays open", () => {
|
|
const render = extractBetween(APP, "function renderOneLinerUi()", "function openOneLinerPanel()");
|
|
const open = extractBetween(APP, "function openOneLinerPanel()", "function closeOneLinerPanel()");
|
|
const close = extractBetween(APP, "function closeOneLinerPanel()", "function readActionForm()");
|
|
|
|
assert.match(APP, /let onelinerRunPollTimer = null;/);
|
|
assert.match(APP, /function clearOneLinerRunPollTimer\(\)/);
|
|
assert.match(APP, /function syncOneLinerRunPolling\(\)/);
|
|
assert.match(render, /syncOneLinerRunPolling\(\);/);
|
|
assert.match(open, /syncOneLinerRunPolling\(\);/);
|
|
assert.match(close, /clearOneLinerRunPollTimer\(\);/);
|
|
assert.match(APP, /setTimeout\(async \(\) => \{/);
|
|
assert.match(APP, /await hydrateSelectedOneLinerRun\(\);/);
|
|
});
|