| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111 |
- import { ComponentFixture, TestBed } from '@angular/core/testing';
- import { provideRouter } from '@angular/router';
- import { AdminSessionService } from '../admin-session.service';
- import { OperationsService } from '../operations.service';
- import { AdminDashboardComponent } from './admin-dashboard.component';
- describe('AdminDashboardComponent', () => {
- let fixture: ComponentFixture<AdminDashboardComponent>;
- let operations: jasmine.SpyObj<OperationsService>;
- let sessions: jasmine.SpyObj<AdminSessionService>;
- beforeEach(async () => {
- operations = jasmine.createSpyObj<OperationsService>('OperationsService', ['dashboard', 'dashboardSnapshot', 'scheduleTrend']);
- operations.dashboardSnapshot.and.returnValue(null);
- sessions = jasmine.createSpyObj<AdminSessionService>('AdminSessionService', ['applyIdentity']);
- operations.dashboard.and.resolveTo({
- identity: { objectId: 'admin-1', username: 'ops', displayName: '运营经理', roles: ['admin', 'ops-manager'], isSuperAdmin: false, operationsRole: 'ops-manager' },
- date: '2026-08-20', metrics: [
- { key: 'members', label: '在学会员', value: 27, hint: '当前有正式学习任务', route: '/admin/members', tone: 'blue' },
- { key: 'today', label: '今日课程', value: 37, hint: '今天计划进行的课程', route: '/admin/schedule', tone: 'amber' },
- { key: 'coaches', label: '在岗陪练', value: 39, hint: '实时业务账号', route: '/admin/coaches', tone: 'green' },
- { key: 'pay', label: '本月预估工资', value: 120, hint: '按有效上课记录估算', route: '/admin/payroll', tone: 'rose' }
- ], alerts: [], scheduleTrend: {
- period: 'week', periodLabel: '近 7 天', dateFrom: '2026-08-14', dateTo: '2026-08-20',
- 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 })),
- todayCourseCount: 7, todayStudentCount: 6, totalCourseCount: 28, totalStudentCount: 12, maxValue: 7,
- refreshedAt: '2026-08-20T04:00:00.000Z', liveSourceAvailable: true, liveSourceMessage: ''
- }
- });
- operations.scheduleTrend.and.resolveTo({
- period: 'month', periodLabel: '本月', dateFrom: '2026-08-01', dateTo: '2026-08-20',
- points: [{ key: '2026-08-20', label: '8月20日', shortLabel: '20日', courseCount: 12, studentCount: 9, completedCount: 5, isToday: true }],
- todayCourseCount: 12, todayStudentCount: 9, totalCourseCount: 12, totalStudentCount: 9, maxValue: 12,
- refreshedAt: '2026-08-20T04:00:00.000Z', liveSourceAvailable: true, liveSourceMessage: ''
- });
- await TestBed.configureTestingModule({ imports: [AdminDashboardComponent], providers: [
- provideRouter([]),
- { provide: OperationsService, useValue: operations },
- { provide: AdminSessionService, useValue: sessions }
- ] }).compileComponents();
- fixture = TestBed.createComponent(AdminDashboardComponent);
- });
- it('refreshes the stored operations role from the cloud identity', async () => {
- fixture.detectChanges();
- await fixture.whenStable();
- expect(sessions.applyIdentity).toHaveBeenCalledWith(jasmine.objectContaining({ operationsRole: 'ops-manager' }));
- });
- it('restores a recent dashboard without repeating its summary request', async () => {
- const data = await operations.dashboard();
- operations.dashboard.calls.reset();
- operations.dashboardSnapshot.and.returnValue({ data, ageMs: 1_000 });
- fixture.detectChanges();
- await fixture.whenStable();
- fixture.detectChanges();
- expect(operations.dashboard).not.toHaveBeenCalled();
- expect(fixture.nativeElement.querySelector('.welcome')?.textContent).toContain('运营经理');
- expect(fixture.componentInstance.loading()).toBeFalse();
- });
- it('keeps the previous dashboard visible when a stale snapshot cannot refresh', async () => {
- const data = await operations.dashboard();
- operations.dashboard.calls.reset();
- operations.dashboard.and.rejectWith(new Error('暂时不可用'));
- operations.dashboardSnapshot.and.returnValue({ data, ageMs: 31_000 });
- fixture.detectChanges();
- await fixture.whenStable();
- fixture.detectChanges();
- expect(operations.dashboard).toHaveBeenCalledTimes(1);
- expect(fixture.nativeElement.querySelector('.welcome')?.textContent).toContain('运营经理');
- expect(fixture.nativeElement.querySelector('.refresh-error')?.textContent).toContain('暂时不可用');
- });
- it('checks an old dashboard in the background when returning to its saved route', async () => {
- const data = await operations.dashboard();
- operations.dashboard.calls.reset();
- operations.dashboardSnapshot.and.returnValue({ data, ageMs: 1_000 });
- fixture.detectChanges();
- await fixture.whenStable();
- operations.dashboardSnapshot.and.returnValue({ data, ageMs: 31_000 });
- fixture.componentInstance.onOperationsRouteAttach();
- expect(fixture.componentInstance.loading()).toBeFalse();
- await fixture.whenStable();
- expect(operations.dashboard).toHaveBeenCalledTimes(1);
- });
- it('loads the default weekly schedule trend and switches to month', async () => {
- fixture.detectChanges();
- await fixture.whenStable();
- expect(fixture.componentInstance.trend()?.points.length).toBe(7);
- expect(fixture.componentInstance.selectedTrendPoint()?.courseCount).toBe(7);
- await fixture.componentInstance.selectTrendPeriod('month');
- expect(operations.scheduleTrend).toHaveBeenCalledWith('month');
- expect(fixture.componentInstance.trend()?.period).toBe('month');
- expect(fixture.componentInstance.chartLine('courseCount')).toContain(',');
- });
- it('merges daily course data into the trend and keeps only coach and pay overview links', async () => {
- fixture.detectChanges();
- await fixture.whenStable();
- fixture.detectChanges();
- const element: HTMLElement = fixture.nativeElement;
- expect(element.querySelector('.metric-grid')).toBeNull();
- expect(element.querySelector('.trend-stat.coaches')?.textContent).toContain('39');
- expect(element.querySelector('.trend-stat.pay')?.textContent).toContain('120');
- expect(element.querySelector('.trend-stat.courses')?.textContent).toContain('7');
- expect(element.querySelector('.trend-stat.students')?.textContent).toContain('6');
- expect(element.querySelectorAll('.trend-stat')).toHaveSize(4);
- });
- });
|