287 lines
14 KiB
JavaScript
287 lines
14 KiB
JavaScript
|
|
/* eslint-env node */
|
||
|
|
import fs from 'node:fs'
|
||
|
|
import path from 'node:path'
|
||
|
|
import { fileURLToPath } from 'node:url'
|
||
|
|
|
||
|
|
const currentDir = path.dirname(fileURLToPath(import.meta.url))
|
||
|
|
const rootDir = path.resolve(currentDir, '../../../..')
|
||
|
|
|
||
|
|
const files = {
|
||
|
|
api: path.resolve(rootDir, 'api/steady/steadyDataView/index.ts'),
|
||
|
|
apiTypes: path.resolve(rootDir, 'api/steady/steadyDataView/interface/index.ts'),
|
||
|
|
page: path.resolve(rootDir, 'views/steady/checksquare/index.vue'),
|
||
|
|
workbench: path.resolve(rootDir, 'views/steady/checksquare/components/ChecksquareWorkbench.vue'),
|
||
|
|
summaryTable: path.resolve(rootDir, 'views/steady/checksquare/components/ChecksquareSummaryTable.vue'),
|
||
|
|
detailPanel: path.resolve(rootDir, 'views/steady/checksquare/components/ChecksquareDetailPanel.vue'),
|
||
|
|
payload: path.resolve(rootDir, 'views/steady/checksquare/utils/checksquarePayload.ts'),
|
||
|
|
table: path.resolve(rootDir, 'views/steady/checksquare/utils/checksquareTable.ts')
|
||
|
|
}
|
||
|
|
|
||
|
|
const read = file => (exists(file) ? fs.readFileSync(file, 'utf8') : '')
|
||
|
|
const exists = file => fs.existsSync(file)
|
||
|
|
|
||
|
|
const checks = [
|
||
|
|
['checksquare query api exists', () => /querySteadyChecksquare/.test(read(files.api))],
|
||
|
|
[
|
||
|
|
'checksquare api posts to expected endpoint',
|
||
|
|
() => /\/steady\/data-view\/checksquare\/query/.test(read(files.api))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'checksquare request type uses single lineId',
|
||
|
|
() => /interface SteadyChecksquareQueryParams[\s\S]*lineId: string/.test(read(files.apiTypes))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'checksquare request type supports per-order harmonic query only',
|
||
|
|
() => {
|
||
|
|
const typeBlock =
|
||
|
|
read(files.apiTypes).match(/interface SteadyChecksquareQueryParams\s*\{[\s\S]*?\n {4}\}/)?.[0] || ''
|
||
|
|
return /harmonicOrders\?: number\[\]/.test(typeBlock) && !/qualityFlag|statTypes|phases|lineIds/.test(typeBlock)
|
||
|
|
}
|
||
|
|
],
|
||
|
|
['workbench component exists', () => exists(files.workbench)],
|
||
|
|
['summary table component exists', () => exists(files.summaryTable)],
|
||
|
|
['detail panel component exists', () => exists(files.detailPanel)],
|
||
|
|
['payload utility exists', () => exists(files.payload)],
|
||
|
|
['table utility exists', () => exists(files.table)],
|
||
|
|
['page reuses steady ledger tree', () => /SteadyLedgerTree/.test(read(files.workbench))],
|
||
|
|
['page reuses shared time period search', () => /TimePeriodSearch/.test(read(files.workbench))],
|
||
|
|
['payload keeps shared time period unit state', () => /timeUnit:\s*TimePeriodUnit/.test(read(files.payload))],
|
||
|
|
[
|
||
|
|
'checksquare time search exposes day week month year custom units',
|
||
|
|
() =>
|
||
|
|
/CHECKSQUARE_TIME_PERIOD_UNITS\s*:\s*TimePeriodUnit\[\]\s*=\s*\['day',\s*'week',\s*'month',\s*'year',\s*'custom'\]/.test(
|
||
|
|
read(files.workbench)
|
||
|
|
) && /:visible-units="CHECKSQUARE_TIME_PERIOD_UNITS"/.test(read(files.workbench))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'checksquare defaults to day range',
|
||
|
|
() =>
|
||
|
|
/timeRange:\s*buildTimePeriodRange\('day',\s*baseDate\)/.test(read(files.payload)) &&
|
||
|
|
/timeUnit:\s*'day'/.test(read(files.payload))
|
||
|
|
],
|
||
|
|
['page no longer tracks floating indicator panel state', () => !/indicatorPanelCollapsed|indicator-panel-collapsed/.test(read(files.page))],
|
||
|
|
[
|
||
|
|
'query form uses tree select for steady indicators',
|
||
|
|
() =>
|
||
|
|
/<el-tree-select/.test(read(files.workbench)) &&
|
||
|
|
/v-model="selectedIndicatorKeys"/.test(read(files.workbench)) &&
|
||
|
|
/multiple/.test(read(files.workbench)) &&
|
||
|
|
/show-checkbox/.test(read(files.workbench))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'query form keeps steady indicator immediately after time selector',
|
||
|
|
() => /class="toolbar-field toolbar-field--time"[\s\S]*class="toolbar-field indicator-form-item"/.test(read(files.workbench))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'query form supports selecting all steady indicators',
|
||
|
|
() =>
|
||
|
|
/@click="handleSelectAllIndicators"/.test(read(files.workbench)) &&
|
||
|
|
/collectAllIndicatorKeys/.test(read(files.workbench))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'checksquare no longer renders floating indicator panel',
|
||
|
|
() => !/SteadyIndicatorFloatingPanel|indicatorPanelCollapsedProxy|is-indicator-expanded/.test(read(files.workbench))
|
||
|
|
],
|
||
|
|
['summary table renders unsupported stats as dash', () => /formatStatMissingRate[\s\S]*'-'/.test(read(files.table))],
|
||
|
|
[
|
||
|
|
'summary table has localized AVG MAX MIN CP95 columns',
|
||
|
|
() => /平均值缺失率[\s\S]*最大值缺失率[\s\S]*最小值缺失率[\s\S]*CP95缺失率/.test(read(files.summaryTable))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'table utility localizes checksquare stat type names',
|
||
|
|
() => /AVG:\s*'平均值'[\s\S]*MAX:\s*'最大值'[\s\S]*MIN:\s*'最小值'/.test(read(files.table))
|
||
|
|
],
|
||
|
|
['detail panel renders missing segments', () => /segments/.test(read(files.detailPanel))]
|
||
|
|
,
|
||
|
|
[
|
||
|
|
'summary table title changed to check result',
|
||
|
|
() => /指标校验结果/.test(read(files.summaryTable)) && !/指标校验总览/.test(read(files.summaryTable))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'summary table shows monitor fallback and keeps meta 15px from title',
|
||
|
|
() => {
|
||
|
|
const summaryTable = read(files.summaryTable)
|
||
|
|
return (
|
||
|
|
/class="summary-meta"/.test(summaryTable) &&
|
||
|
|
/result\.lineName\s*\|\|\s*result\.lineId\s*\|\|\s*'未返回监测点'/.test(summaryTable) &&
|
||
|
|
/\.summary-meta\s*\{[\s\S]*margin-left:\s*15px/.test(summaryTable)
|
||
|
|
)
|
||
|
|
}
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'summary table uses tree rows for harmonic results',
|
||
|
|
() =>
|
||
|
|
/row-key="itemKey"/.test(read(files.summaryTable)) &&
|
||
|
|
/tree-props/.test(read(files.summaryTable)) &&
|
||
|
|
/children/.test(read(files.summaryTable))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'summary table keeps harmonic tree rows collapsed by default',
|
||
|
|
() => !/default-expand-all/.test(read(files.summaryTable))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'summary table removes harmonic order column',
|
||
|
|
() => !/<el-table-column[^>]*prop="harmonicOrder"/.test(read(files.summaryTable))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'summary table uses balanced column widths for check result',
|
||
|
|
() => {
|
||
|
|
const summaryTable = read(files.summaryTable)
|
||
|
|
const indicatorColumn = summaryTable.match(/<el-table-column[^>]*prop="indicatorName"[^>]*>/)?.[0] || ''
|
||
|
|
const hasDataColumn = summaryTable.match(/<el-table-column[^>]*prop="hasData"[^>]*>/)?.[0] || ''
|
||
|
|
const missingRateColumn = summaryTable.match(/<el-table-column[^>]*prop="missingRate"[^>]*>/)?.[0] || ''
|
||
|
|
const avgColumn = summaryTable.match(/<el-table-column[^>]*label="平均值缺失率"[^>]*>/)?.[0] || ''
|
||
|
|
const maxColumn = summaryTable.match(/<el-table-column[^>]*label="最大值缺失率"[^>]*>/)?.[0] || ''
|
||
|
|
const minColumn = summaryTable.match(/<el-table-column[^>]*label="最小值缺失率"[^>]*>/)?.[0] || ''
|
||
|
|
const cp95Column = summaryTable.match(/<el-table-column[^>]*label="CP95缺失率"[^>]*>/)?.[0] || ''
|
||
|
|
const maxMissingColumn =
|
||
|
|
summaryTable.match(/<el-table-column[^>]*prop="maxContinuousMissingMinutes"[^>]*>/)?.[0] || ''
|
||
|
|
const operationColumn = summaryTable.match(/<el-table-column[^>]*label="操作"[^>]*>/)?.[0] || ''
|
||
|
|
const stretchColumns = [
|
||
|
|
hasDataColumn,
|
||
|
|
missingRateColumn,
|
||
|
|
avgColumn,
|
||
|
|
maxColumn,
|
||
|
|
minColumn,
|
||
|
|
cp95Column,
|
||
|
|
maxMissingColumn
|
||
|
|
]
|
||
|
|
|
||
|
|
return (
|
||
|
|
/min-width="208"/.test(indicatorColumn) &&
|
||
|
|
/min-width="120"/.test(hasDataColumn) &&
|
||
|
|
/min-width="130"/.test(missingRateColumn) &&
|
||
|
|
/min-width="130"/.test(avgColumn) &&
|
||
|
|
/min-width="130"/.test(maxColumn) &&
|
||
|
|
/min-width="130"/.test(minColumn) &&
|
||
|
|
/min-width="140"/.test(cp95Column) &&
|
||
|
|
/min-width="150"/.test(maxMissingColumn) &&
|
||
|
|
/width="96"/.test(operationColumn) &&
|
||
|
|
stretchColumns.every(column => /min-width=/.test(column) && !/\swidth=/.test(column)) &&
|
||
|
|
stretchColumns.every(column => /align="center"/.test(column)) &&
|
||
|
|
/align="center"/.test(operationColumn) &&
|
||
|
|
!/align=/.test(indicatorColumn)
|
||
|
|
)
|
||
|
|
}
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'workbench query card follows steady data view toolbar sizing',
|
||
|
|
() => {
|
||
|
|
const workbench = read(files.workbench)
|
||
|
|
return (
|
||
|
|
/\.query-card\s*\{[\s\S]*display:\s*grid[\s\S]*grid-template-columns:\s*minmax\(430px,\s*1\.35fr\)\s+minmax\(0,\s*1fr\)\s+auto[\s\S]*gap:\s*10px[\s\S]*align-items:\s*center[\s\S]*padding:\s*12px/.test(
|
||
|
|
workbench
|
||
|
|
) &&
|
||
|
|
/\.checksquare-time\s*\{[\s\S]*flex:\s*1\s+1\s+0[\s\S]*min-width:\s*0/.test(workbench) &&
|
||
|
|
/\.checksquare-time\s*:deep\(\.time-period-search__unit\)\s*\{[\s\S]*width:\s*88px[\s\S]*flex:\s*0\s+0\s+88px/.test(
|
||
|
|
workbench
|
||
|
|
) &&
|
||
|
|
/\.checksquare-time\s*:deep\(\.time-period-search__picker\)\s*\{[\s\S]*width:\s*136px[\s\S]*flex:\s*0\s+0\s+136px/.test(
|
||
|
|
workbench
|
||
|
|
) &&
|
||
|
|
/\.query-actions\s*\{[\s\S]*display:\s*flex[\s\S]*justify-content:\s*flex-end[\s\S]*gap:\s*8px/.test(workbench)
|
||
|
|
)
|
||
|
|
}
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'summary table exposes detail action',
|
||
|
|
() => /详情/.test(read(files.summaryTable)) && /emit\('detail'/.test(read(files.summaryTable))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'workbench shows detail in dialog instead of inline panel',
|
||
|
|
() =>
|
||
|
|
/<el-dialog/.test(read(files.workbench)) &&
|
||
|
|
/ChecksquareDetailPanel/.test(read(files.workbench)) &&
|
||
|
|
!/class="content-detail"/.test(read(files.workbench))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'page builds pending rows from selected indicators',
|
||
|
|
() => /buildPendingChecksquareResult/.test(read(files.page)) && /refreshPendingResult/.test(read(files.page))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'page queries indicators sequentially',
|
||
|
|
() => /for \(const indicator of queryIndicators\)/.test(read(files.page)) && /mergeChecksquareIndicatorResult/.test(read(files.page))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'page queries harmonic orders with controlled concurrency',
|
||
|
|
() =>
|
||
|
|
/CHECKSQUARE_HARMONIC_QUERY_CONCURRENCY\s*=\s*6/.test(read(files.page)) &&
|
||
|
|
/runChecksquareHarmonicQuery/.test(read(files.page)) &&
|
||
|
|
/workers = Array\.from\(\{[\s\S]*length: Math\.min\(CHECKSQUARE_HARMONIC_QUERY_CONCURRENCY/.test(read(files.page)) &&
|
||
|
|
/await Promise\.all\(workers\)/.test(read(files.page)) &&
|
||
|
|
/const harmonicOrders = \[\.\.\.CHECKSQUARE_HARMONIC_ORDERS\]/.test(read(files.page)) &&
|
||
|
|
/if \(orderIndex >= harmonicOrders\.length\) return/.test(read(files.page))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'table pre-creates harmonic rows from second to fiftieth order',
|
||
|
|
() =>
|
||
|
|
/CHECKSQUARE_HARMONIC_ORDER_MIN\s*=\s*2/.test(read(files.table)) &&
|
||
|
|
/CHECKSQUARE_HARMONIC_ORDER_MAX\s*=\s*50/.test(read(files.table)) &&
|
||
|
|
/CHECKSQUARE_HARMONIC_ORDER_MAX - CHECKSQUARE_HARMONIC_ORDER_MIN \+ 1/.test(read(files.table)) &&
|
||
|
|
/children: isChecksquareHarmonicIndicator\(indicator\)\s*\?\s*buildPendingChecksquareHarmonicItems/.test(
|
||
|
|
read(files.table)
|
||
|
|
)
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'table only merges indicators whose harmonic order range intersects second to fiftieth order',
|
||
|
|
() => {
|
||
|
|
const table = read(files.table)
|
||
|
|
return (
|
||
|
|
/CHECKSQUARE_HARMONIC_ORDER_MIN/.test(table) &&
|
||
|
|
/CHECKSQUARE_HARMONIC_ORDER_MAX/.test(table) &&
|
||
|
|
/hasChecksquareHarmonicOrderRange/.test(table) &&
|
||
|
|
/isChecksquareHarmonicIndicator[\s\S]*hasChecksquareHarmonicOrderRange\(indicator\)/.test(table) &&
|
||
|
|
/const shouldMergeHarmonicItems\s*=\s*isChecksquareHarmonicIndicator\(indicator\)/.test(table) &&
|
||
|
|
/const normalItems\s*=\s*shouldMergeHarmonicItems[\s\S]*resultItems/.test(table) &&
|
||
|
|
/const harmonicItems\s*=\s*shouldMergeHarmonicItems[\s\S]*\[\]/.test(table)
|
||
|
|
)
|
||
|
|
}
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'table summarizes harmonic parent after all orders finish',
|
||
|
|
() =>
|
||
|
|
/buildHarmonicParentSummary/.test(read(files.table)) &&
|
||
|
|
/every\(item => isResolvedChecksquareItem\(item\)\)/.test(read(files.table)) &&
|
||
|
|
/missingPointCount \/ expectedPointCount/.test(read(files.table))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'table marks harmonic parent valid when every order child has data',
|
||
|
|
() =>
|
||
|
|
/hasData:\s*children\.every\(item => item\.hasData === true\),/.test(read(files.table)) &&
|
||
|
|
!/children\.every\(item => \(item\.missingPointCount \|\| 0\) === 0\)/.test(read(files.table))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'table keeps harmonic row keys stable while merging returned order results',
|
||
|
|
() =>
|
||
|
|
/normalizeChecksquareResultItemKey/.test(read(files.table)) &&
|
||
|
|
/normalizeChecksquareResultItemKey\([\s\S]*child\.itemKey/.test(read(files.table)) &&
|
||
|
|
/resolveChecksquareHarmonicOrder/.test(read(files.table)) &&
|
||
|
|
/resolveChecksquareHarmonicOrder\(item\) === child\.harmonicOrder/.test(read(files.table))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'table formats harmonic parent progress before final summary is ready',
|
||
|
|
() =>
|
||
|
|
/resolveChecksquareRowName[\s\S]*getHarmonicProgressText/.test(read(files.table)) &&
|
||
|
|
/已完成 \$\{resolvedCount\}\/\$\{totalCount\}/.test(read(files.table))
|
||
|
|
],
|
||
|
|
[
|
||
|
|
'page keeps selected checksquare detail synced after async row replacement',
|
||
|
|
() =>
|
||
|
|
/syncSelectedItemWithLatestResult/.test(read(files.page)) &&
|
||
|
|
/selectedItem\.value\.itemKey/.test(read(files.page)) &&
|
||
|
|
/mergeChecksquareIndicatorResult\(queryResult\.value/.test(read(files.page))
|
||
|
|
]
|
||
|
|
]
|
||
|
|
|
||
|
|
const failures = checks.filter(([, check]) => !check()).map(([name]) => name)
|
||
|
|
|
||
|
|
if (failures.length) {
|
||
|
|
console.error('checksquare feature contract failed:')
|
||
|
|
for (const failure of failures) {
|
||
|
|
console.error(`- ${failure}`)
|
||
|
|
}
|
||
|
|
process.exit(1)
|
||
|
|
}
|
||
|
|
|
||
|
|
console.log('checksquare feature contract passed')
|