/** @jest-environment node */ import { DEFAULT_PERFORMANCE_SURVEY_TEMPLATES, calculateAggregateScore, calculateResponseScores, getPerformanceSurveyQuestionScoreDetails, getPerformanceSurveyTemplateVariant, listPerformanceSurveyTemplateOptions, } from '@/lib/performance-survey-templates' describe('performance survey templates', () => { test('不含文案的视觉评分为四项每项 10 分', () => { const template = DEFAULT_PERFORMANCE_SURVEY_TEMPLATES.find((item) => item.code === 'visual_brand_tone')! const customized = getPerformanceSurveyTemplateVariant(template, 'without_copy') expect(customized.questions).toHaveLength(4) expect(customized.questions.map((question) => question.id)).not.toContain('copyExpression') expect(customized.questions.every((question) => question.maxScore === 10)).toBe(true) expect(customized.questions.reduce((sum, question) => sum + Number(question.maxScore), 0)).toBe(40) expect(customized.maxTotal).toBe(120) expect(calculateResponseScores( customized, Object.fromEntries(customized.questions.map((question) => [question.id, 10])), ).totalScore).toBe(40) expect(template.questions).toHaveLength(5) expect(template.questions.find((question) => question.id === 'copyExpression')?.maxScore).toBe(8) }) test('含文案的视觉评分为五项每项 8 分', () => { const template = DEFAULT_PERFORMANCE_SURVEY_TEMPLATES.find((item) => item.code === 'visual_brand_tone')! const unchanged = getPerformanceSurveyTemplateVariant(template, 'with_copy') expect(unchanged.questions).toHaveLength(5) expect(unchanged.questions.find((question) => question.id === 'copyExpression')).toBeDefined() expect(unchanged.questions.every((question) => question.maxScore === 8)).toBe(true) expect(unchanged.questions.reduce((sum, question) => sum + Number(question.maxScore), 0)).toBe(40) }) test('创建选项将视觉评分展开为两个版本但保持同一业务编号', () => { const options = listPerformanceSurveyTemplateOptions(DEFAULT_PERFORMANCE_SURVEY_TEMPLATES) const visualOptions = options.filter((item) => item.code === 'visual_brand_tone') expect(visualOptions).toHaveLength(2) expect(visualOptions.map((item) => item.templateOptionKey)).toEqual([ 'visual_brand_tone:with_copy', 'visual_brand_tone:without_copy', ]) expect(DEFAULT_PERFORMANCE_SURVEY_TEMPLATES.filter((item) => item.code === 'visual_brand_tone')).toHaveLength(1) }) test('视觉评分未传版本时默认使用含文案版,保持旧链接可用', () => { const template = DEFAULT_PERFORMANCE_SURVEY_TEMPLATES.find((item) => item.code === 'visual_brand_tone')! expect(getPerformanceSurveyTemplateVariant(template).templateVariant).toBe('with_copy') }) test('拒绝未知视觉版本以及非视觉模板携带版本', () => { const visualTemplate = DEFAULT_PERFORMANCE_SURVEY_TEMPLATES.find((item) => item.code === 'visual_brand_tone')! const courseTemplate = DEFAULT_PERFORMANCE_SURVEY_TEMPLATES.find((item) => item.code === 'course_share')! expect(() => getPerformanceSurveyTemplateVariant(visualTemplate, 'unknown')).toThrow('视觉评分模板版本无效') expect(() => getPerformanceSurveyTemplateVariant(courseTemplate, 'with_copy')).toThrow('非视觉评分模板不支持模板版本') }) test('课程分享直接保留用户提交的 100 分,不做权重或 KPI 折算', () => { const template = DEFAULT_PERFORMANCE_SURVEY_TEMPLATES.find((item) => item.code === 'course_share')! const scores = Object.fromEntries(template.questions.map((question) => [question.id, 20])) const result = calculateResponseScores(template, scores) expect(template.questions).toHaveLength(5) expect(template.maxTotal).toBe(100) expect(template.weight).toBe(0.3) expect(template.aggregationStrategy).toBe('trim_extremes') expect(template.itemName).toBe('课程') expect(template.respondentNameFallback).toBe('匿名学员') expect(template.belowBand).toBe('满意度不足 77%,此项考核为 0') expect(result.totalScore).toBe(100) expect(result.achievementScore).toBe(100) expect(result.kpiScore).toBe(100) expect(result.weightedScore).toBe(100) }) test('人员月度模板直接保留用户提交的 120 分', () => { const template = DEFAULT_PERFORMANCE_SURVEY_TEMPLATES.find((item) => item.code === 'cross_department')! const scores = Object.fromEntries(template.questions.map((question) => [question.id, 20])) const result = calculateResponseScores(template, scores) expect(template.questions).toHaveLength(6) expect(template.maxTotal).toBe(120) expect(template.weight).toBe(0.2) expect(result.totalScore).toBe(120) expect(result.kpiScore).toBe(120) expect(result.weightedScore).toBe(120) }) test('question score details keep every item title, score and maximum', () => { const template = DEFAULT_PERFORMANCE_SURVEY_TEMPLATES.find((item) => item.code === 'course_share')! const details = getPerformanceSurveyQuestionScoreDetails(template, { usefulness: 18, insight: 16.5, }) expect(details).toHaveLength(template.questions.length) expect(details[0]).toEqual({ id: 'usefulness', title: '内容实用度', score: 18, maxScore: 20, }) expect(details[1]).toEqual(expect.objectContaining({ id: 'insight', score: 16.5, maxScore: 20, })) expect(details[2].score).toBeNull() }) test('多份评分使用全部实际得分做普通平均,不剔除最高最低分', () => { const template = DEFAULT_PERFORMANCE_SURVEY_TEMPLATES.find((item) => item.code === 'cross_department')! const result = calculateAggregateScore( [ { totalScore: 60, createdAt: '2026-07-04T00:00:00.000Z', scores: Object.fromEntries(template.questions.map((question) => [question.id, 10])), respondentName: '最低分', }, { totalScore: 100, createdAt: '2026-07-04T00:01:00.000Z', scores: Object.fromEntries(template.questions.map((question) => [question.id, question.id === 'requirementClarity' ? 18 : 16])), respondentName: '有效分', }, { totalScore: 120, createdAt: '2026-07-04T00:02:00.000Z', scores: Object.fromEntries(template.questions.map((question) => [question.id, 20])), respondentName: '最高分', }, ], template.scoreBands, template.weight, template, ) expect(result.droppedHighestLowest).toBe(false) expect(result.effectiveCount).toBe(3) expect(result.rawAverage).toBe(93.33) expect(result.trimmedAverage).toBe(93.33) expect(result.trimNote).toBe('按全部评分直接计算平均分') expect(result.removedHigh).toBeNull() expect(result.removedLow).toBeNull() expect(result.dimensionAverages.requirementClarity).toBe(16) expect(result.achievementScore).toBe(93.33) expect(result.kpiScore).toBe(93.33) expect(result.weightedScore).toBe(93.33) }) test('低分也按实际得分展示,不再因为低于档位折算为 0', () => { const template = DEFAULT_PERFORMANCE_SURVEY_TEMPLATES.find((item) => item.code === 'cross_department')! const result = calculateAggregateScore( [95, 14].map((totalScore, index) => ({ totalScore, createdAt: `2026-07-19T00:0${index}:00.000Z`, })), template.scoreBands, template.weight, template, ) expect(result.rawAverage).toBe(54.5) expect(result.trimmedAverage).toBe(54.5) expect(result.achievementScore).toBe(54.5) expect(result.kpiScore).toBe(54.5) expect(result.weightedScore).toBe(54.5) expect(result.band).toBeNull() }) test.each([ [[40], 40], [[40, 80], 60], [[10, 80, 120], 70], [[60, 70, 80, 100, 120], 86], ])('对 %p 只做全部评分的普通平均,结果为 %s', (scores, expected) => { const template = DEFAULT_PERFORMANCE_SURVEY_TEMPLATES.find((item) => item.code === 'cross_department')! const result = calculateAggregateScore( scores.map((totalScore, index) => ({ totalScore, createdAt: `2026-07-19T00:0${index}:00.000Z`, })), template.scoreBands, template.weight, template, ) expect(result.achievementScore).toBe(expected) expect(result.kpiScore).toBe(expected) expect(result.effectiveCount).toBe(scores.length) }) test('零分是有效得分,无评分时结果保持为空', () => { const template = DEFAULT_PERFORMANCE_SURVEY_TEMPLATES.find((item) => item.code === 'cross_department')! expect(calculateAggregateScore( [{ totalScore: 0, createdAt: '2026-07-19T00:00:00.000Z' }], template.scoreBands, template.weight, template, ).achievementScore).toBe(0) expect(calculateAggregateScore([], template.scoreBands, template.weight, template)).toMatchObject({ responseCount: 0, effectiveCount: 0, rawAverage: null, achievementScore: null, kpiScore: null, weightedScore: null, }) }) test('visual scoring keeps a single evaluator score as the direct average', () => { const template = DEFAULT_PERFORMANCE_SURVEY_TEMPLATES.find((item) => item.code === 'visual_brand_tone')! const result = calculateAggregateScore( [{ totalScore: 88, createdAt: '2026-07-04T00:00:00.000Z' }], template.scoreBands, template.weight, template, ) expect(template.aggregationStrategy).toBe('direct_average') expect(result.droppedHighestLowest).toBe(false) expect(result.effectiveCount).toBe(1) expect(result.achievementScore).toBe(88) expect(result.removedHigh).toBeNull() expect(result.removedLow).toBeNull() }) test('visual scoring directly averages all three evaluator scores without trimming extremes', () => { const template = DEFAULT_PERFORMANCE_SURVEY_TEMPLATES.find((item) => item.code === 'visual_brand_tone')! const legacyTemplateSnapshot = { ...template, aggregationStrategy: undefined } const result = calculateAggregateScore( [60, 90, 120].map((totalScore, index) => ({ totalScore, createdAt: `2026-07-04T00:0${index}:00.000Z`, })), template.scoreBands, template.weight, legacyTemplateSnapshot, ) expect(result.droppedHighestLowest).toBe(false) expect(result.effectiveCount).toBe(3) expect(result.rawAverage).toBe(90) expect(result.achievementScore).toBe(90) expect(result.removedHigh).toBeNull() expect(result.removedLow).toBeNull() }) test('visual scoring directly averages every evaluator score when there are more than three', () => { const template = DEFAULT_PERFORMANCE_SURVEY_TEMPLATES.find((item) => item.code === 'visual_brand_tone')! const result = calculateAggregateScore( [60, 70, 80, 100, 120].map((totalScore, index) => ({ totalScore, createdAt: `2026-07-04T00:0${index}:00.000Z`, })), template.scoreBands, template.weight, template, ) expect(result.droppedHighestLowest).toBe(false) expect(result.effectiveCount).toBe(5) expect(result.rawAverage).toBe(86) expect(result.achievementScore).toBe(86) expect(result.removedHigh).toBeNull() expect(result.removedLow).toBeNull() }) })