import assert from 'node:assert/strict'
|
import { readFileSync } from 'node:fs'
|
import test from 'node:test'
|
|
import {
|
buildPreviewColumns,
|
buildReportStyleMeta
|
} from '../src/components/biz/list-export-print/list-export-print.helpers.js'
|
import { buildPrintDocumentHtml } from '../src/components/biz/list-export-print/list-print-document.js'
|
|
const previewDialogSource = readFileSync(
|
new URL('../src/components/biz/list-export-print/list-print-preview-dialog.vue', import.meta.url),
|
'utf8'
|
)
|
const printDocumentSource = readFileSync(
|
new URL('../src/components/biz/list-export-print/list-print-document.js', import.meta.url),
|
'utf8'
|
)
|
|
const scriptSetupIndex = previewDialogSource.indexOf('<script setup>')
|
const templateBlock =
|
scriptSetupIndex >= 0
|
? previewDialogSource.slice(previewDialogSource.indexOf('<template>'), scriptSetupIndex)
|
: previewDialogSource
|
|
test('report style meta defaults to the shared print preview contract', () => {
|
assert.deepEqual(buildReportStyleMeta({ reportTitle: '角色报表' }).reportStyle, {
|
titleAlign: 'center',
|
titleLevel: 'strong',
|
orientation: 'portrait',
|
density: 'compact',
|
showSequence: true,
|
showBorder: true
|
})
|
})
|
|
test('preview columns do not inject a sequence column when disabled', () => {
|
assert.deepEqual(
|
buildPreviewColumns({
|
columns: [{ source: 'name', label: '角色名称' }],
|
reportStyle: { showSequence: false }
|
}),
|
[{ source: 'name', label: '角色名称' }]
|
)
|
})
|
|
test('preview dialog template uses passed columns for sequence rendering and colspan', () => {
|
assert.equal(templateBlock.includes('>序号<'), false)
|
assert.match(templateBlock, /<div\s+:class="titleClass">\s*\{\{\s*meta\.reportTitle\s*\?\?\s*'--'\s*\}\}\s*<\/div>/)
|
assert.match(templateBlock, /<div\s+v-for="item in metaItems"[\s\S]*?:key="item\.key"[\s\S]*?>/)
|
assert.match(templateBlock, /\{\{\s*item\.label\s*\}\}/)
|
assert.match(templateBlock, /\{\{\s*item\.value\s*\?\?\s*'--'\s*\}\}/)
|
assert.match(templateBlock, /<th[\s\S]*?v-for="column in columns"[\s\S]*?>[\s\S]*?\{\{\s*column\.label\s*\}\}[\s\S]*?<\/th>/)
|
assert.match(
|
templateBlock,
|
/<td[\s\S]*?v-for="column in columns"[\s\S]*?>[\s\S]*?\{\{\s*column\.source === '__sequence__' \? index \+ 1 : row\?\.\[column\.source\] \?\? '--'\s*\}\}[\s\S]*?<\/td>/
|
)
|
assert.ok(templateBlock.includes("column.source === '__sequence__' ? index + 1"))
|
assert.ok(templateBlock.includes('Math.max(columns.length, 1)'))
|
assert.ok(previewDialogSource.includes('报表日期'))
|
assert.ok(previewDialogSource.includes('打印人'))
|
assert.ok(previewDialogSource.includes('打印时间'))
|
assert.ok(previewDialogSource.includes('记录数'))
|
assert.ok(templateBlock.includes('border-b'))
|
assert.ok(templateBlock.includes('print:hidden'))
|
})
|
|
test('preview dialog derives titleClass from reportStyle title alignment and level', () => {
|
assert.ok(previewDialogSource.includes('const titleClass = computed(() =>'))
|
assert.ok(previewDialogSource.includes('meta.reportTitle'))
|
assert.ok(previewDialogSource.includes('titleClass'))
|
assert.ok(previewDialogSource.includes('reportStyle.titleAlign'))
|
assert.ok(previewDialogSource.includes('reportStyle.titleLevel'))
|
assert.ok(previewDialogSource.includes("left: 'text-left'"))
|
assert.ok(previewDialogSource.includes("center: 'text-center'"))
|
assert.ok(previewDialogSource.includes("right: 'text-right'"))
|
assert.ok(previewDialogSource.includes("normal: 'text-[18px] font-medium'"))
|
assert.ok(previewDialogSource.includes("strong: 'text-[22px] font-semibold'"))
|
assert.ok(previewDialogSource.includes("prominent: 'text-[26px] font-bold'"))
|
assert.ok(previewDialogSource.includes("?? alignMap.center"))
|
assert.ok(previewDialogSource.includes("?? levelMap.strong"))
|
})
|
|
test('preview dialog keeps report content compact and prioritizes showing all columns', () => {
|
assert.ok(previewDialogSource.includes("text-[18px] font-medium"))
|
assert.ok(previewDialogSource.includes("text-[22px] font-semibold"))
|
assert.ok(previewDialogSource.includes("text-[26px] font-bold"))
|
assert.ok(previewDialogSource.includes('grid grid-cols-4 gap-2 text-[11px]'))
|
assert.ok(previewDialogSource.includes('table-auto'))
|
assert.ok(previewDialogSource.includes('text-[11px] leading-tight'))
|
assert.ok(previewDialogSource.includes('px-1.5 py-1.5'))
|
assert.ok(previewDialogSource.includes('break-all'))
|
})
|
|
test('preview dialog prints through a standalone report document instead of window.print on the app shell', () => {
|
assert.equal(previewDialogSource.includes('window.print()'), false)
|
assert.ok(previewDialogSource.includes("import { printReportDocument } from './list-print-document.js'"))
|
assert.ok(previewDialogSource.includes('printReportDocument({'))
|
})
|
|
test('standalone print document preserves report title, meta row, sequence column, and print page styles', () => {
|
const html = buildPrintDocumentHtml({
|
title: '角色管理报表',
|
meta: {
|
reportTitle: '角色管理报表',
|
reportDate: '2026/3/29',
|
operator: 'root',
|
printedAt: '2026/3/29 17:31:00',
|
count: 2,
|
reportStyle: {
|
titleAlign: 'center',
|
titleLevel: 'strong',
|
orientation: 'portrait'
|
}
|
},
|
columns: [
|
{ source: '__sequence__', label: '序号', align: 'center' },
|
{ source: 'name', label: '角色名称' },
|
{ source: 'code', label: '角色编码' }
|
],
|
rows: [
|
{ id: 1, name: 'WMS系统管理员', code: 'admin' },
|
{ id: 2, name: 'ERP财务管理员', code: 'erpcw' }
|
]
|
})
|
|
assert.ok(html.includes('<title>角色管理报表</title>'))
|
assert.ok(html.includes('报表日期'))
|
assert.ok(html.includes('打印人'))
|
assert.ok(html.includes('打印时间'))
|
assert.ok(html.includes('记录数'))
|
assert.ok(html.includes('序号'))
|
assert.ok(html.includes('WMS系统管理员'))
|
assert.ok(html.includes('ERP财务管理员'))
|
assert.ok(html.includes('@page'))
|
assert.ok(html.includes('size: A4 portrait;'))
|
const pageRule = html.match(/@page\s*\{[\s\S]*?\}/)?.[0] ?? ''
|
assert.equal(pageRule.includes('margin:'), false)
|
assert.ok(html.includes('print-color-adjust: exact;'))
|
assert.ok(html.includes('window.print()'))
|
assert.ok(html.includes('window.close()'))
|
assert.ok(html.includes('font-size: 11px;'))
|
assert.ok(html.includes('font-size: 22px;'))
|
assert.ok(html.includes('table-layout: auto;'))
|
})
|
|
test('standalone print document opens a writable popup window for document injection', () => {
|
assert.equal(printDocumentSource.includes('noopener,noreferrer'), false)
|
assert.ok(printDocumentSource.includes("window.open('', '_blank')"))
|
assert.ok(printDocumentSource.includes('printWindow.document.write(buildPrintDocumentHtml(payload))'))
|
})
|
|
test('preview dialog caps rendered rows and keeps the table body scrollable for large datasets', () => {
|
assert.ok(previewDialogSource.includes("maxPreviewRows: { type: Number, default: 50 }"))
|
assert.ok(previewDialogSource.includes('const previewRows = computed(() =>'))
|
assert.ok(previewDialogSource.includes('props.rows.slice(0, props.maxPreviewRows)'))
|
assert.ok(previewDialogSource.includes('const hiddenRowCount = computed(() =>'))
|
assert.ok(previewDialogSource.includes('预览仅展示前'))
|
assert.ok(previewDialogSource.includes('min-h-0 flex-1 overflow-auto'))
|
assert.ok(previewDialogSource.includes('v-for="(row, index) in previewRows"'))
|
assert.ok(previewDialogSource.includes('v-if="hiddenRowCount > 0"'))
|
})
|
|
test('preview dialog keeps the whole modal inside the viewport and lets only the table area scroll', () => {
|
assert.ok(previewDialogSource.includes('width="min(96vw, 1100px)"'))
|
assert.ok(previewDialogSource.includes('top="4vh"'))
|
assert.ok(previewDialogSource.includes('class="max-h-[88vh] overflow-hidden"'))
|
assert.ok(previewDialogSource.includes('flex max-h-[calc(88vh-160px)] min-h-0 flex-col'))
|
assert.ok(previewDialogSource.includes('mx-auto flex min-h-0 flex-1 flex-col overflow-hidden'))
|
assert.ok(previewDialogSource.includes(':class="paperClass"'))
|
assert.ok(previewDialogSource.includes(':style="paperStyle"'))
|
assert.ok(previewDialogSource.includes('mt-4 min-h-0 flex-1 overflow-auto'))
|
})
|
|
test('preview dialog lets the user switch between portrait and landscape before printing', () => {
|
assert.ok(previewDialogSource.includes("const currentOrientation = ref('portrait')"))
|
assert.ok(previewDialogSource.includes('watch('))
|
assert.ok(previewDialogSource.includes('v-model="currentOrientation"'))
|
assert.ok(previewDialogSource.includes('label="portrait"'))
|
assert.ok(previewDialogSource.includes('label="landscape"'))
|
assert.ok(previewDialogSource.includes('竖版'))
|
assert.ok(previewDialogSource.includes('横版'))
|
assert.ok(previewDialogSource.includes('const effectiveMeta = computed(() =>'))
|
assert.ok(previewDialogSource.includes('orientation: currentOrientation.value'))
|
assert.ok(previewDialogSource.includes('meta: effectiveMeta.value'))
|
assert.ok(previewDialogSource.includes('const paperClass = computed(() =>'))
|
assert.ok(previewDialogSource.includes("currentOrientation.value === 'landscape'"))
|
assert.ok(previewDialogSource.includes("aspectRatio: currentOrientation.value === 'landscape' ? '297 / 210' : '210 / 297'"))
|
})
|
|
test('preview dialog lets the user toggle table borders and carries the setting into print meta', () => {
|
assert.ok(previewDialogSource.includes("const currentShowBorder = ref(true)"))
|
assert.ok(previewDialogSource.includes('v-model="currentShowBorder"'))
|
assert.ok(previewDialogSource.includes('边框开'))
|
assert.ok(previewDialogSource.includes('边框关'))
|
assert.ok(previewDialogSource.includes('showBorder: currentShowBorder.value'))
|
assert.ok(previewDialogSource.includes('const tableWrapClass = computed(() =>'))
|
assert.ok(previewDialogSource.includes('const headerCellClass = computed(() =>'))
|
assert.ok(previewDialogSource.includes('const bodyCellClass = computed(() =>'))
|
assert.ok(printDocumentSource.includes('const showBorder = reportStyle.showBorder !== false'))
|
assert.ok(printDocumentSource.includes("const tableWrapClass = showBorder ? 'report-table-wrap' : 'report-table-wrap report-table-wrap-borderless'"))
|
})
|