admin-dashboard.component.spec.ts 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import { ComponentFixture, TestBed } from '@angular/core/testing';
  2. import { provideRouter } from '@angular/router';
  3. import { AdminSessionService } from '../admin-session.service';
  4. import { OperationsService } from '../operations.service';
  5. import { AdminDashboardComponent } from './admin-dashboard.component';
  6. describe('AdminDashboardComponent', () => {
  7. let fixture: ComponentFixture<AdminDashboardComponent>;
  8. let operations: jasmine.SpyObj<OperationsService>;
  9. let sessions: jasmine.SpyObj<AdminSessionService>;
  10. beforeEach(async () => {
  11. operations = jasmine.createSpyObj<OperationsService>('OperationsService', ['dashboard', 'dashboardSnapshot', 'scheduleTrend']);
  12. operations.dashboardSnapshot.and.returnValue(null);
  13. sessions = jasmine.createSpyObj<AdminSessionService>('AdminSessionService', ['applyIdentity']);
  14. operations.dashboard.and.resolveTo({
  15. identity: { objectId: 'admin-1', username: 'ops', displayName: '运营经理', roles: ['admin', 'ops-manager'], isSuperAdmin: false, operationsRole: 'ops-manager' },
  16. date: '2026-08-20', metrics: [
  17. { key: 'members', label: '在学会员', value: 27, hint: '当前有正式学习任务', route: '/admin/members', tone: 'blue' },
  18. { key: 'today', label: '今日课程', value: 37, hint: '今天计划进行的课程', route: '/admin/schedule', tone: 'amber' },
  19. { key: 'coaches', label: '在岗陪练', value: 39, hint: '实时业务账号', route: '/admin/coaches', tone: 'green' },
  20. { key: 'pay', label: '本月预估工资', value: 120, hint: '按有效上课记录估算', route: '/admin/payroll', tone: 'rose' }
  21. ], alerts: [], scheduleTrend: {
  22. period: 'week', periodLabel: '近 7 天', dateFrom: '2026-08-14', dateTo: '2026-08-20',
  23. points: Array.from({ length: 7 }, (_, index) => ({ key: `2026-08-${14 + index}`, label: `8月${14 + index}日`, shortLabel: `周${index}`, courseCount: index + 1, studentCount: index, completedCount: index, isToday: index === 6 })),
  24. todayCourseCount: 7, todayStudentCount: 6, totalCourseCount: 28, totalStudentCount: 12, maxValue: 7,
  25. refreshedAt: '2026-08-20T04:00:00.000Z', liveSourceAvailable: true, liveSourceMessage: ''
  26. }
  27. });
  28. operations.scheduleTrend.and.resolveTo({
  29. period: 'month', periodLabel: '本月', dateFrom: '2026-08-01', dateTo: '2026-08-20',
  30. points: [{ key: '2026-08-20', label: '8月20日', shortLabel: '20日', courseCount: 12, studentCount: 9, completedCount: 5, isToday: true }],
  31. todayCourseCount: 12, todayStudentCount: 9, totalCourseCount: 12, totalStudentCount: 9, maxValue: 12,
  32. refreshedAt: '2026-08-20T04:00:00.000Z', liveSourceAvailable: true, liveSourceMessage: ''
  33. });
  34. await TestBed.configureTestingModule({ imports: [AdminDashboardComponent], providers: [
  35. provideRouter([]),
  36. { provide: OperationsService, useValue: operations },
  37. { provide: AdminSessionService, useValue: sessions }
  38. ] }).compileComponents();
  39. fixture = TestBed.createComponent(AdminDashboardComponent);
  40. });
  41. it('refreshes the stored operations role from the cloud identity', async () => {
  42. fixture.detectChanges();
  43. await fixture.whenStable();
  44. expect(sessions.applyIdentity).toHaveBeenCalledWith(jasmine.objectContaining({ operationsRole: 'ops-manager' }));
  45. });
  46. it('restores a recent dashboard without repeating its summary request', async () => {
  47. const data = await operations.dashboard();
  48. operations.dashboard.calls.reset();
  49. operations.dashboardSnapshot.and.returnValue({ data, ageMs: 1_000 });
  50. fixture.detectChanges();
  51. await fixture.whenStable();
  52. fixture.detectChanges();
  53. expect(operations.dashboard).not.toHaveBeenCalled();
  54. expect(fixture.nativeElement.querySelector('.welcome')?.textContent).toContain('运营经理');
  55. expect(fixture.componentInstance.loading()).toBeFalse();
  56. });
  57. it('keeps the previous dashboard visible when a stale snapshot cannot refresh', async () => {
  58. const data = await operations.dashboard();
  59. operations.dashboard.calls.reset();
  60. operations.dashboard.and.rejectWith(new Error('暂时不可用'));
  61. operations.dashboardSnapshot.and.returnValue({ data, ageMs: 31_000 });
  62. fixture.detectChanges();
  63. await fixture.whenStable();
  64. fixture.detectChanges();
  65. expect(operations.dashboard).toHaveBeenCalledTimes(1);
  66. expect(fixture.nativeElement.querySelector('.welcome')?.textContent).toContain('运营经理');
  67. expect(fixture.nativeElement.querySelector('.refresh-error')?.textContent).toContain('暂时不可用');
  68. });
  69. it('checks an old dashboard in the background when returning to its saved route', async () => {
  70. const data = await operations.dashboard();
  71. operations.dashboard.calls.reset();
  72. operations.dashboardSnapshot.and.returnValue({ data, ageMs: 1_000 });
  73. fixture.detectChanges();
  74. await fixture.whenStable();
  75. operations.dashboardSnapshot.and.returnValue({ data, ageMs: 31_000 });
  76. fixture.componentInstance.onOperationsRouteAttach();
  77. expect(fixture.componentInstance.loading()).toBeFalse();
  78. await fixture.whenStable();
  79. expect(operations.dashboard).toHaveBeenCalledTimes(1);
  80. });
  81. it('loads the default weekly schedule trend and switches to month', async () => {
  82. fixture.detectChanges();
  83. await fixture.whenStable();
  84. expect(fixture.componentInstance.trend()?.points.length).toBe(7);
  85. expect(fixture.componentInstance.selectedTrendPoint()?.courseCount).toBe(7);
  86. await fixture.componentInstance.selectTrendPeriod('month');
  87. expect(operations.scheduleTrend).toHaveBeenCalledWith('month');
  88. expect(fixture.componentInstance.trend()?.period).toBe('month');
  89. expect(fixture.componentInstance.chartLine('courseCount')).toContain(',');
  90. });
  91. it('merges daily course data into the trend and keeps only coach and pay overview links', async () => {
  92. fixture.detectChanges();
  93. await fixture.whenStable();
  94. fixture.detectChanges();
  95. const element: HTMLElement = fixture.nativeElement;
  96. expect(element.querySelector('.metric-grid')).toBeNull();
  97. expect(element.querySelector('.trend-stat.coaches')?.textContent).toContain('39');
  98. expect(element.querySelector('.trend-stat.pay')?.textContent).toContain('120');
  99. expect(element.querySelector('.trend-stat.courses')?.textContent).toContain('7');
  100. expect(element.querySelector('.trend-stat.students')?.textContent).toContain('6');
  101. expect(element.querySelectorAll('.trend-stat')).toHaveSize(4);
  102. });
  103. });