62 lines
4.5 KiB
TypeScript
62 lines
4.5 KiB
TypeScript
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)
|
||
})
|