import test from 'node:test' import assert from 'node:assert/strict' import { existsSync, readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' import { dirname, resolve } from 'node:path' const currentDir = dirname(fileURLToPath(import.meta.url)) const srcDir = resolve(currentDir, '..') function readSource(relativePath) { return readFileSync(resolve(srcDir, relativePath), 'utf8') } test('generate page hides the expert mode entry from one-click recovery actions', () => { const source = readSource('views/GenerateView.vue') const deprecatedExpertModeEntry = ['进入', '专家', '模式'].join('') assert.equal(source.includes(deprecatedExpertModeEntry), false) }) test('generate page restores blueprint and flow_config panels in expert mode', () => { assert.equal(existsSync(resolve(srcDir, 'components/AiTaskStatusPanel.vue')), true) assert.equal(existsSync(resolve(srcDir, 'components/AppBlueprintPanel.vue')), true) assert.equal(existsSync(resolve(srcDir, 'components/BusinessBlueprintPanel.vue')), false) const source = readSource('views/GenerateView.vue') assert.equal(source.includes("import AiTaskStatusPanel from '@/components/AiTaskStatusPanel.vue'"), false) assert.equal(source.includes("import AppBlueprintPanel from '@/components/AppBlueprintPanel.vue'"), true) assert.equal(source.includes("import BusinessBlueprintPanel from '@/components/BusinessBlueprintPanel.vue'"), false) assert.equal(source.includes(' { const source = readSource('views/GenerateView.vue') const blueprintPanelSource = readSource('components/AppBlueprintPanel.vue') const summary = source.match(/
[\s\S]*?<\/div>/) assert.notEqual(summary, null) assert.doesNotMatch(summary[0], /业务闭环/) assert.doesNotMatch(summary[0], /businessLoopSummary\.requiredActions/) assert.equal(source.includes('class="expert-pipeline-strip"'), false) assert.equal(source.includes('class="expert-adjustment-note"'), false) assert.equal(source.includes('恢复展示系统角色、前台菜单、后台菜单和模块图。'), false) assert.doesNotMatch(source, /)[\s\S])*生成系统模块设计(?:(?!<\/el-button>)[\s\S])*<\/el-button>/) assert.equal(source.includes('恢复展示非 CRUD 业务动作、规则校验和数据影响。'), false) assert.doesNotMatch(source, /)[\s\S])*生成业务流程设计(?:(?!<\/el-button>)[\s\S])*<\/el-button>/) assert.equal(blueprintPanelSource.includes('class="database-header-actions"'), false) assert.equal(blueprintPanelSource.includes(' { const source = readSource('views/GenerateView.vue') const panelSource = readSource('components/AiTaskStatusPanel.vue') assert.equal(source.includes('listGenerations'), true) assert.match(source, /const generationHistory = ref\(\[\]\)/) assert.match(source, /async function refreshGenerationHistory/) assert.equal(source.includes(':generation-history="generationHistory"'), false) assert.equal(source.includes(' { const source = readSource('views/GenerateView.vue') assert.equal(source.includes(''), false) assert.equal(source.includes('点击生成数据库后,这里会展示可编辑表结构'), false) }) test('generate page hides the database ER diagram center entry', () => { const source = readSource('views/GenerateView.vue') assert.equal(source.includes("import ErDiagramView from '@/components/ErDiagramView.vue'"), false) assert.equal(source.includes(' { const source = readSource('views/GenerateView.vue') assert.doesNotMatch(source, /\.designer-panel\s*\{[\s\S]*?overflow:\s*hidden;[\s\S]*?\}/) }) test('generate page persists flow_config as the only editable business-flow json', () => { const source = readSource('views/GenerateView.vue') assert.equal(source.includes('const flowConfigJson = ref(formatJson(emptyFlowConfig()))'), true) assert.equal(source.includes('async function applyFlowConfigJson()'), true) assert.equal(source.includes('const businessBlueprintJson'), false) assert.equal(source.includes('function applyBusinessBlueprintJson'), false) }) test('generate page links to standalone page designer before preview', () => { const source = readSource('views/GenerateView.vue') assert.equal(source.includes("import FrontendPageDesigner from '@/components/FrontendPageDesigner.vue'"), false) assert.equal(source.includes(' { const source = readSource('views/GenerateView.vue') assert.equal(source.includes('function shouldRemoveMissingPage(error)'), true) assert.equal(source.match(/if \(shouldRemoveMissingPage\(error\)\)/g)?.length, 2) assert.equal(source.includes('页面已不存在,已从列表移除'), true) }) test('generate page renders frontend and admin designer entry cards', () => { const source = readSource('views/GenerateView.vue') assert.equal(source.includes('前台页面设计'), true) assert.equal(source.includes('后台页面设计'), true) assert.equal(source.includes('openPageDesigner(\'frontend\')'), true) assert.equal(source.includes('openPageDesigner(\'admin\')'), true) assert.equal(source.includes('const adminPageDesigns = ref([])'), true) assert.equal(source.includes('const adminPageDesignerRef = ref(null)'), true) assert.equal(source.includes('listPageDesigns(id, \'admin\')'), true) assert.equal(source.includes('initPageDesigns(id, \'admin\')'), true) assert.equal(source.includes("savePendingPageDesigns(adminPageDesignerRef, adminPageDesigns, 'admin')"), true) }) test('generate page keeps page design entry cards styled if page scoped css lags', () => { const source = readSource('views/GenerateView.vue') const globalStyles = readSource('styles/global.scss') assert.equal(source.includes(''), true) assert.equal(source.includes(''), true) assert.match(globalStyles, /\.page-design-summary-grid\s*\{[\s\S]*padding:\s*20px;[\s\S]*\}/) assert.match(globalStyles, /\.page-design-summary-card\s*\{[\s\S]*border-radius:\s*8px;[\s\S]*\}/) assert.match(globalStyles, /\.summary-kicker\s*\{[\s\S]*font-size:\s*12px;[\s\S]*\}/) }) test('generate page keeps expert generation controls out of the hero card', () => { const source = readSource('views/GenerateView.vue') const heroStart = source.indexOf('
= 0 && modulePanelStart >= 0 ? source.slice(heroStart, modulePanelStart) : null assert.notEqual(expertSection, null) assert.doesNotMatch(expertSection, /生成系统模块设计/) assert.doesNotMatch(expertSection, /生成 flow_config/) assert.doesNotMatch(expertSection, /生成数据库/) assert.doesNotMatch(expertSection, /生成业务流程设计/) assert.doesNotMatch(expertSection, / { const source = readSource('views/GenerateView.vue') const actions = source.match(/
[\s\S]*?<\/div>/) assert.notEqual(actions, null) assert.match(actions[0], /@click="handleOpenSourcePreview"/) assert.match(actions[0], /查看源码预览/) assert.doesNotMatch(actions[0], /openCodeAnalysis\(projectId\)/) assert.doesNotMatch(actions[0], /setWorkspaceMode\('one-click'\)/) assert.doesNotMatch(actions[0], /代码解读/) assert.doesNotMatch(actions[0], new RegExp(['返回一键', '模式'].join(''))) }) test('generate page exposes one-click generation as the default flow', () => { const source = readSource('views/GenerateView.vue') assert.match(source, /one_click_project/) assert.match(source, /handleGenerateCompleteProject/) assert.match(source, /validateOneClickInput/) assert.match(source, /oneClickResult/) assert.match(source, /expertMode/) assert.equal(source.includes('const workspaceMode = ref(normalizeWorkspaceMode(route.query.mode))'), true) assert.equal(source.includes('v-if="!expertMode" class="panel one-click-card"'), true) assert.equal(source.includes('v-if="expertMode" class="panel expert-adjustment-panel"'), true) assert.equal(source.includes("mode: normalizedMode"), true) }) test('generate page clears stale one-click task before creating a regenerated task', () => { const source = readSource('views/GenerateView.vue') const handlerIndex = source.indexOf('async function handleGenerateCompleteProject()') const createTaskIndex = source.indexOf('createOneClickProjectTask(id, oneClickTaskPayload())', handlerIndex) const clearStoredTaskIndex = source.indexOf('forgetActiveTask(id, ONE_CLICK_GENERATE_TYPE)', handlerIndex) const clearVisibleTaskIndex = source.indexOf('oneClickAiTask.value = null', handlerIndex) assert.notEqual(handlerIndex, -1) assert.notEqual(createTaskIndex, -1) assert.notEqual(clearStoredTaskIndex, -1) assert.notEqual(clearVisibleTaskIndex, -1) assert.equal(clearStoredTaskIndex < createTaskIndex, true) assert.equal(clearVisibleTaskIndex < createTaskIndex, true) }) test('generate page restores the requested one-click task from the route', () => { const source = readSource('views/GenerateView.vue') const restoreFunction = source.match(/async function restoreTaskById\(id, taskId, expectedGenerateType\) \{[\s\S]*?\n\}/) assert.notEqual(restoreFunction, null) assert.match(restoreFunction[0], /await getAiTask\(id, taskId\)/) assert.match(restoreFunction[0], /task\.generateType !== expectedGenerateType/) assert.match(source, /const requestedTaskId = normalizeProjectId\(route\.query\.taskId\)/) assert.match(source, /requestedTaskId[\s\S]*restoreTaskById\(id, requestedTaskId, ONE_CLICK_GENERATE_TYPE\)[\s\S]*restoreActiveTask\(id, ONE_CLICK_GENERATE_TYPE\)/) assert.match(source, /\(\) => route\.query\.taskId/) }) test('restoring a route task cancels an earlier poll before applying the task', () => { const source = readSource('views/GenerateView.vue') const restoreFunction = source.match(/async function restoreTaskById\(id, taskId, expectedGenerateType\) \{[\s\S]*?\n\}/) assert.notEqual(restoreFunction, null) const cancelIndex = restoreFunction[0].indexOf('pollingVersions[taskChannel(task.generateType)] += 1') const assignIndex = restoreFunction[0].indexOf('targetRef.value = task') assert.notEqual(cancelIndex, -1) assert.notEqual(assignIndex, -1) assert.equal(cancelIndex < assignIndex, true) assert.match(restoreFunction[0], /FINISHED_AI_STATUSES\.includes\(task\.status\)[\s\S]*oneClickGenerating\.value = false/) assert.match(restoreFunction[0], /String\(oneClickAiTask\.value\?\.taskId \|\| ''\) === String\(task\.taskId\)/) }) test('generate page records a newly-created one-click task in the route', () => { const source = readSource('views/GenerateView.vue') const handlerIndex = source.indexOf('async function handleGenerateCompleteProject()') const createTaskIndex = source.indexOf('createOneClickProjectTask(id, oneClickTaskPayload())', handlerIndex) const routeSyncIndex = source.indexOf('syncOneClickTaskRoute(id, task.taskId)', createTaskIndex) const waitTaskIndex = source.indexOf('waitForAiTask(id, task)', createTaskIndex) assert.notEqual(createTaskIndex, -1) assert.notEqual(routeSyncIndex, -1) assert.notEqual(waitTaskIndex, -1) assert.equal(createTaskIndex < routeSyncIndex, true) assert.equal(routeSyncIndex < waitTaskIndex, true) assert.match(source.slice(createTaskIndex, waitTaskIndex), /syncOneClickTaskRoute\(id, task\.taskId\)\.catch\(\(\) => \{\}\)/) }) test('generate page integrates project generation history with route state', () => { const source = readSource('views/GenerateView.vue') assert.match(source, /import OneClickGenerationHistoryDrawer from '@\/components\/OneClickGenerationHistoryDrawer\.vue'/) assert.match(source, / route\.query\.history/) assert.match(source, /history: '1'/) assert.match(source, /delete query\.history/) }) test('generate history views and retries the exact server task', () => { const source = readSource('views/GenerateView.vue') const selectHandler = source.match(/async function handleSelectOneClickHistoryTask\(item\) \{[\s\S]*?\n\}/) const retryHandlerStart = source.indexOf('async function handleRetryOneClickHistoryTask(item)') const retryHandlerEnd = source.indexOf('\n}', retryHandlerStart) const retryHandler = retryHandlerStart >= 0 && retryHandlerEnd > retryHandlerStart ? source.slice(retryHandlerStart, retryHandlerEnd + 2) : '' assert.notEqual(selectHandler, null) assert.match(selectHandler[0], /restoreTaskById\(projectId\.value, item\.taskId, ONE_CLICK_GENERATE_TYPE\)/) assert.match(selectHandler[0], /syncOneClickTaskRoute\(projectId\.value, item\.taskId\)/) assert.match(retryHandler, /ElMessageBox\.confirm/) assert.match(retryHandler, /oneClickStageLabel\(item\.resumeStage\)/) assert.match(retryHandler, /restoreTaskById\(projectId\.value, item\.taskId, ONE_CLICK_GENERATE_TYPE\)/) assert.match(retryHandler, /await retryTask\(oneClickAiTask\)/) assert.match(retryHandler, /generationHistoryDrawerRef\.value\?\.reload\(\)/) }) test('generate page places source preview and download actions next to the one-click generate button', () => { const source = readSource('views/GenerateView.vue') const generateButtonIndex = source.indexOf('@click="handleGenerateCompleteProject"') const previewButtonIndex = source.indexOf('@click="openGeneratedPreview"') const downloadButtonIndex = source.indexOf('@click="handleDownloadOneClickSource"') const progressCardIndex = source.indexOf('class="one-click-progress-card"') const progressActions = source.match(/
[\s\S]*?<\/div>/) const previewButton = source.match(/]*@click="openGeneratedPreview")[^>]*>/) const downloadButton = source.match(/]*@click="handleDownloadOneClickSource")[^>]*>/) assert.notEqual(generateButtonIndex, -1) assert.notEqual(previewButtonIndex, -1) assert.notEqual(downloadButtonIndex, -1) assert.notEqual(progressCardIndex, -1) assert.equal(generateButtonIndex < previewButtonIndex, true) assert.equal(previewButtonIndex < downloadButtonIndex, true) assert.equal(downloadButtonIndex < progressCardIndex, true) assert.match(source, /class="one-click-primary-actions toolbar"/) assert.notEqual(progressActions, null) assert.equal(progressActions[0].includes('openGeneratedPreview'), false) assert.equal(progressActions[0].includes('handleDownloadOneClickSource'), false) assert.notEqual(previewButton, null) assert.notEqual(downloadButton, null) assert.match(previewButton[0], /type="primary"/) assert.match(downloadButton[0], /type="primary"/) }) test('generate page removes code analysis from one-click result actions', () => { const source = readSource('views/GenerateView.vue') const primaryActions = source.match(/
[\s\S]*?<\/div>/) const resultHeader = source.match(/
[\s\S]*?
/) assert.notEqual(primaryActions, null) assert.notEqual(resultHeader, null) assert.doesNotMatch(primaryActions[0], /代码解读/) assert.doesNotMatch(resultHeader[0], /代码解读/) assert.doesNotMatch(source, /openCodeAnalysis/) assert.doesNotMatch(source, /\bSearch\b/) }) test('generate page loads managed style preset choices and sends the selected preset', () => { const source = readSource('views/GenerateView.vue') const oneClickPayload = source.match(/function oneClickTaskPayload\(\) \{([\s\S]*?)\n\}/) const expertPayload = source.match(/function aiTaskPayload\(generateType\) \{([\s\S]*?)\n\}/) const projectPayload = source.match(/function projectPayload\(\) \{([\s\S]*?)\n\}/) assert.match(source, /const availablePageStylePresets = ref\(\[\]\)/) assert.match(source, /stylePreset: ''/) assert.match(source, /projectForm\.stylePreset/) assert.match(source, /const result = await listPageTemplateOptions\(\)/) assert.doesNotMatch(source, /const PAGE_STYLE_PRESETS/) assert.doesNotMatch(source, /const VISUAL_SOLUTION_DEFAULTS/) assert.notEqual(oneClickPayload, null) assert.match(oneClickPayload[1], /stylePreset: projectForm\.stylePreset/) assert.notEqual(expertPayload, null) assert.match(expertPayload[1], /stylePreset: projectForm\.stylePreset/) assert.notEqual(projectPayload, null) assert.match(projectPayload[1], /stylePreset: projectForm\.stylePreset/) }) test('generate page exposes code template bundle selection and submits it', () => { const source = readSource('views/GenerateView.vue') const apiSource = readSource('api/project.js') const advancedSettings = source.match(/[\s\S]*?<\/el-collapse-item>/) const projectPayload = source.match(/function projectPayload\(\) \{([\s\S]*?)\n\}/) const oneClickPayload = source.match(/function oneClickTaskPayload\(\) \{([\s\S]*?)\n\}/) const expertPayload = source.match(/function aiTaskPayload\(generateType\) \{([\s\S]*?)\n\}/) assert.match(apiSource, /export function listCodeTemplateBundles\(\)/) assert.match(apiSource, /url: '\/front\/project\/options\/code-templates'/) assert.match(apiSource, /export function getProjectSupportedTemplateTypes\(projectId\)/) assert.match(source, /const CODE_TEMPLATE_FALLBACK = 'qing'/) assert.match(source, /const codeTemplateBundles = ref\(\[\]\)/) assert.match(source, /projectForm\.codeTemplate/) assert.match(source, /loadCodeTemplateBundles/) assert.match(source, /class="code-template-option"/) assert.notEqual(advancedSettings, null) assert.match(advancedSettings[0], /label="代码模板"/) assert.match(advancedSettings[0], /class="code-template-cover"/) assert.match(advancedSettings[0], /templatePreviewUrl\(selectedCodeTemplateBundle\)/) assert.match(advancedSettings[0], /selectedCodeTemplateBundle/) assert.match(advancedSettings[0], /bundle\.previewImage/) assert.match(source, /function templatePreviewUrl\(bundle\)/) assert.notEqual(projectPayload, null) assert.match(projectPayload[1], /codeTemplate: projectForm\.codeTemplate \|\| CODE_TEMPLATE_FALLBACK/) assert.notEqual(oneClickPayload, null) assert.match(oneClickPayload[1], /codeTemplate: projectForm\.codeTemplate \|\| CODE_TEMPLATE_FALLBACK/) assert.notEqual(expertPayload, null) assert.match(expertPayload[1], /codeTemplate: projectForm\.codeTemplate \|\| CODE_TEMPLATE_FALLBACK/) }) test('generate page presents style presets as visual themes with previews', () => { const source = readSource('views/GenerateView.vue') assert.match(source, /label="视觉方案"/) assert.match(source, /class="style-theme-option"/) assert.match(source, /class="style-theme-preview"/) assert.match(source, /function themePreviewStyle/) assert.match(source, /v-for="preset in availablePageStylePresets"/) assert.match(source, /result\.themes\.map\(normalizePageStylePreset\)/) assert.doesNotMatch(source, /business_clean/) assert.doesNotMatch(source, /card_portal/) assert.match(source, /solutionNavigation\(solution\.shell, 'frontend'\)/) assert.match(source, /solutionNavigation\(solution\.shell, 'admin'\)/) assert.match(source, /navigationLabel\(solutionNavigation\(solution\.shell, 'frontend'\), '前台'\)/) assert.match(source, /navigationLabel\(solutionNavigation\(solution\.shell, 'admin'\), '后台'\)/) assert.match(source, /scope === 'admin' \? 'adminNavigation' : 'frontendNavigation'/) assert.match(source, /列表按图片自适应/) assert.match(source, /'preview-layout-adaptive'/) }) test('generate page shows one managed-theme error and never falls back to local presets', () => { const source = readSource('views/GenerateView.vue') assert.match(source, /v-if="pageStylePresetError"/) assert.match(source, /视觉方案不可用,请先在后台“页面主题管理”中配置并启用方案,然后刷新页面。/) assert.match(source, /availablePageStylePresets\.value = \[\]/) assert.match(source, /pageStylePresetError\.value = PAGE_STYLE_PRESET_ERROR/) assert.doesNotMatch(source, /themes\.length \? themes :/) assert.doesNotMatch(source, /result\?\.solutions/) }) test('generate page keeps visual themes compact and removes the large impact preview', () => { const source = readSource('views/GenerateView.vue') const advancedSettings = source.match(/[\s\S]*?<\/el-collapse-item>/) assert.notEqual(advancedSettings, null) assert.match(advancedSettings[0], /label="视觉方案"/) assert.match(advancedSettings[0], /label="用户前台"/) assert.match(advancedSettings[0], /label="代码模板"/) assert.doesNotMatch(source, /class="style-theme-impact"/) assert.doesNotMatch(source, /class="theme-impact-shell"/) assert.doesNotMatch(source, /class="theme-impact-points"/) assert.doesNotMatch(source, /class="style-theme-note"/) }) test('generate page gives every visual theme card the same dimensions', () => { const source = readSource('views/GenerateView.vue') assert.match(source, /grid-template-columns: repeat\(auto-fill, minmax\(min\(220px, 100%\), 1fr\)\)/) assert.match(source, /\.style-theme-option \{[\s\S]*?width: 100%;[\s\S]*?height: 112px;/) assert.match(source, /\.style-theme-card \{[\s\S]*?width: 100%;[\s\S]*?height: 100%;/) }) test('generate page displays one-click business loop report', () => { const source = readSource('views/GenerateView.vue') assert.equal(source.includes('业务闭环'), true) assert.equal(source.includes('const oneClickReport = computed'), true) assert.equal(source.includes('const oneClickMissingRequiredActions = computed'), true) assert.equal(source.includes('const oneClickBusinessLoopWarnings = computed'), true) assert.equal(source.includes('v-if="oneClickMissingRequiredActions.length"'), true) assert.equal(source.includes('v-if="oneClickBusinessLoopWarnings.length"'), true) assert.equal(source.includes('businessLoopWarnings'), true) assert.equal(source.includes('oneClickReport.businessLoopComplete'), true) assert.equal(source.includes('oneClickReport.loopActions'), true) }) test('generate page recognizes applied business loop actions stored with business prefix', () => { const source = readSource('views/GenerateView.vue') const placementFunction = source.match(/function businessActionPlacement\(design, actionCode\) \{[\s\S]*?\n\}/) const actionCodesFunction = source.match(/function businessActionCodes\(actionCode\) \{[\s\S]*?\n\}/) assert.notEqual(placementFunction, null) assert.notEqual(actionCodesFunction, null) assert.match(placementFunction[0], /businessActionCodes\(actionCode\)/) const businessActionCodes = new Function(`${actionCodesFunction[0]}; return businessActionCodes`)() assert.deepEqual(businessActionCodes('borrow_book'), ['borrow_book', 'business:borrow_book']) assert.deepEqual(businessActionCodes('business:return_book'), ['return_book', 'business:return_book']) }) test('generate page prefers exact page binding before table fallback', () => { const source = readSource('views/GenerateView.vue') const helperStart = source.indexOf('function findBusinessLoopBindingDesign') const placementStart = source.indexOf('function businessActionPlacement') const helperSource = helperStart >= 0 && placementStart > helperStart ? source.slice(helperStart, placementStart) : '' assert.notEqual(helperStart, -1) assert.match(source, /const boundDesign = findBusinessLoopBindingDesign\(designs, binding\)/) assert.match(helperSource, /matchedByPageCode/) assert.match(helperSource, /matchedByTableName/) assert.equal(helperSource.indexOf('matchedByPageCode') < helperSource.indexOf('matchedByTableName'), true) }) test('generate page labels asynchronous run preview states', () => { const source = readSource('views/GenerateView.vue') assert.equal(source.includes('function oneClickPreviewStatusText'), true) assert.equal(source.includes('function oneClickPreviewStatusTagType'), true) assert.equal(source.includes('const oneClickCanRestartRunPreview = computed'), true) assert.equal(source.includes('handleRestartOneClickRunPreview'), true) assert.equal(source.includes('const previewStatus = oneClickDisplayResult.value?.previewStatus'), true) assert.equal(source.includes("case 'STARTING'"), true) assert.equal(source.includes("case 'FAILED'"), true) assert.equal(source.includes('oneClickPreviewStatusText(oneClickResult)'), true) assert.equal(source.includes('oneClickPreviewStatusTagType(oneClickResult)'), true) }) test('generate page displays one-click run preview expiration time', () => { const source = readSource('views/GenerateView.vue') assert.equal(source.includes('预览到期时间'), true) assert.equal(source.includes('oneClickPreviewExpiresAtText(oneClickResult)'), true) assert.match(source, /expiresAt: status\.expiresAt \|\| current\.expiresAt/) }) test('generate page opens one-click result urls in a new page', () => { const source = readSource('views/GenerateView.vue') assert.match(source, /import \{[^}]*Link[^}]*View[^}]*\} from '@element-plus\/icons-vue'/) assert.equal(source.includes('oneClickResultUrlText(oneClickResult.backendUrl)'), true) assert.equal(source.includes('oneClickResultUrlText(oneClickResult.frontendUrl)'), true) assert.equal(source.includes('oneClickResultUrlText(oneClickResult.adminFrontendUrl)'), true) assert.equal(source.includes('@click="openOneClickResultUrl(oneClickResult.backendUrl)"'), true) assert.equal(source.includes('@click="openOneClickResultUrl(oneClickResult.frontendUrl)"'), true) assert.equal(source.includes('@click="openOneClickResultUrl(oneClickResult.adminFrontendUrl)"'), true) assert.match(source, /function oneClickResultUrlText\(url\) \{[\s\S]*?return url \|\| '-'/) assert.match(source, /function openOneClickResultUrl\(url\) \{[\s\S]*?if \(!url\) return[\s\S]*?window\.open\(url, '_blank', 'noopener,noreferrer'\)/) }) test('generate page polls realtime run preview status for pending one-click previews', () => { const source = readSource('views/GenerateView.vue') assert.match(source, /const RUN_PREVIEW_PENDING_STATUSES = \['PREPARING', 'STARTING'\]/) assert.match(source, /let oneClickRunPreviewPollingTimer = null/) assert.match(source, /function startOneClickRunPreviewPolling/) assert.match(source, /function clearOneClickRunPreviewPolling/) assert.match(source, /async function pollOneClickRunPreviewStatus/) assert.match(source, /function applyOneClickRunPreviewStatus/) assert.match(source, /function isRunPreviewProbeCandidate/) assert.match(source, /function shouldPollOneClickRunPreview/) assert.match(source, /await getRunPreviewStatus\(id\)/) assert.match(source, /watch\(\s*\(\) => oneClickDisplayResult\.value\?\.previewStatus/) assert.match(source, /shouldPollOneClickRunPreview\(status\)/) assert.match(source, /clearOneClickRunPreviewPolling\(\)/) assert.match(source, /status === 'FAILED'[\s\S]*result\?\.downloadReady[\s\S]*result\?\.failedStage === 'VERIFY_AND_PREVIEW'/) assert.match(source, /const previewRecovered = status\.status === 'RUNNING' \|\| isRunPreviewPending\(status\.status\)/) assert.match(source, /oneClickAiTask\.value = \{[\s\S]*resultPayload: JSON\.stringify\(nextResult\)/) }) test('generate page one-click validation requires a managed visual theme', () => { const source = readSource('views/GenerateView.vue') const match = source.match(/function validateOneClickInput\(\) \{([\s\S]*?)\n\}/) assert.notEqual(match, null) assert.match(match[1], /projectForm\.projectName/) assert.match(match[1], /projectForm\.stylePreset/) assert.match(match[1], /availablePageStylePresets\.value\.some/) assert.doesNotMatch(match[1], /projectForm\.keyword/) }) test('generate page one-click mode no longer exposes supplemental requirements', () => { const source = readSource('views/GenerateView.vue') const oneClickForm = source.match(/
{ const source = readSource('views/GenerateView.vue') const apiSource = readSource('api/project.js') const oneClickForm = source.match(/
{ const source = readSource('views/GenerateView.vue') const expertSection = source.match(/
/) const expertPayload = source.match(/function aiTaskPayload\(generateType\) \{([\s\S]*?)\n\}/) assert.notEqual(expertSection, null) assert.doesNotMatch(expertSection[0], /label="项目描述"/) assert.doesNotMatch(expertSection[0], /需求关键词/) assert.doesNotMatch(expertSection[0], /projectForm\.keyword/) assert.notEqual(expertPayload, null) assert.match(expertPayload[1], /projectDesc: projectForm\.projectDesc/) assert.match(expertPayload[1], /extraRequirements: ''/) }) test('generate page sends current flow_config with expert AI tasks', () => { const source = readSource('views/GenerateView.vue') const expertPayload = source.match(/function aiTaskPayload\(generateType\) \{([\s\S]*?)\n\}/) assert.notEqual(expertPayload, null) assert.match(source, /function currentFlowConfigPayload\(\)/) assert.match(expertPayload[1], /flowConfig: currentFlowConfigPayload\(\)/) }) test('generate page clears stale one-click failure state before retry polling', () => { const source = readSource('views/GenerateView.vue') assert.match(source, /function prepareTaskForRetry\(task\)/) assert.match(source, /resultPayload: ''/) assert.match(source, /errorMessage: ''/) assert.match(source, /oneClickResult\.value = null/) assert.match(source, /if \(oneClickTaskBusy\.value\) return ''/) assert.match(source, /if \(!retryAccepted\)[\s\S]*targetRef\.value = previousTask/) }) test('generate page no longer exposes backend module selection', () => { const source = readSource('views/GenerateView.vue') assert.equal(source.includes('后台功能模块'), false) assert.equal(source.includes('listAvailableModules'), false) assert.equal(source.includes('projectForm.moduleIds'), false) }) test('generate page no longer exposes industry template input', () => { const source = readSource('views/GenerateView.vue') assert.equal(source.includes('行业模板'), false) assert.equal(source.includes('projectForm.industryTemplate'), false) }) test('generate page leaves enough space for business loop placement status tags', () => { const source = readSource('views/GenerateView.vue') const bindingTable = source.match(/[\s\S]*?<\/el-table>/) const statusColumn = bindingTable?.[0].match(/]*>/) assert.notEqual(bindingTable, null) assert.notEqual(statusColumn, null) assert.match(statusColumn[0], /min-width="1[4-9]\d"/) }) test('generate page projects every flow in a multi-flow config', () => { const source = readSource('views/GenerateView.vue') assert.match(source, /version: '1\.1'/) assert.match(source, /flows: \[\]/) assert.match(source, /const flows = Array\.isArray\(config\?\.flows\) && config\.flows\.length \? config\.flows : \[config\]/) assert.match(source, /flows\.forEach\(\(flow\) =>/) assert.match(source, /stateMachines\.push\(\{/) assert.match(source, /actions\.push\(\.\.\.flowActions\)/) }) test('generate page only reports completion after the server audit passes', () => { const source = readSource('views/GenerateView.vue') const completion = source.match(/const businessLoopPlanComplete = computed\(\(\) => \{([\s\S]*?)\n\}\)/) assert.notEqual(completion, null) assert.match(completion[1], /businessLoopAuditComplete\.value/) assert.match(source, /const requirementLoopAudit = computed/) assert.doesNotMatch(source, /countActionEffects/) })