|
@@ -10,16 +10,18 @@ import { ApiService, ApiError } from './api.service';
|
|
|
import { SessionService } from './session.service';
|
|
import { SessionService } from './session.service';
|
|
|
import { PlatformService } from './platform.service';
|
|
import { PlatformService } from './platform.service';
|
|
|
import { appointmentDate, beijingDate } from './appointment-time';
|
|
import { appointmentDate, beijingDate } from './appointment-time';
|
|
|
|
|
+import { HomeDailyCacheService } from './home-daily-cache.service';
|
|
|
|
|
|
|
|
@Component({standalone:true,template:'课堂内容'}) class ClassroomStub {}
|
|
@Component({standalone:true,template:'课堂内容'}) class ClassroomStub {}
|
|
|
|
|
|
|
|
describe('appointment workflow screens',()=>{
|
|
describe('appointment workflow screens',()=>{
|
|
|
|
|
+ let dataChanges:Subject<{source:'local'|'broadcast';changedAt:string}>;
|
|
|
let failFeedback:boolean;
|
|
let failFeedback:boolean;
|
|
|
let order:Record<string,unknown>,failDetail:boolean,failCourses:boolean,courses:Record<string,unknown>[]|null,delayedUnits:Subject<unknown>|null,calls:Array<{action:string;params:Record<string,unknown>}>;
|
|
let order:Record<string,unknown>,failDetail:boolean,failCourses:boolean,courses:Record<string,unknown>[]|null,delayedUnits:Subject<unknown>|null,calls:Array<{action:string;params:Record<string,unknown>}>;
|
|
|
const user=signal({userId:30,groupId:3}),student=signal<unknown>(null),groupId=signal(3);
|
|
const user=signal({userId:30,groupId:3}),student=signal<unknown>(null),groupId=signal(3);
|
|
|
const response=(result:unknown)=>of({retcode:0,result});
|
|
const response=(result:unknown)=>of({retcode:0,result});
|
|
|
beforeEach(()=>{
|
|
beforeEach(()=>{
|
|
|
- localStorage.clear();sessionStorage.clear();calls=[];failFeedback=false;failDetail=false;failCourses=false;courses=null;delayedUnits=null;user.set({userId:30,groupId:3});groupId.set(3);
|
|
|
|
|
|
|
+ localStorage.clear();sessionStorage.clear();calls=[];failFeedback=false;failDetail=false;failCourses=false;courses=null;delayedUnits=null;dataChanges=new Subject();user.set({userId:30,groupId:3});groupId.set(3);
|
|
|
order={GeneralID:901,szyh:20,pl:30,HoneyName:'测试学员',kcid:801,bindingId:'binding-1',courseCategoryKey:'word',yykcmc:'词表',dszt:0,bxrq:'2099-10-11',sdsd:'19:00',allowedActions:['start'],updatedAt:'2026-10-11T00:00:00Z'};
|
|
order={GeneralID:901,szyh:20,pl:30,HoneyName:'测试学员',kcid:801,bindingId:'binding-1',courseCategoryKey:'word',yykcmc:'词表',dszt:0,bxrq:'2099-10-11',sdsd:'19:00',allowedActions:['start'],updatedAt:'2026-10-11T00:00:00Z'};
|
|
|
const invoke=(action:string,params:Record<string,unknown>={})=>{
|
|
const invoke=(action:string,params:Record<string,unknown>={})=>{
|
|
|
calls.push({action,params});
|
|
calls.push({action,params});
|
|
@@ -43,7 +45,7 @@ describe('appointment workflow screens',()=>{
|
|
|
{path:'pages/shop/stu_get_order',component:AppointmentPageComponent,data:{meta:{path:'pages/shop/stu_get_order'}}},
|
|
{path:'pages/shop/stu_get_order',component:AppointmentPageComponent,data:{meta:{path:'pages/shop/stu_get_order'}}},
|
|
|
{path:'pages/stu/stu_abc',component:ClassroomStub},
|
|
{path:'pages/stu/stu_abc',component:ClassroomStub},
|
|
|
]),
|
|
]),
|
|
|
- {provide:ApiService,useValue:{get:invoke,post:invoke,uploadLessonAttachment:(orderId:string,file:File,contentType:string)=>{calls.push({action:'attachment-upload',params:{orderId,file,contentType}});return response({id:'file-'+file.name,name:file.name,url:'https://files.example/'+encodeURIComponent(file.name),contentType,size:file.size});}}},
|
|
|
|
|
|
|
+ {provide:ApiService,useValue:{get:invoke,post:invoke,dataChanges$:dataChanges,uploadLessonAttachment:(orderId:string,file:File,contentType:string)=>{calls.push({action:'attachment-upload',params:{orderId,file,contentType}});return response({id:'file-'+file.name,name:file.name,url:'https://files.example/'+encodeURIComponent(file.name),contentType,size:file.size});}}},
|
|
|
{provide:SessionService,useValue:{user,student,groupId,token:signal('r:test'),isLoggedIn:()=>true,setStudent:(s:unknown)=>student.set(s)}},
|
|
{provide:SessionService,useValue:{user,student,groupId,token:signal('r:test'),isLoggedIn:()=>true,setStudent:(s:unknown)=>student.set(s)}},
|
|
|
{provide:PlatformService,useValue:{loading:signal(false),toast:()=>{},confirm:async()=>true}},
|
|
{provide:PlatformService,useValue:{loading:signal(false),toast:()=>{},confirm:async()=>true}},
|
|
|
]});
|
|
]});
|
|
@@ -68,6 +70,39 @@ describe('appointment workflow screens',()=>{
|
|
|
(harness.routeNativeElement?.querySelector('.appointment-actions .primary-button') as HTMLButtonElement).click();
|
|
(harness.routeNativeElement?.querySelector('.appointment-actions .primary-button') as HTMLButtonElement).click();
|
|
|
harness.fixture.detectChanges();expect(calls.filter(c=>c.action==='e_order_update_v2').length).toBe(1);expect(component.status()).toBe(10);
|
|
harness.fixture.detectChanges();expect(calls.filter(c=>c.action==='e_order_update_v2').length).toBe(1);expect(component.status()).toBe(10);
|
|
|
});
|
|
});
|
|
|
|
|
+ it('reloads a cached timetable after a writing lesson ends and keeps the selected day on return',async()=>{
|
|
|
|
|
+ order={...order,bxrq:beijingDate(),kcid:0,bindingId:'',courseCategoryKey:'high_writing'};
|
|
|
|
|
+ const {harness}=await screen('/pages/home/shop');
|
|
|
|
|
+ let center=harness.fixture.debugElement.query(By.directive(CourseCenterPageComponent)).componentInstance as CourseCenterPageComponent;
|
|
|
|
|
+ expect(center.statusText(center.schedules()[0])).toBe('未开始');
|
|
|
|
|
+ await harness.navigateByUrl('/pages/shop/get_order?id=901');await harness.fixture.whenStable();harness.fixture.detectChanges();
|
|
|
|
|
+ const detail=harness.fixture.debugElement.query(By.directive(AppointmentPageComponent)).componentInstance as AppointmentPageComponent;
|
|
|
|
|
+ detail.startOrContinue();await harness.fixture.whenStable();
|
|
|
|
|
+ expect(detail.status()).toBe(10);
|
|
|
|
|
+ await detail.endLesson();await harness.fixture.whenStable();
|
|
|
|
|
+ expect(detail.status()).toBe(30);
|
|
|
|
|
+ // ApiService invalidates snapshots before publishing a successful write (covered with real HTTP in api.service.spec).
|
|
|
|
|
+ TestBed.inject(HomeDailyCacheService).invalidateCompanion();
|
|
|
|
|
+ dataChanges.next({source:'local',changedAt:new Date().toISOString()});
|
|
|
|
|
+ await harness.navigateByUrl('/pages/home/shop');harness.fixture.detectChanges();
|
|
|
|
|
+ center=harness.fixture.debugElement.query(By.directive(CourseCenterPageComponent)).componentInstance as CourseCenterPageComponent;
|
|
|
|
|
+ expect(center.selectedDate()).toBe(beijingDate());
|
|
|
|
|
+ expect(center.statusText(center.schedules()[0])).toBe('已结课');
|
|
|
|
|
+ expect(calls.filter(c=>c.action==='app_companion_overview').length).toBe(2);
|
|
|
|
|
+ });
|
|
|
|
|
+ it('refreshes an open timetable when another tab announces a write and removes its listener on leave',async()=>{
|
|
|
|
|
+ order={...order,bxrq:beijingDate()};
|
|
|
|
|
+ const {harness}=await screen('/pages/home/shop');
|
|
|
|
|
+ const center=harness.fixture.debugElement.query(By.directive(CourseCenterPageComponent)).componentInstance as CourseCenterPageComponent;
|
|
|
|
|
+ order={...order,dszt:30};
|
|
|
|
|
+ TestBed.inject(HomeDailyCacheService).invalidateCompanion();
|
|
|
|
|
+ dataChanges.next({source:'broadcast',changedAt:new Date().toISOString()});harness.fixture.detectChanges();
|
|
|
|
|
+ expect(center.statusText(center.schedules()[0])).toBe('已结课');
|
|
|
|
|
+ expect(calls.filter(c=>c.action==='app_companion_overview').length).toBe(2);
|
|
|
|
|
+ await harness.navigateByUrl('/pages/shop/get_order?id=901');
|
|
|
|
|
+ dataChanges.next({source:'broadcast',changedAt:new Date().toISOString()});
|
|
|
|
|
+ expect(calls.filter(c=>c.action==='app_companion_overview').length).toBe(2);
|
|
|
|
|
+ });
|
|
|
for(const stage of ['primary','middle','high'])for(const duration of [30,60]) {
|
|
for(const stage of ['primary','middle','high'])for(const duration of [30,60]) {
|
|
|
it(`closes a ${duration}-minute ${stage} writing lesson without enrollment or vocabulary before saving teacher feedback`,async()=>{
|
|
it(`closes a ${duration}-minute ${stage} writing lesson without enrollment or vocabulary before saving teacher feedback`,async()=>{
|
|
|
order={...order,kcid:0,bindingId:'',courseCategoryKey:stage+'_writing',durationMinutes:duration,yykcmc:'阅读与写作',bxrq:beijingDate()};
|
|
order={...order,kcid:0,bindingId:'',courseCategoryKey:stage+'_writing',durationMinutes:duration,yykcmc:'阅读与写作',bxrq:beijingDate()};
|