| |
| |
| |
|
|
| import { describe, it, expect } from 'vitest'; |
| import { |
| groupProjectsByHierarchy, |
| isDateStale, |
| formatDateShort, |
| getNodeBadgeClass |
| } from './hierarchyUtils'; |
| import { ProjectData } from '@/types/project'; |
|
|
| describe('groupProjectsByHierarchy', () => { |
| it('should group projects by project name and product name', () => { |
| const mockProjects: ProjectData[] = [ |
| { |
| no: '1', |
| identity: { |
| projectName: 'Ziti2', |
| productName: 'housing', |
| moldNumber: 'LA25463', |
| customerName: '罗技', |
| customerBase: '苏州', |
| factory: 'A厂', |
| moldSets: '1', |
| partNumber: '400-009150', |
| cavityNumber: 'CAV1-4', |
| projectManager: '张舜杰', |
| projectEngineer: '袁任杰', |
| moldProject: '胡荣武', |
| qe: '柳彬彬', |
| riskLevel: '正常' |
| }, |
| milestones: { |
| projectStart: '2001-05-14', |
| t1: '2001-06-08', |
| glTime: '2001-12-02', |
| vmp: '2001-10-18', |
| mp: '-', |
| currentStage: 'PQR', |
| trialCount: 'T26', |
| t1SizeQualified: '-', |
| toolingFAI: '79.40%', |
| partFAI: '100%', |
| currentNode: 'PQR' |
| }, |
| details: { |
| detailDate: '2026-01-02', |
| detailProgress: '苏州跑线通过, PB2 越南试产 400PCS,1/26海运, 预计2/9号到达' |
| } |
| }, |
| { |
| no: '2', |
| identity: { |
| projectName: 'Ziti2', |
| productName: 'housing', |
| moldNumber: 'LA25464', |
| customerName: '罗技', |
| customerBase: '苏州', |
| factory: 'A厂', |
| moldSets: '1', |
| partNumber: '400-009151', |
| cavityNumber: 'CAV1-4', |
| projectManager: '张舜杰', |
| projectEngineer: '袁任杰', |
| moldProject: '胡荣武', |
| qe: '柳彬彬', |
| riskLevel: '正常' |
| }, |
| milestones: { |
| projectStart: '2001-05-14', |
| t1: '2001-06-08', |
| glTime: '2001-12-02', |
| vmp: '2001-10-18', |
| mp: '-', |
| currentStage: 'T1', |
| trialCount: 'T10', |
| t1SizeQualified: '-', |
| toolingFAI: '50%', |
| partFAI: '80%', |
| currentNode: 'T1' |
| }, |
| details: { |
| detailDate: '2026-01-15', |
| detailProgress: '第二次试模完成' |
| } |
| } |
| ]; |
|
|
| const result = groupProjectsByHierarchy(mockProjects); |
|
|
| expect(result).toHaveLength(1); |
| expect(result[0].projectName).toBe('Ziti2'); |
| expect(result[0].products).toHaveLength(1); |
| expect(result[0].products[0].productName).toBe('housing'); |
| expect(result[0].products[0].molds).toHaveLength(2); |
| expect(result[0].products[0].molds[0].moldNumber).toBe('LA25463'); |
| expect(result[0].products[0].molds[1].moldNumber).toBe('LA25464'); |
| }); |
|
|
| it('should handle multiple projects and products', () => { |
| const mockProjects: ProjectData[] = [ |
| { |
| no: '1', |
| identity: { |
| projectName: 'Project A', |
| productName: 'Product 1', |
| moldNumber: 'M001', |
| customerName: '', customerBase: '', factory: '', moldSets: '', |
| partNumber: '', cavityNumber: '', projectManager: '', |
| projectEngineer: '', moldProject: '', qe: '', riskLevel: '' |
| }, |
| milestones: { |
| projectStart: '', t1: '', glTime: '', vmp: '', mp: '', |
| currentStage: 'PQR', trialCount: '', t1SizeQualified: '', |
| toolingFAI: '', partFAI: '', currentNode: 'PQR' |
| }, |
| details: { detailDate: '2026-01-01', detailProgress: 'Test' } |
| }, |
| { |
| no: '2', |
| identity: { |
| projectName: 'Project A', |
| productName: 'Product 2', |
| moldNumber: 'M002', |
| customerName: '', customerBase: '', factory: '', moldSets: '', |
| partNumber: '', cavityNumber: '', projectManager: '', |
| projectEngineer: '', moldProject: '', qe: '', riskLevel: '' |
| }, |
| milestones: { |
| projectStart: '', t1: '', glTime: '', vmp: '', mp: '', |
| currentStage: 'T1', trialCount: '', t1SizeQualified: '', |
| toolingFAI: '', partFAI: '', currentNode: 'T1' |
| }, |
| details: { detailDate: '2026-01-01', detailProgress: 'Test' } |
| }, |
| { |
| no: '3', |
| identity: { |
| projectName: 'Project B', |
| productName: 'Product 3', |
| moldNumber: 'M003', |
| customerName: '', customerBase: '', factory: '', moldSets: '', |
| partNumber: '', cavityNumber: '', projectManager: '', |
| projectEngineer: '', moldProject: '', qe: '', riskLevel: '' |
| }, |
| milestones: { |
| projectStart: '', t1: '', glTime: '', vmp: '', mp: '', |
| currentStage: 'MP', trialCount: '', t1SizeQualified: '', |
| toolingFAI: '', partFAI: '', currentNode: 'MP' |
| }, |
| details: { detailDate: '2026-01-01', detailProgress: 'Test' } |
| } |
| ]; |
|
|
| const result = groupProjectsByHierarchy(mockProjects); |
|
|
| expect(result).toHaveLength(2); |
| expect(result[0].projectName).toBe('Project A'); |
| expect(result[0].products).toHaveLength(2); |
| expect(result[1].projectName).toBe('Project B'); |
| expect(result[1].products).toHaveLength(1); |
| }); |
| }); |
|
|
| describe('isDateStale', () => { |
| it('should return true for dates older than 7 days', () => { |
| const oldDate = new Date(); |
| oldDate.setDate(oldDate.getDate() - 10); |
| const dateStr = oldDate.toISOString().split('T')[0]; |
| |
| expect(isDateStale(dateStr)).toBe(true); |
| }); |
|
|
| it('should return false for recent dates', () => { |
| const recentDate = new Date(); |
| recentDate.setDate(recentDate.getDate() - 3); |
| const dateStr = recentDate.toISOString().split('T')[0]; |
| |
| expect(isDateStale(dateStr)).toBe(false); |
| }); |
|
|
| it('should return false for invalid dates', () => { |
| expect(isDateStale('-')).toBe(false); |
| expect(isDateStale('')).toBe(false); |
| expect(isDateStale('invalid')).toBe(false); |
| }); |
| }); |
|
|
| describe('formatDateShort', () => { |
| it('should format date as "月日"', () => { |
| const result = formatDateShort('2026-01-15'); |
| |
| expect(result).toMatch(/\d+月\d+日/); |
| }); |
|
|
| it('should handle different date formats', () => { |
| const result = formatDateShort('1/2/26'); |
| expect(result).toMatch(/\d+月\d+日/); |
| }); |
|
|
| it('should return "-" for invalid dates', () => { |
| expect(formatDateShort('-')).toBe('-'); |
| expect(formatDateShort('')).toBe('-'); |
| }); |
| }); |
|
|
| describe('getNodeBadgeClass', () => { |
| it('should return green class for completed stages', () => { |
| expect(getNodeBadgeClass('MP')).toContain('green'); |
| expect(getNodeBadgeClass('完成')).toContain('green'); |
| }); |
|
|
| it('should return blue class for in-progress stages', () => { |
| expect(getNodeBadgeClass('PQR')).toContain('blue'); |
| expect(getNodeBadgeClass('T1')).toContain('blue'); |
| |
| expect(getNodeBadgeClass('进行中')).toContain('blue'); |
| }); |
|
|
| it('should return slate class for early stages', () => { |
| expect(getNodeBadgeClass('KICK OFF')).toContain('slate'); |
| expect(getNodeBadgeClass('G/L')).toContain('slate'); |
| }); |
|
|
| it('should handle empty or invalid input', () => { |
| const result = getNodeBadgeClass(''); |
| expect(result).toContain('slate'); |
| }); |
| }); |
|
|