62 lines
3.3 KiB
TypeScript
62 lines
3.3 KiB
TypeScript
import ExcelJS from 'exceljs'
|
|
import JSZip from 'jszip'
|
|
import { buildSalaryTemplateWorkbook, salaryExportFileName, saveSalaryTemplateWorkbook } from '@/lib/performance-salary-export'
|
|
|
|
test('导出后重新读取:四岗模板字段、空行、冻结及月份均正确且无隐私数据或公式', async () => {
|
|
const workbook = await buildSalaryTemplateWorkbook('2027-01')
|
|
const buffer = await workbook.xlsx.writeBuffer()
|
|
const reloaded = new ExcelJS.Workbook()
|
|
await reloaded.xlsx.load(buffer)
|
|
expect(reloaded.worksheets.map(sheet => sheet.name)).toEqual(['AI全案策划岗', '摄影中心岗', '空间视觉岗', '管理岗'])
|
|
const expected = [
|
|
{ columns: 25, headerRow: 3, lastHeader: '备注', firstMerge: 'A2:G2' },
|
|
{ columns: 16, headerRow: 2, lastHeader: '备注', firstMerge: 'A1:P1' },
|
|
{ columns: 21, headerRow: 3, lastHeader: '备注', firstMerge: 'A2:F2' },
|
|
{ columns: 23, headerRow: 3, lastHeader: '2026年12月薪资', firstMerge: 'A2:C2' },
|
|
]
|
|
reloaded.worksheets.forEach((sheet, index) => {
|
|
const target = expected[index]
|
|
expect(sheet.state).toBe('visible')
|
|
expect(sheet.columnCount).toBe(target.columns)
|
|
expect(sheet.rowCount).toBe(target.headerRow + 5)
|
|
expect(sheet.getCell(1, 1).value).toBe(`2027-01 ${sheet.name}薪资明细`)
|
|
expect(sheet.getCell(target.headerRow, target.columns).value).toBe(target.lastHeader)
|
|
expect(sheet.model.merges).toContain(target.firstMerge)
|
|
expect(sheet.views[0]).toEqual(expect.objectContaining({ state: 'frozen', xSplit: 1, ySplit: target.headerRow }))
|
|
for (let row = target.headerRow + 1; row <= sheet.rowCount; row += 1) {
|
|
for (let col = 1; col <= target.columns; col += 1) expect(sheet.getCell(row, col).value).toBeNull()
|
|
}
|
|
})
|
|
expect(reloaded.worksheets[1].getCell('N2').value).toBe('2026年11月合计薪资')
|
|
const zip = await JSZip.loadAsync(buffer)
|
|
expect(Object.keys(zip.files).some(path => /externalLinks|hidden/i.test(path))).toBe(false)
|
|
for (const path of Object.keys(zip.files).filter(path => /^xl\/worksheets\/sheet\d+\.xml$/.test(path))) {
|
|
const xml = await zip.file(path)!.async('string')
|
|
expect(xml).not.toMatch(/<f[\s>]/)
|
|
}
|
|
})
|
|
|
|
test('校验导出月份与文件名', async () => {
|
|
expect(salaryExportFileName('2026-09')).toBe('绩效薪资模板_2026-09.xlsx')
|
|
await expect(buildSalaryTemplateWorkbook('../../secret')).rejects.toThrow('有效月份')
|
|
})
|
|
|
|
test('标准下载文件并回收对象URL', async () => {
|
|
const create = jest.fn(() => 'blob:salary-template')
|
|
const revoke = jest.fn()
|
|
Object.defineProperty(URL, 'createObjectURL', { configurable: true, value: create })
|
|
Object.defineProperty(URL, 'revokeObjectURL', { configurable: true, value: revoke })
|
|
const click = jest.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(function(this: HTMLAnchorElement) {
|
|
expect(this.download).toBe('绩效薪资模板_2026-09.xlsx')
|
|
})
|
|
const timer = jest.spyOn(window, 'setTimeout')
|
|
await saveSalaryTemplateWorkbook('2026-09')
|
|
expect(create).toHaveBeenCalledWith(expect.any(Blob))
|
|
expect(click).toHaveBeenCalledTimes(1)
|
|
const cleanup = timer.mock.calls.find(call => call[1] === 1000)?.[0] as (() => void)
|
|
cleanup()
|
|
expect(revoke).toHaveBeenCalledWith('blob:salary-template')
|
|
click.mockRestore()
|
|
timer.mockRestore()
|
|
})
|