|
@@ -1,9 +1,11 @@
|
|
|
import { provideHttpClient } from '@angular/common/http';
|
|
import { provideHttpClient } from '@angular/common/http';
|
|
|
import { signal } from '@angular/core';
|
|
import { signal } from '@angular/core';
|
|
|
import { TestBed } from '@angular/core/testing';
|
|
import { TestBed } from '@angular/core/testing';
|
|
|
|
|
+import { By } from '@angular/platform-browser';
|
|
|
import { provideRouter, Router } from '@angular/router';
|
|
import { provideRouter, Router } from '@angular/router';
|
|
|
import { RouterTestingHarness } from '@angular/router/testing';
|
|
import { RouterTestingHarness } from '@angular/router/testing';
|
|
|
-import { Observable, of } from 'rxjs';
|
|
|
|
|
|
|
+import { Observable, of, Subject } from 'rxjs';
|
|
|
|
|
+import { AppointmentPageComponent } from '../features/appointment/appointment-page.component';
|
|
|
import { routes } from '../app.routes';
|
|
import { routes } from '../app.routes';
|
|
|
import { ApiService } from './api.service';
|
|
import { ApiService } from './api.service';
|
|
|
import { ApiEnvelope, AppUser } from './models';
|
|
import { ApiEnvelope, AppUser } from './models';
|
|
@@ -18,6 +20,9 @@ class LearningModulesApiService {
|
|
|
lastLearningWordUpdate: Record<string, unknown> = {};
|
|
lastLearningWordUpdate: Record<string, unknown> = {};
|
|
|
lastMemoryParams: Record<string, unknown> = {};
|
|
lastMemoryParams: Record<string, unknown> = {};
|
|
|
lastStandaloneLearningRecord: Record<string, unknown> = {};
|
|
lastStandaloneLearningRecord: Record<string, unknown> = {};
|
|
|
|
|
+ appointmentStatus = 0;
|
|
|
|
|
+ completionRequests: Record<string, unknown>[] = [];
|
|
|
|
|
+ completionResponse = new Subject<ApiEnvelope<unknown>>();
|
|
|
unitRows: Record<string, unknown>[] = [
|
|
unitRows: Record<string, unknown>[] = [
|
|
|
{ NodeID: 811, NodeName: 'Unit 1', yx_word: 1, total: 20 },
|
|
{ NodeID: 811, NodeName: 'Unit 1', yx_word: 1, total: 20 },
|
|
|
{ NodeID: 812, NodeName: 'Unit 2', yx_word: 0, total: 18 },
|
|
{ NodeID: 812, NodeName: 'Unit 2', yx_word: 0, total: 18 },
|
|
@@ -31,7 +36,7 @@ class LearningModulesApiService {
|
|
|
HoneyName: '张雅茜',
|
|
HoneyName: '张雅茜',
|
|
|
kcid: 801,
|
|
kcid: 801,
|
|
|
yykcmc: '七年级上册',
|
|
yykcmc: '七年级上册',
|
|
|
- dszt: 0,
|
|
|
|
|
|
|
+ dszt: this.appointmentStatus,
|
|
|
dslx: 2,
|
|
dslx: 2,
|
|
|
bxrq: '2026-08-10',
|
|
bxrq: '2026-08-10',
|
|
|
sdsd: '19:00',
|
|
sdsd: '19:00',
|
|
@@ -114,6 +119,10 @@ class LearningModulesApiService {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
post<T>(action: string, params: Record<string, unknown> = {}): Observable<ApiEnvelope<T>> {
|
|
post<T>(action: string, params: Record<string, unknown> = {}): Observable<ApiEnvelope<T>> {
|
|
|
|
|
+ if (action === 'e_order_complete_feedback') {
|
|
|
|
|
+ this.completionRequests.push(params);
|
|
|
|
|
+ return this.completionResponse as Observable<ApiEnvelope<T>>;
|
|
|
|
|
+ }
|
|
|
if (action === 'e_add_words') {
|
|
if (action === 'e_add_words') {
|
|
|
this.lastLearningWordUpdate = params;
|
|
this.lastLearningWordUpdate = params;
|
|
|
return this.result<T>({ affected: String(params['wordsId'] ?? '').split(',').filter(Boolean).length });
|
|
return this.result<T>({ affected: String(params['wordsId'] ?? '').split(',').filter(Boolean).length });
|
|
@@ -740,6 +749,88 @@ describe('home learning modules', () => {
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ it('opens feedback only on ending a lesson and submits once after validation', async () => {
|
|
|
|
|
+ user.set({ userId: 300, userName: 'coach', honeyName: '张老师', groupId: 3 });
|
|
|
|
|
+ session.groupId.set(3);
|
|
|
|
|
+ const api = TestBed.inject(ApiService) as unknown as LearningModulesApiService;
|
|
|
|
|
+ api.appointmentStatus = 10;
|
|
|
|
|
+ const harness = await RouterTestingHarness.create();
|
|
|
|
|
+ await harness.navigateByUrl('/pages/shop/get_order?id=901');
|
|
|
|
|
+ const component = harness.fixture.debugElement.query(By.directive(AppointmentPageComponent)).componentInstance as AppointmentPageComponent;
|
|
|
|
|
+ await harness.fixture.whenStable();
|
|
|
|
|
+ harness.fixture.detectChanges();
|
|
|
|
|
+ const dialog = harness.routeNativeElement?.querySelector('dialog') as HTMLDialogElement;
|
|
|
|
|
+ const endButton = harness.routeNativeElement?.querySelector('.appointment-actions .primary-button') as HTMLButtonElement;
|
|
|
|
|
+ expect(dialog.open).toBeFalse();
|
|
|
|
|
+ endButton.click();
|
|
|
|
|
+ harness.fixture.detectChanges();
|
|
|
|
|
+ expect(dialog.open).toBeTrue();
|
|
|
|
|
+ expect(api.completionRequests.length).toBe(0);
|
|
|
|
|
+
|
|
|
|
|
+ const submit = dialog.querySelector('.feedback-actions .primary-button') as HTMLButtonElement;
|
|
|
|
|
+ submit.click();
|
|
|
|
|
+ harness.fixture.detectChanges();
|
|
|
|
|
+ expect(dialog.querySelector('[role="alert"]')?.textContent).toContain('请填写本次学习内容');
|
|
|
|
|
+ expect(api.completionRequests.length).toBe(0);
|
|
|
|
|
+ const summary = dialog.querySelector('textarea') as HTMLTextAreaElement;
|
|
|
|
|
+ summary.value = 'Unit 3 词汇';
|
|
|
|
|
+ summary.dispatchEvent(new Event('input'));
|
|
|
|
|
+ await harness.fixture.whenStable();
|
|
|
|
|
+ (dialog.querySelector('.feedback-actions .secondary-button') as HTMLButtonElement).click();
|
|
|
|
|
+ expect(dialog.open).toBeFalse();
|
|
|
|
|
+ expect(api.appointmentStatus).toBe(10);
|
|
|
|
|
+ endButton.click();
|
|
|
|
|
+ harness.fixture.detectChanges();
|
|
|
|
|
+ expect((dialog.querySelector('textarea') as HTMLTextAreaElement).value).toBe('Unit 3 词汇');
|
|
|
|
|
+ component.reviewStatus = '完成';
|
|
|
|
|
+ component.trainingStatus = '需要巩固';
|
|
|
|
|
+ component.trainingSummary = '下次复习 Unit 3';
|
|
|
|
|
+ component.noHomework = true;
|
|
|
|
|
+ submit.click();
|
|
|
|
|
+ submit.click();
|
|
|
|
|
+ harness.fixture.detectChanges();
|
|
|
|
|
+ expect(api.completionRequests.length).toBe(1);
|
|
|
|
|
+ expect(JSON.parse(String(api.completionRequests[0]['feedback']))).toEqual(jasmine.objectContaining({ contentSummary: 'Unit 3 词汇', noHomework: true, homework: '无作业' }));
|
|
|
|
|
+ expect(submit.disabled).toBeTrue();
|
|
|
|
|
+ dialog.dispatchEvent(new Event('cancel', { cancelable: true }));
|
|
|
|
|
+ expect(dialog.open).toBeTrue();
|
|
|
|
|
+ api.appointmentStatus = 11;
|
|
|
|
|
+ api.completionResponse.next({ retcode: 1, result: {} });
|
|
|
|
|
+ api.completionResponse.complete();
|
|
|
|
|
+ await harness.fixture.whenStable();
|
|
|
|
|
+ harness.fixture.detectChanges();
|
|
|
|
|
+ expect(harness.routeNativeElement?.querySelector('dialog')).toBeNull();
|
|
|
|
|
+ expect(harness.routeNativeElement?.textContent).toContain('等待学员确认');
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ it('keeps feedback editable after a failed submission and allows closing with Escape', async () => {
|
|
|
|
|
+ user.set({ userId: 300, userName: 'coach', honeyName: '张老师', groupId: 3 });
|
|
|
|
|
+ session.groupId.set(3);
|
|
|
|
|
+ const api = TestBed.inject(ApiService) as unknown as LearningModulesApiService;
|
|
|
|
|
+ api.appointmentStatus = 10;
|
|
|
|
|
+ const harness = await RouterTestingHarness.create();
|
|
|
|
|
+ await harness.navigateByUrl('/pages/shop/get_order?id=901');
|
|
|
|
|
+ const component = harness.fixture.debugElement.query(By.directive(AppointmentPageComponent)).componentInstance as AppointmentPageComponent;
|
|
|
|
|
+ await harness.fixture.whenStable();
|
|
|
|
|
+ component.openCompletionFeedback();
|
|
|
|
|
+ component.contentSummary = 'Unit 3';
|
|
|
|
|
+ component.reviewStatus = '完成';
|
|
|
|
|
+ component.trainingStatus = '正常进行';
|
|
|
|
|
+ component.trainingSummary = '继续 Unit 4';
|
|
|
|
|
+ component.noHomework = true;
|
|
|
|
|
+ component.finishCoachLesson();
|
|
|
|
|
+ api.completionResponse.error(new Error('提交失败,请重试'));
|
|
|
|
|
+ harness.fixture.detectChanges();
|
|
|
|
|
+ const dialog = harness.routeNativeElement?.querySelector('dialog') as HTMLDialogElement;
|
|
|
|
|
+ expect(dialog.open).toBeTrue();
|
|
|
|
|
+ expect(dialog.querySelector('[role="alert"]')?.textContent).toContain('提交失败,请重试');
|
|
|
|
|
+ expect((dialog.querySelector('fieldset') as HTMLFieldSetElement).disabled).toBeFalse();
|
|
|
|
|
+ expect(component.contentSummary).toBe('Unit 3');
|
|
|
|
|
+ expect(api.appointmentStatus).toBe(10);
|
|
|
|
|
+ dialog.dispatchEvent(new Event('cancel', { cancelable: true }));
|
|
|
|
|
+ expect(dialog.open).toBeFalse();
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
it('counts words marked as known as completed and persists them for the student', async () => {
|
|
it('counts words marked as known as completed and persists them for the student', async () => {
|
|
|
user.set({ userId: 300, userName: 'coach', honeyName: '张老师', groupId: 3 });
|
|
user.set({ userId: 300, userName: 'coach', honeyName: '张老师', groupId: 3 });
|
|
|
session.groupId.set(3);
|
|
session.groupId.set(3);
|