85 lines
4.7 KiB
TypeScript
85 lines
4.7 KiB
TypeScript
import ExcelJS from 'exceljs'
|
|
import JSZip from 'jszip'
|
|
import { buildSalaryTemplateWorkbook, salaryExportFileName, saveSalaryTemplateWorkbook } from '@/lib/performance-salary-export'
|
|
import { INITIAL_SALARY_POSITIONS } from '@/lib/salary-position-seeds'
|
|
import { getPositionSalaryTemplate } from '@/lib/performance-salary-templates'
|
|
|
|
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全案策划岗', '摄影中心岗', '空间视觉岗', '管理岗', 'AI技术设计师岗'])
|
|
const expected = [
|
|
{ columns: 19, headerRow: 3, lastHeader: '备注', firstMerge: 'A2:G2' },
|
|
{ columns: 16, headerRow: 2, lastHeader: '备注', firstMerge: 'A1:P1' },
|
|
{ columns: 16, headerRow: 3, lastHeader: '备注', firstMerge: 'A2:F2' },
|
|
{ columns: 17, headerRow: 3, lastHeader: '2026年12月薪资', firstMerge: 'A2:C2' },
|
|
{ columns: 18, headerRow: 3, lastHeader: '备注', firstMerge: 'A2:G2' },
|
|
]
|
|
reloaded.worksheets.forEach((sheet, index) => {
|
|
const target = expected[index]
|
|
expect(sheet.state).toBe('visible')
|
|
expect(JSON.stringify(sheet.getRow(target.headerRow).values)).not.toMatch(/加权得分|绩效占比/)
|
|
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('按保存岗位导出使用同份单列考核模板,综合占比和两位小数格式一致,空行不含业务数据', async () => {
|
|
const positions = INITIAL_SALARY_POSITIONS.map((seed, index) => ({ ...seed.value, id: `position-${index}`, revision: 1, updatedAt: '', personnelCount: 7 }))
|
|
const buffer = await (await buildSalaryTemplateWorkbook('2026-10', positions)).xlsx.writeBuffer()
|
|
const restored = new ExcelJS.Workbook()
|
|
await restored.xlsx.load(buffer)
|
|
for (const position of positions) {
|
|
const sheet = restored.getWorksheet(position.name)!
|
|
const template = getPositionSalaryTemplate(position, '2026-10')
|
|
expect(sheet.getRow(3).values).toEqual([undefined, ...template.columns.map(column => column.label)])
|
|
expect(JSON.stringify(sheet.getRow(3).values)).not.toMatch(/加权得分/)
|
|
template.columns.forEach((column, index) => {
|
|
for (let row = 4; row <= 8; row++) {
|
|
expect(sheet.getCell(row, index + 1).value).toBeNull()
|
|
if (column.key.endsWith('Score')) expect(sheet.getCell(row, index + 1).numFmt).toBe('0.00')
|
|
}
|
|
})
|
|
}
|
|
})
|
|
|
|
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()
|
|
})
|