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(/]/) } }) 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() })