gongxiangp2/lib/__tests__/salary-position-contract.test.ts

62 lines
4.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { INITIAL_SALARY_POSITIONS } from '../salary-position-seeds'
import { calculatePositionScore, percentageSchema, positionFieldsSchema } from '../salary-position-contract'
import { getPositionSalaryTemplate } from '../performance-salary-templates'
const copy = () => JSON.parse(JSON.stringify(INITIAL_SALARY_POSITIONS[1].value))
test('三岗初始化完整权重,包括零权重指标', () => {
for (const seed of INITIAL_SALARY_POSITIONS) expect(positionFieldsSchema.safeParse(seed.value).success).toBe(true)
expect(INITIAL_SALARY_POSITIONS.map(seed => seed.value.dimensions.map(d => [d.weight, d.metrics.map(m => m.weight)]))).toEqual([
[['70', ['80', '20']], ['20', ['40', '40', '20']], ['10', ['100']]],
[['25', ['100', '0']], ['70', ['10', '90', '0']], ['5', ['100']]],
[['25', ['50', '50']], ['70', ['0', '60', '40']], ['5', ['100']]],
])
})
test('百分比精确边界、空配置与无自动归一化', () => {
for (const value of ['0', '0.00', '0.01', '99.99', '100.00']) expect(percentageSchema.safeParse(value).success).toBe(true)
for (const value of ['-1', '100.01', '1.001', 'NaN', '', '1e2', 50]) expect(percentageSchema.safeParse(value).success).toBe(false)
const value = copy(); value.dimensions = []; expect(positionFieldsSchema.safeParse(value).success).toBe(true)
const invalid = copy(); invalid.dimensions[0].weight = '24.99'; expect(positionFieldsSchema.safeParse(invalid).success).toBe(false)
invalid.dimensions[0].weight = '25'; invalid.dimensions[0].metrics[0].weight = '99.99'; expect(positionFieldsSchema.safeParse(invalid).success).toBe(false)
})
test('重复稳定ID和名称、非法来源及岗位入口被拒绝', () => {
const value = copy(); value.dimensions[0].metrics[0].id = value.dimensions[1].metrics[0].id
expect(positionFieldsSchema.safeParse(value).success).toBe(false)
value.dimensions = []; value.salaryRole = 'director'; expect(positionFieldsSchema.safeParse(value).success).toBe(false)
const source = copy(); source.dimensions[0].metrics[0].source = { kind: 'manual', score: 100 }
expect(positionFieldsSchema.safeParse(source).success).toBe(false)
})
test('两层权重最终舍入,正权重缺失阻断,零权重不阻断', () => {
const dimensions = copy().dimensions
const scores: Record<string, number | null> = {}
for (const d of dimensions) for (const m of d.metrics) scores[m.id] = m.weight === '0' ? null : 88.885
expect(calculatePositionScore(dimensions, scores).totalScore).toBe(88.89)
scores[dimensions[0].metrics[0].id] = null
expect(calculatePositionScore(dimensions, scores).totalScore).toBeNull()
dimensions[0].weight = '0'; dimensions[1].weight = '95'
expect(calculatePositionScore(dimensions, scores).totalScore).toBe(88.89)
expect(calculatePositionScore([], {}).totalScore).toBeNull()
})
test.each(['ai-planning', 'ai-technical', 'management', 'photography', 'spatial-visual'] as const)('动态%s模板字段唯一,含基础字段及绩效得分', salaryRole => {
const template = getPositionSalaryTemplate({ ...copy(), salaryRole, id: 'job', revision: 1, updatedAt: '', personnelCount: 0 }, '2027-01')
expect(new Set(template.columns.map(c => c.key)).size).toBe(template.columns.length)
expect(template.columns.map(c => c.key)).toEqual(expect.arrayContaining(['name', 'nickname', 'department', 'brand', 'baseSalary', 'gradeSalary', 'performanceSalary', 'performanceScore']))
expect(template.columns.some(c => c.key.endsWith('Weighted') || c.label.includes('加权得分'))).toBe(false)
expect(template.columns.filter(c => c.key.endsWith('Score') && c.key !== 'performanceScore')).toHaveLength(6)
})
test('各项考核得分直接乘综合占比,总分使用未舍入贡献且无二次加权', () => {
const dimensions = copy().dimensions
const scores: Record<string, number | null> = {}
for (const d of dimensions) for (const m of d.metrics) scores[m.id] = 100
const result = calculatePositionScore(dimensions, scores)
expect(result.contributions[dimensions[1].metrics[1].id]).toBe(63)
expect(result.totalScore).toBe(100)
expect(result).not.toHaveProperty('weighted')
for (const d of dimensions) for (const m of d.metrics) scores[m.id] = 88.885
expect(calculatePositionScore(dimensions, scores).totalScore).toBe(88.89)
scores[dimensions[0].metrics[1].id] = null
expect(calculatePositionScore(dimensions, scores).contributions[dimensions[0].metrics[1].id]).toBeNull()
scores[dimensions[0].metrics[1].id] = 0
expect(calculatePositionScore(dimensions, scores).contributions[dimensions[0].metrics[1].id]).toBe(0)
})