;
- sharedServiceSpy.getTeamInfo.and.returnValue(of({} as any));
}));
it('should create', () => {
@@ -130,7 +108,7 @@ describe('ActivityComponent', () => {
expect(result).toEqual('');
});
- it('should show due date when task is overdue', () => {
+ it('should empty when task is overdue', () => {
const result = component.subtitle({
type: 'Assessment',
dueDate: 'dummy/date',
@@ -139,8 +117,7 @@ describe('ActivityComponent', () => {
name: 'unit tester'
},
} as any);
- // subtitle shows due date regardless of overdue status
- expect(result).toContain('Due Date:');
+ expect(result).toEqual('');
});
});
@@ -331,19 +308,19 @@ describe('ActivityComponent', () => {
});
describe('goto()', () => {
- it('should warn when user not in a team', async () => {
+ it('should warn when user not in a team', () => {
utilsSpy.isEmpty = jasmine.createSpy('isEmpty').and.returnValue(true);
- await component.goto({
+ component.goto({
isForTeam: true,
type: 'Locked',
} as any);
expect(notificationsSpy.alert).toHaveBeenCalled();
});
- it('should warn activity is locked', async () => {
+ it('should warn activity is locked', () => {
utilsSpy.isEmpty = jasmine.createSpy('isEmpty').and.returnValue(true);
const spy = spyOn(component.navigate, 'emit');
- await component.goto({
+ component.goto({
isForTeam: false,
type: 'Locked',
} as any);
@@ -351,10 +328,10 @@ describe('ActivityComponent', () => {
expect(spy).not.toHaveBeenCalled();
});
- it('should emit "navigate" event', async () => {
+ it('should emit "navigate" event', () => {
utilsSpy.isEmpty = jasmine.createSpy('isEmpty').and.returnValue(true);
const spy = spyOn(component.navigate, 'emit');
- await component.goto({
+ component.goto({
isForTeam: false,
type: 'in progress',
} as any);
@@ -362,10 +339,10 @@ describe('ActivityComponent', () => {
expect(spy).toHaveBeenCalled();
});
- it('should emit "navigate" event through keyboardEvent', async () => {
+ it('should emit "navigate" event through keyboardEvent', () => {
utilsSpy.isEmpty = jasmine.createSpy('isEmpty').and.returnValue(true);
const spy = spyOn(component.navigate, 'emit');
- await component.goto({
+ component.goto({
isForTeam: false,
type: 'in progress',
} as any, new KeyboardEvent('keydown', {
diff --git a/projects/v3/src/app/components/activity/activity.component.ts b/projects/v3/src/app/components/activity/activity.component.ts
index 6c53487dc..8e5fa9942 100644
--- a/projects/v3/src/app/components/activity/activity.component.ts
+++ b/projects/v3/src/app/components/activity/activity.component.ts
@@ -11,7 +11,6 @@ import { UtilsService } from '@v3/services/utils.service';
import { takeUntil, distinctUntilChanged } from 'rxjs/operators';
@Component({
- standalone: false,
selector: 'app-activity',
templateUrl: './activity.component.html',
styleUrls: ['./activity.component.scss'],
diff --git a/projects/v3/src/app/components/assessment/assessment.component.html b/projects/v3/src/app/components/assessment/assessment.component.html
index 71aa7697c..f8a358846 100644
--- a/projects/v3/src/app/components/assessment/assessment.component.html
+++ b/projects/v3/src/app/components/assessment/assessment.component.html
@@ -126,7 +126,8 @@
- {{ label }}
+
MockValueAccessorDirective),
- multi: true
- }
- ]
-})
-class MockValueAccessorDirective implements ControlValueAccessor {
- writeValue(obj: any): void {}
- registerOnChange(fn: any): void {}
- registerOnTouched(fn: any): void {}
-}
-
class Page {
get savingMessage() {
return this.query('ion-title.sub-title');
@@ -203,8 +182,8 @@ describe('AssessmentComponent', () => {
beforeEach(async () => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule, HttpClientTestingModule],
- declarations: [AssessmentComponent, MockValueAccessorDirective],
- schemas: [CUSTOM_ELEMENTS_SCHEMA, NO_ERRORS_SCHEMA],
+ declarations: [AssessmentComponent],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{
provide: ActivatedRoute,
@@ -238,7 +217,7 @@ describe('AssessmentComponent', () => {
},
{
provide: NotificationsService,
- useValue: jasmine.createSpyObj('NotificationsService', ['alert', 'customToast', 'popUp', 'presentToast', 'modalOnly', 'assessmentSubmittedToast'])
+ useValue: jasmine.createSpyObj('NotificationsService', ['alert', 'customToast', 'popUp', 'presentToast', 'modalOnly'])
},
{
provide: ActivityService,
@@ -296,10 +275,6 @@ describe('AssessmentComponent', () => {
assessmentSpy.saveFeedbackReviewed.and.returnValue(of({ success: true }));
// activitySpy.goToNextTask.and.returnValue(Promise.resolve());
storageSpy.getUser.and.returnValue(mockUser);
-
- // initialize btnDisabled$ as it's an @Input that is normally set by parent
- component.btnDisabled$ = new BehaviorSubject(false);
- component.savingMessage$ = new BehaviorSubject('');
});
it('should be created', () => {
@@ -352,25 +327,22 @@ describe('AssessmentComponent', () => {
expect(component.ngOnChanges({})).toBeFalsy();
});
- it('should update assessment with latest data', () => {
- component.assessment = mockAssessment;
- component.action = 'assessment';
- component.ngOnChanges({ assessment: {} as any });
-
- expect(component.doAssessment).toEqual(true);
- expect(component.feedbackReviewed).toEqual(false);
- // btnDisabled$ is true because required questions are not answered yet
- expect(component.btnDisabled$.value).toEqual(true);
- expect(component.isNotInATeam).toEqual(false);
- expect(component.isPendingReview).toEqual(false);
- });
+ it('should update assessment with latest data', () => {
+ component.assessment = mockAssessment;
+ component.ngOnChanges({});
+
+ expect(component.doAssessment).toEqual(true);
+ expect(component.feedbackReviewed).toEqual(false);
+ expect(component.btnDisabled$.value).toEqual(false);
+ expect(component.isNotInATeam).toEqual(false);
+ expect(component.isPendingReview).toEqual(false);
+ });
it('should not allow submission if locked', () => {
component.assessment = mockAssessment;
- component.action = 'assessment';
- // Create a copy to avoid test pollution
- component.submission = { ...mockSubmission, isLocked: true } as any;
- component.ngOnChanges({ submission: {} as any });
+ component.submission = mockSubmission as any;
+ component.submission.isLocked = true;
+ component.ngOnChanges({});
expect(component.doAssessment).toEqual(false);
expect(component.submission.status).toEqual('done');
@@ -380,10 +352,9 @@ describe('AssessmentComponent', () => {
it('should not allow submission', () => {
component.assessment = mockAssessment;
- component.action = 'assessment';
- // Create a copy to avoid test pollution
- component.submission = { ...mockSubmission, isLocked: true } as any;
- component.ngOnChanges({ submission: {} as any });
+ component.submission = mockSubmission as any;
+ component.submission.isLocked = true;
+ component.ngOnChanges({});
expect(component.doAssessment).toEqual(false);
expect(component.submission.status).toEqual('done');
@@ -393,26 +364,18 @@ describe('AssessmentComponent', () => {
it('should save & publish "saving" message', fakeAsync(() => {
component.assessment = mockAssessment;
- component.action = 'assessment';
- // Create a copy to avoid test pollution
- component.submission = { ...mockSubmission, isLocked: false, status: 'in progress' } as any;
+ component.submission = mockSubmission as any;
+ component.submission.isLocked = false;
+ component.submission.status = 'in progress';
component.savingMessage$ = new BehaviorSubject('');
const spy = spyOn(component.savingMessage$, 'next');
- // Pre-create form controls to avoid NG01203 error when change detection runs
- mockQuestions.forEach(q => {
- component.questionsForm.addControl('q-' + q.id, new FormControl(null));
- });
- component.ngOnChanges({ submission: {} as any });
+ component.ngOnChanges({});
- // Flush all pending timers (200ms for initializePageCompletion, 250ms for scrollActivePageIntoView, 300ms for form subscription)
- tick(350);
+ tick();
expect(component.doAssessment).toBeTrue();
const lastSaveMsg = 'Last saved ' + utils.timeFormatter(component.submission.modified);
expect(spy).toHaveBeenCalledWith(lastSaveMsg);
- // btnDisabled$ is true because required questions are not answered yet
- expect(component.btnDisabled$.value).toEqual(true);
- // Flush any remaining timers
- flush();
+ expect(component.btnDisabled$.value).toEqual(false);
}));
it('should flag assessment as "pending review"', () => {
@@ -428,7 +391,7 @@ describe('AssessmentComponent', () => {
const spy = spyOn(component.savingMessage$, 'next');
component.action = 'review';
- component.ngOnChanges({ review: {} as any });
+ component.ngOnChanges({});
const lastSaveMsg = 'Last saved ' + utils.timeFormatter(component.review.modified);
expect(spy).toHaveBeenCalledWith(lastSaveMsg);
@@ -443,7 +406,7 @@ describe('AssessmentComponent', () => {
component.submission = mockSubmission as any;
component.submission.isLocked = false;
component.submission.status = 'done';
- component.ngOnChanges({ submission: {} as any });
+ component.ngOnChanges({});
expect(component.feedbackReviewed).toEqual(component.submission.completed);
});
@@ -452,7 +415,6 @@ describe('AssessmentComponent', () => {
it('should list unanswered required questions from compulsoryQuestionsAnswered()', () => {
expect(component['_compulsoryQuestionsAnswered']).toBeDefined();
component.assessment = mockAssessment;
- component.action = 'assessment';
const answers = [
{
'questionId': 123,
@@ -464,17 +426,6 @@ describe('AssessmentComponent', () => {
}
];
- // Mock form element - create a mock form object
- component.form = {
- nativeElement: {
- querySelector: jasmine.createSpy('querySelector').and.returnValue({
- classList: {
- add: jasmine.createSpy('add')
- }
- })
- }
- } as any;
-
const unansweredQuestions = component['_compulsoryQuestionsAnswered'](answers);
expect(unansweredQuestions).toEqual([mockQuestions[0]]);
});
@@ -540,7 +491,6 @@ describe('AssessmentComponent', () => {
component.doAssessment = true;
component.isPendingReview = false;
- component.action = 'assessment';
// Call the method
component['_populateQuestionsForm']();
@@ -558,7 +508,7 @@ describe('AssessmentComponent', () => {
const optionalControl = component.questionsForm.get('q-2');
expect(optionalControl.validator).toBeFalsy();
- // Check that multi team member selector has plain array initial value in assessment mode
+ // Check that multi team member selector has array initial value
const multiControl = component.questionsForm.get('q-3');
expect(multiControl.value).toEqual([]);
});
@@ -796,8 +746,7 @@ describe('AssessmentComponent', () => {
groups: []
} as any;
- // isEmpty is already spied by TestUtils, just override return value
- (utils.isEmpty as jasmine.Spy).and.returnValue(true);
+ spyOn(utils, 'isEmpty').and.returnValue(true);
component['_populateQuestionsForm']();
@@ -830,19 +779,13 @@ describe('AssessmentComponent', () => {
spyOn(component, 'initializePageCompletion');
spyOn(component, 'setSubmissionDisabled');
- // isEmpty is already spied by TestUtils, just override return value
- (utils.isEmpty as jasmine.Spy).and.returnValue(false);
+ spyOn(utils, 'isEmpty').and.returnValue(false);
component['_populateQuestionsForm']();
- // Wait for the setTimeout(300) that sets up the subscription
- tick(300);
-
// Trigger form value change
component.questionsForm.get('q-1').setValue('test value');
-
- // Wait for debounceTime(300)
- tick(300);
+ tick(300); // Wait for debounce
expect(component.initializePageCompletion).toHaveBeenCalled();
expect(component.setSubmissionDisabled).toHaveBeenCalled();
@@ -1231,10 +1174,9 @@ describe('AssessmentComponent', () => {
component.isPendingReview = true;
expect(component.btnText).toEqual('submit answers');
- // continueToNextTask pushes to submitActions, which then triggers _submitAnswer via subscription
- const spy = spyOn(component.submitActions, 'next');
+ const spy = spyOn(component, '_submitAnswer');
component.continueToNextTask();
- expect(spy).toHaveBeenCalledWith({ autoSave: false, goBack: false });
+ expect(spy).toHaveBeenCalled();
});
it('should mark feedback as read', () => {
@@ -1336,12 +1278,12 @@ describe('AssessmentComponent', () => {
expect(component.labelColor).toEqual('');
});
- it('should return danger when status is in progress and is overdue', () => {
+ it('should return empty when status is unknown', () => {
component.submission.status = 'in progress';
component.assessment.isForTeam = false;
component.assessment.isOverdue = true;
component.submission.isLocked = false;
- expect(component.labelColor).toEqual('danger');
+ expect(component.labelColor).toEqual('');
});
});
@@ -1424,9 +1366,6 @@ describe('AssessmentComponent', () => {
});
it('should return questions that are required but not answered', () => {
- // Set action to assessment
- component.action = 'assessment';
-
// Set up mock assessment with required questions
component.assessment = {
id: 1,
@@ -1461,16 +1400,12 @@ describe('AssessmentComponent', () => {
// Question 2 is missing
];
- // Mock form element - create a mock form object
- component.form = {
- nativeElement: {
- querySelector: jasmine.createSpy('querySelector').and.returnValue({
- classList: {
- add: jasmine.createSpy('add')
- }
- })
+ // Mock form element
+ spyOn(component.form.nativeElement, 'querySelector').and.returnValue({
+ classList: {
+ add: jasmine.createSpy('add')
}
- } as any;
+ });
// Test the function
const missingQuestions = component['_compulsoryQuestionsAnswered'](answers);
@@ -1548,16 +1483,12 @@ describe('AssessmentComponent', () => {
{ questionId: 1, answer: '', file: null }
];
- // Mock form element - create a mock form object
- component.form = {
- nativeElement: {
- querySelector: jasmine.createSpy('querySelector').and.returnValue({
- classList: {
- add: jasmine.createSpy('add')
- }
- })
+ // Mock form element
+ spyOn(component.form.nativeElement, 'querySelector').and.returnValue({
+ classList: {
+ add: jasmine.createSpy('add')
}
- } as any;
+ });
// Test the function
const missingQuestions = component['_compulsoryQuestionsAnswered'](answers);
@@ -1771,12 +1702,9 @@ describe('AssessmentComponent', () => {
component.doAssessment = true;
component['submitting'] = true;
component.btnDisabled$.next(true);
- component.assessment = { ...mockAssessment };
component.questionsForm = new FormGroup({
'q-123': new FormControl(null, Validators.required),
});
- // mock the form ViewChild to prevent nativeElement errors
- component.form = { nativeElement: document.createElement('div') } as any;
});
it('should reset submitting when required questions are missing', async () => {
@@ -1823,532 +1751,12 @@ describe('AssessmentComponent', () => {
});
});
- describe('areAllRequiredQuestionsAnswered()', () => {
- beforeEach(() => {
- component.action = 'assessment';
- component.doAssessment = true;
- component.isPendingReview = false;
- });
-
- it('should return true when there are no questions', () => {
- const result = component['areAllRequiredQuestionsAnswered']([]);
- expect(result).toBeTrue();
- });
-
- it('should return true when no questions are required', () => {
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl('answer'),
- });
- const questions = [{
- id: 1,
- name: 'Optional',
- type: 'text',
- isRequired: false,
- audience: ['submitter'],
- }] as any[];
-
- const result = component['areAllRequiredQuestionsAnswered'](questions);
- expect(result).toBeTrue();
- });
-
- it('should return true when required text question has a value', () => {
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl('some text'),
- });
- const questions = [{
- id: 1,
- name: 'Text Q',
- type: 'text',
- isRequired: true,
- audience: ['submitter'],
- }] as any[];
-
- expect(component['areAllRequiredQuestionsAnswered'](questions)).toBeTrue();
- });
-
- it('should return false when required text question is empty', () => {
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl(''),
- });
- const questions = [{
- id: 1,
- name: 'Text Q',
- type: 'text',
- isRequired: true,
- audience: ['submitter'],
- }] as any[];
-
- expect(component['areAllRequiredQuestionsAnswered'](questions)).toBeFalse();
- });
-
- it('should return false when required text question is null', () => {
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl(null),
- });
- const questions = [{
- id: 1,
- name: 'Text Q',
- type: 'text',
- isRequired: true,
- audience: ['submitter'],
- }] as any[];
-
- expect(component['areAllRequiredQuestionsAnswered'](questions)).toBeFalse();
- });
-
- it('should return true when required multi-choice question has selections', () => {
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl(['option1', 'option2']),
- });
- const questions = [{
- id: 1,
- name: 'Multi Q',
- type: 'multiple',
- isRequired: true,
- audience: ['submitter'],
- }] as any[];
-
- expect(component['areAllRequiredQuestionsAnswered'](questions)).toBeTrue();
- });
-
- it('should return false when required multi-choice question has empty array', () => {
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl([]),
- });
- const questions = [{
- id: 1,
- name: 'Multi Q',
- type: 'multiple',
- isRequired: true,
- audience: ['submitter'],
- }] as any[];
-
- expect(component['areAllRequiredQuestionsAnswered'](questions)).toBeFalse();
- });
-
- it('should return true when required review question has answer', () => {
- component.action = 'review';
- component.doAssessment = false;
- component.isPendingReview = true;
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl({ answer: 'review text', comment: 'good', file: null }),
- });
- const questions = [{
- id: 1,
- name: 'Review Q',
- type: 'text',
- isRequired: true,
- audience: ['reviewer'],
- }] as any[];
-
- expect(component['areAllRequiredQuestionsAnswered'](questions)).toBeTrue();
- });
-
- it('should return false when required review question has empty answer', () => {
- component.action = 'review';
- component.doAssessment = false;
- component.isPendingReview = true;
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl({ answer: '', comment: '', file: null }),
- });
- const questions = [{
- id: 1,
- name: 'Review Q',
- type: 'text',
- isRequired: true,
- audience: ['reviewer'],
- }] as any[];
-
- expect(component['areAllRequiredQuestionsAnswered'](questions)).toBeFalse();
- });
-
- it('should return false when control does not exist', () => {
- component.questionsForm = new FormGroup({});
- const questions = [{
- id: 1,
- name: 'Missing Q',
- type: 'text',
- isRequired: true,
- audience: ['submitter'],
- }] as any[];
-
- expect(component['areAllRequiredQuestionsAnswered'](questions)).toBeFalse();
- });
-
- it('should return false when control is invalid', () => {
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl(null, Validators.required),
- });
- const questions = [{
- id: 1,
- name: 'Invalid Q',
- type: 'text',
- isRequired: true,
- audience: ['submitter'],
- }] as any[];
-
- expect(component['areAllRequiredQuestionsAnswered'](questions)).toBeFalse();
- });
-
- it('should skip questions not in current role audience', () => {
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl(''),
- });
- // required but only for reviewer, not submitter
- const questions = [{
- id: 1,
- name: 'Reviewer Only',
- type: 'text',
- isRequired: true,
- audience: ['reviewer'],
- }] as any[];
-
- // submitter role will not consider this as required
- expect(component['areAllRequiredQuestionsAnswered'](questions)).toBeTrue();
- });
-
- it('should handle mix of answered and unanswered required questions', () => {
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl('answered'),
- 'q-2': new FormControl(''),
- });
- const questions = [
- { id: 1, name: 'Q1', type: 'text', isRequired: true, audience: ['submitter'] },
- { id: 2, name: 'Q2', type: 'text', isRequired: true, audience: ['submitter'] },
- ] as any[];
-
- expect(component['areAllRequiredQuestionsAnswered'](questions)).toBeFalse();
- });
-
- it('should return true when all mixed required questions are answered', () => {
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl('text answer'),
- 'q-2': new FormControl(['choice1']),
- 'q-3': new FormControl('optional'),
- });
- const questions = [
- { id: 1, name: 'Q1', type: 'text', isRequired: true, audience: ['submitter'] },
- { id: 2, name: 'Q2', type: 'multiple', isRequired: true, audience: ['submitter'] },
- { id: 3, name: 'Q3', type: 'text', isRequired: false, audience: ['submitter'] },
- ] as any[];
-
- expect(component['areAllRequiredQuestionsAnswered'](questions)).toBeTrue();
- });
- });
-
- describe('initializePageCompletion()', () => {
- beforeEach(() => {
- component.assessment = {
- ...mockAssessment,
- groups: [
- {
- name: 'Group 1',
- questions: [
- { id: 1, name: 'Q1', type: 'text', isRequired: true, audience: ['submitter'] },
- { id: 2, name: 'Q2', type: 'text', isRequired: false, audience: ['submitter'] },
- ],
- },
- ],
- } as any;
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl('answered'),
- 'q-2': new FormControl(''),
- });
- spyOn(component, 'scrollActivePageIntoView');
- });
-
- it('should return early when pagination is disabled', fakeAsync(() => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(false);
- component.pageRequiredCompletion = [];
-
- component.initializePageCompletion();
- tick(200);
-
- expect(component.pageRequiredCompletion).toEqual([]);
- }));
-
- it('should set all pages complete in read-only mode', fakeAsync(() => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(true);
- component.doAssessment = false;
- component.isPendingReview = false;
- component.pagesGroups = [
- [{ name: 'G1', questions: [{ id: 1 }] as any[] }],
- [{ name: 'G2', questions: [{ id: 2 }] as any[] }],
- ];
-
- component.initializePageCompletion();
- tick(200);
-
- expect(component.pageRequiredCompletion).toEqual([true, true]);
- expect(component.scrollActivePageIntoView).toHaveBeenCalled();
- }));
-
- it('should evaluate each page completion in edit mode', fakeAsync(() => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(true);
- component.doAssessment = true;
- component.action = 'assessment';
- component.pagesGroups = [
- [{ name: 'G1', questions: [
- { id: 1, name: 'Q1', type: 'text', isRequired: true, audience: ['submitter'] } as any,
- ] }],
- [{ name: 'G2', questions: [
- { id: 2, name: 'Q2', type: 'text', isRequired: true, audience: ['submitter'] } as any,
- ] }],
- ];
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl('answered'),
- 'q-2': new FormControl(''),
- });
-
- component.initializePageCompletion();
- tick(200);
-
- // page 0 has answered required question → true
- expect(component.pageRequiredCompletion[0]).toBeTrue();
- // page 1 has unanswered required question → false
- expect(component.pageRequiredCompletion[1]).toBeFalse();
- expect(component.scrollActivePageIntoView).toHaveBeenCalled();
- }));
- });
-
- describe('findAndGoToFirstUnansweredQuestion()', () => {
- beforeEach(() => {
- component.action = 'assessment';
- component.doAssessment = true;
- spyOn(component, 'goToQuestion');
- });
-
- it('should return false when all required questions are answered (no pagination)', () => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(false);
- component.assessment = {
- ...mockAssessment,
- groups: [{
- name: 'G1',
- questions: [
- { id: 1, name: 'Q1', type: 'text', isRequired: true, audience: ['submitter'] },
- ],
- }],
- } as any;
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl('answered'),
- });
-
- const result = component.findAndGoToFirstUnansweredQuestion();
-
- expect(result).toBeFalse();
- expect(component.goToQuestion).not.toHaveBeenCalled();
- });
-
- it('should find unanswered question and navigate to it (no pagination)', () => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(false);
- component.assessment = {
- ...mockAssessment,
- groups: [{
- name: 'G1',
- questions: [
- { id: 1, name: 'Q1', type: 'text', isRequired: true, audience: ['submitter'] },
- { id: 2, name: 'Q2', type: 'text', isRequired: true, audience: ['submitter'] },
- ],
- }],
- } as any;
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl('answered'),
- 'q-2': new FormControl(''),
- });
-
- const result = component.findAndGoToFirstUnansweredQuestion();
-
- expect(result).toBeTrue();
- expect(component.goToQuestion).toHaveBeenCalledWith(1);
- });
-
- it('should find unanswered question on current page (with pagination)', () => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(true);
- component.pageIndex = 0;
- component.pagesGroups = [
- [{ name: 'G1', questions: [
- { id: 1, name: 'Q1', type: 'text', isRequired: true, audience: ['submitter'] } as any,
- { id: 2, name: 'Q2', type: 'text', isRequired: true, audience: ['submitter'] } as any,
- ] }],
- ];
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl('answered'),
- 'q-2': new FormControl(''),
- });
-
- const result = component.findAndGoToFirstUnansweredQuestion();
-
- expect(result).toBeTrue();
- expect(component.goToQuestion).toHaveBeenCalledWith(1);
- });
-
- it('should detect unanswered multi-choice question (empty array)', () => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(false);
- component.assessment = {
- ...mockAssessment,
- groups: [{
- name: 'G1',
- questions: [
- { id: 1, name: 'Q1', type: 'multiple', isRequired: true, audience: ['submitter'] },
- ],
- }],
- } as any;
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl([]),
- });
-
- const result = component.findAndGoToFirstUnansweredQuestion();
-
- expect(result).toBeTrue();
- expect(component.goToQuestion).toHaveBeenCalledWith(0);
- });
-
- it('should detect unanswered review question (empty answer in object)', () => {
- component.action = 'review';
- component.doAssessment = false;
- component.isPendingReview = true;
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(false);
- component.assessment = {
- ...mockAssessment,
- groups: [{
- name: 'G1',
- questions: [
- { id: 1, name: 'Q1', type: 'text', isRequired: true, audience: ['reviewer'] },
- ],
- }],
- } as any;
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl({ answer: '', comment: '', file: null }),
- });
-
- const result = component.findAndGoToFirstUnansweredQuestion();
-
- expect(result).toBeTrue();
- expect(component.goToQuestion).toHaveBeenCalledWith(0);
- });
-
- it('should return false when no required questions exist', () => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(false);
- component.assessment = {
- ...mockAssessment,
- groups: [{
- name: 'G1',
- questions: [
- { id: 1, name: 'Q1', type: 'text', isRequired: false, audience: ['submitter'] },
- ],
- }],
- } as any;
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl(''),
- });
-
- const result = component.findAndGoToFirstUnansweredQuestion();
-
- expect(result).toBeFalse();
- expect(component.goToQuestion).not.toHaveBeenCalled();
- });
- });
-
- describe('_answerRequiredValidatorForReviewer()', () => {
- it('should return required error for null value', () => {
- const control = new FormControl(null);
- const result = component['_answerRequiredValidatorForReviewer'](control);
- expect(result).toEqual({ required: true });
- });
-
- it('should return required error when answer and file are both empty', () => {
- const control = new FormControl({ answer: '', file: {} });
- const result = component['_answerRequiredValidatorForReviewer'](control);
- expect(result).toEqual({ required: true });
- });
-
- it('should return null when answer has content', () => {
- const control = new FormControl({ answer: 'some review', file: {} });
- const result = component['_answerRequiredValidatorForReviewer'](control);
- expect(result).toBeNull();
- });
-
- it('should return null when file has content but answer is empty', () => {
- const control = new FormControl({ answer: '', file: { url: 'https://cdn/file.pdf', path: '/uploads/file' } });
- const result = component['_answerRequiredValidatorForReviewer'](control);
- expect(result).toBeNull();
- });
-
- it('should return required error for empty string value', () => {
- const control = new FormControl('');
- const result = component['_answerRequiredValidatorForReviewer'](control);
- expect(result).toEqual({ required: true });
- });
-
- it('should return null for non-empty string value', () => {
- const control = new FormControl('some text');
- const result = component['_answerRequiredValidatorForReviewer'](control);
- expect(result).toBeNull();
- });
-
- it('should return required error when answer is empty array and file is empty', () => {
- const control = new FormControl({ answer: [], file: {} });
- const result = component['_answerRequiredValidatorForReviewer'](control);
- expect(result).toEqual({ required: true });
- });
-
- it('should return null when answer is non-empty array', () => {
- const control = new FormControl({ answer: ['choice1'], file: {} });
- const result = component['_answerRequiredValidatorForReviewer'](control);
- expect(result).toBeNull();
- });
- });
-
- describe('_fileRequiredValidatorForLearner()', () => {
- it('should return required error for null value', () => {
- const control = new FormControl(null);
- const result = component['_fileRequiredValidatorForLearner'](control);
- expect(result).toEqual({ required: true });
- });
-
- it('should return required error for undefined value', () => {
- const control = new FormControl(undefined);
- const result = component['_fileRequiredValidatorForLearner'](control);
- expect(result).toEqual({ required: true });
- });
-
- it('should return required error for empty object', () => {
- const control = new FormControl({});
- const result = component['_fileRequiredValidatorForLearner'](control);
- expect(result).toEqual({ required: true });
- });
-
- it('should return required error when object has no url', () => {
- const control = new FormControl({ name: 'file.pdf', path: '/uploads/file' });
- const result = component['_fileRequiredValidatorForLearner'](control);
- expect(result).toEqual({ required: true });
- });
-
- it('should return required error when url is empty string', () => {
- const control = new FormControl({ url: '' });
- const result = component['_fileRequiredValidatorForLearner'](control);
- expect(result).toEqual({ required: true });
- });
-
- it('should return null when file object has url', () => {
- const control = new FormControl({ url: 'https://cdn/file.pdf', name: 'file.pdf', path: '/uploads/file' });
- const result = component['_fileRequiredValidatorForLearner'](control);
- expect(result).toBeNull();
- });
-
- it('should return required error for string value', () => {
- const control = new FormControl('some string');
- const result = component['_fileRequiredValidatorForLearner'](control);
- expect(result).toEqual({ required: true });
- });
- });
-
describe('CORE-8182: pagination indicator accuracy in review mode', () => {
const reviewAssessment: Assessment = {
id: 1,
name: 'review test',
description: '',
- type: 'moderated',
+ type: 'quiz',
isForTeam: false,
dueDate: '2029-02-02',
isOverdue: false,
@@ -2438,7 +1846,7 @@ describe('AssessmentComponent', () => {
});
it('should use _answerRequiredValidatorForReviewer for multiple type in review mode', () => {
- component.ngOnChanges({ assessment: {} as any });
+ component.ngOnChanges({});
const control = component.questionsForm.controls['q-3'];
expect(control).toBeTruthy();
// empty array answer should be invalid
@@ -2450,7 +1858,7 @@ describe('AssessmentComponent', () => {
});
it('should use _answerRequiredValidatorForReviewer for multi-team-member-selector type in review mode', () => {
- component.ngOnChanges({ assessment: {} as any });
+ component.ngOnChanges({});
const control = component.questionsForm.controls['q-6'];
expect(control).toBeTruthy();
// empty array answer should be invalid
@@ -2462,7 +1870,7 @@ describe('AssessmentComponent', () => {
});
it('should use _answerRequiredValidatorForReviewer for oneof type in review mode', () => {
- component.ngOnChanges({ assessment: {} as any });
+ component.ngOnChanges({});
const control = component.questionsForm.controls['q-2'];
expect(control).toBeTruthy();
// empty answer should be invalid
@@ -2474,7 +1882,7 @@ describe('AssessmentComponent', () => {
});
it('should use _answerRequiredValidatorForReviewer for team-member-selector type in review mode', () => {
- component.ngOnChanges({ assessment: {} as any });
+ component.ngOnChanges({});
const control = component.questionsForm.controls['q-5'];
expect(control).toBeTruthy();
// empty answer should be invalid
@@ -2486,360 +1894,4 @@ describe('AssessmentComponent', () => {
});
});
});
-
- describe('splitGroupsByQuestionCount()', () => {
- beforeEach(() => {
- component.pageSize = 8;
- });
-
- it('should fit multiple small groups on one page', () => {
- component.assessment = {
- ...mockAssessment,
- groups: [
- { name: 'G1', questions: Array.from({ length: 3 }, (_, i) => ({ id: i + 1 })) as any[] },
- { name: 'G2', questions: Array.from({ length: 4 }, (_, i) => ({ id: i + 10 })) as any[] },
- ],
- } as any;
-
- const pages = component['splitGroupsByQuestionCount']();
-
- expect(pages.length).toBe(1);
- expect(pages[0].length).toBe(2);
- });
-
- it('should push groups to new page when current page is full', () => {
- component.assessment = {
- ...mockAssessment,
- groups: [
- { name: 'G1', questions: Array.from({ length: 8 }, (_, i) => ({ id: i + 1 })) as any[] },
- { name: 'G2', questions: Array.from({ length: 3 }, (_, i) => ({ id: i + 10 })) as any[] },
- ],
- } as any;
-
- const pages = component['splitGroupsByQuestionCount']();
-
- expect(pages.length).toBe(2);
- expect(pages[0][0].questions.length).toBe(8);
- expect(pages[1][0].questions.length).toBe(3);
- });
-
- it('should slice large groups across multiple pages', () => {
- component.assessment = {
- ...mockAssessment,
- groups: [
- { name: 'Big Group', questions: Array.from({ length: 20 }, (_, i) => ({ id: i + 1 })) as any[] },
- ],
- } as any;
-
- const pages = component['splitGroupsByQuestionCount']();
-
- expect(pages.length).toBe(3);
- expect(pages[0][0].questions.length).toBe(8);
- expect(pages[1][0].questions.length).toBe(8);
- expect(pages[2][0].questions.length).toBe(4);
- });
-
- it('should handle empty groups array', () => {
- component.assessment = { ...mockAssessment, groups: [] } as any;
-
- const pages = component['splitGroupsByQuestionCount']();
-
- expect(pages.length).toBe(0);
- });
-
- it('should flush remaining groups on the last page', () => {
- component.assessment = {
- ...mockAssessment,
- groups: [
- { name: 'G1', questions: Array.from({ length: 5 }, (_, i) => ({ id: i + 1 })) as any[] },
- { name: 'G2', questions: Array.from({ length: 5 }, (_, i) => ({ id: i + 10 })) as any[] },
- { name: 'G3', questions: Array.from({ length: 2 }, (_, i) => ({ id: i + 20 })) as any[] },
- ],
- } as any;
-
- const pages = component['splitGroupsByQuestionCount']();
-
- // G1(5) fits on page 0. G2(5) doesn't fit with G1 (5+5>8), flushes G1.
- // G2 goes to page 1 (5 <= 8). G3(2) fits with G2 (5+2=7 <= 8).
- expect(pages.length).toBe(2);
- expect(pages[0][0].name).toBe('G1');
- expect(pages[1][0].name).toBe('G2');
- expect(pages[1][1].name).toBe('G3');
- });
- });
-
- describe('isPaginationEnabled', () => {
- it('should return true by default', () => {
- expect(component.isPaginationEnabled).toBeTrue();
- });
- });
-
- describe('pageCount', () => {
- it('should return pagesGroups.length when pagination enabled', () => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(true);
- component.pagesGroups = [[], [], []];
- expect(component.pageCount).toBe(3);
- });
-
- it('should return 1 when pagination disabled', () => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(false);
- expect(component.pageCount).toBe(1);
- });
- });
-
- describe('pagedGroups', () => {
- it('should return all groups when pagination disabled', () => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(false);
- component.assessment = mockAssessment;
- expect(component.pagedGroups).toEqual(mockAssessment.groups);
- });
-
- it('should return groups for current page when pagination enabled', () => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(true);
- const page0 = [{ name: 'G1', questions: [] }];
- const page1 = [{ name: 'G2', questions: [] }];
- component.pagesGroups = [page0, page1] as any;
- component.pageIndex = 1;
- expect(component.pagedGroups).toEqual(page1 as any);
- });
-
- it('should return empty array for out-of-range page index', () => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(true);
- component.pagesGroups = [];
- component.pageIndex = 5;
- expect(component.pagedGroups).toEqual([]);
- });
- });
-
- describe('prevPage() / nextPage()', () => {
- beforeEach(() => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(true);
- component.pagesGroups = [[], [], []];
- component.pageIndex = 1;
- spyOn(component, 'scrollActivePageIntoView');
- });
-
- it('prevPage should decrement pageIndex', () => {
- component.prevPage();
- expect(component.pageIndex).toBe(0);
- expect(component.scrollActivePageIntoView).toHaveBeenCalled();
- });
-
- it('prevPage should not go below 0', () => {
- component.pageIndex = 0;
- component.prevPage();
- expect(component.pageIndex).toBe(0);
- expect(component.scrollActivePageIntoView).not.toHaveBeenCalled();
- });
-
- it('nextPage should increment pageIndex', () => {
- component.nextPage();
- expect(component.pageIndex).toBe(2);
- expect(component.scrollActivePageIntoView).toHaveBeenCalled();
- });
-
- it('nextPage should not exceed last page', () => {
- component.pageIndex = 2;
- component.nextPage();
- expect(component.pageIndex).toBe(2);
- expect(component.scrollActivePageIntoView).not.toHaveBeenCalled();
- });
- });
-
- describe('prevPage() / nextPage() when pagination disabled', () => {
- it('prevPage should do nothing', () => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(false);
- component.pageIndex = 1;
- component.prevPage();
- expect(component.pageIndex).toBe(1);
- });
-
- it('nextPage should do nothing', () => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(false);
- component.pageIndex = 0;
- component.nextPage();
- expect(component.pageIndex).toBe(0);
- });
- });
-
- describe('goToPage()', () => {
- beforeEach(() => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(true);
- component.pagesGroups = [[], [], [], []];
- spyOn(component, 'scrollActivePageIntoView');
- });
-
- it('should navigate to valid page index', () => {
- component.goToPage(2);
- expect(component.pageIndex).toBe(2);
- expect(component.scrollActivePageIntoView).toHaveBeenCalled();
- });
-
- it('should reject negative page index', () => {
- component.pageIndex = 1;
- component.goToPage(-1);
- expect(component.pageIndex).toBe(1);
- expect(component.scrollActivePageIntoView).not.toHaveBeenCalled();
- });
-
- it('should reject out-of-range page index', () => {
- component.pageIndex = 0;
- component.goToPage(10);
- expect(component.pageIndex).toBe(0);
- expect(component.scrollActivePageIntoView).not.toHaveBeenCalled();
- });
-
- it('should not navigate when pagination disabled', () => {
- component.pageIndex = 0;
- component.pagesGroups = [[], [], []];
- // goToPage checks isPaginationEnabled at the start
- // We can't spyOnProperty twice, so test via prevPage/nextPage instead
- expect(component.pageIndex).toBe(0);
- });
- });
-
- describe('getAllQuestionsForPage()', () => {
- it('should return all questions when pagination disabled', () => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(false);
- component.assessment = {
- ...mockAssessment,
- groups: [
- { name: 'G1', questions: [{ id: 1 }, { id: 2 }] as any[] },
- { name: 'G2', questions: [{ id: 3 }] as any[] },
- ],
- } as any;
-
- const result = component['getAllQuestionsForPage'](0);
-
- expect(result.length).toBe(3);
- expect(result.map(q => q.id)).toEqual([1, 2, 3]);
- });
-
- it('should return questions for specific page when pagination enabled', () => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(true);
- component.pagesGroups = [
- [{ name: 'G1', questions: [{ id: 1 }, { id: 2 }] as any[] }],
- [{ name: 'G2', questions: [{ id: 3 }] as any[] }],
- ];
-
- const result = component['getAllQuestionsForPage'](1);
-
- expect(result.length).toBe(1);
- expect(result[0].id).toBe(3);
- });
-
- it('should return empty array for invalid page index', () => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(true);
- component.pagesGroups = [];
-
- const result = component['getAllQuestionsForPage'](5);
-
- expect(result).toEqual([]);
- });
- });
-
- describe('shouldShowRequiredIndicator()', () => {
- it('should return true when required and doing assessment', () => {
- component.doAssessment = true;
- component.isPendingReview = false;
- const q = { id: 1, name: 'Q', type: 'text', isRequired: true, audience: ['submitter'] } as any;
-
- expect(component.shouldShowRequiredIndicator(q)).toBeTrue();
- });
-
- it('should return true when required and pending review', () => {
- component.doAssessment = false;
- component.isPendingReview = true;
- component.action = 'review';
- const q = { id: 1, name: 'Q', type: 'text', isRequired: true, audience: ['reviewer'] } as any;
-
- expect(component.shouldShowRequiredIndicator(q)).toBeTrue();
- });
-
- it('should return false when not required', () => {
- component.doAssessment = true;
- const q = { id: 1, name: 'Q', type: 'text', isRequired: false, audience: ['submitter'] } as any;
-
- expect(component.shouldShowRequiredIndicator(q)).toBeFalse();
- });
-
- it('should return false in read-only mode', () => {
- component.doAssessment = false;
- component.isPendingReview = false;
- const q = { id: 1, name: 'Q', type: 'text', isRequired: true, audience: ['submitter'] } as any;
-
- expect(component.shouldShowRequiredIndicator(q)).toBeFalse();
- });
- });
-
- describe('setSubmissionDisabled()', () => {
- it('should not change button state in read-only mode', () => {
- component.doAssessment = false;
- component.isPendingReview = false;
- component.btnDisabled$ = new BehaviorSubject(true);
- const spy = spyOn(component.btnDisabled$, 'next');
-
- component.setSubmissionDisabled();
-
- expect(spy).not.toHaveBeenCalled();
- });
-
- it('should disable button when form is invalid', () => {
- component.doAssessment = true;
- component['submitting'] = false;
- component.btnDisabled$ = new BehaviorSubject(false);
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl(null, Validators.required),
- });
-
- component.setSubmissionDisabled();
-
- expect(component.btnDisabled$.getValue()).toBeTrue();
- });
-
- it('should enable button when form is valid', () => {
- component.doAssessment = true;
- component['submitting'] = false;
- component.btnDisabled$ = new BehaviorSubject(true);
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl('answered'),
- });
-
- component.setSubmissionDisabled();
-
- expect(component.btnDisabled$.getValue()).toBeFalse();
- });
- });
-
- describe('_prefillForm() with locked submission', () => {
- it('should keep button disabled when submission is locked', () => {
- component.questionsForm = new FormGroup({
- 'q-1': new FormControl(''),
- });
- component.btnDisabled$ = new BehaviorSubject(true);
- component.action = 'assessment';
- component.doAssessment = false; // locked means doAssessment is false
- component.isPendingReview = false;
- component.submission = {
- id: 1,
- status: 'done',
- isLocked: true,
- answers: { 1: { answer: 'locked answer' } },
- } as any;
-
- component['_prefillForm']();
-
- // locked submission should keep button disabled (not reset to false)
- expect(component.btnDisabled$.getValue()).toBeTrue();
- });
- });
-
- describe('scrollActivePageIntoView()', () => {
- it('should do nothing when pagination disabled', fakeAsync(() => {
- spyOnProperty(component, 'isPaginationEnabled').and.returnValue(false);
- component.scrollActivePageIntoView();
- tick(100);
- // no error thrown
- }));
- });
});
diff --git a/projects/v3/src/app/components/assessment/assessment.component.ts b/projects/v3/src/app/components/assessment/assessment.component.ts
index dcb2249c5..fdec6ee9a 100644
--- a/projects/v3/src/app/components/assessment/assessment.component.ts
+++ b/projects/v3/src/app/components/assessment/assessment.component.ts
@@ -33,7 +33,6 @@ const MAX_QUESTIONS_PER_PAGE = 8; // maximum number of questions to display per
* When enabled, questions are split across multiple pages based on pageSize
*/
@Component({
- standalone: false,
selector: 'app-assessment',
templateUrl: './assessment.component.html',
styleUrls: ['./assessment.component.scss'],
@@ -1312,8 +1311,8 @@ Best regards`;
if (this.doAssessment || this.isPendingReview) {
// in edit mode, check form validation
this.setSubmissionDisabled();
- } else if (!this.submission?.isLocked) {
- // in read-only mode (not locked), ensure button is enabled
+ } else {
+ // in read-only mode, ensure button is enabled
this.btnDisabled$.next(false);
}
}
diff --git a/projects/v3/src/app/components/bottom-action-bar/bottom-action-bar.component.spec.ts b/projects/v3/src/app/components/bottom-action-bar/bottom-action-bar.component.spec.ts
index bf00ea4d0..781076b68 100644
--- a/projects/v3/src/app/components/bottom-action-bar/bottom-action-bar.component.spec.ts
+++ b/projects/v3/src/app/components/bottom-action-bar/bottom-action-bar.component.spec.ts
@@ -1,6 +1,5 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
-import { BehaviorSubject } from 'rxjs';
import { BottomActionBarComponent } from './bottom-action-bar.component';
@@ -24,20 +23,9 @@ describe('BottomActionBarComponent', () => {
expect(component).toBeTruthy();
});
- describe('default input values', () => {
- it('should have correct defaults', () => {
- expect(component.showResubmit).toBe(false);
- expect(component.color).toBe('primary');
- expect(component.buttonType).toBe('');
- expect(component.hasCustomContent).toBe(false);
- expect(component.disabled$).toBeUndefined();
- });
- });
-
it('should set the input properties', () => {
component.text = 'Click me';
component.color = 'secondary';
- component.disabled$ = new BehaviorSubject(false);
component.disabled$.next(true);
component.buttonType = 'submit';
fixture.detectChanges();
@@ -48,59 +36,6 @@ describe('BottomActionBarComponent', () => {
expect(component.buttonType).toEqual('submit');
});
- describe('onClick()', () => {
- it('should emit handleClick for a click event when not disabled', () => {
- component.disabled$ = new BehaviorSubject(false);
- spyOn(component.handleClick, 'emit');
-
- const clickEvent = new MouseEvent('click');
- component.onClick(clickEvent);
-
- expect(component.handleClick.emit).toHaveBeenCalledWith(clickEvent);
- });
-
- it('should not emit handleClick when disabled$ is true', () => {
- component.disabled$ = new BehaviorSubject(true);
- spyOn(component.handleClick, 'emit');
-
- const clickEvent = new MouseEvent('click');
- component.onClick(clickEvent);
-
- expect(component.handleClick.emit).not.toHaveBeenCalled();
- });
-
- it('should not emit handleClick for non-click event types', () => {
- component.disabled$ = new BehaviorSubject(false);
- spyOn(component.handleClick, 'emit');
-
- const keyEvent = new KeyboardEvent('keydown');
- component.onClick(keyEvent);
-
- expect(component.handleClick.emit).not.toHaveBeenCalled();
- });
-
- it('should handle missing disabled$ (optional input)', () => {
- component.disabled$ = undefined;
- spyOn(component.handleClick, 'emit');
-
- const clickEvent = new MouseEvent('click');
- component.onClick(clickEvent);
-
- expect(component.handleClick.emit).toHaveBeenCalledWith(clickEvent);
- });
- });
-
- describe('onResubmit()', () => {
- it('should emit handleResubmit event', () => {
- spyOn(component.handleResubmit, 'emit');
-
- const clickEvent = new MouseEvent('click');
- component.onResubmit(clickEvent);
-
- expect(component.handleResubmit.emit).toHaveBeenCalledWith(clickEvent);
- });
- });
-
it('should emit event when handleClick is called', () => {
spyOn(component.handleClick, 'emit');
diff --git a/projects/v3/src/app/components/bottom-action-bar/bottom-action-bar.component.ts b/projects/v3/src/app/components/bottom-action-bar/bottom-action-bar.component.ts
index e1b8df616..abc58f5b6 100644
--- a/projects/v3/src/app/components/bottom-action-bar/bottom-action-bar.component.ts
+++ b/projects/v3/src/app/components/bottom-action-bar/bottom-action-bar.component.ts
@@ -2,7 +2,6 @@ import { Component, Input, Output, EventEmitter, OnChanges } from '@angular/core
import { BehaviorSubject } from 'rxjs';
@Component({
- standalone: false,
selector: 'app-bottom-action-bar',
templateUrl: 'bottom-action-bar.component.html',
styleUrls: ['./bottom-action-bar.component.scss'],
diff --git a/projects/v3/src/app/components/branding-logo/branding-logo.component.ts b/projects/v3/src/app/components/branding-logo/branding-logo.component.ts
index 3bfa0271a..b9df6abe8 100644
--- a/projects/v3/src/app/components/branding-logo/branding-logo.component.ts
+++ b/projects/v3/src/app/components/branding-logo/branding-logo.component.ts
@@ -2,7 +2,6 @@ import { Component, Input } from '@angular/core';
import { BrowserStorageService } from '@v3/services/storage.service';
@Component({
- standalone: false,
selector: 'app-branding-logo',
templateUrl: './branding-logo.component.html',
})
diff --git a/projects/v3/src/app/components/circle-progress/circle-progress.component.spec.ts b/projects/v3/src/app/components/circle-progress/circle-progress.component.spec.ts
index 6a966b334..93e5b9091 100644
--- a/projects/v3/src/app/components/circle-progress/circle-progress.component.spec.ts
+++ b/projects/v3/src/app/components/circle-progress/circle-progress.component.spec.ts
@@ -97,8 +97,10 @@ describe('CircleProgressComponent', () => {
describe('isMobile()', () => {
it('should return utils.isMobile value', () => {
- // isMobile is set during ngOnInit which runs during fixture.detectChanges()
- // The TestUtils mock's isMobile returns false by default, so component.isMobile should be false
+ utilsSpy.isMobile = jasmine.createSpy('isMobile').and.returnValue(true);
+ expect(component.isMobile).toEqual(true);
+
+ utilsSpy.isMobile = jasmine.createSpy('isMobile').and.returnValue(false);
expect(component.isMobile).toEqual(false);
});
});
diff --git a/projects/v3/src/app/components/circle-progress/circle-progress.component.ts b/projects/v3/src/app/components/circle-progress/circle-progress.component.ts
index 267e04c69..a1375f931 100644
--- a/projects/v3/src/app/components/circle-progress/circle-progress.component.ts
+++ b/projects/v3/src/app/components/circle-progress/circle-progress.component.ts
@@ -3,7 +3,6 @@ import { CircleProgressOptionsInterface } from 'ng-circle-progress';
import { UtilsService } from '@v3/services/utils.service';
@Component({
- standalone: false,
selector: 'app-circle-progress',
templateUrl: './circle-progress.component.html',
styleUrls: ['./circle-progress.component.scss'],
diff --git a/projects/v3/src/app/components/clickable-item/clickable-item.component.ts b/projects/v3/src/app/components/clickable-item/clickable-item.component.ts
index ed9728843..7e742c710 100644
--- a/projects/v3/src/app/components/clickable-item/clickable-item.component.ts
+++ b/projects/v3/src/app/components/clickable-item/clickable-item.component.ts
@@ -1,7 +1,6 @@
import { Component, Input } from '@angular/core';
@Component({
- standalone: false,
selector: 'app-clickable-item',
templateUrl: './clickable-item.component.html',
styleUrls: ['./clickable-item.component.scss']
diff --git a/projects/v3/src/app/components/components.module.ts b/projects/v3/src/app/components/components.module.ts
index 884ff1ffe..427e3be85 100644
--- a/projects/v3/src/app/components/components.module.ts
+++ b/projects/v3/src/app/components/components.module.ts
@@ -1,4 +1,4 @@
-import { DashboardModalComponent, DashboardComponent } from '@uppy/angular';
+import { UppyAngularDashboardModalModule, UppyAngularDashboardModule } from '@uppy/angular';
import { TrafficLightComponent } from './traffic-light/traffic-light.component';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@@ -71,8 +71,8 @@ const largeCircleDefaultConfig = {
FormsModule,
ReactiveFormsModule,
ToggleLabelDirective,
- DashboardModalComponent,
- DashboardComponent,
+ UppyAngularDashboardModalModule,
+ UppyAngularDashboardModule,
NgCircleProgressModule.forRoot(largeCircleDefaultConfig),
],
declarations: [
diff --git a/projects/v3/src/app/components/contact-number-form/contact-number-form.component.spec.ts b/projects/v3/src/app/components/contact-number-form/contact-number-form.component.spec.ts
index a3249a3a0..155f9c445 100644
--- a/projects/v3/src/app/components/contact-number-form/contact-number-form.component.spec.ts
+++ b/projects/v3/src/app/components/contact-number-form/contact-number-form.component.spec.ts
@@ -181,7 +181,7 @@ describe('ContactNumberFormComponent', () => {
[cancelBtn, submitBtn] = res.buttons;
submitBtn.handler();
- expect(submitBtn.text).toEqual('OK');
+ expect(submitBtn.text).toEqual('Okay');
});
component.countryModel = COUNTRIES['US'];
@@ -197,7 +197,7 @@ describe('ContactNumberFormComponent', () => {
[cancelBtn, submitBtn] = res.buttons;
submitBtn.handler();
- expect(submitBtn.text).toEqual('OK');
+ expect(submitBtn.text).toEqual('Okay');
});
component.countryModel = COUNTRIES['AUS'];
diff --git a/projects/v3/src/app/components/contact-number-form/contact-number-form.component.ts b/projects/v3/src/app/components/contact-number-form/contact-number-form.component.ts
index 1ccbb348d..67f7cd1a8 100644
--- a/projects/v3/src/app/components/contact-number-form/contact-number-form.component.ts
+++ b/projects/v3/src/app/components/contact-number-form/contact-number-form.component.ts
@@ -16,7 +16,6 @@ export enum COUNTRIES {
};
@Component({
- standalone: false,
selector: 'app-contact-number-form',
templateUrl: './contact-number-form.component.html',
styleUrls: ['./contact-number-form.component.scss']
diff --git a/projects/v3/src/app/components/description/description.component.spec.ts b/projects/v3/src/app/components/description/description.component.spec.ts
index 44668f180..e1a6ed412 100644
--- a/projects/v3/src/app/components/description/description.component.spec.ts
+++ b/projects/v3/src/app/components/description/description.component.spec.ts
@@ -1,5 +1,5 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { DescriptionComponent } from './description.component';
@@ -7,7 +7,7 @@ describe('DescriptionComponent', () => {
// let component: DescriptionComponent;
// let fixture: ComponentFixture;
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
// TestBed.configureTestingModule({
// declarations: [ DescriptionComponent ],
// schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
diff --git a/projects/v3/src/app/components/description/description.component.ts b/projects/v3/src/app/components/description/description.component.ts
index 45463198f..9f9cfe6d4 100644
--- a/projects/v3/src/app/components/description/description.component.ts
+++ b/projects/v3/src/app/components/description/description.component.ts
@@ -3,7 +3,6 @@ import { SafeHtml } from '@angular/platform-browser';
import { BrowserStorageService } from '@v3/services/storage.service';
@Component({
- standalone: false,
selector: 'app-description',
templateUrl: 'description.component.html',
styleUrls: ['./description.component.scss'],
diff --git a/projects/v3/src/app/components/fast-feedback/fast-feedback.component.spec.ts b/projects/v3/src/app/components/fast-feedback/fast-feedback.component.spec.ts
index 79c881e1f..d31da9165 100644
--- a/projects/v3/src/app/components/fast-feedback/fast-feedback.component.spec.ts
+++ b/projects/v3/src/app/components/fast-feedback/fast-feedback.component.spec.ts
@@ -5,14 +5,11 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { UtilsService } from '@v3/services/utils.service';
import { BrowserStorageService } from '@v3/services/storage.service';
import { NotificationsService } from '@v3/services/notifications.service';
-import { ModalController, NavParams } from '@ionic/angular';
+import { ModalController } from '@ionic/angular';
import { FastFeedbackComponent } from './fast-feedback.component';
import { FormGroup, FormControl, Validators } from '@angular/forms';
import { TestUtils } from '@testingv3/utils';
import { FastFeedbackService } from '@v3/services/fast-feedback.service';
-import { HomeService } from '@v3/app/services/home.service';
-import { RequestService } from 'request';
-import { DemoService } from '@v3/app/services/demo.service';
class Page {
get questions() {
@@ -57,46 +54,7 @@ describe('FastFeedbackComponent', () => {
},
{
provide: BrowserStorageService,
- useValue: jasmine.createSpyObj('BrowserStorageService', {
- set: null,
- getUser: { teamId: 1 }
- })
- },
- {
- provide: FastFeedbackService,
- useValue: jasmine.createSpyObj('FastFeedbackService', ['submit', 'pullFastFeedback'])
- },
- {
- provide: NotificationsService,
- useValue: jasmine.createSpyObj('NotificationsService', ['alert', 'presentToast'])
- },
- {
- provide: NavParams,
- useValue: {
- get: jasmine.createSpy('get').and.callFake((key: string) => {
- if (key === 'modal') {
- return { closable: true, componentProps: {} };
- }
- return null;
- })
- }
- },
- {
- provide: HomeService,
- useValue: jasmine.createSpyObj('HomeService', {
- getProgress: of({}),
- getActivities: of([]),
- getPulseCheckStatuses: of({}),
- getPulseCheckSkills: of({})
- })
- },
- {
- provide: RequestService,
- useValue: jasmine.createSpyObj('RequestService', ['post', 'get'])
- },
- {
- provide: DemoService,
- useValue: jasmine.createSpyObj('DemoService', ['isDemoApp'])
+ useValue: jasmine.createSpyObj('BrowserStorageService', ['set'])
},
],
})
@@ -161,29 +119,24 @@ describe('FastFeedbackComponent', () => {
team_name: 'team',
assessment_name: 'asmt'
};
- // Set up fastfeedbackSpy.submit to return an Observable
- fastfeedbackSpy.submit.and.returnValue(of({}));
+ spyOn(component, 'submitData').and.returnValue(of({}));
});
afterEach(() => {
- expect(fastfeedbackSpy.submit).toHaveBeenCalledTimes(1);
+ expect(component.submitData).toBe(1);
expect(modalSpy.dismiss.calls.count()).toBe(1);
});
describe('should submit correct data', () => {
beforeEach(() => {
- // set closable to false to test the meta.team_id path
- // Note: ngOnInit() is already called in the outer beforeEach, so we can override closable after
component.ngOnInit();
- component.closable = false;
});
it('when submission answer is provided in full', fakeAsync(() => {
component.submit();
tick(2500);
- expect(fastfeedbackSpy.submit.calls.first().args[1]).toEqual({
- contextId: 1,
- teamId: 2,
- targetUserId: null
+ expect(component.submitData).toEqual({
+ context_id: 1,
+ team_id: 2
});
}));
@@ -192,10 +145,9 @@ describe('FastFeedbackComponent', () => {
component.submit();
tick(2500);
- expect(fastfeedbackSpy.submit.calls.first().args[1]).toEqual({
- contextId: 1,
- teamId: null,
- targetUserId: 3
+ expect(component.submitData).toEqual({
+ context_id: 1,
+ target_user_id: 3
});
}));
@@ -204,10 +156,8 @@ describe('FastFeedbackComponent', () => {
component.meta.target_user_id = null;
component.submit();
tick(2500);
- expect(fastfeedbackSpy.submit.calls.first().args[1]).toEqual({
- contextId: 1,
- teamId: null,
- targetUserId: null
+ expect(component.submitData).toEqual({
+ context_id: 1
});
}));
});
@@ -220,8 +170,7 @@ describe('FastFeedbackComponent', () => {
component.ngOnInit();
component.submit();
- // flush all pending timers (2000ms delay + 500ms in dismiss)
- tick(2500);
+ flushMicrotasks();
expect(component.submissionCompleted).toBeTruthy();
expect(modalSpy.dismiss).toHaveBeenCalled();
}));
diff --git a/projects/v3/src/app/components/fast-feedback/fast-feedback.component.ts b/projects/v3/src/app/components/fast-feedback/fast-feedback.component.ts
index cbae22c75..f9f710089 100644
--- a/projects/v3/src/app/components/fast-feedback/fast-feedback.component.ts
+++ b/projects/v3/src/app/components/fast-feedback/fast-feedback.component.ts
@@ -21,7 +21,6 @@ export interface Meta {
}
@Component({
- standalone: false,
selector: "app-fast-feedback",
templateUrl: "./fast-feedback.component.html",
styleUrls: ["./fast-feedback.component.scss"],
diff --git a/projects/v3/src/app/components/file-display/file-display.component.spec.ts b/projects/v3/src/app/components/file-display/file-display.component.spec.ts
index 611fd6ebb..1b040dab5 100644
--- a/projects/v3/src/app/components/file-display/file-display.component.spec.ts
+++ b/projects/v3/src/app/components/file-display/file-display.component.spec.ts
@@ -1,14 +1,12 @@
/* eslint-disable no-console */
import { CUSTOM_ELEMENTS_SCHEMA, SimpleChange, DebugElement } from '@angular/core';
-import { ComponentFixture, TestBed, fakeAsync, flushMicrotasks, waitForAsync, tick } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed, fakeAsync, flushMicrotasks, waitForAsync, tick } from '@angular/core/testing';
import { FileDisplayComponent } from './file-display.component';
import { FilestackService } from '@v3/services/filestack.service';
import { ReactiveFormsModule, FormControl } from '@angular/forms';
import { UtilsService } from '@v3/services/utils.service';
import { TestUtils } from '@testingv3/utils';
import { environment } from '@v3/environments/environment';
-import { FileInput, TusFileResponse } from '../types/assessment';
-import { ModalController } from '@ionic/angular';
class OnChangedValues extends SimpleChange {
constructor(older, latest) {
@@ -40,13 +38,6 @@ describe('FileDisplayComponent', () => {
'metadata'
])
},
- {
- provide: ModalController,
- useValue: jasmine.createSpyObj('ModalController', {
- create: Promise.resolve({ present: jasmine.createSpy('present').and.returnValue(Promise.resolve()) }),
- dismiss: Promise.resolve()
- })
- },
],
})
.compileComponents();
@@ -63,62 +54,26 @@ describe('FileDisplayComponent', () => {
expect(component).toBeDefined();
});
- it('should preview file with modal', async () => {
- const modalControllerSpy = TestBed.inject(ModalController) as jasmine.SpyObj;
- await component.previewFile({
- bucket: 'test-bucket',
- path: 'test-path',
- name: 'test-file',
- url: 'DUMMY_URL',
- extension: 'jpg',
- type: 'image/jpeg',
- size: 1000
- });
- expect(modalControllerSpy.create).toHaveBeenCalled();
+ it('should preview file', () => {
+ component.previewFile({url: 'DUMMY_URL'});
+ expect(filestackSpy.previewFile.calls.count()).toBe(1);
});
- it('should open application files in new window', async () => {
- spyOn(window, 'open');
- component.file = {
- bucket: 'test-bucket',
- path: 'test-path',
- name: 'test-file.pdf',
- filename: 'test-file.pdf',
- url: 'DUMMY_URL',
- extension: 'pdf',
- type: 'application/pdf',
- mimetype: 'application/pdf',
- size: 1000,
- directUrl: 'DUMMY_URL',
- cdnUrl: 'DUMMY_URL',
- };
- await component.previewFile({
- bucket: 'test-bucket',
- path: 'test-path',
- name: 'test-file.pdf',
- url: 'DUMMY_URL',
- extension: 'pdf',
- type: 'application/pdf',
- size: 1000
+ it('should fail, if preview file api is faulty', fakeAsync(() => {
+ const error = 'PREVIEW FILE SAMPLE ERROR';
+ // filestackSpy.metadata.and.rejectWith(error);
+ filestackSpy.previewFile.and.rejectWith(error);
+ component.previewFile('file').then(res => {
+ console.info('afterPreview', res);
});
- expect(window.open).toHaveBeenCalledWith('DUMMY_URL', '_system');
- });
+ flushMicrotasks();
+ }));
describe('UI logic', () => {
const url = 'test.com/uilogic';
beforeEach(() => {
component.file = {
- bucket: 'test-bucket',
- path: 'test-path',
- name: 'test-file',
- filename: 'test-file',
- url: url,
- extension: 'jpg',
- type: 'image/jpeg',
- mimetype: 'image/jpeg',
- size: 1000,
- directUrl: url,
- cdnUrl: url,
+ url
};
});
it('should display image element based on filetype', () => {
@@ -127,8 +82,10 @@ describe('FileDisplayComponent', () => {
const imageEle: HTMLElement = fixture.nativeElement.querySelector('app-img');
const videoEle: HTMLElement = fixture.nativeElement.querySelector('video');
+ const anyEle: HTMLElement = fixture.nativeElement.querySelector('div');
expect(imageEle).toBeTruthy();
expect(videoEle).toBeFalsy();
+ expect(anyEle).toBeFalsy();
});
it('should display video element based on filetype', () => {
@@ -137,20 +94,140 @@ describe('FileDisplayComponent', () => {
const imageEle: HTMLElement = fixture.nativeElement.querySelector('app-img');
const videoEle: HTMLElement = fixture.nativeElement.querySelector('video');
+ const anyEle: HTMLElement = fixture.nativeElement.querySelector('div');
expect(imageEle).toBeFalsy();
expect(videoEle).toBeTruthy();
+ expect(anyEle).toBeFalsy();
});
- it('should display list-item element for "any" filetype', () => {
+ it('should display "any" element based on filetype', () => {
component.fileType = 'any';
fixture.detectChanges();
const imageEle: HTMLElement = fixture.nativeElement.querySelector('app-img');
const videoEle: HTMLElement = fixture.nativeElement.querySelector('video');
- const listItemEle: HTMLElement = fixture.nativeElement.querySelector('app-list-item');
+ const anyEle: HTMLElement = fixture.nativeElement.querySelector('div');
expect(imageEle).toBeFalsy();
expect(videoEle).toBeFalsy();
- expect(listItemEle).toBeTruthy();
+ expect(anyEle).toBeTruthy();
+ });
+ });
+
+ describe('ngOnInit()', () => {
+ beforeEach(() => {
+ component.updateWorkflowStatus = jasmine.createSpy('updateWorkflowStatus');
+ });
+
+ it('should check workflow status if workflow object is available', () => {
+ component.file = {
+ workflows: 'isAvailable'
+ };
+ component.ngOnInit();
+ expect(component.updateWorkflowStatus).toHaveBeenCalled();
+ });
+
+ it('should not update workflow status if file not available', () => {
+ component.file = undefined;
+ component.ngOnInit();
+ expect(component.updateWorkflowStatus).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('ngOnChanges', () => {
+ it('should track fileupload json changes', () => {
+ component.updateWorkflowStatus = jasmine.createSpy('updateWorkflowStatus');
+ const jsonData = { just: 'first test' };
+ const newJsonData = {
+ jsonData, ...{
+ and: 'second test',
+ without: 'workflow',
+ }
+ };
+
+ component.ngOnChanges({
+ file: new OnChangedValues(jsonData, newJsonData),
+ });
+
+ expect(component.updateWorkflowStatus).not.toHaveBeenCalled();
+ });
+
+ it('should not track fileupload changes if workflow is not available', () => {
+ component.updateWorkflowStatus = jasmine.createSpy('updateWorkflowStatus');
+ const jsonData = { just: 'first test' };
+ const newJsonData = {
+ jsonData, ...{
+ and: 'second test',
+ without: 'workflows',
+ }
+ };
+
+ component.ngOnChanges({
+ file: new OnChangedValues(jsonData, newJsonData),
+ });
+
+ expect(component.updateWorkflowStatus).not.toHaveBeenCalled();
+ });
+
+ it('should track fileupload changes if workflow is available', fakeAsync(() => {
+ const virus_detection = {
+ data: 'virus_detection_test_data',
+ };
+ const quarantine = {
+ data: 'quarantine_test_data',
+ };
+ filestackSpy.getWorkflowStatus.and.returnValue(Promise.resolve([
+ {
+ results: {
+ virus_detection,
+ quarantine,
+ },
+ status: 'FINISHED',
+ }
+ ]));
+ component.updateWorkflowStatus = jasmine.createSpy('updateWorkflowStatus');
+
+ const jsonData = { just: 'first test' };
+ const newJsonData = {
+ ...jsonData, ...{
+ and: 'second test',
+ workflows: true,
+ }
+ };
+ component.videoEle = {
+ nativeElement: {
+ load: () => jasmine.createSpy()
+ }
+ };
+ component.ngOnChanges({
+ file: new OnChangedValues(jsonData, newJsonData),
+ });
+
+ flushMicrotasks();
+ expect(component.updateWorkflowStatus).toHaveBeenCalled();
+ return;
+ // can't test the following in development
+ expect(filestackSpy.getWorkflowStatus).toHaveBeenCalledWith(newJsonData.workflows);
+ expect(component['virusDetection']).toEqual(virus_detection.data);
+ expect(component['quarantine']).toEqual(quarantine.data);
+ }));
+ });
+
+ describe('updateWorkflowStatus()', () => {
+ it('should update workflow status', () => {
+ utilsSpy.isEmpty.and.returnValue(true);
+ filestackSpy.getWorkflowStatus.and.returnValue(Promise.resolve([{
+ results: {
+ virus_detection: { data: {} },
+ quarantine: { data: {} },
+ },
+ status: 'finished'
+ }]));
+
+ environment.production = true;
+ component.updateWorkflowStatus();
+ expect(filestackSpy.getWorkflowStatus).toHaveBeenCalled();
+ expect(component.virusDetection).toEqual({});
+ expect(component['quarantine']).toEqual({});
});
});
@@ -159,34 +236,43 @@ describe('FileDisplayComponent', () => {
component.removeFile.emit = spyOn(component.removeFile, 'emit');
});
- it('should download file when index is 0', () => {
+ it('should remove uploaded file', () => {
+ component.fileType = 'not any';
component.actionBtnClick({
- bucket: 'test-bucket',
- path: 'test-path',
- name: 'test-file',
- url: 'http://dummy.com',
- directUrl: 'http://dummy.com/direct',
- extension: 'jpg',
- type: 'image/jpeg',
- size: 1000
- } as TusFileResponse, 0);
+ handle: '1234567abc',
+ url: 'http://dummy.com'
+ }, 999);
- expect(utilsSpy.downloadFile).toHaveBeenCalled();
+ expect(component.removeFile.emit).toHaveBeenCalled();
});
- it('should remove uploaded file when index is 1', () => {
+ it('should execute based on index code', fakeAsync(() => {
+ component.fileType = 'any';
+
+ component.actionBtnClick({
+ handle: '1234567abc',
+ url: 'http://dummy.com'
+ }, 0);
+
+ // expect(component.removeFile.emit).toHaveBeenCalled();
+ expect(utilsSpy.downloadFile).toHaveBeenCalled();
+
+ component.actionBtnClick({
+ handle: '1234567abc',
+ url: 'http://dummy.com'
+ }, 1);
+
+ tick();
+ expect(filestackSpy.previewFile).toHaveBeenCalled();
+
component.actionBtnClick({
- bucket: 'test-bucket',
- path: 'test-path',
- name: 'test-file',
- url: 'http://dummy.com',
- extension: 'jpg',
- type: 'image/jpeg',
- size: 1000
- } as TusFileResponse, 1);
+ handle: '1234567abc',
+ url: 'http://dummy.com'
+ }, 2);
+ tick();
expect(component.removeFile.emit).toHaveBeenCalled();
- });
+ }));
});
});
diff --git a/projects/v3/src/app/components/file-display/file-display.component.ts b/projects/v3/src/app/components/file-display/file-display.component.ts
index 79f9a9155..90a4e5fb9 100644
--- a/projects/v3/src/app/components/file-display/file-display.component.ts
+++ b/projects/v3/src/app/components/file-display/file-display.component.ts
@@ -19,7 +19,6 @@ interface FileStackCompatible extends TusFileResponse {
}
@Component({
- standalone: false,
selector: 'app-file-display',
templateUrl: 'file-display.component.html',
styleUrls: ['file-display.component.scss'],
diff --git a/projects/v3/src/app/components/file-popup/file-popup.component.spec.ts b/projects/v3/src/app/components/file-popup/file-popup.component.spec.ts
deleted file mode 100644
index 8ed78485f..000000000
--- a/projects/v3/src/app/components/file-popup/file-popup.component.spec.ts
+++ /dev/null
@@ -1,78 +0,0 @@
-import { DomSanitizer } from '@angular/platform-browser';
-import { ModalController } from '@ionic/angular';
-import { FilePopupComponent } from './file-popup.component';
-
-describe('FilePopupComponent', () => {
- let component: FilePopupComponent;
- let modalController: jasmine.SpyObj;
-
- beforeEach(() => {
- modalController = jasmine.createSpyObj('ModalController', ['dismiss']);
- component = new FilePopupComponent(modalController, {} as DomSanitizer);
- component.file = { url: 'https://example.com/file.pdf' };
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-
- it('should open file on download without keyboard event', () => {
- spyOn(window, 'open');
-
- component.download();
-
- expect(window.open).toHaveBeenCalledWith('https://example.com/file.pdf', '_system');
- });
-
- it('should prevent default and download on keyboard Space', () => {
- spyOn(window, 'open');
- const keyboardEvent = jasmine.createSpyObj('KeyboardEvent', ['preventDefault'], {
- code: 'Space'
- });
-
- component.download(keyboardEvent);
-
- expect(keyboardEvent.preventDefault).toHaveBeenCalled();
- expect(window.open).toHaveBeenCalledWith('https://example.com/file.pdf', '_system');
- });
-
- it('should not download for unsupported keyboard key', () => {
- spyOn(window, 'open');
- const keyboardEvent = jasmine.createSpyObj('KeyboardEvent', ['preventDefault'], {
- code: 'Escape'
- });
-
- component.download(keyboardEvent);
-
- expect(keyboardEvent.preventDefault).not.toHaveBeenCalled();
- expect(window.open).not.toHaveBeenCalled();
- });
-
- it('should close modal without keyboard event', () => {
- component.close();
-
- expect(modalController.dismiss).toHaveBeenCalled();
- });
-
- it('should prevent default and close modal on keyboard Enter', () => {
- const keyboardEvent = jasmine.createSpyObj('KeyboardEvent', ['preventDefault'], {
- code: 'Enter'
- });
-
- component.close(keyboardEvent);
-
- expect(keyboardEvent.preventDefault).toHaveBeenCalled();
- expect(modalController.dismiss).toHaveBeenCalled();
- });
-
- it('should not close modal for unsupported keyboard key', () => {
- const keyboardEvent = jasmine.createSpyObj('KeyboardEvent', ['preventDefault'], {
- code: 'Tab'
- });
-
- component.close(keyboardEvent);
-
- expect(keyboardEvent.preventDefault).not.toHaveBeenCalled();
- expect(modalController.dismiss).not.toHaveBeenCalled();
- });
-});
diff --git a/projects/v3/src/app/components/file-popup/file-popup.component.ts b/projects/v3/src/app/components/file-popup/file-popup.component.ts
index 98f29e3ca..8ff1faa54 100644
--- a/projects/v3/src/app/components/file-popup/file-popup.component.ts
+++ b/projects/v3/src/app/components/file-popup/file-popup.component.ts
@@ -3,7 +3,6 @@ import { ModalController } from '@ionic/angular';
import { DomSanitizer } from '@angular/platform-browser';
@Component({
- standalone: false,
selector: 'app-file-popup',
templateUrl: 'file-popup.component.html',
styleUrls: ['file-popup.component.scss']
diff --git a/projects/v3/src/app/components/file-upload/file-upload.component.html b/projects/v3/src/app/components/file-upload/file-upload.component.html
index 6ca3a6fc3..703b0c5ba 100644
--- a/projects/v3/src/app/components/file-upload/file-upload.component.html
+++ b/projects/v3/src/app/components/file-upload/file-upload.component.html
@@ -3,7 +3,7 @@
- Learner's Answer
+ Learner's answer
diff --git a/projects/v3/src/app/components/file-upload/file-upload.component.spec.ts b/projects/v3/src/app/components/file-upload/file-upload.component.spec.ts
index 165263004..6d4902cac 100644
--- a/projects/v3/src/app/components/file-upload/file-upload.component.spec.ts
+++ b/projects/v3/src/app/components/file-upload/file-upload.component.spec.ts
@@ -1,534 +1,23 @@
-import { FormControl } from '@angular/forms';
-import { Subject } from 'rxjs';
-import { UppyUploaderService } from '../uppy-uploader/uppy-uploader.service';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FileUploadComponent } from './file-upload.component';
describe('FileUploadComponent', () => {
let component: FileUploadComponent;
- let uppyUploaderService: jasmine.SpyObj;
+ let fixture: ComponentFixture;
- beforeEach(() => {
- uppyUploaderService = jasmine.createSpyObj('UppyUploaderService', ['createUppyInstance']);
- component = new FileUploadComponent(uppyUploaderService);
- component.control = new FormControl('');
- component.submitActions$ = new Subject();
- component.question = {
- id: 11,
- name: 'Test Question',
- description: '',
- isRequired: false,
- fileType: 'any',
- audience: ['participant'],
- canAnswer: true,
- canComment: true,
- } as any;
- component.submissionId = 123;
- component.reviewId = 456;
- component.review = { answer: null, comment: 'old comment', file: {} };
- component.submission = { answer: null };
- component.uppy = jasmine.createSpyObj('Uppy', ['removeFile', 'clear', 'destroy']) as any;
+ beforeEach(async () => {
+ await TestBed.configureTestingModule({
+ imports: [FileUploadComponent]
+ })
+ .compileComponents();
+
+ fixture = TestBed.createComponent(FileUploadComponent);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
-
- it('should return video note message for video fileType', () => {
- component.question.fileType = 'video';
-
- expect(component.noteMessage()).toContain('Videos only');
- });
-
- it('should return image note message for image fileType', () => {
- component.question.fileType = 'image';
-
- expect(component.noteMessage()).toContain('Images only');
- });
-
- it('should return default note message for any fileType', () => {
- component.question.fileType = 'any';
-
- expect(component.noteMessage()).toContain('Docs, images and videos only');
- });
-
- it('should parse tus response body in onAfterResponse', () => {
- const response = {
- getBody: () => JSON.stringify({ path: '/uploads/a', bucket: 'b', cdnUrl: 'c', directUrl: 'd' })
- };
-
- component.onAfterResponse({}, response);
-
- expect(component.tusResponse).toEqual({ path: '/uploads/a', bucket: 'b', cdnUrl: 'c', directUrl: 'd' });
- });
-
- it('should return empty object from fileRequestFormat when uploadedFile is empty', () => {
- component.uploadedFile = null as any;
-
- expect(component.fileRequestFormat()).toEqual({} as any);
- });
-
- it('should map uploadedFile in fileRequestFormat', () => {
- component.uploadedFile = {
- name: 'a.png',
- type: 'image/png',
- size: 10,
- extension: 'png',
- bucket: 'bucket',
- path: '/uploads/a',
- cdnUrl: 'https://cdn/a.png',
- } as any;
-
- expect(component.fileRequestFormat()).toEqual({
- name: 'a.png',
- type: 'image/png',
- size: 10,
- extension: 'png',
- bucket: 'bucket',
- path: '/uploads/a',
- url: 'https://cdn/a.png',
- });
- });
-
- it('should set review answer and trigger save in onChange with type', () => {
- component.doReview = true;
- component.uploadedFile = {
- name: 'a.pdf',
- type: 'application/pdf',
- size: 10,
- extension: 'pdf',
- bucket: 'bucket',
- path: '/uploads/a',
- cdnUrl: 'https://cdn/a.pdf',
- } as any;
- spyOn(component, 'triggerSave');
-
- component.onChange('new comment', 'comment');
-
- expect(component.innerValue.comment).toBe('new comment');
- expect(component.innerValue.file.url).toBe('https://cdn/a.pdf');
- expect(component.triggerSave).toHaveBeenCalled();
- });
-
- it('should set assessment value and trigger save in onChange without type', () => {
- component.doAssessment = true;
- component.uploadedFile = {
- name: 'a.pdf',
- type: 'application/pdf',
- size: 10,
- extension: 'pdf',
- bucket: 'bucket',
- path: '/uploads/a',
- cdnUrl: 'https://cdn/a.pdf',
- } as any;
- spyOn(component, 'triggerSave');
-
- component.onChange('');
-
- expect(component.innerValue.url).toBe('https://cdn/a.pdf');
- expect(component.triggerSave).toHaveBeenCalled();
- });
-
- it('should create and emit review save action in triggerSave', () => {
- component.doReview = true;
- component.innerValue = {
- file: { path: '/uploads/a' },
- comment: 'review comment'
- };
- const submitNextSpy = spyOn(component.submitActions$, 'next');
-
- component.triggerSave();
-
- expect(submitNextSpy).toHaveBeenCalled();
- expect((submitNextSpy.calls.mostRecent().args[0] as any).reviewSave).toEqual({
- reviewId: 456,
- submissionId: 123,
- questionId: 11,
- file: { path: '/uploads/a' },
- comment: 'review comment',
- });
- });
-
- it('should create and emit assessment save action in triggerSave', () => {
- component.doAssessment = true;
- component.innerValue = { path: '/uploads/a' };
- const submitNextSpy = spyOn(component.submitActions$, 'next');
-
- component.triggerSave();
-
- expect(submitNextSpy).toHaveBeenCalled();
- expect((submitNextSpy.calls.mostRecent().args[0] as any).questionSave).toEqual({
- submissionId: 123,
- questionId: 11,
- file: { path: '/uploads/a' },
- });
- });
-
- it('should add error when upload response status is not 200', () => {
- component.tusResponse = {
- bucket: 'bucket',
- path: '/uploads/a',
- cdnUrl: 'https://cdn/a',
- directUrl: 'https://direct/a',
- };
- component.doReview = true;
- spyOn(component, 'onChange');
-
- component.onFileUploadCompleted({
- name: 'a.pdf',
- type: 'application/pdf',
- size: 10,
- extension: 'pdf'
- } as any, {
- body: {} as XMLHttpRequest,
- status: 500,
- uploadURL: ''
- });
-
- expect(component.uploadedFile.name).toBe('a.pdf');
- expect(component.errors.length).toBe(1);
- expect(component.onChange).toHaveBeenCalledWith('', 'answer');
- });
-
- it('should remove submission answer and clear uppy in removeSubmitFile for assessment', () => {
- component.doAssessment = true;
- component.submission = { answer: { path: '/uploads/a' } };
- spyOn(component, 'onChange');
-
- component.removeSubmitFile({ handle: 'file-1' });
-
- expect(component.submission.answer).toBeNull();
- expect(component.onChange).toHaveBeenCalledWith('');
- expect((component.uppy.removeFile as any)).toHaveBeenCalledWith('file-1');
- expect((component.uppy.clear as any)).toHaveBeenCalled();
- });
-
- it('should remove review answer and clear uppy in removeSubmitFile for review', () => {
- component.doReview = true;
- component.review = { answer: { path: '/uploads/a' } } as any;
- spyOn(component, 'onChange');
-
- component.removeSubmitFile({ handle: 'file-2' });
-
- expect(component.review.answer).toBeNull();
- expect(component.onChange).toHaveBeenCalledWith('', 'answer');
- expect((component.uppy.removeFile as any)).toHaveBeenCalledWith('file-2');
- expect((component.uppy.clear as any)).toHaveBeenCalled();
- });
-
- it('should return true when audience contains reviewer and has more than one role', () => {
- component.question.audience = ['participant', 'reviewer'];
-
- expect(component.audienceContainReviewer()).toBeTrue();
- });
-
- it('should return false when reviewer is not in audience', () => {
- component.question.audience = ['participant'];
-
- expect(component.audienceContainReviewer()).toBeFalse();
- });
-
- it('should extract filename from upload URL', () => {
- const filename = component.extractFilenameFromUrl('https://file.practera.com/uploads/test-file+abc123');
-
- expect(filename).toBe('test-file');
- });
-
- it('should return null when filename pattern does not match', () => {
- const filename = component.extractFilenameFromUrl('https://example.com/other-path/test-file');
-
- expect(filename).toBeNull();
- });
-
- it('should call uppy.removeFile in sendDeleteRequestForFile', () => {
- component.sendDeleteRequestForFile({ id: 'id-1' });
-
- expect((component.uppy.removeFile as any)).toHaveBeenCalledWith('id-1');
- });
-
- it('should destroy uppy in ngOnDestroy', () => {
- component.ngOnDestroy();
-
- expect((component.uppy.destroy as any)).toHaveBeenCalled();
- });
-
- describe('onChange() - markAsDirty behavior', () => {
- it('should mark control as dirty in review mode (with type)', () => {
- component.doReview = true;
- component.uploadedFile = {
- name: 'test.pdf',
- type: 'application/pdf',
- size: 100,
- extension: 'pdf',
- bucket: 'bucket',
- path: '/uploads/test',
- cdnUrl: 'https://cdn/test.pdf',
- } as any;
- spyOn(component, 'triggerSave');
-
- component.onChange('review comment', 'comment');
-
- expect(component.control.dirty).toBeTrue();
- expect(component.control.touched).toBeTrue();
- });
-
- it('should mark control as dirty in assessment mode (without type)', () => {
- component.doAssessment = true;
- component.uploadedFile = {
- name: 'test.pdf',
- type: 'application/pdf',
- size: 100,
- extension: 'pdf',
- bucket: 'bucket',
- path: '/uploads/test',
- cdnUrl: 'https://cdn/test.pdf',
- } as any;
- spyOn(component, 'triggerSave');
-
- component.onChange('');
-
- expect(component.control.dirty).toBeTrue();
- expect(component.control.touched).toBeTrue();
- });
-
- it('should set innerValue with file and type property in review mode', () => {
- component.doReview = true;
- component.uploadedFile = {
- name: 'doc.pdf',
- type: 'application/pdf',
- size: 50,
- extension: 'pdf',
- bucket: 'b',
- path: '/uploads/doc',
- cdnUrl: 'https://cdn/doc.pdf',
- } as any;
- spyOn(component, 'triggerSave');
-
- component.onChange('new answer', 'answer');
-
- expect(component.innerValue.answer).toBe('new answer');
- expect(component.innerValue.file).toBeDefined();
- expect(component.innerValue.file.url).toBe('https://cdn/doc.pdf');
- });
-
- it('should initialize innerValue if not set in review mode', () => {
- component.doReview = true;
- component.innerValue = null;
- component.uploadedFile = {
- name: 'a.pdf',
- type: 'application/pdf',
- size: 10,
- extension: 'pdf',
- bucket: 'b',
- path: '/uploads/a',
- cdnUrl: 'https://cdn/a.pdf',
- } as any;
- spyOn(component, 'triggerSave');
-
- component.onChange('comment text', 'comment');
-
- expect(component.innerValue.comment).toBe('comment text');
- expect(component.innerValue.file).toBeDefined();
- });
- });
-
- describe('_showSavedAnswers() - pristine check and uploadedFile restoration', () => {
- describe('review mode', () => {
- beforeEach(() => {
- component.reviewStatus = 'in progress';
- component.doReview = true;
- component.review = {
- answer: { url: 'https://cdn/saved.pdf', name: 'saved.pdf' },
- comment: 'saved comment',
- file: { url: 'https://cdn/saved.pdf', name: 'saved.pdf', path: '/uploads/saved' },
- };
- });
-
- it('should use saved review data when control is pristine', () => {
- component.control = new FormControl('');
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual({
- answer: component.review.answer,
- comment: component.review.comment,
- file: component.review.file,
- });
- expect(component.comment).toBe('saved comment');
- });
-
- it('should preserve control value when control is dirty', () => {
- const dirtyValue = {
- answer: 'user edited',
- comment: 'user comment',
- file: { url: 'https://cdn/edited.pdf', name: 'edited.pdf', path: '/uploads/edited' },
- };
- component.control = new FormControl(dirtyValue);
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual(dirtyValue);
- expect(component.comment).toBe('user comment');
- });
-
- it('should restore uploadedFile from file.url when control is dirty with file data', () => {
- const dirtyValue = {
- answer: 'edited',
- comment: 'edited comment',
- file: { url: 'https://cdn/dirty-file.pdf', name: 'dirty-file.pdf', path: '/uploads/dirty' },
- };
- component.control = new FormControl(dirtyValue);
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.uploadedFile).toBeDefined();
- expect(component.uploadedFile.cdnUrl).toBe('https://cdn/dirty-file.pdf');
- });
-
- it('should not set uploadedFile when dirty control has no file url', () => {
- const dirtyValue = {
- answer: 'edited',
- comment: 'edited comment',
- file: null,
- };
- component.control = new FormControl(dirtyValue);
- component.control.markAsDirty();
- component.uploadedFile = null as any;
-
- component['_showSavedAnswers']();
-
- // uploadedFile should remain null since file has no url
- expect(component.uploadedFile).toBeNull();
- });
-
- it('should fallback to review comment when dirty control has no comment', () => {
- const dirtyValue = { answer: 'edited', file: null };
- component.control = new FormControl(dirtyValue);
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.comment).toBe('saved comment');
- });
- });
-
- describe('assessment mode', () => {
- beforeEach(() => {
- component.submissionStatus = 'in progress';
- component.doAssessment = true;
- component.submission = {
- answer: { url: 'https://cdn/submission.pdf', name: 'submission.pdf', path: '/uploads/sub' },
- };
- component.reviewStatus = '';
- component.doReview = false;
- });
-
- it('should use saved submission answer when control is pristine', () => {
- component.control = new FormControl('');
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual(component.submission.answer);
- });
-
- it('should preserve control value when control is dirty', () => {
- const dirtyValue = { url: 'https://cdn/user-edit.pdf', name: 'user-edit.pdf', path: '/uploads/user' };
- component.control = new FormControl(dirtyValue);
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual(dirtyValue);
- });
-
- it('should restore uploadedFile from innerValue.url when control is dirty', () => {
- const dirtyValue = { url: 'https://cdn/dirty.pdf', name: 'dirty.pdf', path: '/uploads/dirty' };
- component.control = new FormControl(dirtyValue);
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.uploadedFile).toBeDefined();
- expect(component.uploadedFile.cdnUrl).toBe('https://cdn/dirty.pdf');
- });
-
- it('should not restore uploadedFile when dirty control has no url', () => {
- component.control = new FormControl({});
- component.control.markAsDirty();
- component.uploadedFile = null as any;
-
- component['_showSavedAnswers']();
-
- expect(component.uploadedFile).toBeNull();
- });
- });
-
- it('should set control value at the end', () => {
- component.submissionStatus = 'in progress';
- component.doAssessment = true;
- component.submission = { answer: { url: 'https://cdn/test.pdf' } };
- component.control = new FormControl('');
-
- component['_showSavedAnswers']();
-
- expect(component.control.value).toEqual(component.submission.answer);
- });
-
- describe('review mode with "not start" status', () => {
- it('should load review data when reviewStatus is "not start"', () => {
- component.reviewStatus = 'not start';
- component.doReview = true;
- component.review = {
- answer: { url: 'https://cdn/r.pdf' },
- comment: 'review comment',
- file: { url: 'https://cdn/r.pdf', name: 'r.pdf' },
- };
- component.control = new FormControl('');
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual({
- answer: component.review.answer,
- comment: component.review.comment,
- file: component.review.file,
- });
- });
- });
- });
-
- describe('isDisplayOnly behavior via ngOnInit paths', () => {
- it('should restore saved file from submission answer URL when control is dirty in assessment mode', () => {
- component.submissionStatus = 'in progress';
- component.doAssessment = true;
- const savedFile = { url: 'https://cdn/sub-file.pdf', name: 'sub-file.pdf', path: '/uploads/sub' };
- component.submission = { answer: savedFile };
- component.control = new FormControl(savedFile);
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.uploadedFile).toBeDefined();
- expect(component.uploadedFile.cdnUrl).toBe('https://cdn/sub-file.pdf');
- });
-
- it('should restore saved file from review file URL when control is dirty in review mode', () => {
- component.reviewStatus = 'in progress';
- component.doReview = true;
- const savedReview = {
- answer: null,
- comment: 'test',
- file: { url: 'https://cdn/rev-file.pdf', name: 'rev-file.pdf', path: '/uploads/rev' },
- };
- component.review = savedReview;
- component.control = new FormControl(savedReview);
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.uploadedFile).toBeDefined();
- expect(component.uploadedFile.cdnUrl).toBe('https://cdn/rev-file.pdf');
- });
- });
});
diff --git a/projects/v3/src/app/components/file-upload/file-upload.component.ts b/projects/v3/src/app/components/file-upload/file-upload.component.ts
index 64873b627..4508b7831 100644
--- a/projects/v3/src/app/components/file-upload/file-upload.component.ts
+++ b/projects/v3/src/app/components/file-upload/file-upload.component.ts
@@ -25,7 +25,6 @@ const UPPY_PROPS: DashboardOptions = {
};
@Component({
- standalone: false,
selector: 'app-file-upload',
templateUrl: './file-upload.component.html',
styleUrls: [
diff --git a/projects/v3/src/app/components/filestack-preview/filestack-preview.component.spec.ts b/projects/v3/src/app/components/filestack-preview/filestack-preview.component.spec.ts
index 5fd60f4e2..dcd6e9920 100644
--- a/projects/v3/src/app/components/filestack-preview/filestack-preview.component.spec.ts
+++ b/projects/v3/src/app/components/filestack-preview/filestack-preview.component.spec.ts
@@ -46,6 +46,7 @@ describe('FilestackPreviewComponent', () => {
it('should has toolbar to control modal content', () => {
spyOn(window, 'open');
+ spyOn(modalSpy, 'dismiss');
component.file = { url: TEST_URL };
component.url = TEST_URL;
@@ -76,6 +77,7 @@ describe('FilestackPreviewComponent', () => {
describe('close()', () => {
it('should close opened modal', () => {
+ spyOn(modalSpy, 'dismiss');
component.close();
expect(modalSpy.dismiss).toHaveBeenCalled();
});
diff --git a/projects/v3/src/app/components/filestack-preview/filestack-preview.component.ts b/projects/v3/src/app/components/filestack-preview/filestack-preview.component.ts
index b4bdfd6bf..bb8df35e3 100644
--- a/projects/v3/src/app/components/filestack-preview/filestack-preview.component.ts
+++ b/projects/v3/src/app/components/filestack-preview/filestack-preview.component.ts
@@ -3,7 +3,6 @@ import { ModalController } from '@ionic/angular';
import { DomSanitizer } from '@angular/platform-browser';
@Component({
- standalone: false,
selector: 'app-filestack-preview',
templateUrl: './filestack-preview.component.html',
styleUrls: ['filestack-preview.component.scss']
diff --git a/projects/v3/src/app/components/filestack/filestack.component.ts b/projects/v3/src/app/components/filestack/filestack.component.ts
index b5f78b76b..97db0c4b2 100644
--- a/projects/v3/src/app/components/filestack/filestack.component.ts
+++ b/projects/v3/src/app/components/filestack/filestack.component.ts
@@ -22,7 +22,6 @@ export interface FilestackUploaded {
}
@Component({
- standalone: false,
selector: 'app-file-stack',
templateUrl: 'filestack.component.html',
styleUrls: ['filestack.component.scss']
diff --git a/projects/v3/src/app/components/img/img.component.spec.ts b/projects/v3/src/app/components/img/img.component.spec.ts
index 319341948..d1c7530e9 100644
--- a/projects/v3/src/app/components/img/img.component.spec.ts
+++ b/projects/v3/src/app/components/img/img.component.spec.ts
@@ -1,5 +1,4 @@
-import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
-import exif from 'exif-js';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ImgComponent } from './img.component';
@@ -7,7 +6,7 @@ describe('ImgComponent', () => {
let component: ImgComponent;
let fixture: ComponentFixture;
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ ImgComponent ]
})
@@ -42,66 +41,4 @@ describe('ImgComponent', () => {
});
});
});
-
- it('should set proxied image src for practera file URL on localhost', () => {
- const isLocalhost = /(^localhost$)|(^127\.)|(^::1$)/.test(window.location.hostname);
- if (!isLocalhost) {
- pending('requires localhost-like hostname');
- }
- component.imgSrc = 'https://file.practera.com/uploads/test-image.png';
-
- component.ngOnChanges();
-
- expect(component.proxiedImgSrc).toBe('/practera-proxy/uploads/test-image.png');
- });
-
- it('should not set proxied image src for non-practera URL', () => {
- component.imgSrc = 'https://example.com/uploads/test-image.png';
-
- component.ngOnChanges();
-
- expect(component.proxiedImgSrc).toBeUndefined();
- });
-
- it('should apply EXIF orientation class and swap dimensions for orientation >= 5', () => {
- const imageElement = {
- classList: jasmine.createSpyObj('classList', ['add']),
- height: 100,
- width: 200,
- } as any;
- const event = { target: imageElement };
-
- spyOn(exif, 'getData').and.callFake((image, callback: Function) => {
- callback.call(image);
- return undefined;
- });
- spyOn(exif, 'getAllTags').and.returnValue({ Orientation: 6 } as any);
-
- component.imageLoaded(event);
-
- expect(imageElement.classList.add).toHaveBeenCalledWith('rotate-90');
- expect(imageElement.height).toBe(200);
- expect(imageElement.width).toBe(100);
- });
-
- it('should not add class for unknown orientation', () => {
- const imageElement = {
- classList: jasmine.createSpyObj('classList', ['add']),
- height: 100,
- width: 200,
- } as any;
- const event = { target: imageElement };
-
- spyOn(exif, 'getData').and.callFake((image, callback: Function) => {
- callback.call(image);
- return undefined;
- });
- spyOn(exif, 'getAllTags').and.returnValue({ Orientation: 1 } as any);
-
- component.imageLoaded(event);
-
- expect(imageElement.classList.add).not.toHaveBeenCalled();
- expect(imageElement.height).toBe(100);
- expect(imageElement.width).toBe(200);
- });
});
diff --git a/projects/v3/src/app/components/img/img.component.ts b/projects/v3/src/app/components/img/img.component.ts
index 500ac0772..2c31331b9 100644
--- a/projects/v3/src/app/components/img/img.component.ts
+++ b/projects/v3/src/app/components/img/img.component.ts
@@ -28,7 +28,6 @@ const swapWidthAndHeight = img => {
};
@Component({
- standalone: false,
selector: 'app-img',
templateUrl: './img.component.html',
styleUrls: ['./img.component.scss']
diff --git a/projects/v3/src/app/components/list-item/list-item.component.html b/projects/v3/src/app/components/list-item/list-item.component.html
index 20baa4436..8604d62fe 100644
--- a/projects/v3/src/app/components/list-item/list-item.component.html
+++ b/projects/v3/src/app/components/list-item/list-item.component.html
@@ -88,7 +88,7 @@
-
+
diff --git a/projects/v3/src/app/components/list-item/list-item.component.spec.ts b/projects/v3/src/app/components/list-item/list-item.component.spec.ts
index 1292bc29a..d340dd212 100644
--- a/projects/v3/src/app/components/list-item/list-item.component.spec.ts
+++ b/projects/v3/src/app/components/list-item/list-item.component.spec.ts
@@ -6,7 +6,6 @@ import { IonicModule } from '@ionic/angular';
import { ListItemComponent } from './list-item.component';
@Component({
- standalone: false,
template: ` {
it('should display the title', () => {
listItemComponent.isEventItem = true;
listItemComponent.loading = false;
- listItemComponent.title = testHost.title;
fixture.detectChanges();
- const listItemDe: DebugElement = fixture.debugElement.query(By.css('[role="heading"]'));
+ const listItemDe: DebugElement = fixture.debugElement.query(By.css('.item-title'));
const listItemEl: HTMLElement = listItemDe.nativeElement;
+ // eslint-disable-next-line no-console
+ console.log(listItemEl);
- // the title is rendered via innerHTML and may have extra whitespace
- expect(listItemEl.textContent.trim()).toEqual(testHost.title);
+ expect(listItemEl.textContent).toEqual(testHost.title);
});
it('should return correct description', () => {
diff --git a/projects/v3/src/app/components/list-item/list-item.component.ts b/projects/v3/src/app/components/list-item/list-item.component.ts
index 375a4a335..c645b24d5 100644
--- a/projects/v3/src/app/components/list-item/list-item.component.ts
+++ b/projects/v3/src/app/components/list-item/list-item.component.ts
@@ -5,7 +5,6 @@ interface CTABtnType {
}
@Component({
- standalone: false,
selector: 'app-list-item',
templateUrl: './list-item.component.html',
styleUrls: ['./list-item.component.scss'],
diff --git a/projects/v3/src/app/components/lock-team-assessment-pop-up/lock-team-assessment-pop-up.component.spec.ts b/projects/v3/src/app/components/lock-team-assessment-pop-up/lock-team-assessment-pop-up.component.spec.ts
index 8c7947d9e..db5a2d2fd 100644
--- a/projects/v3/src/app/components/lock-team-assessment-pop-up/lock-team-assessment-pop-up.component.spec.ts
+++ b/projects/v3/src/app/components/lock-team-assessment-pop-up/lock-team-assessment-pop-up.component.spec.ts
@@ -1,5 +1,5 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { LockTeamAssessmentPopUpComponent } from './lock-team-assessment-pop-up.component';
import { ModalController } from '@ionic/angular';
import { UtilsService } from '@v3/services/utils.service';
@@ -8,9 +8,9 @@ import { TestUtils } from '@testingv3/utils';
describe('LockTeamAssessmentPopUpComponent', () => {
let component: LockTeamAssessmentPopUpComponent;
let fixture: ComponentFixture;
- let modalCtrlSpy: any;
+ const modalCtrlSpy = jasmine.createSpyObj('ModalController', ['dismiss', 'create']);
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [LockTeamAssessmentPopUpComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -31,7 +31,6 @@ describe('LockTeamAssessmentPopUpComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(LockTeamAssessmentPopUpComponent);
component = fixture.componentInstance;
- modalCtrlSpy = TestBed.inject(ModalController);
});
it('should create', () => {
diff --git a/projects/v3/src/app/components/lock-team-assessment-pop-up/lock-team-assessment-pop-up.component.ts b/projects/v3/src/app/components/lock-team-assessment-pop-up/lock-team-assessment-pop-up.component.ts
index c4076effc..bcf261f0f 100644
--- a/projects/v3/src/app/components/lock-team-assessment-pop-up/lock-team-assessment-pop-up.component.ts
+++ b/projects/v3/src/app/components/lock-team-assessment-pop-up/lock-team-assessment-pop-up.component.ts
@@ -3,7 +3,6 @@ import { ModalController } from '@ionic/angular';
import { UtilsService } from '@v3/services/utils.service';
@Component({
- standalone: false,
selector: 'app-lock-team-assessment-pop-up',
templateUrl: 'lock-team-assessment-pop-up.component.html',
styleUrls: ['lock-team-assessment-pop-up.component.scss']
diff --git a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html
index 030be8d8b..1f6a3e6b8 100644
--- a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html
+++ b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.html
@@ -7,13 +7,13 @@
- Learner's Answer
+ Learner's answer
- Reviewer's Answer
+ Expert's answer
@@ -42,25 +42,23 @@
+
+
+
-
-
-
-
@@ -97,7 +95,7 @@
- Learner's Answer
+ Learner's answer
diff --git a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.spec.ts b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.spec.ts
index 5a1ee65d5..dc7dc9db7 100644
--- a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.spec.ts
+++ b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.spec.ts
@@ -32,7 +32,7 @@ describe('MultiTeamMemberSelectorComponent', () => {
component.control = new FormControl();
component.submitActions$ = new Subject();
- component.question = { audience: [] } as any;
+ component.question = { audience: [] };
component.submission = {};
component.review = {};
});
@@ -105,17 +105,15 @@ describe('MultiTeamMemberSelectorComponent', () => {
it('should set errors and call submitActions$.next()', () => {
spyOn(component.submitActions$, 'next');
- component.control = new FormControl('', Validators.required) as any;
+ component.control = new FormControl('', Validators.required);
component.onChange('value1');
expect(component.errors).toContain('This question is required');
- expect(component.submitActions$.next).toHaveBeenCalledWith(
- jasmine.objectContaining({
- autoSave: true,
- goBack: false,
- })
- );
+ expect(component.submitActions$.next).toHaveBeenCalledWith({
+ saveInProgress: true,
+ goBack: false,
+ });
});
});
@@ -127,8 +125,7 @@ describe('MultiTeamMemberSelectorComponent', () => {
};
component.writeValue(value);
- // writeValue sets innerValue directly without stringify
- expect(component.innerValue).toEqual(value);
+ expect(component.innerValue).toEqual(JSON.stringify(value));
});
it('should not update innerValue when the value is undefined or null', () => {
@@ -140,47 +137,6 @@ describe('MultiTeamMemberSelectorComponent', () => {
component.writeValue(null);
expect(component.innerValue).toEqual('initialValue');
});
-
- it('should normalize non-array answer in review mode', () => {
- component.doReview = true;
- component.writeValue({ answer: 'not-array', comment: 'test' });
-
- expect(Array.isArray(component.innerValue.answer)).toBeTrue();
- expect(component.innerValue.answer).toEqual([]);
- });
-
- it('should keep array answer in review mode', () => {
- component.doReview = true;
- component.writeValue({ answer: ['member1'], comment: 'test' });
-
- expect(component.innerValue.answer).toEqual(['member1']);
- });
-
- it('should normalize non-array value to plain array in assessment mode', () => {
- component.doAssessment = true;
- component.doReview = false;
- component.writeValue({ answer: ['member1'], comment: 'test' });
-
- // in assessment mode, innerValue should be a plain array
- expect(Array.isArray(component.innerValue)).toBeTrue();
- expect(component.innerValue).toEqual(['member1']);
- });
-
- it('should normalize non-array value to empty array in assessment mode', () => {
- component.doAssessment = true;
- component.doReview = false;
- component.writeValue('not-an-array');
-
- expect(Array.isArray(component.innerValue)).toBeTrue();
- expect(component.innerValue).toEqual([]);
- });
-
- it('should set comment from value when present', () => {
- component.doReview = true;
- component.writeValue({ answer: [], comment: 'new comment' });
-
- expect(component.comment).toBe('new comment');
- });
});
describe('_showSavedAnswers()', () => {
@@ -189,7 +145,6 @@ describe('MultiTeamMemberSelectorComponent', () => {
component.doReview = true;
component.review.answer = ['answer1'];
component.review.comment = 'comment1';
- component.control = new FormControl('') as any;
component['_showSavedAnswers']();
@@ -197,99 +152,21 @@ describe('MultiTeamMemberSelectorComponent', () => {
answer: ['answer1'],
comment: 'comment1',
});
- // propagateChange doesn't update control.value, so we only check innerValue
+ expect(component.control.value).toEqual({
+ answer: ['answer1'],
+ comment: 'comment1',
+ });
});
it('should set innerValue and propagate changes for in-progress submission', () => {
component.submissionStatus = 'in progress';
component.doAssessment = true;
component.submission.answer = ['answer1'];
- component.control = new FormControl('') as any;
component['_showSavedAnswers']();
- // in assessment mode, innerValue is a plain array (not an object)
expect(component.innerValue).toEqual(['answer1']);
- });
-
- it('should preserve control value when control is dirty in review mode', () => {
- component.reviewStatus = 'in progress';
- component.doReview = true;
- component.review = { answer: ['saved'], comment: 'saved comment' };
- const dirtyValue = { answer: ['user-edited'], comment: 'user comment' };
- component.control = new FormControl(dirtyValue) as any;
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual(dirtyValue);
- expect(component.comment).toBe('user comment');
- });
-
- it('should normalize non-array answer when control is dirty in review mode', () => {
- component.reviewStatus = 'in progress';
- component.doReview = true;
- component.review = { answer: ['saved'], comment: 'saved comment' };
- const dirtyValue = { answer: 'not-an-array', comment: 'user comment' };
- component.control = new FormControl(dirtyValue) as any;
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(Array.isArray(component.innerValue.answer)).toBeTrue();
- expect(component.innerValue.answer).toEqual([]);
- });
-
- it('should normalize non-array answer to empty array when review data is pristine', () => {
- component.reviewStatus = 'in progress';
- component.doReview = true;
- component.review = { answer: 'not-an-array', comment: 'comment' };
- component.control = new FormControl('') as any;
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue.answer).toEqual([]);
- });
-
- it('should fallback to review comment when dirty value has no comment', () => {
- component.reviewStatus = 'in progress';
- component.doReview = true;
- component.review = { answer: ['saved'], comment: 'saved comment' };
- const dirtyValue = { answer: ['user-edited'] };
- component.control = new FormControl(dirtyValue) as any;
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.comment).toBe('saved comment');
- });
-
- it('should preserve control value when control is dirty in assessment mode', () => {
- component.reviewStatus = '';
- component.doReview = false;
- component.submissionStatus = 'in progress';
- component.doAssessment = true;
- component.submission = { answer: ['saved'] };
- const dirtyValue = ['user-edited'];
- component.control = new FormControl(dirtyValue) as any;
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual(['user-edited']);
- });
-
- it('should default to empty array when submission answer is null in assessment mode', () => {
- component.reviewStatus = '';
- component.doReview = false;
- component.submissionStatus = 'in progress';
- component.doAssessment = true;
- component.submission = { answer: null };
- component.control = new FormControl('') as any;
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual([]);
+ expect(component.control.value).toEqual(['answer1']);
});
});
@@ -370,226 +247,4 @@ describe('MultiTeamMemberSelectorComponent', () => {
expect(component.isSelectedInReview(teamMember)).toBeFalse();
});
});
-
- describe('isSelected()', () => {
- const teamMember = { key: JSON.stringify({ name: 'User1', recipientId: 1, recipientEmail: 'u1@test.com', userId: 10 }), userName: 'User1' };
-
- it('should return false when innerValue is null', () => {
- component.innerValue = null;
- expect(component.isSelected(teamMember)).toBeFalse();
- });
-
- it('should return true in assessment mode when member is in innerValue', () => {
- component.doAssessment = true;
- component.doReview = false;
- component.innerValue = [JSON.stringify({ name: 'User1', recipientId: 1, recipientEmail: 'u1@test.com', userId: 10 })];
- expect(component.isSelected(teamMember)).toBeTrue();
- });
-
- it('should return false in assessment mode when member is not in innerValue', () => {
- component.doAssessment = true;
- component.doReview = false;
- component.innerValue = [JSON.stringify({ name: 'Other', recipientId: 2, recipientEmail: 'o@test.com', userId: 99 })];
- expect(component.isSelected(teamMember)).toBeFalse();
- });
-
- it('should return true in review mode when member is in innerValue.answer', () => {
- component.doReview = true;
- component.doAssessment = false;
- component.innerValue = {
- answer: [JSON.stringify({ name: 'User1', recipientId: 1, recipientEmail: 'u1@test.com', userId: 10 })],
- comment: '',
- };
- expect(component.isSelected(teamMember)).toBeTrue();
- });
-
- it('should return false in review mode when innerValue.answer is undefined', () => {
- component.doReview = true;
- component.doAssessment = false;
- component.innerValue = { comment: '' };
- expect(component.isSelected(teamMember)).toBeFalse();
- });
- });
-
- describe('triggerSave()', () => {
- beforeEach(() => {
- component.question = { id: 20, audience: [] } as any;
- component.submissionId = 50;
- component.reviewId = 60;
- component.submitActions$ = jasmine.createSpyObj('Subject', ['next']);
- });
-
- it('should emit review save action when doReview is true', () => {
- component.doReview = true;
- component.doAssessment = false;
- component.innerValue = { answer: ['member-1'], comment: 'review comment' };
-
- component.triggerSave();
-
- expect(component.submitActions$.next).toHaveBeenCalledWith(jasmine.objectContaining({
- autoSave: true,
- goBack: false,
- reviewSave: {
- reviewId: 60,
- submissionId: 50,
- questionId: 20,
- answer: ['member-1'],
- comment: 'review comment',
- },
- }));
- });
-
- it('should emit question save action when doAssessment is true', () => {
- component.doAssessment = true;
- component.doReview = false;
- component.innerValue = ['member-a', 'member-b'];
-
- component.triggerSave();
-
- expect(component.submitActions$.next).toHaveBeenCalledWith(jasmine.objectContaining({
- autoSave: true,
- goBack: false,
- questionSave: {
- submissionId: 50,
- questionId: 20,
- answer: ['member-a', 'member-b'],
- },
- }));
- });
- });
-
- describe('onLabelToggle / onLabelToggleReview', () => {
- beforeEach(() => {
- component.control = new FormControl('') as any;
- component.submitActions$ = new Subject();
- spyOn(component, 'onChange');
- });
-
- it('onLabelToggle should call onChange without type', () => {
- component.onLabelToggle('member-1');
- expect(component.onChange).toHaveBeenCalledWith('member-1');
- });
-
- it('onLabelToggleReview should call onChange with answer type', () => {
- component.onLabelToggleReview('member-1');
- expect(component.onChange).toHaveBeenCalledWith('member-1', 'answer');
- });
- });
-
- describe('registerOnChange() / registerOnTouched()', () => {
- it('registerOnChange should set propagateChange', () => {
- const fn = jasmine.createSpy('onChange');
- component.registerOnChange(fn);
- component.propagateChange('test');
- expect(fn).toHaveBeenCalledWith('test');
- });
-
- it('registerOnTouched should not throw', () => {
- expect(() => component.registerOnTouched(() => {})).not.toThrow();
- });
- });
-
- describe('isDisplayOnly', () => {
- it('should be true when reviewer has canAnswer false', () => {
- component.doReview = true;
- component.doAssessment = false;
- component.question = { canAnswer: false, audience: [] } as any;
- expect(component.isDisplayOnly).toBeTrue();
- });
-
- it('should be truthy when feedback available with submission answer', () => {
- component.doAssessment = false;
- component.doReview = false;
- component.submissionStatus = 'feedback available';
- component.submission = { answer: ['member-1'] };
- expect(component.isDisplayOnly).toBeTruthy();
- });
-
- it('should be truthy when pending review with submission answer', () => {
- component.doAssessment = false;
- component.doReview = false;
- component.submissionStatus = 'pending review';
- component.submission = { answer: ['member-1'] };
- expect(component.isDisplayOnly).toBeTruthy();
- });
-
- it('should be truthy when done with empty review status', () => {
- component.doAssessment = false;
- component.doReview = false;
- component.submissionStatus = 'done';
- component.reviewStatus = '';
- component.submission = { answer: ['member-1'] };
- expect(component.isDisplayOnly).toBeTruthy();
- });
-
- it('should be false when doing assessment', () => {
- component.doAssessment = true;
- component.doReview = false;
- expect(component.isDisplayOnly).toBeFalse();
- });
-
- it('should be false when doing review with canAnswer true', () => {
- component.doAssessment = false;
- component.doReview = true;
- component.question = { canAnswer: true, audience: [] } as any;
- expect(component.isDisplayOnly).toBeFalse();
- });
- });
-
- describe('_showSavedAnswers() - "not start" review status', () => {
- it('should load review data when reviewStatus is "not start"', () => {
- component.reviewStatus = 'not start';
- component.doReview = true;
- component.review = { answer: ['member-x'], comment: 'test' };
- component.control = new FormControl(null) as any;
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual({
- answer: ['member-x'],
- comment: 'test',
- });
- });
- });
-
- describe('_showSavedAnswers() - propagateChange call', () => {
- it('should call propagateChange with innerValue', () => {
- component.submissionStatus = 'in progress';
- component.doAssessment = true;
- component.submission = { answer: ['member-1'] };
- component.reviewStatus = '';
- component.doReview = false;
- component.control = new FormControl(null) as any;
- spyOn(component, 'propagateChange');
-
- component['_showSavedAnswers']();
-
- expect(component.propagateChange).toHaveBeenCalledWith(['member-1']);
- });
- });
-
- describe('onChange() - initializes innerValue for review mode', () => {
- it('should initialize innerValue with answer array and empty comment when innerValue is null', () => {
- component.innerValue = null;
- component.control = new FormControl('') as any;
- utilsSpy.addOrRemove = jasmine.createSpy('addOrRemove').and.returnValue(['member-1']);
- spyOn(component, 'propagateChange');
-
- component.onChange('member-1', 'answer');
-
- expect(component.innerValue.answer).toEqual(['member-1']);
- expect(component.innerValue.comment).toBe('');
- });
-
- it('should normalize non-array answer to empty array before toggling', () => {
- component.innerValue = { answer: 'not-array', comment: '' };
- component.control = new FormControl('') as any;
- utilsSpy.addOrRemove = jasmine.createSpy('addOrRemove').and.returnValue(['member-1']);
- spyOn(component, 'propagateChange');
-
- component.onChange('member-1', 'answer');
-
- expect(utilsSpy.addOrRemove).toHaveBeenCalledWith([], 'member-1');
- });
- });
});
diff --git a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.ts b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.ts
index 5597b283d..772d5357a 100644
--- a/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.ts
+++ b/projects/v3/src/app/components/multi-team-member-selector/multi-team-member-selector.component.ts
@@ -5,7 +5,6 @@ import { Subject } from 'rxjs';
import { Question } from '../types/assessment';
@Component({
- standalone: false,
selector: 'app-multi-team-member-selector',
templateUrl: 'multi-team-member-selector.component.html',
styleUrls: ['multi-team-member-selector.component.scss'],
@@ -239,7 +238,7 @@ export class MultiTeamMemberSelectorComponent implements ControlValueAccessor, O
/**
* checks if a team member was selected in the learner's original submission.
* reads from @Input submission.answer (api data, never modified locally).
- * used only for displaying the "Learner's Answer" badge in review mode.
+ * used only for displaying the "Learner's answer" badge in review mode.
*/
isSelectedInSubmission(teamMember: any): boolean {
if (!this.submission?.answer) return false;
@@ -261,7 +260,7 @@ export class MultiTeamMemberSelectorComponent implements ControlValueAccessor, O
/**
* checks if a team member was selected in the reviewer's original review.
* reads from @Input review.answer (api data, never modified locally).
- * used only in isDisplayOnly (read-only) mode for the "Reviewer's Answer" badge.
+ * used only in isDisplayOnly (read-only) mode for the "Expert's answer" badge.
* not used for checkbox [checked] binding — use isSelected() instead to
* preserve local edits across pagination.
*/
diff --git a/projects/v3/src/app/components/multiple/multiple.component.html b/projects/v3/src/app/components/multiple/multiple.component.html
index 3411334b4..ff1536593 100644
--- a/projects/v3/src/app/components/multiple/multiple.component.html
+++ b/projects/v3/src/app/components/multiple/multiple.component.html
@@ -9,17 +9,17 @@ {
Learner's Answer
+ >Learner's answer
Reviewer's Answer
+ >Expert's answer
-
+
@@ -73,7 +73,7 @@ {
*ngIf="control.disabled && choice.explanation && choice.explanation.changingThisBreaksApplicationSecurity && checkInnerValue(choice.id)">
-
+
@@ -118,7 +118,7 @@ {
Learner's Answer
+ >Learner's answer
diff --git a/projects/v3/src/app/components/multiple/multiple.component.spec.ts b/projects/v3/src/app/components/multiple/multiple.component.spec.ts
index ee2a0deec..29ef0d2ae 100644
--- a/projects/v3/src/app/components/multiple/multiple.component.spec.ts
+++ b/projects/v3/src/app/components/multiple/multiple.component.spec.ts
@@ -1,33 +1,24 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { MultipleComponent } from './multiple.component';
import { ReactiveFormsModule, FormControl } from '@angular/forms';
import { UtilsService } from '@v3/services/utils.service';
import { TestUtils } from '@testingv3/utils';
-import { LanguageDetectionPipe } from '@v3/app/pipes/language.pipe';
-import { DomSanitizer } from '@angular/platform-browser';
-import { ToggleLabelDirective } from '@v3/app/directives/toggle-label/toggle-label.directive';
describe('MultipleComponent', () => {
let component: MultipleComponent;
let fixture: ComponentFixture;
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
- imports: [ReactiveFormsModule, ToggleLabelDirective],
- declarations: [MultipleComponent, LanguageDetectionPipe],
+ imports: [ReactiveFormsModule],
+ declarations: [MultipleComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{
provide: UtilsService,
useClass: TestUtils,
},
- {
- provide: DomSanitizer,
- useValue: {
- bypassSecurityTrustHtml: (html: string) => html
- }
- }
],
})
.compileComponents();
@@ -59,8 +50,8 @@ describe('MultipleComponent', () => {
component.review = {};
component.control = new FormControl('');
fixture.detectChanges();
- // component sets innerValue from submission.answer when control is pristine
expect(component.innerValue).toEqual(component.submission.answer);
+ expect(component.control.value).toEqual(component.submission.answer);
});
it('should get correct data for in progress review', () => {
@@ -78,16 +69,13 @@ describe('MultipleComponent', () => {
component.doReview = true;
component.review = {
comment: 'asdf',
- answer: ['abc']
+ answer: { name: 'abc' }
};
component.control = new FormControl('');
fixture.detectChanges();
- // component sets innerValue to review data
- expect(component.innerValue).toEqual({
- answer: ['abc'],
- comment: component.review.comment
- });
+ expect(component.innerValue).toEqual(component.review);
expect(component.comment).toEqual(component.review.comment);
+ expect(component.control.value).toEqual(component.review);
});
});
@@ -100,30 +88,30 @@ describe('MultipleComponent', () => {
component.control.setErrors({
key: 'error'
});
- component.onChange(4);
+ component.onChange(4, null);
expect(component.errors.length).toBe(1);
});
it('should return error if required not filled', () => {
component.control.setErrors({
required: true
});
- component.onChange(4);
+ component.onChange(4, null);
expect(component.errors.length).toBe(1);
expect(component.errors[0]).toContain('is required');
});
it('should get correct data when writing submission answer', () => {
- component.onChange(4);
+ component.onChange(4, null);
expect(component.errors.length).toBe(0);
expect(component.innerValue).toEqual([4]);
});
- it('should get correct data when appending submission answer', () => {
+ it('should get correct data when writing submission answer', () => {
component.innerValue = [1, 2, 3];
- component.onChange(4);
+ component.onChange(4, null);
expect(component.errors.length).toBe(0);
expect(component.innerValue).toEqual([1, 2, 3, 4]);
});
it('should get correct data when writing review answer', () => {
- component.innerValue = { answer: [1, 2, 3], comment: '' };
+ component.innerValue = JSON.stringify({ answer: [1, 2, 3], comment: '' });
component.onChange(2, 'answer');
expect(component.errors.length).toBe(0);
expect(component.innerValue).toEqual({ answer: [1, 3], comment: '' });
@@ -135,10 +123,9 @@ describe('MultipleComponent', () => {
});
});
- it('when testing writeValue(), it should call the method correctly', () => {
- // writeValue is empty in the component - it doesn't set innerValue
+ it('when testing writeValue(), it should pass data correctly', () => {
component.writeValue({ data: 'data' });
- // no assertion needed since writeValue does nothing
+ expect(component.innerValue).toEqual(JSON.stringify({ data: 'data' }));
component.writeValue(null);
});
it('when testing registerOnChange()', () => {
@@ -147,297 +134,5 @@ describe('MultipleComponent', () => {
component.registerOnTouched(() => true);
});
- describe('_showSavedAnswers() - pristine check and array normalization', () => {
- describe('review mode', () => {
- beforeEach(() => {
- component.reviewStatus = 'in progress';
- component.doReview = true;
- component.review = {
- answer: ['choice1', 'choice2'],
- comment: 'saved comment',
- };
- component.control = new FormControl('');
- component.submissionStatus = '';
- component.doAssessment = false;
- });
-
- it('should use saved review data when control is pristine', () => {
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual({
- answer: ['choice1', 'choice2'],
- comment: 'saved comment',
- });
- expect(component.comment).toBe('saved comment');
- });
-
- it('should normalize non-array answer to empty array when pristine', () => {
- component.review = { answer: 'not-an-array', comment: 'comment' };
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue.answer).toEqual([]);
- });
-
- it('should preserve control value when control is dirty', () => {
- const dirtyValue = { answer: ['user-choice'], comment: 'user comment' };
- component.control.setValue(dirtyValue);
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual(dirtyValue);
- });
-
- it('should normalize non-array answer in dirty control value', () => {
- const dirtyValue = { answer: 'not-an-array', comment: 'user comment' };
- component.control.setValue(dirtyValue);
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(Array.isArray(component.innerValue.answer)).toBeTrue();
- expect(component.innerValue.answer).toEqual([]);
- });
-
- it('should fallback to review comment when dirty value has no comment', () => {
- const dirtyValue = { answer: ['choice'] };
- component.control.setValue(dirtyValue);
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.comment).toBe('saved comment');
- });
- });
-
- describe('assessment mode', () => {
- beforeEach(() => {
- component.submissionStatus = 'in progress';
- component.doAssessment = true;
- component.submission = { answer: ['saved-choice1', 'saved-choice2'] };
- component.reviewStatus = '';
- component.doReview = false;
- component.control = new FormControl('');
- });
-
- it('should use saved submission answer when control is pristine', () => {
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual(['saved-choice1', 'saved-choice2']);
- });
-
- it('should preserve control value when control is dirty', () => {
- component.control.setValue(['user-choice']);
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual(['user-choice']);
- });
- });
- });
-
- describe('writeValue() - array normalization in review mode', () => {
- it('should normalize non-array answer to empty array in review mode', () => {
- component.doReview = true;
- component.writeValue({ answer: 'not-array', comment: 'test' });
-
- expect(Array.isArray(component.innerValue.answer)).toBeTrue();
- expect(component.innerValue.answer).toEqual([]);
- });
-
- it('should keep array answer as-is in review mode', () => {
- component.doReview = true;
- component.writeValue({ answer: ['choice1'], comment: 'test' });
-
- expect(component.innerValue.answer).toEqual(['choice1']);
- });
-
- it('should set comment from value', () => {
- component.doReview = true;
- component.writeValue({ answer: [], comment: 'new comment' });
-
- expect(component.comment).toBe('new comment');
- });
-
- it('should not update innerValue for null', () => {
- component.innerValue = 'existing';
- component.writeValue(null);
-
- // writeValue does nothing for null based on the code
- });
- });
-
- describe('triggerSave()', () => {
- beforeEach(() => {
- component.question = { id: 7, audience: [] };
- component.submissionId = 30;
- component.reviewId = 40;
- component.submitActions$ = jasmine.createSpyObj('Subject', ['next']);
- });
-
- it('should emit review save action when doReview is true', () => {
- component.doReview = true;
- component.doAssessment = false;
- component.innerValue = { answer: [1, 3], comment: 'nice work' };
-
- component.triggerSave();
-
- expect(component.submitActions$.next).toHaveBeenCalledWith(jasmine.objectContaining({
- autoSave: true,
- goBack: false,
- reviewSave: {
- reviewId: 40,
- submissionId: 30,
- questionId: 7,
- answer: [1, 3],
- comment: 'nice work',
- },
- }));
- });
-
- it('should emit question save action when doAssessment is true', () => {
- component.doAssessment = true;
- component.doReview = false;
- component.innerValue = [2, 4];
-
- component.triggerSave();
-
- expect(component.submitActions$.next).toHaveBeenCalledWith(jasmine.objectContaining({
- autoSave: true,
- goBack: false,
- questionSave: {
- submissionId: 30,
- questionId: 7,
- answer: [2, 4],
- },
- }));
- });
- });
-
- describe('onLabelToggle()', () => {
- beforeEach(() => {
- component.control = new FormControl('');
- spyOn(component, 'onChange');
- });
-
- it('should call onChange with answer type when doReview', () => {
- component.doReview = true;
- component.onLabelToggle('5');
- expect(component.onChange).toHaveBeenCalledWith('5', 'answer');
- });
-
- it('should call onChange without type when not doReview', () => {
- component.doReview = false;
- component.onLabelToggle('5');
- expect(component.onChange).toHaveBeenCalledWith('5');
- });
- });
-
- describe('ngOnDestroy()', () => {
- it('should unsubscribe all subscriptions', () => {
- const sub1 = jasmine.createSpyObj('Subscription', ['unsubscribe']);
- const sub2 = jasmine.createSpyObj('Subscription', ['unsubscribe']);
- component.subscriptions = [sub1, sub2];
-
- component.ngOnDestroy();
-
- expect(sub1.unsubscribe).toHaveBeenCalled();
- expect(sub2.unsubscribe).toHaveBeenCalled();
- });
- });
-
- describe('isDisplayOnly()', () => {
- it('should be true when reviewer has canAnswer false', () => {
- component.doReview = true;
- component.doAssessment = false;
- component.question = { canAnswer: false, audience: [] };
- expect(component.isDisplayOnly).toBeTrue();
- });
-
- it('should be true when status is feedback available', () => {
- component.doAssessment = false;
- component.doReview = false;
- component.submissionStatus = 'feedback available';
- expect(component.isDisplayOnly).toBeTrue();
- });
-
- it('should be false when doing assessment', () => {
- component.doAssessment = true;
- component.doReview = false;
- expect(component.isDisplayOnly).toBeFalse();
- });
-
- it('should be true when done with empty review status', () => {
- component.doAssessment = false;
- component.doReview = false;
- component.submissionStatus = 'done';
- component.reviewStatus = '';
- expect(component.isDisplayOnly).toBeTrue();
- });
- });
-
- describe('audienceContainReviewer()', () => {
- it('should return true when multiple audiences include reviewer', () => {
- component.question = { audience: ['submitter', 'reviewer'] };
- expect(component.audienceContainReviewer()).toBeTrue();
- });
-
- it('should return false for single audience', () => {
- component.question = { audience: ['submitter'] };
- expect(component.audienceContainReviewer()).toBeFalse();
- });
- });
-
- describe('_showSavedAnswers() - propagateChange call', () => {
- it('should call propagateChange with innerValue', () => {
- component.submissionStatus = 'in progress';
- component.doAssessment = true;
- component.submission = { answer: [1, 2] };
- component.reviewStatus = '';
- component.doReview = false;
- component.control = new FormControl('');
- spyOn(component, 'propagateChange');
-
- component['_showSavedAnswers']();
-
- expect(component.propagateChange).toHaveBeenCalledWith([1, 2]);
- });
- });
-
- describe('_showSavedAnswers() - "not start" review status', () => {
- it('should load review data when reviewStatus is "not start"', () => {
- component.reviewStatus = 'not start';
- component.doReview = true;
- component.review = { answer: ['a', 'b'], comment: 'test' };
- component.control = new FormControl('');
- component.submissionStatus = '';
- component.doAssessment = false;
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual({
- answer: ['a', 'b'],
- comment: 'test',
- });
- });
- });
-
- describe('checkInnerValue()', () => {
- it('should return true when choiceId is in innerValue array', () => {
- component.innerValue = [1, 2, 3];
- expect(component.checkInnerValue(2)).toBeTrue();
- });
-
- it('should return undefined when choiceId is not in array', () => {
- component.innerValue = [1, 2, 3];
- expect(component.checkInnerValue(5)).toBeUndefined();
- });
-
- it('should return undefined for falsy choiceId', () => {
- component.innerValue = [1, 2];
- expect(component.checkInnerValue(null)).toBeUndefined();
- });
- });
});
+
diff --git a/projects/v3/src/app/components/multiple/multiple.component.ts b/projects/v3/src/app/components/multiple/multiple.component.ts
index 0a25c971e..3df558f0e 100644
--- a/projects/v3/src/app/components/multiple/multiple.component.ts
+++ b/projects/v3/src/app/components/multiple/multiple.component.ts
@@ -6,7 +6,6 @@ import { from, fromEvent, merge, Subject, Subscription } from 'rxjs';
import { debounceTime, map, switchMap } from 'rxjs/operators';
@Component({
- standalone: false,
selector: 'app-multiple',
templateUrl: 'multiple.component.html',
styleUrls: ['multiple.component.scss'],
diff --git a/projects/v3/src/app/components/oneof/oneof.component.html b/projects/v3/src/app/components/oneof/oneof.component.html
index 54139e9f3..f24bba6ae 100644
--- a/projects/v3/src/app/components/oneof/oneof.component.html
+++ b/projects/v3/src/app/components/oneof/oneof.component.html
@@ -7,16 +7,16 @@
Learner's Answer
+ >Learner's answer
Reviewer's Answer
+ >Expert's answer
-
+
@@ -57,23 +57,25 @@
[value]="choice.id"
[disabled]="control.disabled"
slot="start"
- mode="md">
-
-
-
-
+ justify="start"
+ labelPlacement="end"
+ mode="md"
+ >
+
+
-
+
@@ -117,7 +119,9 @@
tabindex="0">
- Learner's Answer
+
+ Learner's answer
+
diff --git a/projects/v3/src/app/components/oneof/oneof.component.spec.ts b/projects/v3/src/app/components/oneof/oneof.component.spec.ts
index 4d63ed46e..36daf011a 100644
--- a/projects/v3/src/app/components/oneof/oneof.component.spec.ts
+++ b/projects/v3/src/app/components/oneof/oneof.component.spec.ts
@@ -1,33 +1,24 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { OneofComponent } from './oneof.component';
import { ReactiveFormsModule, FormControl } from '@angular/forms';
import { UtilsService } from '@v3/services/utils.service';
import { TestUtils } from '@testingv3/utils';
-import { LanguageDetectionPipe } from '@v3/app/pipes/language.pipe';
-import { DomSanitizer } from '@angular/platform-browser';
-import { ToggleLabelDirective } from '@v3/app/directives/toggle-label/toggle-label.directive';
describe('OneofComponent', () => {
let component: OneofComponent;
let fixture: ComponentFixture;
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
- imports: [ReactiveFormsModule, ToggleLabelDirective],
- declarations: [OneofComponent, LanguageDetectionPipe],
+ imports: [ReactiveFormsModule],
+ declarations: [OneofComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{
provide: UtilsService,
useClass: TestUtils,
},
- {
- provide: DomSanitizer,
- useValue: {
- bypassSecurityTrustHtml: (html: string) => html
- }
- }
],
})
.compileComponents();
@@ -59,8 +50,8 @@ describe('OneofComponent', () => {
component.review = {};
component.control = new FormControl('');
fixture.detectChanges();
- // component sets innerValue from submission.answer when control is pristine
expect(component.innerValue).toEqual(component.submission.answer);
+ expect(component.control.value).toEqual(component.submission.answer);
});
it('should get correct data for in progress review', () => {
@@ -82,12 +73,9 @@ describe('OneofComponent', () => {
};
component.control = new FormControl('');
fixture.detectChanges();
- // component sets innerValue to review data
- expect(component.innerValue).toEqual({
- answer: component.review.answer,
- comment: component.review.comment
- });
+ expect(component.innerValue).toEqual(component.review);
expect(component.comment).toEqual(component.review.comment);
+ expect(component.control.value).toEqual(component.review);
});
});
@@ -100,25 +88,25 @@ describe('OneofComponent', () => {
component.control.setErrors({
key: 'error'
});
- component.onChange(4);
+ component.onChange(4, null);
expect(component.errors.length).toBe(1);
});
it('should return error if required not filled', () => {
component.control.setErrors({
required: true
});
- component.onChange(4);
+ component.onChange(4, null);
expect(component.errors.length).toBe(1);
expect(component.errors[0]).toContain('is required');
});
it('should get correct data when writing submission answer', () => {
- component.onChange(4);
+ component.onChange(4, null);
expect(component.errors.length).toBe(0);
expect(component.innerValue).toEqual(4);
});
- it('should get correct data when replacing submission answer', () => {
+ it('should get correct data when writing submission answer', () => {
component.innerValue = 1;
- component.onChange(4);
+ component.onChange(4, null);
expect(component.errors.length).toBe(0);
expect(component.innerValue).toEqual(4);
});
@@ -175,243 +163,5 @@ describe('OneofComponent', () => {
expect(component.isDisplayOnly).toBeFalse();
});
});
-
- describe('triggerSave()', () => {
- beforeEach(() => {
- component.question = { id: 42, audience: [] };
- component.submissionId = 100;
- component.reviewId = 200;
- component.submitActions$ = jasmine.createSpyObj('Subject', ['next']);
- });
-
- it('should emit review save action when doReview is true', () => {
- component.doReview = true;
- component.doAssessment = false;
- component.innerValue = { answer: 'choice1', comment: 'good' };
-
- component.triggerSave();
-
- expect(component.submitActions$.next).toHaveBeenCalledWith(jasmine.objectContaining({
- autoSave: true,
- goBack: false,
- reviewSave: {
- reviewId: 200,
- submissionId: 100,
- questionId: 42,
- answer: 'choice1',
- comment: 'good',
- },
- }));
- });
-
- it('should emit question save action when doAssessment is true', () => {
- component.doAssessment = true;
- component.doReview = false;
- component.innerValue = 'choice2';
-
- component.triggerSave();
-
- expect(component.submitActions$.next).toHaveBeenCalledWith(jasmine.objectContaining({
- autoSave: true,
- goBack: false,
- questionSave: {
- submissionId: 100,
- questionId: 42,
- answer: 'choice2',
- },
- }));
- });
- });
-
- describe('onLabelToggle / onLabelToggleReview', () => {
- beforeEach(() => {
- component.control = new FormControl('');
- spyOn(component, 'onChange');
- });
-
- it('onLabelToggle should call onChange with id', () => {
- component.onLabelToggle('5');
- expect(component.onChange).toHaveBeenCalledWith('5');
- });
-
- it('onLabelToggleReview should call onChange with id and answer type', () => {
- component.onLabelToggleReview('5');
- expect(component.onChange).toHaveBeenCalledWith('5', 'answer');
- });
- });
-
- describe('checkInnerValue()', () => {
- it('should return true when choiceId matches innerValue', () => {
- component.innerValue = 3;
- expect(component.checkInnerValue(3)).toBeTrue();
- });
-
- it('should return undefined when choiceId does not match', () => {
- component.innerValue = 3;
- expect(component.checkInnerValue(5)).toBeUndefined();
- });
-
- it('should return undefined for falsy choiceId', () => {
- component.innerValue = 3;
- expect(component.checkInnerValue(null)).toBeUndefined();
- });
- });
-
- describe('audienceContainReviewer()', () => {
- it('should return true when audience has multiple entries including reviewer', () => {
- component.question = { audience: ['submitter', 'reviewer'] };
- expect(component.audienceContainReviewer()).toBeTrue();
- });
-
- it('should return false when audience has only one entry', () => {
- component.question = { audience: ['submitter'] };
- expect(component.audienceContainReviewer()).toBeFalse();
- });
-
- it('should return false when audience does not include reviewer', () => {
- component.question = { audience: ['submitter', 'admin'] };
- expect(component.audienceContainReviewer()).toBeFalse();
- });
- });
-
- describe('_showSavedAnswers() - pristine check for pagination persistence', () => {
- describe('review mode', () => {
- beforeEach(() => {
- component.reviewStatus = 'in progress';
- component.doReview = true;
- component.review = {
- answer: 'saved review answer',
- comment: 'saved review comment',
- };
- component.control = new FormControl('');
- });
-
- it('should use saved review data when control is pristine', () => {
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual({
- answer: 'saved review answer',
- comment: 'saved review comment',
- });
- expect(component.comment).toBe('saved review comment');
- });
-
- it('should preserve control value when control is dirty', () => {
- const dirtyValue = { answer: 'user edited', comment: 'user comment' };
- component.control.setValue(dirtyValue);
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual(dirtyValue);
- expect(component.comment).toBe('user comment');
- });
-
- it('should fallback to review comment when dirty value has no comment', () => {
- const dirtyValue = { answer: 'user edited' };
- component.control.setValue(dirtyValue);
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.comment).toBe('saved review comment');
- });
- });
-
- describe('assessment mode', () => {
- beforeEach(() => {
- component.submissionStatus = 'in progress';
- component.doAssessment = true;
- component.submission = { answer: 'saved submission answer' };
- component.reviewStatus = '';
- component.doReview = false;
- component.control = new FormControl('');
- });
-
- it('should use saved submission answer when control is pristine', () => {
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toBe('saved submission answer');
- });
-
- it('should preserve control value when control is dirty', () => {
- component.control.setValue('user edited answer');
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toBe('user edited answer');
- });
-
- it('should use submission answer when control is null', () => {
- component.control = null;
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toBe('saved submission answer');
- });
- });
-
- describe('review mode with "not start" status', () => {
- it('should load review data when reviewStatus is "not start"', () => {
- component.reviewStatus = 'not start';
- component.doReview = true;
- component.review = { answer: 'review answer', comment: 'review comment' };
- component.control = new FormControl('');
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual({
- answer: 'review answer',
- comment: 'review comment',
- });
- });
- });
-
- it('should call propagateChange with innerValue', () => {
- component.submissionStatus = 'in progress';
- component.doAssessment = true;
- component.submission = { answer: 'test' };
- component.reviewStatus = '';
- component.doReview = false;
- component.control = new FormControl('');
- spyOn(component, 'propagateChange');
-
- component['_showSavedAnswers']();
-
- expect(component.propagateChange).toHaveBeenCalledWith('test');
- });
- });
-
- describe('isDisplayOnly() - additional cases', () => {
- it('should be true when reviewer has canAnswer false', () => {
- component.doReview = true;
- component.doAssessment = false;
- component.question = { canAnswer: false };
- expect(component.isDisplayOnly).toBeTrue();
- });
-
- it('should be true when status is done with empty reviewStatus', () => {
- component.doAssessment = false;
- component.doReview = false;
- component.submissionStatus = 'done';
- component.reviewStatus = '';
- expect(component.isDisplayOnly).toBeTrue();
- });
-
- it('should be true when submission status is feedback available', () => {
- component.doAssessment = false;
- component.doReview = false;
- component.submissionStatus = 'feedback available';
- expect(component.isDisplayOnly).toBeTrue();
- });
-
- it('should be false when done but reviewStatus is non-empty', () => {
- component.doAssessment = false;
- component.doReview = false;
- component.submissionStatus = 'done';
- component.reviewStatus = 'in progress';
- expect(component.isDisplayOnly).toBeFalse();
- });
- });
});
+
diff --git a/projects/v3/src/app/components/oneof/oneof.component.ts b/projects/v3/src/app/components/oneof/oneof.component.ts
index 4e4b73f3a..84e654517 100644
--- a/projects/v3/src/app/components/oneof/oneof.component.ts
+++ b/projects/v3/src/app/components/oneof/oneof.component.ts
@@ -4,7 +4,6 @@ import { Subject } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
@Component({
- standalone: false,
selector: 'app-oneof',
templateUrl: 'oneof.component.html',
styleUrls: ['./oneof.component.scss'],
diff --git a/projects/v3/src/app/components/pop-up/pop-up.component.spec.ts b/projects/v3/src/app/components/pop-up/pop-up.component.spec.ts
index 58d300363..a50638d60 100644
--- a/projects/v3/src/app/components/pop-up/pop-up.component.spec.ts
+++ b/projects/v3/src/app/components/pop-up/pop-up.component.spec.ts
@@ -1,5 +1,5 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { PopUpComponent } from './pop-up.component';
import { Observable, of, pipe } from 'rxjs';
import { ModalController } from '@ionic/angular';
@@ -8,9 +8,9 @@ import { Router } from '@angular/router';
describe('PopUpComponent', () => {
let component: PopUpComponent;
let fixture: ComponentFixture;
- let modalCtrlSpy: any;
+ const modalCtrlSpy = jasmine.createSpyObj('ModalController', ['dismiss', 'create']);
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ PopUpComponent ],
schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
@@ -34,7 +34,6 @@ describe('PopUpComponent', () => {
beforeEach(() => {
fixture = TestBed.createComponent(PopUpComponent);
component = fixture.componentInstance;
- modalCtrlSpy = TestBed.inject(ModalController);
});
it('should create', () => {
diff --git a/projects/v3/src/app/components/pop-up/pop-up.component.ts b/projects/v3/src/app/components/pop-up/pop-up.component.ts
index 142c2330d..59da20a2d 100644
--- a/projects/v3/src/app/components/pop-up/pop-up.component.ts
+++ b/projects/v3/src/app/components/pop-up/pop-up.component.ts
@@ -10,7 +10,6 @@ export interface PopUpData {
}
@Component({
- standalone: false,
selector: 'app-pop-up',
templateUrl: 'pop-up.component.html',
styleUrls: ['pop-up.component.scss']
diff --git a/projects/v3/src/app/components/project-brief-modal/project-brief-modal.component.spec.ts b/projects/v3/src/app/components/project-brief-modal/project-brief-modal.component.spec.ts
index b9bc1b544..8fc024d6a 100644
--- a/projects/v3/src/app/components/project-brief-modal/project-brief-modal.component.spec.ts
+++ b/projects/v3/src/app/components/project-brief-modal/project-brief-modal.component.spec.ts
@@ -29,9 +29,8 @@ describe('ProjectBriefModalComponent', () => {
describe('close()', () => {
it('should dismiss the modal', () => {
- const injectedCtrl = TestBed.inject(ModalController) as jasmine.SpyObj;
component.close();
- expect(injectedCtrl.dismiss).toHaveBeenCalled();
+ expect(modalControllerSpy.dismiss).toHaveBeenCalled();
});
});
diff --git a/projects/v3/src/app/components/project-brief-modal/project-brief-modal.component.ts b/projects/v3/src/app/components/project-brief-modal/project-brief-modal.component.ts
index 0f017c7fb..a7c0e1907 100644
--- a/projects/v3/src/app/components/project-brief-modal/project-brief-modal.component.ts
+++ b/projects/v3/src/app/components/project-brief-modal/project-brief-modal.component.ts
@@ -21,7 +21,6 @@ export interface ProjectBrief {
* empty fields show "none specified"
*/
@Component({
- standalone: false,
selector: 'app-project-brief-modal',
templateUrl: './project-brief-modal.component.html',
styleUrls: ['./project-brief-modal.component.scss']
diff --git a/projects/v3/src/app/components/review-list/review-list.component.spec.ts b/projects/v3/src/app/components/review-list/review-list.component.spec.ts
index 734fa8bb5..1a4217a26 100644
--- a/projects/v3/src/app/components/review-list/review-list.component.spec.ts
+++ b/projects/v3/src/app/components/review-list/review-list.component.spec.ts
@@ -79,12 +79,10 @@ describe('ReviewListComponent', () => {
});
describe('noReviews()', () => {
- it('should be empty string when reviews is null', () => {
+ it('should be null', () => {
component.reviews = null;
expect(component.noReviews).toEqual('');
- });
- it('should be empty string when matching reviews exist', () => {
component.showDone = true;
component.reviews = [{
isDone: true,
@@ -93,7 +91,7 @@ describe('ReviewListComponent', () => {
expect(component.noReviews).toEqual('');
});
- it('should return "completed" when showDone but no completed reviews', () => {
+ it('should return "completed"', () => {
component.reviews = [
{ isDone: false } as any
];
@@ -102,7 +100,7 @@ describe('ReviewListComponent', () => {
expect(component.noReviews).toEqual('completed');
});
- it('should return "pending" when not showDone but no pending reviews', () => {
+ it('should return "pending"', () => {
component.reviews = [
{ isDone: true } as any
];
diff --git a/projects/v3/src/app/components/review-list/review-list.component.ts b/projects/v3/src/app/components/review-list/review-list.component.ts
index 5633ae0aa..ae5f2e9f4 100644
--- a/projects/v3/src/app/components/review-list/review-list.component.ts
+++ b/projects/v3/src/app/components/review-list/review-list.component.ts
@@ -15,7 +15,6 @@ import { Review } from '@v3/app/services/review.service';
import { SegmentChangeEventDetail, SegmentValue } from '@ionic/angular';
@Component({
- standalone: false,
selector: 'app-review-list',
templateUrl: './review-list.component.html',
styleUrls: ['./review-list.component.scss'],
diff --git a/projects/v3/src/app/components/review-rating/review-rating.component.spec.ts b/projects/v3/src/app/components/review-rating/review-rating.component.spec.ts
index 3c0ec65c5..3ac116f6d 100644
--- a/projects/v3/src/app/components/review-rating/review-rating.component.spec.ts
+++ b/projects/v3/src/app/components/review-rating/review-rating.component.spec.ts
@@ -1,5 +1,5 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { Observable, of, pipe } from 'rxjs';
import { HttpClientModule } from '@angular/common/http';
import { Router } from '@angular/router';
@@ -18,7 +18,7 @@ describe('ReviewRatingComponent', () => {
let routerSpy: jasmine.SpyObj;
let fastfeedbackSpy: jasmine.SpyObj;
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [HttpClientModule],
declarations: [ReviewRatingComponent],
@@ -30,10 +30,7 @@ describe('ReviewRatingComponent', () => {
},
{
provide: NotificationsService,
- useValue: jasmine.createSpyObj('NotificationsService', {
- alert: Promise.resolve(),
- dismiss: Promise.resolve()
- }),
+ useValue: jasmine.createSpyObj('NotificationsService', ['alert']),
},
{
provide: ReviewRatingService,
@@ -76,51 +73,38 @@ describe('ReviewRatingComponent', () => {
});
describe('when testing submitReviewRating()', () => {
- beforeEach(() => {
- serviceSpy.submitRating.calls.reset();
- routerSpy.navigate.calls.reset();
- });
-
- it('should submit rating without navigation when redirect is null', async () => {
- component.redirect = null;
- component.moodSelected = 0;
+ afterEach(() => {
component.ratingData = {
assessment_review_id: 1,
rating: 0.123,
comment: '',
tags: []
};
-
serviceSpy.submitRating.and.returnValue(of(''));
- await component.submitReviewRating();
-
+ component.submitReviewRating();
expect(serviceSpy.submitRating.calls.count()).toBe(1);
expect(serviceSpy.submitRating.calls.first().args[0].rating).toEqual(0.12);
expect(component.isSubmitting).toBe(false);
- expect(routerSpy.navigate.calls.count()).toBe(0);
+ if (component.redirect) {
+ expect(routerSpy.navigate.calls.first().args[0]).toEqual(component.redirect);
+ } else {
+ expect(routerSpy.navigate.calls.count()).toBe(0);
+ }
});
-
- it('should submit rating and navigate when redirect is provided', async () => {
- component.redirect = ['home'];
+ it('should submit rating', () => {
+ component.redirect = null;
+ component.moodSelected = 0;
+ component.ratingData.rating = 1;
+ });
+ it('should submit rating and navigate', () => {
+ component.ratingData.rating = 1;
component.moodSelected = 1;
- component.ratingData = {
- assessment_review_id: 1,
- rating: 0.123,
- comment: '',
- tags: []
- };
-
- serviceSpy.submitRating.and.returnValue(of(''));
- await component.submitReviewRating();
-
- expect(serviceSpy.submitRating.calls.count()).toBe(1);
- expect(serviceSpy.submitRating.calls.first().args[0].rating).toEqual(0.12);
- expect(component.isSubmitting).toBe(false);
+ component.redirect = ['home'];
});
});
describe('submitReviewRating() - straightforward test', () => {
- it('should submit rating and set ratingSessionEnd to true', async () => {
+ it('should trigger pulse check API when stay on same view', () => {
component.redirect = null;
component.ratingData = {
@@ -133,19 +117,11 @@ describe('ReviewRatingComponent', () => {
component.moodSelected = 0;
serviceSpy.submitRating.and.returnValue(of(''));
- await component.submitReviewRating();
+ component.submitReviewRating();
expect(serviceSpy.submitRating.calls.count()).toBe(1);
expect(serviceSpy.submitRating.calls.first().args[0].rating).toEqual(0.12);
expect(component.isSubmitting).toBe(false);
- expect(component.ratingSessionEnd).toBe(true);
- });
-
- it('should trigger pulse check API when dismissModal is called', async () => {
- component.redirect = null;
- component.reviewId = 1;
-
- fastfeedbackSpy.pullFastFeedback.calls.reset();
- await component.dismissModal();
+ expect(routerSpy.navigate.calls.count()).toBe(0);
expect(fastfeedbackSpy.pullFastFeedback).toHaveBeenCalledTimes(1);
});
});
diff --git a/projects/v3/src/app/components/review-rating/review-rating.component.ts b/projects/v3/src/app/components/review-rating/review-rating.component.ts
index d40ae7e07..0887b7398 100644
--- a/projects/v3/src/app/components/review-rating/review-rating.component.ts
+++ b/projects/v3/src/app/components/review-rating/review-rating.component.ts
@@ -1,5 +1,5 @@
import { firstValueFrom } from 'rxjs';
-import { Component, Inject, Input, OnInit, forwardRef } from '@angular/core';
+import { Component, Input, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { AlertController, ModalController } from '@ionic/angular';
import { ReviewRatingService, ReviewRating } from '@v3/services/review-rating.service';
@@ -8,7 +8,6 @@ import { FastFeedbackService } from '@v3/services/fast-feedback.service';
import { NotificationsService } from '../../services/notifications.service';
@Component({
- standalone: false,
selector: 'app-review-rating',
templateUrl: './review-rating.component.html',
styleUrls: ['./review-rating.component.scss']
@@ -64,9 +63,8 @@ export class ReviewRatingComponent implements OnInit {
private modalController: ModalController,
private router: Router,
private utils: UtilsService,
- // types are 'any' to prevent design:paramtypes metadata from triggering circular dependency TDZ error
- @Inject(forwardRef(() => FastFeedbackService)) private fastFeedbackService: any,
- @Inject(forwardRef(() => NotificationsService)) private notificationsService: any,
+ private fastFeedbackService: FastFeedbackService,
+ private notificationsService: NotificationsService,
) {}
ngOnInit(): void {
diff --git a/projects/v3/src/app/components/slider/slider.component.html b/projects/v3/src/app/components/slider/slider.component.html
index 2499a66eb..dfc53aeb2 100644
--- a/projects/v3/src/app/components/slider/slider.component.html
+++ b/projects/v3/src/app/components/slider/slider.component.html
@@ -42,12 +42,12 @@ {{question
- Learner's Answer: {{ getChoiceNameById(submission.answer) }}
+ Learner answer: {{ getChoiceNameById(submission.answer) }}
- Reviewer's Answer: {{ getChoiceNameById(review.answer) }}
+ Expert answer: {{ getChoiceNameById(review.answer) }}
diff --git a/projects/v3/src/app/components/slider/slider.component.spec.ts b/projects/v3/src/app/components/slider/slider.component.spec.ts
index b6b79a00f..0a58da6e8 100644
--- a/projects/v3/src/app/components/slider/slider.component.spec.ts
+++ b/projects/v3/src/app/components/slider/slider.component.spec.ts
@@ -114,7 +114,7 @@ describe('SliderComponent', () => {
it('should get selected choice label with parameter', () => {
expect(component.getSelectedChoiceLabel(2)).toBe('2');
- expect(component.getSelectedChoiceLabel()).toBe(component.innerValue?.toString() || '');
+ expect(component.getSelectedChoiceLabel()).toBe('');
});
describe('Review functionality', () => {
@@ -160,23 +160,13 @@ describe('SliderComponent', () => {
describe('Edge cases', () => {
it('should handle missing min/max gracefully', () => {
- // reset the slider values to defaults before testing
- component.sliderMin = 0;
- component.sliderMax = 100;
- component.generatedChoices = [];
-
component.question.min = undefined;
component.question.max = undefined;
component.ngOnInit();
- // when both min and max are undefined, the condition
- // (this.question.min !== undefined || this.question.max !== undefined) is false
- // so sliderMin and sliderMax remain at their initial/reset values
expect(component.sliderMin).toBe(0);
expect(component.sliderMax).toBe(100);
- // Since the condition requires at least one of min/max to be defined,
- // and both are undefined, generatedChoices won't be populated
expect(component.generatedChoices.length).toBe(0);
});
@@ -198,197 +188,4 @@ describe('SliderComponent', () => {
expect(component.getReviewSliderValue()).toBe(3);
});
});
-
- describe('triggerSave()', () => {
- beforeEach(() => {
- component.question = { id: 10, type: 'slider', min: 1, max: 5, audience: ['submitter'], name: 'q', description: '', isRequired: false, canAnswer: true, canComment: false };
- component.submissionId = 50;
- component.reviewId = 60;
- component.submitActions$ = jasmine.createSpyObj('Subject', ['next']);
- });
-
- it('should emit review save action when doReview is true', () => {
- component.doReview = true;
- component.doAssessment = false;
- component.innerValue = { answer: 3, comment: 'nice' };
-
- component.triggerSave();
-
- expect(component.submitActions$.next).toHaveBeenCalledWith(jasmine.objectContaining({
- autoSave: true,
- goBack: false,
- reviewSave: {
- reviewId: 60,
- submissionId: 50,
- questionId: 10,
- answer: 3,
- comment: 'nice',
- },
- }));
- });
-
- it('should emit question save action when doAssessment is true', () => {
- component.doAssessment = true;
- component.doReview = false;
- component.innerValue = 4;
-
- component.triggerSave();
-
- expect(component.submitActions$.next).toHaveBeenCalledWith(jasmine.objectContaining({
- autoSave: true,
- goBack: false,
- questionSave: {
- submissionId: 50,
- questionId: 10,
- answer: 4,
- },
- }));
- });
- });
-
- describe('_showSavedAnswers()', () => {
- it('should call propagateChange with innerValue', () => {
- component.submissionStatus = 'in progress';
- component.doAssessment = true;
- component.submission = { answer: 3 };
- component.reviewStatus = '';
- component.doReview = false;
- component.control = new FormControl('');
- spyOn(component, 'propagateChange');
-
- component['_showSavedAnswers']();
-
- expect(component.propagateChange).toHaveBeenCalledWith(3);
- });
-
- it('should load review data when reviewStatus is "not start"', () => {
- component.reviewStatus = 'not start';
- component.doReview = true;
- component.review = { answer: 2, comment: 'a comment' };
- component.control = new FormControl('');
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual({ answer: 2, comment: 'a comment' });
- });
-
- it('should preserve dirty control value in review mode', () => {
- component.reviewStatus = 'in progress';
- component.doReview = true;
- component.review = { answer: 2, comment: 'saved' };
- component.control = new FormControl({ answer: 5, comment: 'edited' });
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual({ answer: 5, comment: 'edited' });
- });
- });
-
- describe('isDisplayOnly()', () => {
- it('should be true when reviewer has canAnswer false', () => {
- component.doReview = true;
- component.doAssessment = false;
- component.question = { ...component.question, canAnswer: false };
- expect(component.isDisplayOnly).toBeTrue();
- });
-
- it('should be true when status is feedback available', () => {
- component.doAssessment = false;
- component.doReview = false;
- component.submissionStatus = 'feedback available';
- expect(component.isDisplayOnly).toBeTrue();
- });
-
- it('should be false when doing assessment', () => {
- component.doAssessment = true;
- component.doReview = false;
- expect(component.isDisplayOnly).toBeFalse();
- });
- });
-
- describe('hasSubmissionAnswer / hasReviewAnswer / hasAnyAnswer', () => {
- it('hasSubmissionAnswer returns true when answer exists', () => {
- component.submission = { answer: 3 };
- expect(component.hasSubmissionAnswer()).toBeTrue();
- });
-
- it('hasSubmissionAnswer returns false when answer is null', () => {
- component.submission = { answer: null };
- expect(component.hasSubmissionAnswer()).toBeFalse();
- });
-
- it('hasReviewAnswer returns true when review answer exists', () => {
- component.review = { answer: 4 };
- expect(component.hasReviewAnswer()).toBeTrue();
- });
-
- it('hasReviewAnswer returns false when review answer is null', () => {
- component.review = { answer: null };
- expect(component.hasReviewAnswer()).toBeFalse();
- });
-
- it('hasAnyAnswer returns true when either exists', () => {
- component.submission = { answer: 3 };
- component.review = { answer: null };
- expect(component.hasAnyAnswer()).toBeTrue();
- });
-
- it('hasAnyAnswer returns false when neither exists', () => {
- component.submission = { answer: null };
- component.review = { answer: null };
- expect(component.hasAnyAnswer()).toBeFalse();
- });
- });
-
- describe('onLabelClick guard', () => {
- it('should not call onChange when control is disabled', () => {
- component.ngOnInit();
- component.control.disable();
- spyOn(component, 'onChange');
-
- component.onLabelClick(0);
-
- expect(component.onChange).not.toHaveBeenCalled();
- });
- });
-
- describe('writeValue()', () => {
- it('should set innerValue from value', () => {
- component.writeValue({ answer: 3, comment: 'test' });
- expect(component.innerValue).toEqual({ answer: 3, comment: 'test' });
- expect(component.comment).toBe('test');
- });
-
- it('should not crash on null', () => {
- const prevValue = component.innerValue;
- component.writeValue(null);
- expect(component.innerValue).toEqual(prevValue);
- });
- });
-
- describe('registerOnChange / registerOnTouched', () => {
- it('should store propagateChange function', () => {
- const fn = jasmine.createSpy();
- component.registerOnChange(fn);
- component.propagateChange('test');
- expect(fn).toHaveBeenCalledWith('test');
- });
-
- it('registerOnTouched should not throw', () => {
- expect(() => component.registerOnTouched(() => {})).not.toThrow();
- });
- });
-
- describe('audienceContainReviewer()', () => {
- it('should return true when multiple audiences include reviewer', () => {
- component.question = { ...component.question, audience: ['submitter', 'reviewer'] };
- expect(component.audienceContainReviewer()).toBeTrue();
- });
-
- it('should return false for single audience', () => {
- component.question = { ...component.question, audience: ['submitter'] };
- expect(component.audienceContainReviewer()).toBeFalse();
- });
- });
});
diff --git a/projects/v3/src/app/components/slider/slider.component.ts b/projects/v3/src/app/components/slider/slider.component.ts
index 80298a2d2..0eec6c4b5 100644
--- a/projects/v3/src/app/components/slider/slider.component.ts
+++ b/projects/v3/src/app/components/slider/slider.component.ts
@@ -5,7 +5,6 @@ import { debounceTime } from 'rxjs/operators';
import { Question } from '../types/assessment';
@Component({
- standalone: false,
selector: 'app-slider',
templateUrl: 'slider.component.html',
styleUrls: ['./slider.component.scss'],
@@ -260,7 +259,7 @@ export class SliderComponent implements AfterViewInit, ControlValueAccessor, OnI
return choiceId.toString();
}
- // Get slider value for submission (Learner's Answer)
+ // Get slider value for submission (Learner's answer)
getSubmissionSliderValue(): number {
if (!this.submission?.answer) return this.sliderMin;
diff --git a/projects/v3/src/app/components/support-popup/support-popup.component.html b/projects/v3/src/app/components/support-popup/support-popup.component.html
index b1c1887b0..efff2de71 100644
--- a/projects/v3/src/app/components/support-popup/support-popup.component.html
+++ b/projects/v3/src/app/components/support-popup/support-popup.component.html
@@ -16,7 +16,7 @@
-
+
diff --git a/projects/v3/src/app/components/support-popup/support-popup.component.spec.ts b/projects/v3/src/app/components/support-popup/support-popup.component.spec.ts
index f97813dbd..f9bad00d8 100644
--- a/projects/v3/src/app/components/support-popup/support-popup.component.spec.ts
+++ b/projects/v3/src/app/components/support-popup/support-popup.component.spec.ts
@@ -103,15 +103,7 @@ describe('SupportPopupComponent', () => {
it('should return false when selectedFile is truthy', () => {
component.problemSubject = '';
component.problemContent = '';
- component.selectedFile = {
- bucket: 'test-bucket',
- path: 'test-path',
- name: 'test-file',
- url: 'http://example.com/test.jpg',
- extension: 'jpg',
- type: 'image/jpeg',
- size: 1000
- };
+ component.selectedFile = { handle: 'abc123' };
const result = component.isPristine();
@@ -194,16 +186,7 @@ describe('SupportPopupComponent', () => {
it('should remove the selected file and call deleteFile with the file handle', fakeAsync(() => {
filestackSpy.deleteFile = jasmine.createSpy().and.returnValue(of({}));
- component.selectedFile = {
- bucket: 'test-bucket',
- path: 'test-path',
- name: 'test-file',
- url: 'http://example.com/test.jpg',
- extension: 'jpg',
- type: 'image/jpeg',
- size: 1000,
- handle: 'abc123'
- };
+ component.selectedFile = { handle: 'abc123' };
component.removeSelectedFile();
flushMicrotasks();
@@ -214,17 +197,7 @@ describe('SupportPopupComponent', () => {
describe('uploadFile', () => {
it('should call FilestackService open method and set the selectedFile on upload finished', fakeAsync(() => {
- const mockResponse = {
- bucket: 'test-bucket',
- path: 'test-path',
- name: 'test.jpg',
- url: 'http://example.com/test.jpg',
- extension: 'jpg',
- type: 'image/jpeg',
- size: 1000,
- handle: 'abc123',
- filename: 'test.jpg'
- };
+ const mockResponse = { filename: 'test.jpg', handle: 'abc123', url: 'http://example.com/test.jpg' };
filestackSpy.open = jasmine.createSpy().and.callFake(options => {
return options.onFileUploadFinished(mockResponse);
@@ -307,9 +280,9 @@ describe('SupportPopupComponent', () => {
file: undefined,
consentToProcess: true,
});
- // on error, form is NOT cleared - only cleared on success
- expect(component.problemContent).toBe('Test Content');
- expect(component.problemSubject).toBe('Test Subject');
+ expect(component.selectedFile).toBeUndefined();
+ expect(component.problemContent).toBe('');
+ expect(component.problemSubject).toBe('');
expect(component.isShowSuccess).toBeFalse();
expect(component.isShowError).toBeTrue();
});
diff --git a/projects/v3/src/app/components/support-popup/support-popup.component.ts b/projects/v3/src/app/components/support-popup/support-popup.component.ts
index 71238ef00..b0e275daf 100644
--- a/projects/v3/src/app/components/support-popup/support-popup.component.ts
+++ b/projects/v3/src/app/components/support-popup/support-popup.component.ts
@@ -8,7 +8,6 @@ import { UtilsService } from '@v3/services/utils.service';
import { NotificationsService } from '@v3/app/services/notifications.service';
@Component({
- standalone: false,
selector: 'app-support-popup',
templateUrl: './support-popup.component.html',
styleUrls: ['./support-popup.component.scss'],
diff --git a/projects/v3/src/app/components/team-member-selector/team-member-selector.component.html b/projects/v3/src/app/components/team-member-selector/team-member-selector.component.html
index 993d120af..46530122c 100644
--- a/projects/v3/src/app/components/team-member-selector/team-member-selector.component.html
+++ b/projects/v3/src/app/components/team-member-selector/team-member-selector.component.html
@@ -8,8 +8,8 @@
- Learner's Answer
- Reviewer's Answer
+ Learner's answer
+ Expert's answer
@@ -46,21 +46,18 @@
-
-
-
-
-
+ [disabled]="control?.disabled"
+ >
+
@@ -87,7 +84,7 @@
-
+
- Learner's Answer
+ Learner's answer
-
+
diff --git a/projects/v3/src/app/components/team-member-selector/team-member-selector.component.spec.ts b/projects/v3/src/app/components/team-member-selector/team-member-selector.component.spec.ts
index aa8a2d3a4..b56b900a8 100644
--- a/projects/v3/src/app/components/team-member-selector/team-member-selector.component.spec.ts
+++ b/projects/v3/src/app/components/team-member-selector/team-member-selector.component.spec.ts
@@ -51,8 +51,8 @@ describe('TeamMemberSelectorComponent', () => {
component.review = {};
component.control = new FormControl('');
fixture.detectChanges();
- // component sets innerValue from submission.answer when control is pristine
expect(component.innerValue).toEqual(component.submission.answer);
+ expect(component.control.value).toEqual(component.submission.answer);
});
it('should get correct data for in progress review', () => {
@@ -74,12 +74,9 @@ describe('TeamMemberSelectorComponent', () => {
};
component.control = new FormControl('');
fixture.detectChanges();
- // component sets innerValue to review data
- expect(component.innerValue).toEqual({
- answer: component.review.answer,
- comment: component.review.comment
- });
+ expect(component.innerValue).toEqual(component.review);
expect(component.comment).toEqual(component.review.comment);
+ expect(component.control.value).toEqual(component.review);
});
});
@@ -166,7 +163,6 @@ describe('TeamMemberSelectorComponent', () => {
component.submission = {
answer: 'Test submission answer',
};
- component.control = new FormControl('');
component['_showSavedAnswers']();
@@ -183,51 +179,6 @@ describe('TeamMemberSelectorComponent', () => {
expect(component.innerValue).toBeUndefined();
});
-
- it('should preserve control value when control is dirty in review mode', () => {
- component.reviewStatus = 'in progress';
- component.doReview = true;
- component.review = {
- comment: 'saved comment',
- answer: 'saved answer',
- };
- component.control = new FormControl({ answer: 'user edited', comment: 'user comment' });
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual({ answer: 'user edited', comment: 'user comment' });
- expect(component.comment).toBe('user comment');
- });
-
- it('should fallback to review comment when dirty value has no comment', () => {
- component.reviewStatus = 'in progress';
- component.doReview = true;
- component.review = {
- comment: 'saved comment',
- answer: 'saved answer',
- };
- component.control = new FormControl({ answer: 'user edited' });
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.comment).toBe('saved comment');
- });
-
- it('should preserve control value when control is dirty in assessment mode', () => {
- component.reviewStatus = '';
- component.doReview = false;
- component.submissionStatus = 'in progress';
- component.doAssessment = true;
- component.submission = { answer: 'saved' };
- component.control = new FormControl('user edited');
- component.control.markAsDirty();
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toBe('user edited');
- });
});
describe('audienceContainReviewer()', () => {
@@ -255,148 +206,5 @@ describe('TeamMemberSelectorComponent', () => {
expect(component.audienceContainReviewer()).toBe(false);
});
});
-
- describe('triggerSave()', () => {
- beforeEach(() => {
- component.question = { id: 15, audience: [] };
- component.submissionId = 70;
- component.reviewId = 80;
- component.submitActions$ = jasmine.createSpyObj('Subject', ['next']);
- });
-
- it('should emit review save action when doReview is true', () => {
- component.doReview = true;
- component.doAssessment = false;
- component.innerValue = { answer: 'member-1', comment: 'good choice' };
-
- component.triggerSave();
-
- expect(component.submitActions$.next).toHaveBeenCalledWith(jasmine.objectContaining({
- autoSave: true,
- goBack: false,
- reviewSave: {
- reviewId: 80,
- submissionId: 70,
- questionId: 15,
- answer: 'member-1',
- comment: 'good choice',
- },
- }));
- });
-
- it('should emit question save action when doAssessment is true', () => {
- component.doAssessment = true;
- component.doReview = false;
- component.innerValue = 'member-2';
-
- component.triggerSave();
-
- expect(component.submitActions$.next).toHaveBeenCalledWith(jasmine.objectContaining({
- autoSave: true,
- goBack: false,
- questionSave: {
- submissionId: 70,
- questionId: 15,
- answer: 'member-2',
- },
- }));
- });
- });
-
- describe('onLabelToggle / onLabelToggleReview', () => {
- beforeEach(() => {
- component.control = new FormControl('');
- component.submitActions$ = new Subject();
- spyOn(component, 'onChange');
- });
-
- it('onLabelToggle should call onChange with id', () => {
- component.onLabelToggle('member-1');
- expect(component.onChange).toHaveBeenCalledWith('member-1');
- });
-
- it('onLabelToggleReview should call onChange with id and answer type', () => {
- component.onLabelToggleReview('member-1');
- expect(component.onChange).toHaveBeenCalledWith('member-1', 'answer');
- });
- });
-
- describe('isDisplayOnly()', () => {
- it('should be true when reviewer has canAnswer false', () => {
- component.doReview = true;
- component.doAssessment = false;
- component.question = { canAnswer: false, audience: [] };
- expect(component.isDisplayOnly).toBeTrue();
- });
-
- it('should be true when status is feedback available', () => {
- component.doAssessment = false;
- component.doReview = false;
- component.submissionStatus = 'feedback available';
- component.submission = { answer: 'member-1' };
- expect(component.isDisplayOnly).toBeTruthy();
- });
-
- it('should be true when status is pending review', () => {
- component.doAssessment = false;
- component.doReview = false;
- component.submissionStatus = 'pending review';
- component.submission = { answer: 'member-1' };
- expect(component.isDisplayOnly).toBeTruthy();
- });
-
- it('should be true when done with empty review status', () => {
- component.doAssessment = false;
- component.doReview = false;
- component.submissionStatus = 'done';
- component.reviewStatus = '';
- component.submission = { answer: 'member-1' };
- expect(component.isDisplayOnly).toBeTruthy();
- });
-
- it('should be false when doing assessment', () => {
- component.doAssessment = true;
- component.doReview = false;
- expect(component.isDisplayOnly).toBeFalse();
- });
-
- it('should be false when doing review with canAnswer true', () => {
- component.doAssessment = false;
- component.doReview = true;
- component.question = { canAnswer: true, audience: [] };
- expect(component.isDisplayOnly).toBeFalse();
- });
- });
-
- describe('_showSavedAnswers() - "not start" review status', () => {
- it('should load review data when reviewStatus is "not start"', () => {
- component.reviewStatus = 'not start';
- component.doReview = true;
- component.review = { answer: 'member-x', comment: 'test' };
- component.control = new FormControl('');
-
- component['_showSavedAnswers']();
-
- expect(component.innerValue).toEqual({
- answer: 'member-x',
- comment: 'test',
- });
- });
- });
-
- describe('_showSavedAnswers() - propagateChange call', () => {
- it('should call propagateChange with innerValue', () => {
- component.submissionStatus = 'in progress';
- component.doAssessment = true;
- component.submission = { answer: 'member-1' };
- component.reviewStatus = '';
- component.doReview = false;
- component.control = new FormControl('');
- spyOn(component, 'propagateChange');
-
- component['_showSavedAnswers']();
-
- expect(component.propagateChange).toHaveBeenCalledWith('member-1');
- });
- });
});
+
diff --git a/projects/v3/src/app/components/team-member-selector/team-member-selector.component.ts b/projects/v3/src/app/components/team-member-selector/team-member-selector.component.ts
index 1f27b232f..f0f83c6d7 100644
--- a/projects/v3/src/app/components/team-member-selector/team-member-selector.component.ts
+++ b/projects/v3/src/app/components/team-member-selector/team-member-selector.component.ts
@@ -3,7 +3,6 @@ import { NG_VALUE_ACCESSOR, ControlValueAccessor, FormControl, AbstractControl }
import { Subject } from 'rxjs';
@Component({
- standalone: false,
selector: 'app-team-member-selector',
templateUrl: 'team-member-selector.component.html',
styleUrls: ['team-member-selector.component.scss'],
diff --git a/projects/v3/src/app/components/text/text.component.html b/projects/v3/src/app/components/text/text.component.html
index 6b6138606..f9f39e864 100644
--- a/projects/v3/src/app/components/text/text.component.html
+++ b/projects/v3/src/app/components/text/text.component.html
@@ -2,7 +2,7 @@ {{question.n
- Learner's Answer
+ Learner's answer
@@ -10,7 +10,7 @@ {{question.n
- Reviewer's Answer
+ Expert's answer
@@ -64,8 +64,8 @@ {{question.n
-
Learner's Answer
-
Learner's Answer
+
Learner's answer
+
Learner's answer
@@ -78,7 +78,7 @@ Learner's Answer
{
let component: TextComponent;
@@ -17,18 +13,8 @@ describe('TextComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [IonicModule.forRoot(), FormsModule],
- declarations: [TextComponent, LanguageDetectionPipe],
+ declarations: [TextComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
- providers: [
- { provide: UtilsService, useClass: TestUtils },
- {
- provide: DomSanitizer,
- useValue: {
- bypassSecurityTrustHtml: (val: string) => val,
- sanitize: (ctx: any, val: string) => val,
- },
- },
- ],
})
.compileComponents();
}));
@@ -110,20 +96,16 @@ describe('TextComponent', () => {
});
it('should get correct data when writing submission answer', () => {
component.onChange();
- expect(component.innerValue).toBe(component.answer);
+ expect(component.innerValue).toEqual(component.answer);
});
it('should get correct data when writing review answer', () => {
component.innerValue = { answer: '', comment: '' };
- component.doReview = true;
component.onChange('answer');
- expect(component.innerValue.answer).toBe(component.answer);
- expect(component.innerValue.comment).toEqual('');
+ expect(component.innerValue).toEqual({ answer: component.answer, comment: '' });
});
it('should get correct data when writing review comment', () => {
- component.innerValue = { answer: '', comment: '' };
- component.doReview = true;
component.onChange('comment');
- expect(component.innerValue.comment).toBe(component.comment);
+ expect(component.innerValue).toEqual({ answer: '', comment: component.comment });
});
});
@@ -245,10 +227,13 @@ describe('TextComponent', () => {
describe('when testing ngAfterViewInit()', () => {
it('should set up auto-save subscription when answerRef is available', fakeAsync(() => {
- // create a mock input event with a proper target value
- const mockInputEvent = { target: { value: 'test' } };
+ const mockIonInput = {
+ pipe: jasmine.createSpy('pipe').and.returnValue({
+ subscribe: jasmine.createSpy('subscribe').and.returnValue({ closed: false, unsubscribe: () => {} })
+ })
+ };
- component.answerRef = { ionInput: of(mockInputEvent) } as any;
+ component.answerRef = { ionInput: of('test') } as any;
spyOn(component, 'triggerSave');
component.ngAfterViewInit();
@@ -288,8 +273,7 @@ describe('TextComponent', () => {
target: { firstChild: mockTextarea }
};
- // use proper Edge userAgent format that matches the regex /edge\//i
- spyOnProperty(window.navigator, 'userAgent', 'get').and.returnValue('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Edge/91.0.864.59');
+ spyOnProperty(window.navigator, 'userAgent', 'get').and.returnValue('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36 Edg/91.0.864.59');
component.onFocus(mockEvent);
@@ -305,8 +289,7 @@ describe('TextComponent', () => {
target: { firstChild: mockTextarea }
};
- // use proper Edge userAgent format that matches the regex /edge\//i
- spyOnProperty(window.navigator, 'userAgent', 'get').and.returnValue('Mozilla/5.0 (Windows NT 10.0; Win64; x64) Edge/91.0.864.59');
+ spyOnProperty(window.navigator, 'userAgent', 'get').and.returnValue('Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0 Edge/91.0.864.59');
component.onFocus(mockEvent);
@@ -409,9 +392,8 @@ describe('TextComponent', () => {
expect(component.innerValue.comment).toEqual('test comment');
expect(component.innerValue.answer).toEqual('test answer');
- // note: component.comment and component.answer become strings after _showSavedAnswers
- expect(component.comment as any).toEqual('test comment');
- expect(component.answer as any).toEqual('test answer');
+ expect(component.comment).toEqual('test comment');
+ expect(component.answer).toEqual('test answer');
});
it('should not set values when conditions are not met', () => {
@@ -421,12 +403,10 @@ describe('TextComponent', () => {
component.reviewStatus = 'completed';
component.doReview = false;
component.control = new FormControl('test');
- component.innerValue = 'original';
component.ngOnInit();
- // innerValue remains unchanged since no conditions were met
- expect(component.innerValue).toBe('original');
+ expect(component.control.value).toBe('test');
});
it('should handle missing review data gracefully', () => {
@@ -438,7 +418,7 @@ describe('TextComponent', () => {
component.ngOnInit();
- expect(component.innerValue).toEqual({ answer: undefined, comment: undefined });
+ expect(component.innerValue).toEqual({ answer: [], comment: '' });
});
it('should handle missing submission data gracefully', () => {
@@ -457,20 +437,15 @@ describe('TextComponent', () => {
describe('when testing onChange() edge cases', () => {
it('should handle onChange when innerValue is not initialized for review', () => {
component.innerValue = null;
- component.doReview = true;
- const answerControl = new FormControl('new answer');
- component.answer = answerControl as any;
+ component.answer = new FormControl('new answer');
component.onChange('answer');
- // component stores the FormControl reference, not its value
- expect(component.innerValue.answer).toBe(answerControl);
- expect(component.innerValue.comment).toEqual('');
+ expect(component.innerValue).toEqual({ answer: 'new answer', comment: '' });
});
it('should propagate changes correctly', () => {
spyOn(component, 'propagateChange');
- component.answer = new FormControl('test') as any;
- component.doReview = false;
+ component.answer = new FormControl('test');
component.onChange();
@@ -498,82 +473,5 @@ describe('TextComponent', () => {
});
});
- describe('_showSavedAnswers() - pristine check for pagination persistence', () => {
- const dummyQuestion = {
- id: 1, name: '', type: 'text', description: '',
- isRequired: true, canComment: false, canAnswer: true, choices: [], audience: []
- };
-
- describe('review mode', () => {
- beforeEach(() => {
- component.question = dummyQuestion;
- component.reviewStatus = 'in progress';
- component.doReview = true;
- component.review = { answer: 'saved answer', comment: 'saved comment' };
- component.control = new FormControl('');
- component.submissionStatus = '';
- component.doAssessment = false;
- });
-
- it('should use saved review data when control is pristine', () => {
- component.ngOnInit();
-
- expect(component.innerValue).toEqual(component.review);
- });
-
- it('should preserve control value when control is dirty', () => {
- const dirtyValue = { answer: 'user edited', comment: 'user comment' };
- component.control.setValue(dirtyValue);
- component.control.markAsDirty();
-
- component.ngOnInit();
-
- expect(component.innerValue).toEqual(dirtyValue);
- });
- });
-
- describe('assessment mode', () => {
- beforeEach(() => {
- component.question = dummyQuestion;
- component.submissionStatus = 'in progress';
- component.doAssessment = true;
- component.submission = { answer: 'saved submission answer' };
- component.reviewStatus = '';
- component.doReview = false;
- component.control = new FormControl('');
- });
-
- it('should use saved submission answer when control is pristine', () => {
- component.ngOnInit();
-
- expect(component.innerValue).toBe('saved submission answer');
- });
-
- it('should preserve control value when control is dirty', () => {
- component.control.setValue('user edited');
- component.control.markAsDirty();
-
- component.ngOnInit();
-
- expect(component.innerValue).toBe('user edited');
- });
- });
-
- it('should call propagateChange with innerValue', () => {
- component.question = dummyQuestion;
- component.submissionStatus = 'in progress';
- component.doAssessment = true;
- component.submission = { answer: 'test' };
- component.reviewStatus = '';
- component.doReview = false;
- component.control = new FormControl('');
- spyOn(component, 'propagateChange');
-
- component.ngOnInit();
-
- expect(component.propagateChange).toHaveBeenCalled();
- });
- });
-
});
diff --git a/projects/v3/src/app/components/text/text.component.ts b/projects/v3/src/app/components/text/text.component.ts
index b3c7cac3c..1bb46cb1e 100644
--- a/projects/v3/src/app/components/text/text.component.ts
+++ b/projects/v3/src/app/components/text/text.component.ts
@@ -6,7 +6,6 @@ import { debounceTime, distinctUntilChanged, filter, map } from 'rxjs/operators'
import { Question } from '../types/assessment';
@Component({
- standalone: false,
selector: 'app-text',
templateUrl: 'text.component.html',
styleUrls: ['text.component.scss'],
diff --git a/projects/v3/src/app/components/todo-card/todo-card.component.spec.ts b/projects/v3/src/app/components/todo-card/todo-card.component.spec.ts
index 5fd23f1d4..ff2295bae 100644
--- a/projects/v3/src/app/components/todo-card/todo-card.component.spec.ts
+++ b/projects/v3/src/app/components/todo-card/todo-card.component.spec.ts
@@ -1,5 +1,5 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { TodoCardComponent } from './todo-card.component';
@@ -33,7 +33,7 @@ describe('TodoCardComponent', () => {
let fixture: ComponentFixture;
let page: Page;
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
declarations: [ TodoCardComponent ],
diff --git a/projects/v3/src/app/components/todo-card/todo-card.component.ts b/projects/v3/src/app/components/todo-card/todo-card.component.ts
index 11c42f27f..6d50ad66e 100644
--- a/projects/v3/src/app/components/todo-card/todo-card.component.ts
+++ b/projects/v3/src/app/components/todo-card/todo-card.component.ts
@@ -2,7 +2,6 @@ import { Component, EventEmitter, Input, Output } from '@angular/core';
import { TodoItem } from '@v3/app/services/notifications.service';
@Component({
- standalone: false,
selector: 'app-todo-card',
templateUrl: './todo-card.component.html',
styleUrls: ['./todo-card.component.scss']
diff --git a/projects/v3/src/app/components/topic/topic.component.spec.ts b/projects/v3/src/app/components/topic/topic.component.spec.ts
index 88df682f3..64fd29e2c 100644
--- a/projects/v3/src/app/components/topic/topic.component.spec.ts
+++ b/projects/v3/src/app/components/topic/topic.component.spec.ts
@@ -15,7 +15,6 @@ import { UtilsService } from '@v3/services/utils.service';
import { TestUtils } from '@testingv3/utils';
import { ActivityService } from '@v3/services/activity.service';
import { EmbedVideoService } from '@v3/services/ngx-embed-video.service';
-import { ModalController } from '@ionic/angular';
describe('TopicComponent', () => {
let component: TopicComponent;
@@ -31,12 +30,12 @@ describe('TopicComponent', () => {
let activitySpy: jasmine.SpyObj;
beforeEach(async () => {
- topicSpy = jasmine.createSpyObj('TopicService', ['getTopic', 'getTopicProgress', 'updateTopicProgress', 'clearTopic']);
+ topicSpy = jasmine.createSpyObj('TopicService', ['getTopic', 'getTopicProgress', 'updateTopicProgress']);
filestackSpy = jasmine.createSpyObj('FilestackService', ['previewFile']);
embedSpy = jasmine.createSpyObj('EmbedVideoService', ['embed']);
- embedSpy.embed.and.returnValue(''); // return valid embed html
sharedSpy = jasmine.createSpyObj('SharedService', ['stopPlayingVideos']);
routerSpy = jasmine.createSpyObj('Router', ['navigate']);
+ utilsSpy = jasmine.createSpyObj('UtilsService', ['downloadFile']);
notificationSpy = jasmine.createSpyObj('NotificationsService', ['alert', 'presentToast']);
storageSpy = jasmine.createSpyObj('BrowserStorageService', ['getUser', 'get', 'remove']);
activitySpy = jasmine.createSpyObj('ActivityService', ['gotoNextTask']);
@@ -53,16 +52,14 @@ describe('TopicComponent', () => {
{ provide: NotificationsService, useValue: notificationSpy },
{ provide: SharedService, useValue: sharedSpy },
{ provide: BrowserStorageService, useValue: storageSpy },
- { provide: UtilsService, useClass: TestUtils },
+ { provide: UtilsService, useValue: utilsSpy },
{ provide: ActivityService, useValue: activitySpy },
{ provide: ActivatedRouteStub, useValue: new ActivatedRouteStub({ activityId: 1, id: 2 }) },
- { provide: ModalController, useValue: jasmine.createSpyObj('ModalController', ['create', 'dismiss']) },
]
}).compileComponents();
fixture = TestBed.createComponent(TopicComponent);
component = fixture.componentInstance;
- utilsSpy = TestBed.inject(UtilsService) as jasmine.SpyObj;
storageSpy.getUser.and.returnValue({ teamId: 1, projectId: 2 });
storageSpy.get.and.returnValue({});
@@ -73,46 +70,27 @@ describe('TopicComponent', () => {
});
it('should call stopPlayingVideos on ionViewWillLeave', () => {
- sharedSpy.stopPlayingVideos.and.returnValue(undefined);
+ sharedSpy.stopPlayingVideos.and.returnValue('');
component.ionViewWillLeave();
expect(sharedSpy.stopPlayingVideos).toHaveBeenCalledTimes(1);
});
describe('ngOnChanges', () => {
it('should embed video when video element found', fakeAsync(() => {
- const originalQSA = component['document'].querySelectorAll.bind(component['document']);
- spyOn(component['document'], 'querySelectorAll').and.callFake((selector: string) => {
- if (selector === 'audio' || selector === 'video' || selector === '.plyr__video-embed') {
- return [] as any;
- }
- if (selector === '.video-embed') {
- return [{
- classList: {
- add: () => true,
- remove: () => true,
- contains: jasmine.createSpy('contains').and.returnValue(true),
- },
- nodeName: 'VIDEO',
- setAttribute: jasmine.createSpy('setAttribute'),
- removeAttribute: jasmine.createSpy('removeAttribute'),
- innerHTML: '',
- }] as any;
+ spyOn(component['document'], 'querySelectorAll').and.returnValue([
+ {
+ classList: {
+ add: () => true,
+ remove: () => true,
+ contains: jasmine.createSpy('contains').and.returnValue(true),
+ },
+ nodeName: 'VIDEO',
}
- return originalQSA(selector);
- });
+ ] as any);
- component.topic = {
- videolink: 'test.com/vimeo',
- } as any;
- component.ngOnChanges({
- topic: {
- currentValue: component.topic,
- firstChange: true,
- previousValue: undefined,
- isFirstChange: () => true
- }
- });
- expect(component.continuing).toEqual(false);
+ component.topic = { videolink: 'test.com/vimeo' } as any;
+ component.ngOnChanges();
+ expect(component.continuing).toBe(false);
tick(500);
@@ -120,38 +98,20 @@ describe('TopicComponent', () => {
}));
it('should not embed video when no video element found', fakeAsync(() => {
- const originalQSA = component['document'].querySelectorAll.bind(component['document']);
- spyOn(component['document'], 'querySelectorAll').and.callFake((selector: string) => {
- if (selector === 'audio' || selector === 'video' || selector === '.plyr__video-embed') {
- return [] as any;
- }
- if (selector === '.video-embed') {
- return [{
- classList: {
- add: () => true,
- remove: () => true,
- contains: jasmine.createSpy('contains').and.returnValue(false),
- },
- nodeName: 'NON_VIDEO',
- setAttribute: jasmine.createSpy('setAttribute'),
- removeAttribute: jasmine.createSpy('removeAttribute'),
- }] as any;
+ spyOn(component['document'], 'querySelectorAll').and.returnValue([
+ {
+ classList: {
+ add: () => true,
+ remove: () => true,
+ contains: jasmine.createSpy('contains').and.returnValue(false),
+ },
+ nodeName: 'NON_VIDEO',
}
- return originalQSA(selector);
- });
+ ] as any);
- component.topic = {
- videolink: 'test.com',
- } as any;
- component.ngOnChanges({
- topic: {
- currentValue: component.topic,
- firstChange: true,
- previousValue: undefined,
- isFirstChange: () => true
- }
- });
- expect(component.continuing).toEqual(false);
+ component.topic = { videolink: 'test.com' } as any;
+ component.ngOnChanges();
+ expect(component.continuing).toBe(false);
tick(500);
@@ -177,7 +137,7 @@ describe('TopicComponent', () => {
it('should handle preview file failure', fakeAsync(() => {
const SAMPLE_RESULT = 'FAILED_SAMPLE';
let result: any;
- notificationSpy.alert.and.returnValue(Promise.resolve(SAMPLE_RESULT as any));
+ notificationSpy.alert.and.returnValue(Promise.resolve(SAMPLE_RESULT));
filestackSpy.previewFile.and.rejectWith(new Error('File preview test error'));
component.isLoadingPreview = false;
@@ -237,6 +197,7 @@ describe('TopicComponent', () => {
const file = { url: 'https://example.com/document.pdf', name: 'document.pdf' };
component.actionBtnClick(file, 1);
expect(window.open).toHaveBeenCalledWith(file.url, '_blank');
+ expect(notificationSpy.presentToast).toHaveBeenCalled();
});
it('should open new tab for non-filestack url even without extension', () => {
@@ -307,7 +268,7 @@ describe('TopicComponent', () => {
describe('previewVideoFile', () => {
it('should open video modal with mp4 mime type', async () => {
const modalSpy = jasmine.createSpyObj('Modal', ['present']);
- (component['modalController'].create as jasmine.Spy).and.returnValue(Promise.resolve(modalSpy));
+ spyOn(component['modalController'], 'create').and.returnValue(Promise.resolve(modalSpy));
const file = { url: 'https://example.com/video.mp4', name: 'test.mp4' };
await component.previewVideoFile(file);
@@ -327,7 +288,7 @@ describe('TopicComponent', () => {
it('should open video modal with webm mime type', async () => {
const modalSpy = jasmine.createSpyObj('Modal', ['present']);
- (component['modalController'].create as jasmine.Spy).and.returnValue(Promise.resolve(modalSpy));
+ spyOn(component['modalController'], 'create').and.returnValue(Promise.resolve(modalSpy));
const file = { url: 'https://example.com/video.webm', name: 'test.webm' };
await component.previewVideoFile(file);
@@ -347,7 +308,7 @@ describe('TopicComponent', () => {
it('should open video modal with ogg mime type', async () => {
const modalSpy = jasmine.createSpyObj('Modal', ['present']);
- (component['modalController'].create as jasmine.Spy).and.returnValue(Promise.resolve(modalSpy));
+ spyOn(component['modalController'], 'create').and.returnValue(Promise.resolve(modalSpy));
const file = { url: 'https://example.com/video.ogg', name: 'test.ogg' };
await component.previewVideoFile(file);
diff --git a/projects/v3/src/app/components/topic/topic.component.ts b/projects/v3/src/app/components/topic/topic.component.ts
index 7c70b67e5..1f58c77e0 100644
--- a/projects/v3/src/app/components/topic/topic.component.ts
+++ b/projects/v3/src/app/components/topic/topic.component.ts
@@ -3,7 +3,7 @@ import { Component, Input, Output, EventEmitter, Inject, OnChanges, SimpleChange
import { DOCUMENT } from '@angular/common';
import { UtilsService } from '@v3/services/utils.service';
import { SharedService } from '@v3/services/shared.service';
-import Plyr from 'plyr';
+import * as Plyr from 'plyr';
import { EmbedVideoService } from '@v3/services/ngx-embed-video.service';
import { SafeHtml, DomSanitizer } from '@angular/platform-browser';
import { FilestackService } from '@v3/app/services/filestack.service';
@@ -15,7 +15,6 @@ import { ModalController } from '@ionic/angular';
import { FilePopupComponent } from '../file-popup/file-popup.component';
@Component({
- standalone: false,
selector: 'app-topic',
templateUrl: './topic.component.html',
styleUrls: ['./topic.component.scss']
diff --git a/projects/v3/src/app/components/traffic-light-group/traffic-light-group.component.spec.ts b/projects/v3/src/app/components/traffic-light-group/traffic-light-group.component.spec.ts
deleted file mode 100644
index d123b0d1c..000000000
--- a/projects/v3/src/app/components/traffic-light-group/traffic-light-group.component.spec.ts
+++ /dev/null
@@ -1,110 +0,0 @@
-import { FastFeedbackService } from '@v3/services/fast-feedback.service';
-import { BrowserStorageService } from '@v3/app/services/storage.service';
-import { NotificationsService } from '@v3/services/notifications.service';
-import { of } from 'rxjs';
-import { TrafficLightGroupComponent } from './traffic-light-group.component';
-
-describe('TrafficLightGroupComponent', () => {
- let component: TrafficLightGroupComponent;
- let fastFeedbackService: jasmine.SpyObj;
- let storageService: jasmine.SpyObj;
- let notificationsService: jasmine.SpyObj;
-
- beforeEach(() => {
- fastFeedbackService = jasmine.createSpyObj('FastFeedbackService', [
- 'pullFastFeedback',
- ]);
- fastFeedbackService.pullFastFeedback.and.returnValue(of(null) as any);
-
- notificationsService = jasmine.createSpyObj('NotificationsService', [
- 'showTeamCheckInAlert',
- ]);
- notificationsService.showTeamCheckInAlert.and.returnValue(Promise.resolve() as any);
-
- storageService = jasmine.createSpyObj('BrowserStorageService', ['getUser', 'set']);
- storageService.getUser.and.returnValue({ role: 'participant' } as any);
-
- component = new TrafficLightGroupComponent(
- fastFeedbackService,
- storageService,
- notificationsService
- );
- component.lights = {
- self: 0.2,
- expert: 0.6,
- team: 0.7,
- teams: [{ teamName: 'Team A', average: 0.4 }]
- } as any;
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-
- it('should identify mentor role', () => {
- storageService.getUser.and.returnValue({ role: 'mentor' } as any);
-
- expect(component.isMentor).toBeTrue();
- });
-
- it('should expose learner groups', () => {
- expect(component.learnerGroups).toEqual(['self', 'team', 'expert']);
- });
-
- it('should expose team groups from lights', () => {
- expect(component.teamGroups).toEqual([{ teamName: 'Team A', average: 0.4 }]);
- });
-
- it('should return empty team groups when lights undefined', () => {
- component.lights = undefined;
-
- expect(component.teamGroups).toEqual([]);
- });
-
- it('should not navigate when displayOnly is true', async () => {
- component.displayOnly = true;
-
- await component.navigateToPulseCheck('self');
-
- expect(fastFeedbackService.pullFastFeedback).not.toHaveBeenCalled();
- expect(storageService.set).not.toHaveBeenCalled();
- });
-
- it('should navigate to pulse check and reset loading state', async () => {
- await component.navigateToPulseCheck('self');
-
- expect(component.loading.self).toBeFalse();
- expect(fastFeedbackService.pullFastFeedback).toHaveBeenCalledWith({
- skipChecking: true,
- closable: true
- });
- expect(storageService.set).toHaveBeenCalledWith('fastFeedbackOpening', false);
- });
-
- it('should route self click to pulse check', async () => {
- spyOn(component, 'navigateToPulseCheck').and.returnValue(Promise.resolve());
-
- await component.handleTrafficLightClick('self', 0.2);
-
- expect(component.navigateToPulseCheck).toHaveBeenCalledWith('self');
- expect(notificationsService.showTeamCheckInAlert).not.toHaveBeenCalled();
- });
-
- it('should skip alert when value is undefined', async () => {
- await component.handleTrafficLightClick('team', undefined as any);
-
- expect(notificationsService.showTeamCheckInAlert).not.toHaveBeenCalled();
- });
-
- it('should skip alert when value is above threshold', async () => {
- await component.handleTrafficLightClick('team', 0.8);
-
- expect(notificationsService.showTeamCheckInAlert).not.toHaveBeenCalled();
- });
-
- it('should show alert when value is at or below threshold', async () => {
- await component.handleTrafficLightClick('team', 0.65);
-
- expect(notificationsService.showTeamCheckInAlert).toHaveBeenCalled();
- });
-});
diff --git a/projects/v3/src/app/components/traffic-light-group/traffic-light-group.component.ts b/projects/v3/src/app/components/traffic-light-group/traffic-light-group.component.ts
index 0ef1dfe41..799aee3bb 100644
--- a/projects/v3/src/app/components/traffic-light-group/traffic-light-group.component.ts
+++ b/projects/v3/src/app/components/traffic-light-group/traffic-light-group.component.ts
@@ -4,7 +4,6 @@ import { BrowserStorageService } from "@v3/app/services/storage.service";
import { NotificationsService } from '@v3/services/notifications.service';
@Component({
- standalone: false,
selector: "app-traffic-light-group",
templateUrl: "./traffic-light-group.component.html",
styleUrls: ["./traffic-light-group.component.scss"],
diff --git a/projects/v3/src/app/components/traffic-light/traffic-light.component.spec.ts b/projects/v3/src/app/components/traffic-light/traffic-light.component.spec.ts
deleted file mode 100644
index 943b34d10..000000000
--- a/projects/v3/src/app/components/traffic-light/traffic-light.component.spec.ts
+++ /dev/null
@@ -1,51 +0,0 @@
-import { TrafficLightComponent } from './traffic-light.component';
-
-describe('TrafficLightComponent', () => {
- let component: TrafficLightComponent;
-
- beforeEach(() => {
- component = new TrafficLightComponent();
- });
-
- it('should create', () => {
- expect(component).toBeTruthy();
- });
-
- it('should return grey when value is null', () => {
- component.value = null;
-
- expect(component.color).toBe('grey');
- });
-
- it('should return grey when value is undefined', () => {
- component.value = undefined as any;
-
- expect(component.color).toBe('grey');
- });
-
- it('should return red when value is less than 0.32', () => {
- component.value = 0.31;
-
- expect(component.color).toBe('red');
- });
-
- it('should return green when value is greater than 0.65', () => {
- component.value = 0.66;
-
- expect(component.color).toBe('green');
- });
-
- it('should return orange when value is in threshold range', () => {
- component.value = 0.5;
-
- expect(component.color).toBe('orange');
- });
-
- it('should return orange at exact lower and upper thresholds', () => {
- component.value = 0.32;
- expect(component.color).toBe('orange');
-
- component.value = 0.65;
- expect(component.color).toBe('orange');
- });
-});
diff --git a/projects/v3/src/app/components/traffic-light/traffic-light.component.ts b/projects/v3/src/app/components/traffic-light/traffic-light.component.ts
index 0b3f4ab63..0088fdc6c 100644
--- a/projects/v3/src/app/components/traffic-light/traffic-light.component.ts
+++ b/projects/v3/src/app/components/traffic-light/traffic-light.component.ts
@@ -1,7 +1,6 @@
import { Component, Input } from '@angular/core';
@Component({
- standalone: false,
selector: 'app-traffic-light',
templateUrl: './traffic-light.component.html',
styleUrls: ['./traffic-light.component.scss']
diff --git a/projects/v3/src/app/components/uppy-uploader/uppy-uploader.component.ts b/projects/v3/src/app/components/uppy-uploader/uppy-uploader.component.ts
index 3be19ae40..e6ca39d98 100644
--- a/projects/v3/src/app/components/uppy-uploader/uppy-uploader.component.ts
+++ b/projects/v3/src/app/components/uppy-uploader/uppy-uploader.component.ts
@@ -10,7 +10,6 @@ type FileMetadata = { [key: string]: any };
type FileBody = { [key: string]: any };
@Component({
- standalone: false,
selector: "app-uppy-uploader",
templateUrl: "./uppy-uploader.component.html",
styleUrls: ["./uppy-uploader.component.scss"],
@@ -24,7 +23,7 @@ export class UppyUploaderComponent implements OnInit, OnDestroy {
uppy: Uppy;
// Uppy UI
- uppyProps: any;
+ uppyProps = this.uppyUploaderService.uppyProps;
s3Info: {
path: string;
@@ -38,7 +37,6 @@ export class UppyUploaderComponent implements OnInit, OnDestroy {
private storageService: BrowserStorageService,
private uppyUploaderService: UppyUploaderService,
) {
- this.uppyProps = this.uppyUploaderService.uppyProps;
this.uppyProps.height = '500px';
this.uppyProps.note = "Upload a file here";
}
diff --git a/projects/v3/src/app/components/uppy-uploader/uppy-uploader.service.spec.ts b/projects/v3/src/app/components/uppy-uploader/uppy-uploader.service.spec.ts
deleted file mode 100644
index 4c43d8f7a..000000000
--- a/projects/v3/src/app/components/uppy-uploader/uppy-uploader.service.spec.ts
+++ /dev/null
@@ -1,145 +0,0 @@
-import { TestBed } from '@angular/core/testing';
-import { ModalController } from '@ionic/angular';
-import { UppyUploaderService } from './uppy-uploader.service';
-import { BrowserStorageService } from '../../services/storage.service';
-import { Uppy, UppyFile } from '@uppy/core';
-import { environment } from '../../../environments/environment';
-
-describe('UppyUploaderService', () => {
- let service: UppyUploaderService;
- let modalControllerSpy: jasmine.SpyObj;
- let storageServiceSpy: jasmine.SpyObj;
- let uppyInstanceSpy: jasmine.SpyObj>;
- let modalSpy: any;
-
- beforeEach(() => {
- modalSpy = jasmine.createSpyObj('HTMLIonModalElement', ['present']);
- modalControllerSpy = jasmine.createSpyObj('ModalController', ['create']);
- modalControllerSpy.create.and.returnValue(Promise.resolve(modalSpy));
-
- storageServiceSpy = jasmine.createSpyObj('BrowserStorageService', ['getUser', 'clearByName']);
- storageServiceSpy.getUser.and.returnValue({ apikey: 'test-api-key' });
- storageServiceSpy.clearByName.and.returnValue({});
-
- uppyInstanceSpy = jasmine.createSpyObj('Uppy', ['use', 'on']);
- uppyInstanceSpy.on.and.returnValue(uppyInstanceSpy); // To allow method chaining
-
- // Mock environment config
- environment.uppyConfig = {
- tusUrl: 'https://example.com/uploads',
- uploadPreset: 'test-preset',
- restrictions: {
- minFileSize: 0,
- maxFileSize: 1000000,
- minNumberOfFiles: 1,
- maxNumberOfFiles: 10,
- maxTotalFileSize: 10000000,
- requiredMetaFields: []
- }
- };
- environment.stackName = 'test-stack';
-
- TestBed.configureTestingModule({
- providers: [
- UppyUploaderService,
- { provide: ModalController, useValue: modalControllerSpy },
- { provide: BrowserStorageService, useValue: storageServiceSpy }
- ]
- });
-
- service = TestBed.inject(UppyUploaderService);
- });
-
- it('should be created', () => {
- expect(service).toBeTruthy();
- });
-
- describe('createUppyInstance', () => {
- it('should create an Uppy instance with correct options', () => {
- const events = {
- onAfterResponse: jasmine.createSpy('onAfterResponse'),
- onUploadSuccess: jasmine.createSpy('onUploadSuccess')
- };
-
- const options = {
- allowedFileTypes: ['image/*']
- };
-
- const result = service.createUppyInstance('chat', 'https://upload.example.com', events, options);
-
- // verify the result is an Uppy instance by checking it has expected methods
- expect(result).toBeTruthy();
- expect(typeof result.use).toBe('function');
- expect(typeof result.on).toBe('function');
- });
-
- it('should log error if environment config is missing', () => {
- const originalConfig = environment.uppyConfig;
- const originalStackName = environment.stackName;
- environment.uppyConfig = null;
- environment.stackName = '';
-
- const consoleSpy = spyOn(console, 'error');
- const events = {
- onAfterResponse: jasmine.createSpy('onAfterResponse'),
- onUploadSuccess: jasmine.createSpy('onUploadSuccess')
- };
-
- // this will log error but not throw since the config check just logs
- try {
- service.createUppyInstance('chat', 'https://upload.example.com', events);
- } catch (e) {
- // expected - uppyConfig is null so restrictions will throw
- }
-
- expect(consoleSpy).toHaveBeenCalledWith('Uppy configuration is missing or incomplete.');
-
- // restore config
- environment.uppyConfig = originalConfig;
- environment.stackName = originalStackName;
- });
- });
-
- describe('initializeEventHandlers', () => {
- it('should set up event handlers on the Uppy instance', () => {
- const onUploadSuccessSpy = jasmine.createSpy('onUploadSuccess');
- const file = { id: 'file-123' } as UppyFile;
- const response = { status: 200 };
-
- (service as any).initializeEventHandlers(uppyInstanceSpy, onUploadSuccessSpy);
-
- // Skip directly calling the handler as it has type issues
- // Instead, simulate the behavior that would happen when the handler is called
- onUploadSuccessSpy(file, response);
-
- expect(onUploadSuccessSpy).toHaveBeenCalledWith(file, response);
- });
-
- it('should clear cache when upload completes successfully', () => {
- const onUploadSuccessSpy = jasmine.createSpy('onUploadSuccess');
- const result = {
- successful: [{ id: 'file-123' }],
- failed: []
- };
-
- (service as any).initializeEventHandlers(uppyInstanceSpy, onUploadSuccessSpy);
-
- // Instead of invoking the handler directly, we'll test the behavior
- // by calling the method that the handler would trigger
- service['storageService'].clearByName('file-123');
-
- expect(storageServiceSpy.clearByName).toHaveBeenCalledWith('file-123');
- });
- });
-
- describe('getPatchValue', () => {
- it('should return the correct patch value for a given id', () => {
- const testId = 'test-id';
- const testValue = { path: 'test-path', bucket: 'test-bucket' };
-
- service['patchValue'] = { [testId]: testValue };
-
- expect(service.getPatchValue(testId)).toEqual(testValue);
- });
- });
-});
diff --git a/projects/v3/src/app/components/uppy-uploader/uppy-uploader.service.ts b/projects/v3/src/app/components/uppy-uploader/uppy-uploader.service.ts
index 6fdcea158..3f6ac9e71 100644
--- a/projects/v3/src/app/components/uppy-uploader/uppy-uploader.service.ts
+++ b/projects/v3/src/app/components/uppy-uploader/uppy-uploader.service.ts
@@ -4,6 +4,7 @@ import { ModalController } from '@ionic/angular';
import { Injectable } from '@angular/core';
import { UploadResult, Uppy, UppyFile, UppyOptions } from '@uppy/core';
import Tus from '@uppy/tus';
+import { UppyUploaderComponent } from './uppy-uploader.component';
import { BrowserStorageService } from '../../services/storage.service';
import { Dashboard } from 'uppy';
import { environment } from '../../../environments/environment';
@@ -200,8 +201,6 @@ export class UppyUploaderService {
* @return {Promise}
*/
async open(source: 'chat' | 'user-profile' | 'assessment' | 'media-manager' | 'static' | null): Promise {
- // dynamic import to break circular dependency with UppyUploaderComponent
- const { UppyUploaderComponent } = await import('./uppy-uploader.component');
const modal = await this.modalController.create({
component: UppyUploaderComponent,
componentProps: {
diff --git a/projects/v3/src/app/components/video-conversion/video-conversion.component.spec.ts b/projects/v3/src/app/components/video-conversion/video-conversion.component.spec.ts
index 4e1221291..859416096 100644
--- a/projects/v3/src/app/components/video-conversion/video-conversion.component.spec.ts
+++ b/projects/v3/src/app/components/video-conversion/video-conversion.component.spec.ts
@@ -60,15 +60,7 @@ describe('VideoConversionComponent', () => {
describe('convertVideo()', () => {
it('should perform filestack video conversion and wait', fakeAsync(() => {
component.stop$ = new Subject();
- component.convertVideo({
- bucket: 'test-bucket',
- path: 'test-path',
- name: 'test-video',
- url: 'http://test.com/video.mp4',
- extension: 'mp4',
- type: 'video/mp4',
- size: 1000
- });
+ component.convertVideo({ handle: 'abcdefg'});
tick(10000);
expect(component.result).toEqual({ status: 'completed' });
}));
diff --git a/projects/v3/src/app/components/video-conversion/video-conversion.component.ts b/projects/v3/src/app/components/video-conversion/video-conversion.component.ts
index 7fdf80614..4aee4a07b 100644
--- a/projects/v3/src/app/components/video-conversion/video-conversion.component.ts
+++ b/projects/v3/src/app/components/video-conversion/video-conversion.component.ts
@@ -11,7 +11,6 @@ interface FilestackConversionResponse {
}
@Component({
- standalone: false,
selector: 'app-video-conversion',
templateUrl: 'video-conversion.component.html',
styleUrls: ['video-conversion.component.scss'],
diff --git a/projects/v3/src/app/directives/autoresize/autoresize.directive.spec.ts b/projects/v3/src/app/directives/autoresize/autoresize.directive.spec.ts
deleted file mode 100644
index 273acf762..000000000
--- a/projects/v3/src/app/directives/autoresize/autoresize.directive.spec.ts
+++ /dev/null
@@ -1,79 +0,0 @@
-import { ElementRef } from '@angular/core';
-import { AutoresizeDirective } from './autoresize.directive';
-
-describe('AutoresizeDirective', () => {
- const createDirective = (textArea?: Partial) => {
- const querySelector = jasmine.createSpy('querySelector').and.returnValue(textArea || null);
- const elementRef = {
- nativeElement: {
- querySelector
- }
- } as ElementRef;
- const directive = new AutoresizeDirective(elementRef);
-
- return { directive, querySelector };
- };
-
- it('should create', () => {
- const { directive } = createDirective();
-
- expect(directive).toBeTruthy();
- });
-
- it('should resize using scrollHeight when maxHeight is not set', () => {
- const textArea = {
- style: { overflow: '', height: '' },
- scrollHeight: 160
- } as unknown as HTMLTextAreaElement;
- const { directive } = createDirective(textArea);
-
- directive.adjust();
-
- expect(textArea.style.overflow).toBe('auto');
- expect(textArea.style.height).toBe('160px');
- });
-
- it('should cap resize to numeric maxHeight', () => {
- const textArea = {
- style: { overflow: '', height: '' },
- scrollHeight: 300
- } as unknown as HTMLTextAreaElement;
- const { directive } = createDirective(textArea);
- directive.maxHeight = '200';
-
- directive.adjust();
-
- expect(directive.maxHeight).toBe(200);
- expect(textArea.style.height).toBe('200px');
- });
-
- it('should call adjust on init', () => {
- const textArea = {
- style: { overflow: '', height: '' },
- scrollHeight: 120
- } as unknown as HTMLTextAreaElement;
- const { directive } = createDirective(textArea);
- spyOn(directive, 'adjust');
-
- directive.ngOnInit();
-
- expect(directive.adjust).toHaveBeenCalled();
- });
-
- it('should call adjust on input host listener', () => {
- const { directive } = createDirective();
- spyOn(directive, 'adjust');
-
- directive.onInput({} as HTMLTextAreaElement);
-
- expect(directive.adjust).toHaveBeenCalled();
- });
-
- it('should do nothing when textarea is not found', () => {
- const { directive, querySelector } = createDirective();
-
- directive.adjust();
-
- expect(querySelector).toHaveBeenCalledWith('textarea');
- });
-});
diff --git a/projects/v3/src/app/directives/autoresize/autoresize.directive.ts b/projects/v3/src/app/directives/autoresize/autoresize.directive.ts
index d28c0d710..6c7849132 100644
--- a/projects/v3/src/app/directives/autoresize/autoresize.directive.ts
+++ b/projects/v3/src/app/directives/autoresize/autoresize.directive.ts
@@ -1,7 +1,6 @@
import { Directive, ElementRef, Input, HostListener, OnInit } from '@angular/core';
@Directive({
- standalone: false,
selector: '[appAutoresize]'
})
export class AutoresizeDirective implements OnInit {
diff --git a/projects/v3/src/app/directives/background-image/background-image.directive.ts b/projects/v3/src/app/directives/background-image/background-image.directive.ts
index e5f342c43..8ce081593 100644
--- a/projects/v3/src/app/directives/background-image/background-image.directive.ts
+++ b/projects/v3/src/app/directives/background-image/background-image.directive.ts
@@ -2,7 +2,6 @@ import { Directive, ElementRef, Input, OnDestroy, OnInit, Renderer2 } from '@ang
import { BrowserStorageService } from '@v3/app/services/storage.service';
@Directive({
- standalone: false,
selector: '[appBackgroundImage]'
})
export class BackgroundImageDirective implements OnInit, OnDestroy {
diff --git a/projects/v3/src/app/directives/drag-and-drop/drag-and-drop.directive.spec.ts b/projects/v3/src/app/directives/drag-and-drop/drag-and-drop.directive.spec.ts
index e5cc74e97..60cf3d6ee 100644
--- a/projects/v3/src/app/directives/drag-and-drop/drag-and-drop.directive.spec.ts
+++ b/projects/v3/src/app/directives/drag-and-drop/drag-and-drop.directive.spec.ts
@@ -1,119 +1,8 @@
import { DragAndDropDirective } from './drag-and-drop.directive';
describe('DragAndDropDirective', () => {
- let directive: DragAndDropDirective;
-
- const createEvent = (files: any[] = []) => {
- const preventDefault = jasmine.createSpy('preventDefault');
- const stopPropagation = jasmine.createSpy('stopPropagation');
- return {
- preventDefault,
- stopPropagation,
- dataTransfer: {
- files
- }
- } as any;
- };
-
- beforeEach(() => {
- directive = new DragAndDropDirective();
- });
-
it('should create an instance', () => {
+ const directive = new DragAndDropDirective();
expect(directive).toBeTruthy();
});
-
- it('should set fileOver on dragover when enabled', () => {
- const event = createEvent();
- directive.disabled = false;
-
- directive.ondragover(event);
-
- expect(event.preventDefault).toHaveBeenCalled();
- expect(event.stopPropagation).toHaveBeenCalled();
- expect(directive.fileOver).toBeTrue();
- });
-
- it('should not set fileOver on dragover when disabled', () => {
- const event = createEvent();
- directive.disabled = true;
-
- directive.ondragover(event);
-
- expect(directive.fileOver).not.toBeTrue();
- });
-
- it('should unset fileOver on dragleave', () => {
- const event = createEvent();
- directive.fileOver = true;
-
- directive.ondragleave(event);
-
- expect(directive.fileOver).toBeFalse();
- expect(event.preventDefault).toHaveBeenCalled();
- expect(event.stopPropagation).toHaveBeenCalled();
- });
-
- it('should return early on drop when disabled', () => {
- const event = createEvent([{ type: 'text/plain' }]);
- directive.disabled = true;
- spyOn(directive.fileDropped, 'emit');
-
- directive.ondrop(event);
-
- expect(directive.fileDropped.emit).not.toHaveBeenCalled();
- });
-
- it('should emit error when more than one file is dropped', () => {
- const event = createEvent([{ type: 'text/plain' }, { type: 'text/plain' }]);
- spyOn(directive.fileDropped, 'emit');
-
- directive.ondrop(event);
-
- expect(directive.fileDropped.emit).toHaveBeenCalledWith({
- success: false,
- message: 'More than one file droped'
- });
- });
-
- it('should emit error when file type does not match acceptFileType', () => {
- const event = createEvent([{ type: 'image/png' }]);
- directive.acceptFileType = 'application/pdf';
- spyOn(directive.fileDropped, 'emit');
-
- directive.ondrop(event);
-
- expect(directive.fileDropped.emit).toHaveBeenCalledWith({
- success: false,
- message: 'Not a matching file type'
- });
- });
-
- it('should emit success when acceptFileType is any', () => {
- const file = { type: 'image/png' } as any;
- const event = createEvent([file]);
- directive.acceptFileType = 'any';
- spyOn(directive.fileDropped, 'emit');
-
- directive.ondrop(event);
-
- expect(directive.fileDropped.emit).toHaveBeenCalledWith({
- success: true,
- file
- });
- });
-
- it('should emit success when file type matches acceptFileType', () => {
- const file = { type: 'image/png' } as any;
- const event = createEvent([file]);
- directive.acceptFileType = 'image';
- spyOn(directive.fileDropped, 'emit');
-
- directive.ondrop(event);
-
- expect(directive.fileDropped.emit).toHaveBeenCalledWith({
- success: true,
- file
- });
- });
});
diff --git a/projects/v3/src/app/directives/drag-and-drop/drag-and-drop.directive.ts b/projects/v3/src/app/directives/drag-and-drop/drag-and-drop.directive.ts
index 617a379cc..830c57ee5 100644
--- a/projects/v3/src/app/directives/drag-and-drop/drag-and-drop.directive.ts
+++ b/projects/v3/src/app/directives/drag-and-drop/drag-and-drop.directive.ts
@@ -1,7 +1,6 @@
import { Directive, HostListener, HostBinding, Output, EventEmitter, Input } from '@angular/core';
@Directive({
- standalone: false,
selector: '[appDragAndDrop]'
})
export class DragAndDropDirective {
diff --git a/projects/v3/src/app/directives/fallback-image/fallback-image.directive.ts b/projects/v3/src/app/directives/fallback-image/fallback-image.directive.ts
index 4cd8849a7..dab32aa47 100644
--- a/projects/v3/src/app/directives/fallback-image/fallback-image.directive.ts
+++ b/projects/v3/src/app/directives/fallback-image/fallback-image.directive.ts
@@ -1,7 +1,6 @@
import { Directive, ElementRef, HostListener, Input } from '@angular/core';
@Directive({
- standalone: false,
selector: '[appFallbackImage]'
})
export class FallbackImageDirective {
diff --git a/projects/v3/src/app/directives/toggle-label/toggle-label.directive.spec.ts b/projects/v3/src/app/directives/toggle-label/toggle-label.directive.spec.ts
index bc6ba54ed..afdb14361 100644
--- a/projects/v3/src/app/directives/toggle-label/toggle-label.directive.spec.ts
+++ b/projects/v3/src/app/directives/toggle-label/toggle-label.directive.spec.ts
@@ -6,7 +6,7 @@ import { ToggleLabelDirective } from './toggle-label.directive';
@Component({
template: `
Test Label
diff --git a/projects/v3/src/app/directives/tooltip/tooltip.directive.ts b/projects/v3/src/app/directives/tooltip/tooltip.directive.ts
index 23296caa3..dc02027a5 100644
--- a/projects/v3/src/app/directives/tooltip/tooltip.directive.ts
+++ b/projects/v3/src/app/directives/tooltip/tooltip.directive.ts
@@ -1,7 +1,6 @@
import { Directive, ElementRef, HostListener, Input, OnDestroy, Renderer2 } from '@angular/core';
@Directive({
- standalone: false,
selector: '[appTooltip]'
})
export class TooltipDirective implements OnDestroy {
diff --git a/projects/v3/src/app/guards/single-page-deactivate.guard.spec.ts b/projects/v3/src/app/guards/single-page-deactivate.guard.spec.ts
index 27dcfd750..9b9e65313 100644
--- a/projects/v3/src/app/guards/single-page-deactivate.guard.spec.ts
+++ b/projects/v3/src/app/guards/single-page-deactivate.guard.spec.ts
@@ -1,4 +1,4 @@
-import { TestBed, waitForAsync, inject } from '@angular/core/testing';
+import { TestBed, async, inject } from '@angular/core/testing';
import { BrowserStorageService } from '@v3/services/storage.service';
import { SinglePageDeactivateGuard } from './single-page-deactivate.guard';
diff --git a/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.spec.ts b/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.spec.ts
index b63ea50a0..8ce69dee5 100644
--- a/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.spec.ts
+++ b/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.spec.ts
@@ -5,15 +5,12 @@ import { AssessmentService } from '@v3/services/assessment.service';
import { BrowserStorageService } from '@v3/services/storage.service';
import { UtilsService } from '@v3/services/utils.service';
import { TopicService } from '@v3/services/topic.service';
-import { ReviewService } from '@v3/services/review.service';
import { IonicModule } from '@ionic/angular';
import { ActivatedRouteStub } from '@testingv3/activated-route-stub';
import { MockRouter } from '@testingv3/mocked.service';
import { TestUtils } from '@testingv3/utils';
import { NotificationsService } from '@v3/services/notifications.service';
import { of } from 'rxjs';
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ActivityDesktopPage } from './activity-desktop.page';
import { NormalisedTaskFixture, TaskFixture } from '@testingv3/fixtures/tasks';
@@ -61,24 +58,21 @@ describe('ActivityDesktopPage', () => {
provide: TopicService,
useValue: jasmine.createSpyObj('TopicService', {
updateTopicProgress: of(true),
- clearTopic: undefined,
}, {
topic$: of(true)
}),
},
{
provide: AssessmentService,
- useValue: jasmine.createSpyObj('AssessmentService', {
- saveAnswers: of(true),
- getAssessment: of(null),
- saveFeedbackReviewed: of(true),
- fetchAssessment: of({ submission: { status: 'in progress' } }),
- submitAssessment: of({ data: { submitAssessment: { success: true } } }),
- }, {
+ useValue: jasmine.createSpyObj('AssessmentService', [
+ 'saveAnswers',
+ 'getAssessment',
+ 'saveFeedbackReviewed',
+ 'popUpReviewRating',
+ ], {
'assessment$': of(true),
- 'assessment': null,
'submission$': of(true),
- 'review$': of({ id: 1, status: 'done' }),
+ 'review$': of(true),
}),
},
{
@@ -86,34 +80,22 @@ describe('ActivityDesktopPage', () => {
useValue: jasmine.createSpyObj('NotificationsService', [
'assessmentSubmittedToast',
'alert',
- 'getTodoItems',
- 'getCurrentTodoItems',
- 'markTodoItemAsDone',
- 'markMultipleTodoItemsAsDone',
]),
},
{
provide: BrowserStorageService,
useValue: jasmine.createSpyObj('BrowserStorageService', {
- 'getUser': { hasReviewRating: true },
- 'lastVisited': null,
- 'get': null,
- 'getFeature': null,
+ 'getUser': {
+ hasReviewRating: true
+ }
}),
},
{
provide: UtilsService,
useClass: TestUtils
},
- {
- provide: ReviewService,
- useValue: jasmine.createSpyObj('ReviewService', {
- 'popUpReviewRating': Promise.resolve(),
- }),
- },
],
- imports: [IonicModule.forRoot(), HttpClientTestingModule],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
+ imports: [IonicModule.forRoot()]
}).compileComponents();
fixture = TestBed.createComponent(ActivityDesktopPage);
@@ -156,7 +138,6 @@ describe('ActivityDesktopPage', () => {
id: 1,
name: 'test',
tasks: [NormalisedTaskFixture],
- unlockConditions: []
};
component.ionViewDidEnter();
@@ -175,11 +156,6 @@ describe('ActivityDesktopPage', () => {
});
describe('topicComplete()', () => {
- beforeEach(() => {
- // set required activity object for all tests in this block
- component.activity = { id: 1, name: 'Test Activity' } as any;
- });
-
it('should request to update progress', fakeAsync(() => {
component.topicComplete(NormalisedTaskFixture);
activitySpy.getActivity = jasmine.createSpy().and.callFake((id, anything, task, cb) => {
@@ -193,7 +169,7 @@ describe('ActivityDesktopPage', () => {
it('should go to next task when task is done', () => {
const task = NormalisedTaskFixture;
- task.status = 'done';
+ task.status = 'done';2
component.topicComplete(task);
expect(topicSpy.updateTopicProgress).not.toHaveBeenCalled();
expect(activitySpy.goToNextTask).toHaveBeenCalled();
@@ -201,69 +177,61 @@ describe('ActivityDesktopPage', () => {
});
describe('saveAssessment()', () => {
- beforeEach(() => {
- // set required activity object for all tests in this block
- component.activity = { id: 1, name: 'Test Activity' } as any;
- });
-
it('should save answers', fakeAsync(() => {
- assessmentSpy.fetchAssessment = jasmine.createSpy().and.returnValue(of({ submission: { status: 'in progress' } }));
- assessmentSpy.submitAssessment = jasmine.createSpy().and.returnValue(of({ data: { submitAssessment: { success: true } } }));
+ assessmentSpy.saveAnswers = jasmine.createSpy().and.returnValue({
+ toPromise: jasmine.createSpy()
+ });
const saveTextSpy = spyOn(component.savingText$, 'next');
const btnDisabledSpy = spyOn(component.btnDisabled$, 'next');
component.saveAssessment({
- assessmentId: 1,
- submissionId: 1,
- contextId: 1,
+ assessment: { id: 1, inProgress: true, submssionId: 1, contextId: 1 },
answers: {},
- autoSave: true,
+ action: '',
}, NormalisedTaskFixture);
tick();
- expect(assessmentSpy.fetchAssessment).toHaveBeenCalled();
- expect(assessmentSpy.submitAssessment).toHaveBeenCalled();
+ expect(assessmentSpy.saveAnswers).toHaveBeenCalled();
expect(saveTextSpy).toHaveBeenCalled();
expect(btnDisabledSpy).toHaveBeenCalled();
- tick(10000); // wait for SAVE_PROGRESS_TIMEOUT (10 seconds)
expect(component.loading).toBeFalse();
}));
it('should save answers (when not in progress)', fakeAsync(() => {
- assessmentSpy.fetchAssessment = jasmine.createSpy().and.returnValue(of({ submission: { status: 'done' } }));
- notificationsSpy.assessmentSubmittedToast = jasmine.createSpy();
+ assessmentSpy.saveAnswers = jasmine.createSpy().and.returnValue({
+ toPromise: jasmine.createSpy()
+ });
activitySpy.getActivity = jasmine.createSpy().and.callFake((id, anything, task, cb) => {
- if (cb) cb();
+ cb();
});
const saveTextSpy = spyOn(component.savingText$, 'next');
const btnDisabledSpy = spyOn(component.btnDisabled$, 'next');
component.saveAssessment({
- assessmentId: 1,
- submissionId: 1,
- contextId: 1,
+ assessment: {
+ id: 1,
+ inProgress: false,
+ submssionId: 1,
+ contextId: 1,
+ },
answers: {},
- autoSave: false,
+ action: '',
}, NormalisedTaskFixture);
tick();
- expect(assessmentSpy.fetchAssessment).toHaveBeenCalled();
+ expect(assessmentSpy.saveAnswers).toHaveBeenCalled();
expect(notificationsSpy.assessmentSubmittedToast).toHaveBeenCalled();
expect(saveTextSpy).toHaveBeenCalled();
expect(btnDisabledSpy).toHaveBeenCalled();
- tick(1000);
expect(component.loading).toBeFalse();
}));
});
describe('readFeedback()', () => {
it('should mark feedback as read', fakeAsync(() => {
- assessmentSpy.saveFeedbackReviewed = jasmine.createSpy().and.returnValue(of(true));
- notificationsSpy.getTodoItems = jasmine.createSpy().and.returnValue(of([]));
- // set required activity object
- component.activity = { id: 1, name: 'Test Activity' } as any;
+ assessmentSpy.saveFeedbackReviewed = jasmine.createSpy().and.returnValue({ toPromise: jasmine.createSpy() });
component.readFeedback(1, NormalisedTaskFixture);
// const spy = spyOn(assessmentSpy.saveFeedbackReviewed);
@@ -271,7 +239,7 @@ describe('ActivityDesktopPage', () => {
expect(assessmentSpy.saveFeedbackReviewed).toHaveBeenCalled();
// expect(activitySpy.getActivity).toHaveBeenCalled();
tick(1000);
- // expect(assessmentSpy.popUpReviewRating).toHaveBeenCalled(); // Removed as popUpReviewRating does not exist on AssessmentService
+ expect(notificationsSpy.popUpReviewRating).toHaveBeenCalled();
}));
});
@@ -296,7 +264,7 @@ describe('ActivityDesktopPage', () => {
});
component.reviewRatingPopUp();
tick();
- // expect(assessmentSpy.popUpReviewRating).not.toHaveBeenCalled(); // Removed as popUpReviewRating does not exist on AssessmentService
+ expect(notificationsSpy.popUpReviewRating).not.toHaveBeenCalled();
}));
});
});
diff --git a/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.ts b/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.ts
index fe24bf120..691ff2652 100644
--- a/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.ts
+++ b/projects/v3/src/app/pages/activity-desktop/activity-desktop.page.ts
@@ -4,12 +4,12 @@ import { DOCUMENT } from '@angular/common';
import { Component, Inject, ViewChild } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivityService, Task, Activity } from '@v3/app/services/activity.service';
-import { Assessment, AssessmentReview, AssessmentService, Submission } from '@v3/app/services/assessment.service';
+import { AssessmentReview, AssessmentService, Submission } from '@v3/app/services/assessment.service';
import { NotificationsService } from '@v3/app/services/notifications.service';
import { BrowserStorageService } from '@v3/app/services/storage.service';
import { Topic, TopicService } from '@v3/app/services/topic.service';
import { UtilsService } from '@v3/app/services/utils.service';
-import { BehaviorSubject, Observable, firstValueFrom } from 'rxjs';
+import { BehaviorSubject, firstValueFrom } from 'rxjs';
import { delay, filter, tap, distinctUntilChanged, takeUntil, debounceTime } from 'rxjs/operators';
import { TopicComponent } from '@v3/app/components/topic/topic.component';
import { ComponentCleanupService } from '@v3/app/services/component-cleanup.service';
@@ -17,7 +17,6 @@ import { ComponentCleanupService } from '@v3/app/services/component-cleanup.serv
const SAVE_PROGRESS_TIMEOUT = 10000;
@Component({
- standalone: false,
selector: 'app-activity-desktop',
templateUrl: './activity-desktop.page.html',
styleUrls: ['./activity-desktop.page.scss'],
@@ -25,7 +24,7 @@ const SAVE_PROGRESS_TIMEOUT = 10000;
export class ActivityDesktopPage {
activity: Activity;
currentTask: Task;
- assessment: Observable;
+ assessment = this.assessmentService.assessment$;
submission: Submission;
review: AssessmentReview;
topic: Topic;
@@ -72,7 +71,6 @@ export class ActivityDesktopPage {
private componentCleanupService: ComponentCleanupService,
@Inject(DOCUMENT) private readonly document: Document,
) {
- this.assessment = this.assessmentService.assessment$;
// slow down the scroll event trigger
this.scrolSubject
.pipe(debounceTime(300))
diff --git a/projects/v3/src/app/pages/activity-mobile/activity-mobile.page.spec.ts b/projects/v3/src/app/pages/activity-mobile/activity-mobile.page.spec.ts
index 655a2a0f6..482433144 100644
--- a/projects/v3/src/app/pages/activity-mobile/activity-mobile.page.spec.ts
+++ b/projects/v3/src/app/pages/activity-mobile/activity-mobile.page.spec.ts
@@ -2,66 +2,47 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivityService } from '@v3/services/activity.service';
import { AssessmentService } from '@v3/services/assessment.service';
-import { UtilsService } from '@v3/services/utils.service';
import { IonicModule } from '@ionic/angular';
-import { HttpClientTestingModule } from '@angular/common/http/testing';
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ActivityMobilePage } from './activity-mobile.page';
-import { of, Subject } from 'rxjs';
+import { of } from 'rxjs';
+import { ActivatedRouteStub } from '@testingv3/activated-route-stub';
+import { MockRouter } from '@testingv3/mocked.service';
describe('ActivityMobilePage', () => {
let component: ActivityMobilePage;
let fixture: ComponentFixture;
- let routeParams$: Subject;
- let activity$: Subject;
- let submission$: Subject;
- let routerSpy: jasmine.SpyObj;
- let activityServiceSpy: jasmine.SpyObj;
- let utilsSpy: jasmine.SpyObj;
beforeEach(waitForAsync(() => {
- routeParams$ = new Subject();
- activity$ = new Subject();
- submission$ = new Subject();
-
TestBed.configureTestingModule({
declarations: [ ActivityMobilePage ],
- imports: [IonicModule.forRoot(), HttpClientTestingModule],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
+ imports: [IonicModule.forRoot()],
providers: [
{
provide: ActivatedRoute,
- useValue: {
- snapshot: {
- paramMap: {
- get: (_key: string) => '1',
- },
- },
- params: routeParams$.asObservable(),
- },
+ // useClass: ActivatedRouteStub,
+ useValue: jasmine.createSpyObj('ActivatedRoute', [], {
+ params: of(true),
+ }),
},
{
provide: Router,
- useValue: jasmine.createSpyObj('Router', ['navigate']),
+ useClass: MockRouter,
+ // useValue: jasmine.createSpyObj('Router', ['navigate']),
},
{
provide: ActivityService,
- useValue: jasmine.createSpyObj('ActivityService', ['getActivity', 'goToTask'], {
- activity$: activity$.asObservable(),
+ useValue: jasmine.createSpyObj('ActivityService', {
+ 'getActivity': of(),
+ 'goToTask': of(),
+ }, {
+ 'activity$': of(),
}),
},
{
provide: AssessmentService,
useValue: jasmine.createSpyObj('AssessmentService', [], {
- submission$: submission$.asObservable(),
- }),
- },
- {
- provide: UtilsService,
- useValue: jasmine.createSpyObj('UtilsService', {
- setPageTitle: undefined,
- getEvent: new Subject(),
+ 'submission$': of(),
}),
},
],
@@ -69,55 +50,10 @@ describe('ActivityMobilePage', () => {
fixture = TestBed.createComponent(ActivityMobilePage);
component = fixture.componentInstance;
- routerSpy = TestBed.inject(Router) as jasmine.SpyObj;
- activityServiceSpy = TestBed.inject(ActivityService) as jasmine.SpyObj;
- utilsSpy = TestBed.inject(UtilsService) as jasmine.SpyObj;
-
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
-
- it('should load activity and submission data on init', () => {
- routeParams$.next({ id: 1 });
- submission$.next({ id: 10, status: 'in progress' } as any);
- activity$.next({ id: 1, name: 'Activity A' } as any);
-
- expect(activityServiceSpy.getActivity).toHaveBeenCalledWith(1, false);
- expect(component.submission).toEqual(jasmine.objectContaining({ id: 10 }));
- expect(component.activity).toEqual(jasmine.objectContaining({ id: 1, name: 'Activity A' }));
- expect(utilsSpy.setPageTitle).toHaveBeenCalledWith('Activity A - Practera');
- });
-
- it('should ignore activity events with non-matching id', () => {
- activity$.next({ id: 999, name: 'Other Activity' } as any);
-
- expect(component.activity).toBeUndefined();
- expect(utilsSpy.setPageTitle).not.toHaveBeenCalled();
- });
-
- it('should navigate to assessment task route', () => {
- component.activity = { id: 55 } as any;
-
- component.goToTask({ id: 9, contextId: 77, type: 'Assessment' } as any);
-
- expect(activityServiceSpy.goToTask).toHaveBeenCalledWith(jasmine.objectContaining({ id: 9 }), false);
- expect(routerSpy.navigate).toHaveBeenCalledWith(['assessment-mobile', 'assessment', 55, 77, 9]);
- });
-
- it('should navigate to topic task route', () => {
- component.activity = { id: 66 } as any;
-
- component.goToTask({ id: 3, type: 'Topic' } as any);
-
- expect(routerSpy.navigate).toHaveBeenCalledWith(['topic-mobile', 66, 3]);
- });
-
- it('should go back to home', () => {
- component.goBack();
-
- expect(routerSpy.navigate).toHaveBeenCalledWith(['v3', 'home']);
- });
});
diff --git a/projects/v3/src/app/pages/activity-mobile/activity-mobile.page.ts b/projects/v3/src/app/pages/activity-mobile/activity-mobile.page.ts
index 6ec457126..ff0cf419b 100644
--- a/projects/v3/src/app/pages/activity-mobile/activity-mobile.page.ts
+++ b/projects/v3/src/app/pages/activity-mobile/activity-mobile.page.ts
@@ -8,7 +8,6 @@ import { UnlockIndicatorService } from '@v3/app/services/unlock-indicator.servic
import { NotificationsService } from '@v3/app/services/notifications.service';
@Component({
- standalone: false,
selector: 'app-activity-mobile',
templateUrl: './activity-mobile.page.html',
styleUrls: ['./activity-mobile.page.scss'],
diff --git a/projects/v3/src/app/pages/assessment-mobile/assessment-mobile.page.spec.ts b/projects/v3/src/app/pages/assessment-mobile/assessment-mobile.page.spec.ts
index 6b4218174..59270c872 100644
--- a/projects/v3/src/app/pages/assessment-mobile/assessment-mobile.page.spec.ts
+++ b/projects/v3/src/app/pages/assessment-mobile/assessment-mobile.page.spec.ts
@@ -1,7 +1,7 @@
import { ComponentFixture, fakeAsync, flushMicrotasks, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
-import { ActivityService, Task } from '@v3/services/activity.service';
-import { AssessmentService, Assessment, Submission, AssessmentReview } from '@v3/services/assessment.service';
+import { ActivityService } from '@v3/services/activity.service';
+import { AssessmentService } from '@v3/services/assessment.service';
import { BrowserStorageService } from '@v3/services/storage.service';
import { UtilsService } from '@v3/services/utils.service';
import { IonicModule } from '@ionic/angular';
@@ -9,11 +9,7 @@ import { ActivatedRouteStub } from '@testingv3/activated-route-stub';
import { MockRouter } from '@testingv3/mocked.service';
import { TestUtils } from '@testingv3/utils';
import { NotificationsService } from '@v3/services/notifications.service';
-import { of, Subscription } from 'rxjs';
-import { ReviewService } from '@v3/app/services/review.service';
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-
-const SAVE_PROGRESS_TIMEOUT = 10000;
+import { of } from 'rxjs';
import { AssessmentMobilePage } from './assessment-mobile.page';
import { ElementRef } from '@angular/core';
@@ -29,13 +25,12 @@ describe('AssessmentMobilePage', () => {
let activitySpy: jasmine.SpyObj;
let notificationSpy: jasmine.SpyObj;
let storageSpy: jasmine.SpyObj;
- let reviewSpy: jasmine.SpyObj;
+ let elespy: jasmine.SpyObj;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ AssessmentMobilePage ],
imports: [IonicModule.forRoot()],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{
provide: ActivatedRoute,
@@ -50,17 +45,14 @@ describe('AssessmentMobilePage', () => {
},
{
provide: AssessmentService,
- useValue: jasmine.createSpyObj('AssessmentService', {
- getAssessment: of(true),
- fetchAssessment: of(true),
- submitAssessment: of(true),
- submitReview: of(true),
- pullFastFeedback: Promise.resolve(),
- saveFeedbackReviewed: of({}),
- }, {
+ useValue: jasmine.createSpyObj('AssessmentService', [
+ 'getAssessment',
+ 'saveAnswers',
+ 'saveFeedbackReviewed',
+ ], {
assessment$: of(true),
submission$: of(true),
- review$: of({ id: 1, status: 'done' }),
+ review$: of(true),
}),
},
{
@@ -68,10 +60,7 @@ describe('AssessmentMobilePage', () => {
useValue: jasmine.createSpyObj('ActivityService', [
'goToNextTask',
'getActivity',
- ], {
- currentTask$: of(null),
- activity$: of(null),
- }),
+ ]),
},
{
provide: BrowserStorageService,
@@ -83,17 +72,12 @@ describe('AssessmentMobilePage', () => {
'assessmentSubmittedToast',
'alert',
'popUpReviewRating',
- 'getTodoItems',
]),
},
{
provide: UtilsService,
useClass: TestUtils
},
- {
- provide: ReviewService,
- useValue: jasmine.createSpyObj('ReviewService', ['popUpReviewRating', 'getReviews']),
- },
]
}).compileComponents();
@@ -104,16 +88,15 @@ describe('AssessmentMobilePage', () => {
activitySpy = TestBed.inject(ActivityService) as jasmine.SpyObj;
storageSpy = TestBed.inject(BrowserStorageService) as jasmine.SpyObj;
notificationSpy = TestBed.inject(NotificationsService) as jasmine.SpyObj;
- reviewSpy = TestBed.inject(ReviewService) as jasmine.SpyObj;
}));
it('should create', () => {
expect(component).toBeTruthy();
});
- it('should call goToNextTask when continuing', () => {
+ it('should call continue()', () => {
component.currentTask = { id: 1, type: 'Assessment', name: 'Test', status: 'done' };
- component['activityService'].goToNextTask();
+ component.nextTask();
expect(activitySpy.goToNextTask).toHaveBeenCalled();
});
@@ -123,172 +106,66 @@ describe('AssessmentMobilePage', () => {
expect(component['router'].navigate).toHaveBeenCalled();
});
- it('should call saveAssessment() when action is assessment and autoSave is true', fakeAsync(() => {
- assessmentSpy.fetchAssessment.and.returnValue(of({
- assessment: {} as Assessment,
- submission: { status: 'in progress' } as Submission,
- review: {} as AssessmentReview
- }));
- assessmentSpy.submitAssessment.and.returnValue(of({ data: { submitAssessment: { success: true } } }));
- const event = {
- assessmentId: 1,
- contextId: 1,
- submissionId: 1,
- answers: [],
- autoSave: true,
- };
- component.action = 'assessment';
- component.saving = false;
- component.assessment = { pulseCheck: false, id: 1, name: 'Test Assessment', type: 'quiz', description: '' } as Assessment;
- component.activityId = 1;
-
- component.saveAssessment(event);
- tick();
-
- expect(assessmentSpy.fetchAssessment).toHaveBeenCalledWith(event.assessmentId, 'assessment', 1, event.contextId, event.submissionId);
- expect(assessmentSpy.submitAssessment).toHaveBeenCalledWith(event.submissionId, event.assessmentId, event.contextId, event.answers);
- expect(notificationSpy.assessmentSubmittedToast).not.toHaveBeenCalled();
- expect(activitySpy.getActivity).not.toHaveBeenCalled();
- expect(component.savingText$.getValue()).toContain('Last saved');
- tick(SAVE_PROGRESS_TIMEOUT);
- expect(component.btnDisabled$.getValue()).toBe(false);
- expect(component.saving).toBe(false);
- }));
-
- it('should call saveAssessment() when action is assessment and autoSave is false', fakeAsync(() => {
- assessmentSpy.fetchAssessment.and.returnValue(of({
- assessment: {} as Assessment,
- submission: { status: 'in progress' } as Submission,
- review: {} as AssessmentReview
- }));
- assessmentSpy.submitAssessment.and.returnValue(of({ data: { submitAssessment: { success: true } } }));
- activitySpy.getActivity.and.callFake((activityId, navigate, task, callback) => {
- if (callback) {
- callback();
- }
- return new Subscription(); // Return a Subscription
+ it('should call saveAssessment() with inProgress as true', fakeAsync(() => {
+ assessmentSpy.saveAnswers = jasmine.createSpy().and.returnValue({
+ toPromise: jasmine.createSpy()
});
-
const event = {
- assessmentId: 1,
- contextId: 1,
- submissionId: 1,
- answers: [],
- autoSave: false,
+ assessment: { id: 1, inProgress: true },
+ answers: 'test answers',
+ action: 'save',
};
- component.action = 'assessment';
component.saving = false;
- component.assessment = { pulseCheck: true, id: 1, name: 'Test Assessment', type: 'quiz', description: '' } as Assessment;
- component.activityId = 1;
- component.contextId = 1;
- component.submissionId = 1;
-
-
- component.saveAssessment(event);
- tick();
- flushMicrotasks();
+ component.saveAssessment(event).then(() => {
+ expect(assessmentSpy.saveAnswers).toHaveBeenCalledWith(event.assessment, event.answers as any, event.action, undefined);
+ expect(notificationSpy.assessmentSubmittedToast).not.toHaveBeenCalled();
+ expect(activitySpy.getActivity).not.toHaveBeenCalled();
+ expect(assessmentSpy.getAssessment).not.toHaveBeenCalledTimes(2); // ngOnInit x 1, saveAssessment x 0
+ });
- expect(assessmentSpy.fetchAssessment).toHaveBeenCalledWith(event.assessmentId, 'assessment', 1, event.contextId, event.submissionId);
- expect(assessmentSpy.submitAssessment).toHaveBeenCalledWith(event.submissionId, event.assessmentId, event.contextId, event.answers);
- expect(assessmentSpy.pullFastFeedback).toHaveBeenCalled();
- expect(notificationSpy.assessmentSubmittedToast).toHaveBeenCalledWith({ isReview: false });
- expect(assessmentSpy.fetchAssessment).toHaveBeenCalledWith(1, 'assessment', 1, 1, 1);
- expect(activitySpy.getActivity).toHaveBeenCalled();
- expect(component.savingText$.getValue()).toContain('Last saved');
- expect(component.btnDisabled$.getValue()).toBe(false);
- expect(component.saving).toBe(false);
+ tick(10000); // SAVE_PROGRESS_TIMEOUT = 10000
}));
- it('should call saveAssessment() when action is review and autoSave is false', fakeAsync(() => {
- assessmentSpy.fetchAssessment.and.returnValue(of({
- assessment: {} as Assessment,
- submission: { status: 'pending review' } as Submission,
- review: {} as AssessmentReview
- }));
- assessmentSpy.submitReview.and.returnValue(of({ data: { submitReview: { success: true } } }));
- component.review = { id: 1, reviewerId: 1, status: 'pending', answers: [], submitted: '', modified: '' } as AssessmentReview;
-
+ it('should call saveAssessment() with inProgress as false', fakeAsync(() => {
+ assessmentSpy.saveAnswers = jasmine.createSpy().and.returnValue(of({}));
+ activitySpy.getActivity = jasmine.createSpy();
+ assessmentSpy.getAssessment = jasmine.createSpy();
const event = {
- assessmentId: 1,
- contextId: 1,
- submissionId: 1,
- answers: [],
- autoSave: false,
+ assessment: { id: 1, inProgress: false },
+ answers: 'test answers',
+ action: 'save',
};
- component.action = 'review';
- component.saving = false;
- component.assessment = { pulseCheck: true, id: 1, name: 'Test Assessment', type: 'quiz', description: '' } as Assessment;
- component.activityId = 1;
- component.contextId = 1;
- component.submissionId = 1;
-
- component.saveAssessment(event);
- tick();
-
- expect(assessmentSpy.fetchAssessment).toHaveBeenCalledWith(event.assessmentId, 'review', 1, event.contextId, event.submissionId);
- expect(assessmentSpy.submitReview).toHaveBeenCalledWith(event.assessmentId, component.review.id, event.submissionId, event.answers);
- expect(reviewSpy.getReviews).toHaveBeenCalled();
- expect(assessmentSpy.pullFastFeedback).toHaveBeenCalled();
- expect(notificationSpy.assessmentSubmittedToast).toHaveBeenCalledWith({ isReview: true });
- expect(assessmentSpy.fetchAssessment).toHaveBeenCalledWith(1, 'review', 1, 1, 1);
- expect(component.savingText$.getValue()).toContain('Last saved');
- expect(component.btnDisabled$.getValue()).toBe(false);
- expect(component.saving).toBe(false);
- }));
-
- it('should handle error in saveAssessment()', fakeAsync(() => {
- assessmentSpy.fetchAssessment.and.returnValue(of({
- assessment: {} as Assessment,
- submission: { status: 'in progress' } as Submission,
- review: {} as AssessmentReview
- }));
- assessmentSpy.submitAssessment.and.throwError('submit error');
- const event = {
- assessmentId: 1,
- contextId: 1,
- submissionId: 1,
- answers: [],
- autoSave: false,
- };
- component.action = 'assessment';
component.saving = false;
- component.assessment = { pulseCheck: false, id: 1, name: 'Test Assessment', type: 'quiz', description: '' } as Assessment;
-
component.saveAssessment(event);
+
tick();
- expect(notificationSpy.assessmentSubmittedToast).toHaveBeenCalledWith({ isFail: true });
- expect(component.btnDisabled$.getValue()).toBe(false);
- expect(component.saving).toBe(false);
+ expect(assessmentSpy.saveAnswers).toHaveBeenCalledWith(event.assessment, event.answers as any, event.action, undefined);
+ expect(notificationSpy.assessmentSubmittedToast).toHaveBeenCalled();
+ expect(activitySpy.getActivity).toHaveBeenCalled();
+ expect(assessmentSpy.getAssessment).toHaveBeenCalled();
}));
-
it('should call readFeedback()', async () => {
storageSpy.getUser.and.returnValue({ hasReviewRating: true });
- assessmentSpy.saveFeedbackReviewed.and.returnValue(of({}));
- notificationSpy.getTodoItems.and.returnValue(of({}));
- notificationSpy.popUpReviewRating.and.resolveTo();
- activitySpy.getActivity.and.returnValue(new Subscription());
-
- const submissionId = 1;
- component.review = { id: 1 } as AssessmentReview;
- await component.readFeedback(submissionId);
- expect(assessmentSpy.saveFeedbackReviewed).toHaveBeenCalledWith(submissionId);
+ assessmentSpy.saveFeedbackReviewed = jasmine.createSpy().and.returnValue({
+ toPromise: jasmine.createSpy()
+ });
+ const event = { id: 1, data: 'test data' };
+ await component.readFeedback(event);
+ expect(assessmentSpy.saveFeedbackReviewed).toHaveBeenCalledWith(event);
expect(notificationSpy.popUpReviewRating).toHaveBeenCalled();
- expect(notificationSpy.getTodoItems).toHaveBeenCalled();
expect(activitySpy.getActivity).toHaveBeenCalled();
});
it('should call nextTask()', () => {
- component.activityId = 1;
component.nextTask();
- expect(activitySpy.getActivity).toHaveBeenCalledWith(1, true, jasmine.anything());
+ expect(activitySpy.goToNextTask).toHaveBeenCalled();
});
it('should call reviewRatingPopUp() with hasReviewRating as true', async () => {
storageSpy.getUser.and.returnValue({ hasReviewRating: true });
- notificationSpy.popUpReviewRating.and.resolveTo();
await component.reviewRatingPopUp();
expect(notificationSpy.popUpReviewRating).toHaveBeenCalled();
diff --git a/projects/v3/src/app/pages/assessment-mobile/assessment-mobile.page.ts b/projects/v3/src/app/pages/assessment-mobile/assessment-mobile.page.ts
index 5ee4db11e..c50cc2437 100644
--- a/projects/v3/src/app/pages/assessment-mobile/assessment-mobile.page.ts
+++ b/projects/v3/src/app/pages/assessment-mobile/assessment-mobile.page.ts
@@ -14,7 +14,6 @@ import { debounceTime } from 'rxjs/operators';
const SAVE_PROGRESS_TIMEOUT = 10000;
@Component({
- standalone: false,
selector: 'app-assessment-mobile',
templateUrl: './assessment-mobile.page.html',
styleUrls: ['./assessment-mobile.page.scss'],
@@ -218,7 +217,6 @@ export class AssessmentMobilePage implements OnInit, OnDestroy {
// get the latest activity tasks and refresh the assessment submission data
this.activityService.getActivity(this.activityId, false, null, () => {
this.btnDisabled$.next(false);
- this.saving = false;
});
} else {
this.btnDisabled$.next(false);
diff --git a/projects/v3/src/app/pages/auth/auth-direct-login/auth-direct-login.component.spec.ts b/projects/v3/src/app/pages/auth/auth-direct-login/auth-direct-login.component.spec.ts
index a4a91ad73..3314a24a1 100644
--- a/projects/v3/src/app/pages/auth/auth-direct-login/auth-direct-login.component.spec.ts
+++ b/projects/v3/src/app/pages/auth/auth-direct-login/auth-direct-login.component.spec.ts
@@ -1,8 +1,8 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { waitForAsync, ComponentFixture, TestBed, fakeAsync, tick, flushMicrotasks } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed, fakeAsync, tick, flushMicrotasks } from '@angular/core/testing';
import { AuthDirectLoginComponent } from './auth-direct-login.component';
import { AuthService } from '@v3/services/auth.service';
-import { of, throwError } from 'rxjs';
+import { of } from 'rxjs';
import { Router, ActivatedRoute, convertToParamMap } from '@angular/router';
import { UtilsService } from '@v3/services/utils.service';
import { NotificationsService } from '@v3/services/notifications.service';
@@ -25,7 +25,7 @@ describe('AuthDirectLoginComponent', () => {
let storageSpy: jasmine.SpyObj;
let sharedSpy: jasmine.SpyObj;
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [],
declarations: [AuthDirectLoginComponent],
@@ -46,32 +46,13 @@ describe('AuthDirectLoginComponent', () => {
{
provide: AuthService,
useValue: jasmine.createSpyObj('AuthService', {
- 'directLogin': of(true),
- 'authenticate': of({}),
- 'autologin': of({ experience: { timelineId: 1 } }),
- 'clearCache': Promise.resolve(),
- 'logout': Promise.resolve(),
- 'getMyInfo': of({
- data: {
- user: {
- id: 1,
- uuid: 'test-uuid',
- name: 'Test User',
- firstName: 'Test',
- lastName: 'User',
- email: 'test@example.com',
- image: 'test-image.jpg',
- role: 'participant',
- contactNumber: '123456789',
- userHash: 'test-hash'
- }
- }
- })
+ 'directLogin': of(true)
})
},
{
provide: ExperienceService,
useValue: jasmine.createSpyObj('ExperienceService', {
+ 'getMyInfo': of(true),
'switchProgram': of(true)
})
},
@@ -114,43 +95,30 @@ describe('AuthDirectLoginComponent', () => {
});
beforeEach(() => {
- authServiceSpy.autologin.and.returnValue(of({ experience: { timelineId: 1 } }));
- authServiceSpy.getMyInfo.and.returnValue(of({
- data: {
- user: {
- id: 1,
- uuid: 'test-uuid',
- name: 'Test User',
- firstName: 'Test',
- lastName: 'User',
- email: 'test@example.com',
- image: 'test-image.jpg',
- role: 'participant',
- contactNumber: '123456789',
- userHash: 'test-hash'
- }
- }
- }));
+ authServiceSpy.authenticate.and.returnValue(of({} as any));
+ authServiceSpy.getMyInfo.and.returnValue(of({} as any));
switcherSpy.switchProgram.and.returnValue(Promise.resolve(of({})));
storageSpy.get.and.returnValue([{ timeline: { id: 1 } }]);
storageSpy.getConfig.and.returnValue({ logo: null });
});
describe('when testing ngOnInit()', () => {
- it('should pop up alert if auth token is not provided', async () => {
+ it('should pop up alert if auth token is not provided', fakeAsync(() => {
const params = { authToken: null };
routeSpy.snapshot.paramMap.get = jasmine.createSpy().and.callFake(key => params[key]);
- notificationSpy.alert.and.returnValue(Promise.resolve() as any);
-
- await component.ngOnInit();
+ utils.isEmpty = jasmine.createSpy('isEmpty').and.returnValue(true);
- expect(notificationSpy.alert.calls.count()).toBe(1);
- });
+ tick(50);
+ fixture.detectChanges();
+ fixture.whenStable().then(() => {
+ expect(notificationSpy.alert.calls.count()).toBe(1);
+ });
+ }));
it('should pop up alert if direct login service throw error', fakeAsync(() => {
const params = { authToken: 'abc' };
routeSpy.snapshot.paramMap.get = jasmine.createSpy().and.callFake(key => params[key]);
- authServiceSpy.autologin.and.returnValue(throwError(() => new Error('Login failed')));
+ authServiceSpy.authenticate.and.throwError('');
fixture.detectChanges();
tick(50);
fixture.detectChanges();
@@ -159,7 +127,7 @@ describe('AuthDirectLoginComponent', () => {
const button = notificationSpy.alert.calls.first().args[0].buttons[0];
(typeof button === 'string') ? button : button.handler(true);
- expect(authServiceSpy.logout).toHaveBeenCalled();
+ expect(routerSpy.navigate.calls.first().args[0]).toEqual(['login']);
}));
describe('should navigate to', () => {
@@ -198,10 +166,10 @@ describe('AuthDirectLoginComponent', () => {
fixture.detectChanges();
if (doAuthentication) {
- expect(authServiceSpy.autologin.calls.count()).toBe(1);
+ expect(authServiceSpy.authenticate.calls.count()).toBe(1);
expect(authServiceSpy.getMyInfo.calls.count()).toBe(1);
} else {
- expect(authServiceSpy.autologin.calls.count()).toBe(0);
+ expect(authServiceSpy.authenticate.calls.count()).toBe(0);
expect(authServiceSpy.getMyInfo.calls.count()).toBe(0);
}
@@ -218,11 +186,10 @@ describe('AuthDirectLoginComponent', () => {
}));
it('skip authentication if auth token match', () => {
- // note: component always calls autologin when token is provided
switchProgram = false;
redirect = ['experiences'];
storageSpy.get.and.returnValue('abc');
- doAuthentication = true;
+ doAuthentication = false;
});
it('program switcher page if timeline id is not passed in', () => {
@@ -298,8 +265,8 @@ describe('AuthDirectLoginComponent', () => {
tmpParams.act,
{
task: 'assessment',
- contextId: tmpParams.ctxt,
- assessmentId: tmpParams.asmt,
+ task_id: tmpParams.asmt,
+ context_id: tmpParams.ctxt
}
];
// redirect = ['assessment', 'assessment', tmpParams.act, tmpParams.ctxt, tmpParams.asmt];
@@ -359,8 +326,8 @@ describe('AuthDirectLoginComponent', () => {
tmpParams.act,
{
task: 'assessment',
- contextId: tmpParams.ctxt,
- assessmentId: tmpParams.asmt,
+ task_id: tmpParams.asmt,
+ context_id: tmpParams.ctxt
}
];
setReferrerCalled = true;
diff --git a/projects/v3/src/app/pages/auth/auth-direct-login/auth-direct-login.component.ts b/projects/v3/src/app/pages/auth/auth-direct-login/auth-direct-login.component.ts
index 2e54fb94e..d5dcf2475 100644
--- a/projects/v3/src/app/pages/auth/auth-direct-login/auth-direct-login.component.ts
+++ b/projects/v3/src/app/pages/auth/auth-direct-login/auth-direct-login.component.ts
@@ -9,7 +9,6 @@ import { SharedService } from '@v3/services/shared.service';
import { environment } from '@v3/environments/environment';
@Component({
- standalone: false,
selector: 'app-auth-direct-login',
templateUrl: 'auth-direct-login.component.html',
})
@@ -245,7 +244,7 @@ export class AuthDirectLoginComponent implements OnInit {
return this.navigate(['auth', 'registration', res.data.user.email, res.data.user.key]);
}
- const errorMessage = res?.message?.includes('User not enrolled') ? res.message : $localize`Your link is invalid or expired.`;
+ const errorMessage = res.message.includes('User not enrolled') ? res.message : $localize`Your link is invalid or expired.`;
return this.notificationsService.alert({
message: errorMessage,
diff --git a/projects/v3/src/app/pages/auth/auth-forgot-password/auth-forgot-password.component.spec.ts b/projects/v3/src/app/pages/auth/auth-forgot-password/auth-forgot-password.component.spec.ts
index ef21ed262..e4359d55d 100644
--- a/projects/v3/src/app/pages/auth/auth-forgot-password/auth-forgot-password.component.spec.ts
+++ b/projects/v3/src/app/pages/auth/auth-forgot-password/auth-forgot-password.component.spec.ts
@@ -1,5 +1,5 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { waitForAsync, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AuthForgotPasswordComponent } from './auth-forgot-password.component';
import { AuthService } from '@v3/services/auth.service';
@@ -18,7 +18,7 @@ describe('AuthForgotPasswordComponent', () => {
let notificationSpy: jasmine.SpyObj;
let storageSpy: jasmine.SpyObj;
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule, HttpClientTestingModule],
declarations: [AuthForgotPasswordComponent],
diff --git a/projects/v3/src/app/pages/auth/auth-forgot-password/auth-forgot-password.component.ts b/projects/v3/src/app/pages/auth/auth-forgot-password/auth-forgot-password.component.ts
index 6c553cca0..81189a742 100644
--- a/projects/v3/src/app/pages/auth/auth-forgot-password/auth-forgot-password.component.ts
+++ b/projects/v3/src/app/pages/auth/auth-forgot-password/auth-forgot-password.component.ts
@@ -4,7 +4,6 @@ import { UtilsService } from '@v3/services/utils.service';
import { AuthService } from '@v3/services/auth.service';
@Component({
- standalone: false,
selector: 'app-auth-forgot-password',
templateUrl: 'auth-forgot-password.component.html',
styleUrls: ['auth-forgot-password.component.scss']
diff --git a/projects/v3/src/app/pages/auth/auth-global-login/auth-global-login.component.spec.ts b/projects/v3/src/app/pages/auth/auth-global-login/auth-global-login.component.spec.ts
index ac86adfce..5fcc89209 100644
--- a/projects/v3/src/app/pages/auth/auth-global-login/auth-global-login.component.spec.ts
+++ b/projects/v3/src/app/pages/auth/auth-global-login/auth-global-login.component.spec.ts
@@ -1,5 +1,5 @@
-import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+import { ReactiveFormsModule } from '@angular/forms';
import { RouterTestingModule } from '@angular/router/testing';
import { of, throwError } from 'rxjs';
import { UtilsService } from '@v3/services/utils.service';
@@ -7,158 +7,68 @@ import { AuthService } from '@v3/services/auth.service';
import { BrowserStorageService } from '@v3/services/storage.service';
import { NotificationsService } from '@v3/services/notifications.service';
import { ExperienceService } from '@v3/services/experience.service';
-import { AuthGlobalLoginComponent } from './auth-global-login.component';
-import { ActivatedRoute, Router } from '@angular/router';
-
-describe('AuthGlobalLoginComponent', () => {
- let component: AuthGlobalLoginComponent;
- let fixture: ComponentFixture;
- let mockAuthService: jasmine.SpyObj;
- let mockUtilsService: jasmine.SpyObj;
- let mockStorageService: jasmine.SpyObj;
- let mockNotificationService: jasmine.SpyObj;
- let mockExperienceService: jasmine.SpyObj;
- let mockRouter: jasmine.SpyObj;
- let activatedRoute: any;
-
- beforeEach(waitForAsync(() => {
- mockAuthService = jasmine.createSpyObj('AuthService', ['autologin', 'getMyInfo', 'logout']);
- mockUtilsService = jasmine.createSpyObj('UtilsService', ['redirectToUrl']);
- mockStorageService = jasmine.createSpyObj('BrowserStorageService', ['get', 'set', 'remove']);
- mockNotificationService = jasmine.createSpyObj('NotificationsService', ['alert']);
- mockExperienceService = jasmine.createSpyObj('ExperienceService', ['switchProgram']);
- mockRouter = jasmine.createSpyObj('Router', ['navigate']);
-
- activatedRoute = {
- snapshot: {
- paramMap: {
- get: jasmine.createSpy('get').and.callFake((key: string) => {
- if (key === 'apikey') return 'test-apikey';
- if (key === 'multiple') return null;
- return null;
- })
- }
- }
- };
-
- TestBed.configureTestingModule({
- declarations: [AuthGlobalLoginComponent],
- imports: [RouterTestingModule],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
+import { AuthRegistrationComponent } from '../auth-registration/auth-registration.component';
+
+describe('AuthRegistrationComponent', () => {
+ let component: AuthRegistrationComponent;
+ let fixture: ComponentFixture;
+ let mockAuthService, mockUtilsService, mockStorageService, mockNotificationService, mockExperienceService;
+
+ beforeEach(async () => {
+ mockAuthService = jasmine.createSpyObj(['verifyRegistration', 'checkDomain', 'saveRegistration', 'authenticate']);
+ mockUtilsService = jasmine.createSpyObj(['find']);
+ mockStorageService = jasmine.createSpyObj(['get', 'set', 'remove', 'setUser']);
+ mockNotificationService = jasmine.createSpyObj(['popUp', 'alert']);
+ mockExperienceService = jasmine.createSpyObj(['switchProgram']);
+
+ await TestBed.configureTestingModule({
+ declarations: [AuthRegistrationComponent],
+ imports: [
+ ReactiveFormsModule,
+ RouterTestingModule
+ ],
providers: [
{ provide: AuthService, useValue: mockAuthService },
{ provide: UtilsService, useValue: mockUtilsService },
{ provide: BrowserStorageService, useValue: mockStorageService },
{ provide: NotificationsService, useValue: mockNotificationService },
- { provide: ExperienceService, useValue: mockExperienceService },
- { provide: Router, useValue: mockRouter },
- { provide: ActivatedRoute, useValue: activatedRoute }
+ { provide: ExperienceService, useValue: mockExperienceService }
]
- }).compileComponents();
- }));
+ })
+ .compileComponents();
- beforeEach(() => {
- fixture = TestBed.createComponent(AuthGlobalLoginComponent);
+ fixture = TestBed.createComponent(AuthRegistrationComponent);
component = fixture.componentInstance;
+ fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
- it('should handle missing apikey on init', async () => {
- activatedRoute.snapshot.paramMap.get.and.returnValue(null);
- mockNotificationService.alert.and.returnValue(Promise.resolve());
-
- await component.ngOnInit();
-
- expect(mockNotificationService.alert).toHaveBeenCalled();
- });
-
- it('should login and navigate on valid apikey', async () => {
- const mockExperience = {
- id: 1,
- locale: 'en-US'
- };
- mockAuthService.autologin.and.returnValue(of({ experience: mockExperience }));
- mockAuthService.getMyInfo.and.returnValue(of({
- data: {
- user: {
- id: 1,
- uuid: 'test-uuid',
- name: 'Test User',
- firstName: 'Test',
- lastName: 'User',
- email: 'test@example.com',
- image: 'test.jpg',
- role: 'participant',
- contactNumber: '+1234567890',
- userHash: 'hash123'
- }
- }
- }));
- mockExperienceService.switchProgram.and.returnValue(Promise.resolve());
- mockRouter.navigate.and.returnValue(Promise.resolve(true));
-
- await component.ngOnInit();
-
- expect(mockAuthService.autologin).toHaveBeenCalledWith({ apikey: 'test-apikey' });
- expect(mockAuthService.getMyInfo).toHaveBeenCalled();
- expect(mockExperienceService.switchProgram).toHaveBeenCalledWith({ experience: mockExperience });
- });
-
- it('should set hasMultipleStacks when multiple param is true', async () => {
- activatedRoute.snapshot.paramMap.get.and.callFake((key: string) => {
- if (key === 'apikey') return 'test-apikey';
- if (key === 'multiple') return 'true';
- return null;
- });
- const mockExperience = {
- id: 1,
- locale: 'en-US'
- };
- mockAuthService.autologin.and.returnValue(of({ experience: mockExperience }));
- mockAuthService.getMyInfo.and.returnValue(of({
- data: {
- user: {
- id: 1,
- uuid: 'test-uuid',
- name: 'Test User',
- firstName: 'Test',
- lastName: 'User',
- email: 'test@example.com',
- image: 'test.jpg',
- role: 'participant',
- contactNumber: '+1234567890',
- userHash: 'hash123'
- }
- }
- }));
- mockExperienceService.switchProgram.and.returnValue(Promise.resolve());
- mockRouter.navigate.and.returnValue(Promise.resolve(true));
-
- await component.ngOnInit();
-
- expect(mockStorageService.set).toHaveBeenCalledWith('hasMultipleStacks', true);
+ it('should initialize the form', () => {
+ component.initForm();
+ expect(component.registerationForm).toBeDefined();
+ expect(component.registerationForm.get('email').value).toEqual('');
});
- it('should show error alert on login failure', async () => {
- mockAuthService.autologin.and.returnValue(throwError(() => ({ message: 'Login failed' })));
- mockNotificationService.alert.and.returnValue(Promise.resolve());
+ it('should validate query parameters', () => {
+ mockAuthService.verifyRegistration.and.returnValue(of(true));
+ mockAuthService.checkDomain.and.returnValue(of(true));
- await component.ngOnInit();
+ component.validateQueryParams();
- expect(mockNotificationService.alert).toHaveBeenCalled();
+ expect(mockAuthService.verifyRegistration).toHaveBeenCalled();
+ expect(mockAuthService.checkDomain).toHaveBeenCalled();
});
- it('should show specific error for user not enrolled', async () => {
- mockAuthService.autologin.and.returnValue(throwError(() => ({ message: 'User not enrolled in program' })));
- mockNotificationService.alert.and.returnValue(Promise.resolve());
+ it('should register the user', () => {
+ mockAuthService.saveRegistration.and.returnValue(of(true));
+ mockAuthService.authenticate.and.returnValue(of(true));
- await component.ngOnInit();
+ component.register();
- expect(mockNotificationService.alert).toHaveBeenCalledWith(jasmine.objectContaining({
- message: jasmine.stringContaining('User not enrolled')
- }));
+ expect(mockAuthService.saveRegistration).toHaveBeenCalled();
+ expect(mockAuthService.authenticate).toHaveBeenCalled();
});
});
diff --git a/projects/v3/src/app/pages/auth/auth-global-login/auth-global-login.component.ts b/projects/v3/src/app/pages/auth/auth-global-login/auth-global-login.component.ts
index af7e3ace5..b3be208ce 100644
--- a/projects/v3/src/app/pages/auth/auth-global-login/auth-global-login.component.ts
+++ b/projects/v3/src/app/pages/auth/auth-global-login/auth-global-login.component.ts
@@ -8,7 +8,6 @@ import { environment } from '@v3/environments/environment';
import { UtilsService } from '@v3/app/services/utils.service';
@Component({
- standalone: false,
selector: 'app-auth-global-login',
templateUrl: 'auth-global-login.component.html'
})
@@ -81,7 +80,7 @@ export class AuthGlobalLoginComponent implements OnInit {
}
private _error(res?): Promise {
- const errorMessage = res?.message?.includes('User not enrolled') ? res.message : $localize`Your link is invalid or expired.`;
+ const errorMessage = res.message.includes('User not enrolled') ? res.message : $localize`Your link is invalid or expired.`;
return this.notificationsService.alert({
message: errorMessage,
diff --git a/projects/v3/src/app/pages/auth/auth-login/auth-login.component.spec.ts b/projects/v3/src/app/pages/auth/auth-login/auth-login.component.spec.ts
index da3dfb76c..14a34ca31 100644
--- a/projects/v3/src/app/pages/auth/auth-login/auth-login.component.spec.ts
+++ b/projects/v3/src/app/pages/auth/auth-login/auth-login.component.spec.ts
@@ -1,5 +1,5 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { waitForAsync, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AuthLoginComponent } from './auth-login.component';
import { AuthService } from '@v3/services/auth.service';
diff --git a/projects/v3/src/app/pages/auth/auth-login/auth-login.component.ts b/projects/v3/src/app/pages/auth/auth-login/auth-login.component.ts
index 847d8299d..987a6b573 100644
--- a/projects/v3/src/app/pages/auth/auth-login/auth-login.component.ts
+++ b/projects/v3/src/app/pages/auth/auth-login/auth-login.component.ts
@@ -7,7 +7,6 @@ import { UtilsService } from '@v3/services/utils.service';
import { ExperienceService } from '@v3/services/experience.service';
@Component({
- standalone: false,
selector: 'app-auth-login',
templateUrl: 'auth-login.component.html',
styleUrls: ['auth-login.component.scss']
diff --git a/projects/v3/src/app/pages/auth/auth-logout/auth-logout.component.spec.ts b/projects/v3/src/app/pages/auth/auth-logout/auth-logout.component.spec.ts
index 41e13f4e3..839f39f6f 100644
--- a/projects/v3/src/app/pages/auth/auth-logout/auth-logout.component.spec.ts
+++ b/projects/v3/src/app/pages/auth/auth-logout/auth-logout.component.spec.ts
@@ -1,5 +1,5 @@
import { AuthLogoutComponent } from './auth-logout.component';
-import { waitForAsync, ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed, fakeAsync } from '@angular/core/testing';
import { AuthService } from '@v3/services/auth.service';
import { Router, ActivatedRoute } from '@angular/router';
// import { NewRelicService } from '@v3/services/new-relic.service';
@@ -17,7 +17,7 @@ describe('AuthLogoutComponent', () => {
// let newRelicSpy: jasmine.SpyObj;
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [AuthLogoutComponent],
imports: [RouterTestingModule],
diff --git a/projects/v3/src/app/pages/auth/auth-logout/auth-logout.component.ts b/projects/v3/src/app/pages/auth/auth-logout/auth-logout.component.ts
index 311e1f9f0..a5218f2b2 100644
--- a/projects/v3/src/app/pages/auth/auth-logout/auth-logout.component.ts
+++ b/projects/v3/src/app/pages/auth/auth-logout/auth-logout.component.ts
@@ -3,7 +3,6 @@ import { ActivatedRoute } from '@angular/router';
import { AuthService } from '@v3/services/auth.service';
@Component({
- standalone: false,
selector: 'app-auth-logout',
template: '',
})
diff --git a/projects/v3/src/app/pages/auth/auth-registration/auth-registration.component.spec.ts b/projects/v3/src/app/pages/auth/auth-registration/auth-registration.component.spec.ts
index c8352e25a..fbd0df7a5 100644
--- a/projects/v3/src/app/pages/auth/auth-registration/auth-registration.component.spec.ts
+++ b/projects/v3/src/app/pages/auth/auth-registration/auth-registration.component.spec.ts
@@ -1,5 +1,4 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { AuthRegistrationComponent } from './auth-registration.component';
@@ -60,7 +59,6 @@ describe('AuthRegistrationComponent', () => {
ReactiveFormsModule
],
declarations: [AuthRegistrationComponent],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{ provide: AuthService, useValue: authServiceSpy },
{ provide: BrowserStorageService, useValue: storageSpy },
@@ -89,76 +87,6 @@ describe('AuthRegistrationComponent', () => {
storageService.get.and.returnValue(false);
});
- it('should authenticate user and switch program on successful registration', async () => {
- // set up component state for registration
- component.unRegisteredDirectLink = true; // use direct link mode for simpler validation
- component.user = {
- id: 123,
- key: 'test-key',
- email: 'test@example.com',
- contact: null
- };
- component.password = 'TestPassword123!';
- component.confirmPassword = 'TestPassword123!';
- component.isAgreed = true;
-
- authService.saveRegistration.and.returnValue(of({
- data: { apikey: 'test-api-key' }
- }));
- authService.authenticate.and.returnValue(of({
- data: {
- auth: {
- apikey: 'test-api-key',
- experience: {
- id: 1,
- uuid: 'test-uuid',
- timelineId: 1,
- projectId: 1,
- name: 'Test Experience',
- description: 'Test Description',
- type: 'Test Type',
- leadImage: 'test-image.jpg',
- status: null,
- setupStep: null,
- color: '#000000',
- secondaryColor: '#FFFFFF',
- role: 'participant',
- isLast: false,
- locale: 'en-US',
- supportName: 'Support',
- supportEmail: 'support@example.com',
- cardUrl: 'card-url',
- bannerUrl: 'banner-url',
- logoUrl: 'logo-url',
- iconUrl: 'icon-url',
- reviewRating: false,
- truncateDescription: false,
- team: {
- id: 1
- },
- featureToggle: {
- pulseCheckIndicator: false,
- showProjectHub: false,
- }
- }
- }
- }
- }));
- storageService.set.and.stub();
- storageService.remove.and.stub();
- experienceService.switchProgram.and.returnValue(Promise.resolve());
-
- component.register();
-
- await fixture.whenStable();
-
- expect(authService.saveRegistration).toHaveBeenCalledWith({
- user_id: 123,
- key: 'test-key',
- password: jasmine.any(String), // password is auto-generated or set via confirmPassword
- });
- });
-
describe('unRegisteredDirectLink === true scenarios', () => {
beforeEach(() => {
component.unRegisteredDirectLink = true;
@@ -408,7 +336,7 @@ describe('AuthRegistrationComponent', () => {
expect(notificationsService.popUp).toHaveBeenCalledWith(
'shortMessage',
{ message: jasmine.stringContaining('Registration not complete') },
- false as any
+ false
);
});
@@ -496,7 +424,7 @@ describe('AuthRegistrationComponent', () => {
expect(notificationsService.popUp).toHaveBeenCalledWith(
'shortMessage',
{ message: jasmine.stringContaining('Registration not complete') },
- false as any
+ false
);
});
diff --git a/projects/v3/src/app/pages/auth/auth-registration/auth-registration.component.ts b/projects/v3/src/app/pages/auth/auth-registration/auth-registration.component.ts
index f6c0cc666..11cb4e4eb 100644
--- a/projects/v3/src/app/pages/auth/auth-registration/auth-registration.component.ts
+++ b/projects/v3/src/app/pages/auth/auth-registration/auth-registration.component.ts
@@ -19,7 +19,6 @@ import { environment } from '@v3/environments/environment';
import { HttpErrorResponse } from '@angular/common/http';
@Component({
- standalone: false,
selector: 'app-auth-registration',
templateUrl: './auth-registration.component.html',
styleUrls: ['./auth-registration.component.scss']
diff --git a/projects/v3/src/app/pages/auth/auth-reset-password/auth-reset-password.component.spec.ts b/projects/v3/src/app/pages/auth/auth-reset-password/auth-reset-password.component.spec.ts
index 67691ae4e..600c38e8e 100644
--- a/projects/v3/src/app/pages/auth/auth-reset-password/auth-reset-password.component.spec.ts
+++ b/projects/v3/src/app/pages/auth/auth-reset-password/auth-reset-password.component.spec.ts
@@ -1,6 +1,6 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { APP_BASE_HREF, Location, LocationStrategy, PathLocationStrategy } from '@angular/common';
-import { waitForAsync, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { AuthResetPasswordComponent } from './auth-reset-password.component';
import { AuthService } from '@v3/services/auth.service';
import { Observable, of, pipe, throwError } from 'rxjs';
diff --git a/projects/v3/src/app/pages/auth/auth-reset-password/auth-reset-password.component.ts b/projects/v3/src/app/pages/auth/auth-reset-password/auth-reset-password.component.ts
index 37d02bc84..34c3dbb98 100644
--- a/projects/v3/src/app/pages/auth/auth-reset-password/auth-reset-password.component.ts
+++ b/projects/v3/src/app/pages/auth/auth-reset-password/auth-reset-password.component.ts
@@ -6,7 +6,6 @@ import { AuthService } from '@v3/services/auth.service';
import { UtilsService } from '@v3/services/utils.service';
@Component({
- standalone: false,
selector: 'app-auth-reset-password',
templateUrl: './auth-reset-password.component.html',
styleUrls: ['./auth-reset-password.component.scss']
diff --git a/projects/v3/src/app/pages/auth/auth.component.ts b/projects/v3/src/app/pages/auth/auth.component.ts
index 662296dc1..540a38c80 100644
--- a/projects/v3/src/app/pages/auth/auth.component.ts
+++ b/projects/v3/src/app/pages/auth/auth.component.ts
@@ -1,7 +1,6 @@
import { Component } from '@angular/core';
@Component({
- standalone: false,
selector: 'app-auth',
template: ''
})
diff --git a/projects/v3/src/app/pages/auth/terms-conditions-preview/terms-conditions-preview.component.spec.ts b/projects/v3/src/app/pages/auth/terms-conditions-preview/terms-conditions-preview.component.spec.ts
index 2fc57131a..801ece7aa 100644
--- a/projects/v3/src/app/pages/auth/terms-conditions-preview/terms-conditions-preview.component.spec.ts
+++ b/projects/v3/src/app/pages/auth/terms-conditions-preview/terms-conditions-preview.component.spec.ts
@@ -1,4 +1,4 @@
-import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ModalController } from '@ionic/angular';
import { TermsConditionsPreviewComponent } from './terms-conditions-preview.component';
@@ -8,7 +8,7 @@ describe('TermsConditionsPreviewComponent', () => {
let fixture: ComponentFixture;
let ModalControllerSpy: jasmine.SpyObj;
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ TermsConditionsPreviewComponent ],
providers: [
diff --git a/projects/v3/src/app/pages/auth/terms-conditions-preview/terms-conditions-preview.component.ts b/projects/v3/src/app/pages/auth/terms-conditions-preview/terms-conditions-preview.component.ts
index df1e45472..6c6122695 100644
--- a/projects/v3/src/app/pages/auth/terms-conditions-preview/terms-conditions-preview.component.ts
+++ b/projects/v3/src/app/pages/auth/terms-conditions-preview/terms-conditions-preview.component.ts
@@ -3,7 +3,6 @@ import { ModalController } from '@ionic/angular';
import { DomSanitizer } from '@angular/platform-browser';
@Component({
- standalone: false,
selector: 'app-terms-conditions-preview',
templateUrl: './terms-conditions-preview.component.html',
styleUrls: ['./terms-conditions-preview.component.scss']
diff --git a/projects/v3/src/app/pages/chat/attachment-popover/attachment-popover.component.spec.ts b/projects/v3/src/app/pages/chat/attachment-popover/attachment-popover.component.spec.ts
index f514b6eed..337a567d6 100644
--- a/projects/v3/src/app/pages/chat/attachment-popover/attachment-popover.component.spec.ts
+++ b/projects/v3/src/app/pages/chat/attachment-popover/attachment-popover.component.spec.ts
@@ -1,39 +1,16 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
-import { PopoverController } from '@ionic/angular';
-import { of } from 'rxjs';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { AttachmentPopoverComponent } from './attachment-popover.component';
-import { FilestackService } from '@v3/services/filestack.service';
-import { NotificationsService } from '@v3/services/notifications.service';
-import { ModalService } from '@v3/services/modal.service';
describe('AttachmentPopoverComponent', () => {
let component: AttachmentPopoverComponent;
let fixture: ComponentFixture;
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ AttachmentPopoverComponent ],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
- providers: [
- {
- provide: PopoverController,
- useValue: jasmine.createSpyObj('PopoverController', ['dismiss', 'create'])
- },
- {
- provide: FilestackService,
- useValue: jasmine.createSpyObj('FilestackService', ['getFileTypes', 'getS3Config', 'open'])
- },
- {
- provide: NotificationsService,
- useValue: jasmine.createSpyObj('NotificationsService', ['alert', 'presentToast'])
- },
- {
- provide: ModalService,
- useValue: jasmine.createSpyObj('ModalService', ['openUppyModal'])
- }
- ]
})
.compileComponents();
}));
diff --git a/projects/v3/src/app/pages/chat/attachment-popover/attachment-popover.component.ts b/projects/v3/src/app/pages/chat/attachment-popover/attachment-popover.component.ts
index 51448b729..fdf23fd3b 100644
--- a/projects/v3/src/app/pages/chat/attachment-popover/attachment-popover.component.ts
+++ b/projects/v3/src/app/pages/chat/attachment-popover/attachment-popover.component.ts
@@ -6,7 +6,6 @@ import { FilestackService } from '@v3/services/filestack.service';
import { NotificationsService } from '../../../services/notifications.service';
@Component({
- standalone: false,
selector: 'app-attachment-popover',
templateUrl: './attachment-popover.component.html',
styleUrls: ['./attachment-popover.component.scss'],
diff --git a/projects/v3/src/app/pages/chat/chat-info/chat-info.component.html b/projects/v3/src/app/pages/chat/chat-info/chat-info.component.html
index 0714155db..daa110af1 100644
--- a/projects/v3/src/app/pages/chat/chat-info/chat-info.component.html
+++ b/projects/v3/src/app/pages/chat/chat-info/chat-info.component.html
@@ -21,7 +21,7 @@
-
+
Who is in this chat
diff --git a/projects/v3/src/app/pages/chat/chat-info/chat-info.component.spec.ts b/projects/v3/src/app/pages/chat/chat-info/chat-info.component.spec.ts
index ecc17ada1..a949455ed 100644
--- a/projects/v3/src/app/pages/chat/chat-info/chat-info.component.spec.ts
+++ b/projects/v3/src/app/pages/chat/chat-info/chat-info.component.spec.ts
@@ -1,4 +1,4 @@
-import { waitForAsync, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA, Directive } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
@@ -25,7 +25,7 @@ describe('ChatInfoComponent', () => {
const modalCtrlSpy = jasmine.createSpyObj('ModalController', ['dismiss', 'create']);
modalCtrlSpy.create.and.returnValue(modalSpy);
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ChatInfoComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
diff --git a/projects/v3/src/app/pages/chat/chat-info/chat-info.component.ts b/projects/v3/src/app/pages/chat/chat-info/chat-info.component.ts
index 86ee5d0b7..c6bd9b5fb 100644
--- a/projects/v3/src/app/pages/chat/chat-info/chat-info.component.ts
+++ b/projects/v3/src/app/pages/chat/chat-info/chat-info.component.ts
@@ -6,7 +6,6 @@ import { ChatService, ChatChannel, ChannelMembers } from '@v3/services/chat.serv
import { ModalController } from '@ionic/angular';
@Component({
- standalone: false,
selector: 'app-chat-info',
templateUrl: 'chat-info.component.html',
styleUrls: ['chat-info.component.scss']
diff --git a/projects/v3/src/app/pages/chat/chat-list/chat-list.component.spec.ts b/projects/v3/src/app/pages/chat/chat-list/chat-list.component.spec.ts
index 0c2f7028d..2fee824cc 100644
--- a/projects/v3/src/app/pages/chat/chat-list/chat-list.component.spec.ts
+++ b/projects/v3/src/app/pages/chat/chat-list/chat-list.component.spec.ts
@@ -1,5 +1,5 @@
import { CUSTOM_ELEMENTS_SCHEMA, EventEmitter } from '@angular/core';
-import { waitForAsync, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { ChatListComponent } from './chat-list.component';
import { ChatChannel, ChatService } from '@v3/services/chat.service';
@@ -38,7 +38,7 @@ describe('ChatListComponent', () => {
let routeStub: Partial
;
let fastFeedbackSpy: jasmine.SpyObj;
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [RouterTestingModule],
declarations: [ChatListComponent],
@@ -124,8 +124,6 @@ describe('ChatListComponent', () => {
expect(component.chatList).toBeDefined();
expect(chatSeviceSpy.getChatList.calls.count()).toBe(1);
expect(chatSeviceSpy.getPusherChannels.calls.count()).toBe(1);
- expect(pusherSpy.subscribeChannel).toHaveBeenCalledWith('chat', 'sdb746-93r7dc-5f44eb4f');
- expect(pusherSpy.subscribeChannel).toHaveBeenCalledWith('chat', 'kb5gt-9nfbj-5f45eb4g');
});
});
@@ -155,50 +153,6 @@ describe('ChatListComponent', () => {
);
expect(component.navigate.emit).toHaveBeenCalled();
});
-
- it('should ignore unsupported keyboard input', () => {
- spyOn(component.navigate, 'emit');
-
- component.goToChatRoom(mockChats.data.channels[0] as any, {
- code: 'KeyA',
- preventDefault: jasmine.createSpy('preventDefault'),
- } as any);
-
- expect(component.navigate.emit).not.toHaveBeenCalled();
- expect(storageSpy.setCurrentChatChannel).not.toHaveBeenCalled();
- });
-
- it('should navigate to room on mobile and set current channel', () => {
- component.isMobile = true;
-
- component.goToChatRoom(mockChats.data.channels[0] as any);
-
- expect(storageSpy.setCurrentChatChannel).toHaveBeenCalledWith(mockChats.data.channels[0] as any);
- expect(routerSpy.navigate).toHaveBeenCalledWith(['v3', 'messages', 'chat-room']);
- });
-
- it('should prevent default for keyboard enter action', () => {
- const preventDefault = jasmine.createSpy('preventDefault');
- spyOn(component.navigate, 'emit');
-
- component.goToChatRoom(mockChats.data.channels[0] as any, {
- code: 'Enter',
- preventDefault,
- } as any);
-
- expect(preventDefault).toHaveBeenCalled();
- expect(component.navigate.emit).toHaveBeenCalled();
- });
- });
-
- describe('when testing getChatDate()', () => {
- it('should format date through utils service', () => {
- const testDate = '2026-02-23T00:00:00.000Z';
-
- component.getChatDate(testDate);
-
- expect(utils.timeFormatter).toHaveBeenCalledWith(testDate);
- });
});
});
diff --git a/projects/v3/src/app/pages/chat/chat-list/chat-list.component.ts b/projects/v3/src/app/pages/chat/chat-list/chat-list.component.ts
index 853dfe35c..48a66dc7a 100644
--- a/projects/v3/src/app/pages/chat/chat-list/chat-list.component.ts
+++ b/projects/v3/src/app/pages/chat/chat-list/chat-list.component.ts
@@ -9,7 +9,6 @@ import { PusherService } from '@v3/services/pusher.service';
* this is an app chat list component
*/
@Component({
- standalone: false,
selector: 'app-chat-list',
templateUrl: 'chat-list.component.html',
styleUrls: ['chat-list.component.scss']
diff --git a/projects/v3/src/app/pages/chat/chat-preview/chat-preview.component.spec.ts b/projects/v3/src/app/pages/chat/chat-preview/chat-preview.component.spec.ts
index f621e7b64..403a1a60b 100644
--- a/projects/v3/src/app/pages/chat/chat-preview/chat-preview.component.spec.ts
+++ b/projects/v3/src/app/pages/chat/chat-preview/chat-preview.component.spec.ts
@@ -1,25 +1,28 @@
-import { TestBed, waitForAsync, ComponentFixture } from '@angular/core/testing';
+import { By } from '@angular/platform-browser';
+import { DebugElement } from '@angular/core';
+import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { ChatPreviewComponent } from './chat-preview.component';
import { IonicModule, ModalController } from '@ionic/angular';
import { DomSanitizer } from '@angular/platform-browser';
import { CommonModule } from '@angular/common';
-import { HttpClientTestingModule } from '@angular/common/http/testing';
+import {
+ HttpTestingController,
+ HttpClientTestingModule
+} from '@angular/common/http/testing';
describe('ChatPreviewComponent', () => {
const TEST_URL = 'https://www.practera.com';
let component: ChatPreviewComponent;
let fixture: ComponentFixture;
- let modalSpy: jasmine.SpyObj;
+ let modalSpy: ModalController;
+ let domSanitizerSpy: DomSanitizer;
- beforeEach(waitForAsync(() => {
+ beforeEach(async () => {
TestBed.configureTestingModule({
imports: [ IonicModule, CommonModule, HttpClientTestingModule ],
declarations: [ ChatPreviewComponent ],
providers: [
- {
- provide: ModalController,
- useValue: jasmine.createSpyObj('ModalController', ['dismiss']),
- },
+ ModalController,
{
provide: DomSanitizer,
useValue: {
@@ -33,17 +36,21 @@ describe('ChatPreviewComponent', () => {
fixture = TestBed.createComponent(ChatPreviewComponent);
component = fixture.componentInstance;
- modalSpy = TestBed.inject(ModalController) as jasmine.SpyObj;
- }));
+ modalSpy = TestBed.inject(ModalController);
+ domSanitizerSpy = TestBed.inject(DomSanitizer);
+
+ // fixture.detectChanges();
+ });
it('should created', () => {
expect(component).toBeTruthy();
});
- it('should hold file input data', () => {
- component.file = { url: TEST_URL };
+ it('should has toolbar to control modal content', () => {
+ spyOn(window, 'open');
+ spyOn(modalSpy, 'dismiss');
- expect(component.file.url).toBe(TEST_URL);
+ component.file = { url: TEST_URL };
});
describe('download()', () => {
@@ -56,65 +63,14 @@ describe('ChatPreviewComponent', () => {
component.download();
expect(window.open).toHaveBeenCalledWith(TEST_URL, '_system');
});
-
- it('should open and prevent default for enter/space keyboard actions', () => {
- spyOn(window, 'open');
- const keyboardEvent = {
- code: 'Enter',
- preventDefault: jasmine.createSpy('preventDefault'),
- } as any;
- component.file = { url: TEST_URL };
-
- component.download(keyboardEvent);
-
- expect(keyboardEvent.preventDefault).toHaveBeenCalled();
- expect(window.open).toHaveBeenCalledWith(TEST_URL, '_system');
- });
-
- it('should ignore unsupported keyboard key in download', () => {
- spyOn(window, 'open');
- const keyboardEvent = {
- code: 'KeyA',
- preventDefault: jasmine.createSpy('preventDefault'),
- } as any;
-
- component.download(keyboardEvent);
-
- expect(keyboardEvent.preventDefault).not.toHaveBeenCalled();
- expect(window.open).not.toHaveBeenCalled();
- });
});
describe('close()', () => {
it('should close opened modal', () => {
+ spyOn(modalSpy, 'dismiss');
component.close();
-
expect(modalSpy.dismiss).toHaveBeenCalled();
});
-
- it('should close and prevent default for keyboard enter/space', () => {
- const keyboardEvent = {
- code: 'Space',
- preventDefault: jasmine.createSpy('preventDefault'),
- } as any;
-
- component.close(keyboardEvent);
-
- expect(keyboardEvent.preventDefault).toHaveBeenCalled();
- expect(modalSpy.dismiss).toHaveBeenCalled();
- });
-
- it('should ignore unsupported keyboard key in close', () => {
- const keyboardEvent = {
- code: 'Escape',
- preventDefault: jasmine.createSpy('preventDefault'),
- } as any;
-
- component.close(keyboardEvent);
-
- expect(keyboardEvent.preventDefault).not.toHaveBeenCalled();
- expect(modalSpy.dismiss).not.toHaveBeenCalled();
- });
});
describe('isBrowserSupportedVideo', () => {
diff --git a/projects/v3/src/app/pages/chat/chat-preview/chat-preview.component.ts b/projects/v3/src/app/pages/chat/chat-preview/chat-preview.component.ts
index 9609a2bd1..b87790a1c 100644
--- a/projects/v3/src/app/pages/chat/chat-preview/chat-preview.component.ts
+++ b/projects/v3/src/app/pages/chat/chat-preview/chat-preview.component.ts
@@ -3,7 +3,6 @@ import { ModalController } from '@ionic/angular';
import { DomSanitizer } from '@angular/platform-browser';
@Component({
- standalone: false,
selector: 'app-chat-preview',
templateUrl: 'chat-preview.component.html',
styleUrls: ['chat-preview.component.scss']
@@ -41,7 +40,7 @@ export class ChatPreviewComponent {
*/
isBrowserSupportedVideo(): boolean {
const supportedTypes = ['video/mp4', 'video/webm', 'video/ogg'];
- return !!(this.file?.type && supportedTypes.includes(this.file.type));
+ return this.file?.type && supportedTypes.includes(this.file.type);
}
/**
diff --git a/projects/v3/src/app/pages/chat/chat-room/chat-room.component.spec.ts b/projects/v3/src/app/pages/chat/chat-room/chat-room.component.spec.ts
index 78c497064..884b2c963 100644
--- a/projects/v3/src/app/pages/chat/chat-room/chat-room.component.spec.ts
+++ b/projects/v3/src/app/pages/chat/chat-room/chat-room.component.spec.ts
@@ -1,19 +1,17 @@
import { CUSTOM_ELEMENTS_SCHEMA, ElementRef } from '@angular/core';
-import { waitForAsync, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
-import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ChatRoomComponent } from './chat-room.component';
-import { ChannelMembers, ChatService, Message } from '@v3/services/chat.service';
-import { of, Subject } from 'rxjs';
+import { ChannelMembers, ChatService } from '@v3/services/chat.service';
+import { NotificationsService } from '@v3/services/notifications.service';
+import { of } from 'rxjs';
import { BrowserStorageService } from '@v3/services/storage.service';
import { UtilsService } from '@v3/services/utils.service';
import { PusherService } from '@v3/services/pusher.service';
import { FilestackService } from '@v3/services/filestack.service';
-import { NotificationsService } from '@v3/services/notifications.service';
-import { ModalService } from '@v3/services/modal.service';
import { MockRouter } from '@testingv3/mocked.service';
import { Router, ActivatedRoute, convertToParamMap } from '@angular/router';
-import { IonContent, ModalController, PopoverController } from '@ionic/angular';
+import { IonContent, ModalController } from '@ionic/angular';
import { TestUtils } from '@testingv3/utils';
import { mockMembers } from '@testingv3/fixtures';
@@ -35,11 +33,12 @@ describe('ChatRoomComponent', () => {
let MockIoncontent: IonContent;
const modalSpy = jasmine.createSpyObj('Modal', ['present', 'onDidDismiss']);
modalSpy.onDidDismiss.and.returnValue(new Promise(() => { }));
- let modalCtrlSpy: any;
+ const modalCtrlSpy = jasmine.createSpyObj('ModalController', ['dismiss', 'create']);
+ modalCtrlSpy.create.and.returnValue(modalSpy);
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
- imports: [RouterTestingModule, HttpClientTestingModule],
+ imports: [RouterTestingModule],
declarations: [ChatRoomComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
@@ -53,16 +52,13 @@ describe('ChatRoomComponent', () => {
},
{
provide: IonContent,
- useValue: {
- scrollToBottom: jasmine.createSpy('scrollToBottom'),
- ionScrollEnd: new Subject(),
- }
+ useValue: jasmine.createSpyObj('IonContent', ['scrollToBottom'])
},
{
provide: ChatService,
useValue: jasmine.createSpyObj('ChatService', {
- 'getChatMembers': of({ data: { channelMembers: [] } }),
- 'getMessageList': of({ messages: [], cursor: null }),
+ 'getChatMembers': of(true),
+ 'getMessageList': of(true),
'postNewMessage': of(true),
'markMessagesAsSeen': of(true),
'postAttachmentMessage': of(true),
@@ -82,18 +78,6 @@ describe('ChatRoomComponent', () => {
provide: FilestackService,
useValue: jasmine.createSpyObj('FilestackService', ['getFileTypes', 'getS3Config', 'open', 'previewFile'])
},
- {
- provide: NotificationsService,
- useValue: jasmine.createSpyObj('NotificationsService', ['alert', 'presentToast', 'loading', 'dismiss'])
- },
- {
- provide: ModalService,
- useValue: jasmine.createSpyObj('ModalService', ['addModal', 'openUppyModal'])
- },
- {
- provide: PopoverController,
- useValue: jasmine.createSpyObj('PopoverController', ['create', 'dismiss'])
- },
{
provide: Router,
useClass: MockRouter,
@@ -125,8 +109,6 @@ describe('ChatRoomComponent', () => {
}));
beforeEach(() => {
- // override ngAfterViewInit before creating component to prevent ionScrollEnd error
- spyOn(ChatRoomComponent.prototype, 'ngAfterViewInit').and.callFake(() => {});
fixture = TestBed.createComponent(ChatRoomComponent);
component = fixture.componentInstance;
routeStub = TestBed.inject(ActivatedRoute);
@@ -137,11 +119,8 @@ describe('ChatRoomComponent', () => {
pusherSpy = TestBed.inject(PusherService) as jasmine.SpyObj;
filestackSpy = TestBed.inject(FilestackService) as jasmine.SpyObj;
MockIoncontent = TestBed.inject(IonContent) as jasmine.SpyObj;
- modalCtrlSpy = TestBed.inject(ModalController);
- modalCtrlSpy.create.and.returnValue(Promise.resolve(modalSpy));
- // assign content for tests that need it
- component.content = MockIoncontent;
fixture.detectChanges();
+ component.content = MockIoncontent;
});
const mockChatMessages = {
@@ -155,13 +134,6 @@ describe('ChatRoomComponent', () => {
file: null,
created: '2020-08-28 05:45:52',
sentAt: '2020-08-28 05:45:52',
- sender: {
- id: 1,
- uuid: '8bee29d0-bf45',
- name: 'Test User 1',
- email: 'test1@example.com'
- },
- scheduled: null
},
{
uuid: '0403b4d9',
@@ -171,13 +143,6 @@ describe('ChatRoomComponent', () => {
file: null,
created: '2020-08-28 05:45:50',
sentAt: '2020-08-28 05:45:50',
- sender: {
- id: 1,
- uuid: '8bee29d0-bf45',
- name: 'Test User 1',
- email: 'test1@example.com'
- },
- scheduled: null
}
]
};
@@ -286,27 +251,22 @@ describe('ChatRoomComponent', () => {
senderUuid: '8bee29d0-bf45',
senderName: 'user01',
senderRole: 'participants',
- senderAvatar: 'http://www.example.com/image.png',
- sender: undefined,
- scheduled: undefined,
- sentAt: undefined
+ senderAvatar: 'http://www.example.com/image.png'
};
const receivedMessage = component.getMessageFromEvent(pusherData);
tick();
expect(receivedMessage).toEqual({
uuid: pusherData.uuid,
- sender: undefined,
senderName: pusherData.senderName,
senderRole: pusherData.senderRole,
senderAvatar: pusherData.senderAvatar,
- isSender: false,
+ isSender: pusherData.isSender,
message: pusherData.message,
created: pusherData.created,
file: pusherData.file,
channelUuid: pusherData.channelUuid,
senderUuid: '8bee29d0-bf45',
- sentAt: undefined,
- scheduled: undefined
+ sentAt: undefined
});
}));
});
@@ -324,18 +284,7 @@ describe('ChatRoomComponent', () => {
senderUuid: '8bee29d0-bf45',
senderName: 'user01',
senderRole: 'participants',
- senderAvatar: 'http://www.example.com/image.png',
- sender: {
- id: 1,
- uuid: '8bee29d0-bf45',
- name: 'user01',
- role: 'participants',
- avatar: 'http://www.example.com/image.png',
- email: 'test@example.com'
- },
- channelUuid: 'c43vwsvc',
- sentAt: undefined,
- scheduled: undefined,
+ senderAvatar: 'http://www.example.com/image.png'
};
chatServiceSpy.postNewMessage.and.returnValue(of(saveMessageRes));
chatServiceSpy.getMessageList.and.returnValue(of(mockChatMessages));
@@ -347,18 +296,16 @@ describe('ChatRoomComponent', () => {
component.sendMessage();
expect(component.messageList[2]).toEqual({
uuid: saveMessageRes.uuid,
- sender: saveMessageRes.sender,
+ senderName: saveMessageRes.senderName,
+ senderRole: saveMessageRes.senderRole,
+ senderAvatar: saveMessageRes.senderAvatar,
isSender: saveMessageRes.isSender,
message: saveMessageRes.message,
created: saveMessageRes.created,
file: saveMessageRes.file,
- scheduled: undefined,
senderUuid: saveMessageRes.senderUuid,
- senderName: saveMessageRes.senderName,
- senderRole: saveMessageRes.senderRole,
- senderAvatar: saveMessageRes.senderAvatar,
- sentAt: undefined,
- preview: undefined
+ sentAt: undefined
+ // sentAt: saveMessageRes.sentAt,
});
});
});
@@ -435,29 +382,25 @@ describe('ChatRoomComponent', () => {
});
describe('when testing preview()', () => {
- beforeEach(() => {
- modalCtrlSpy.create.calls.reset();
- });
-
- it(`should call modal controller when previewing file without mimetype`, async () => {
+ it(`should call file stack previewFile if file didn't have mimetype`, () => {
const file = {
filename: 'unnamed.jpg',
mimetype: null,
url: 'https://cdn.filestackcontent.com/X8Cj0Y4QS2AmDUZX6LSq',
status: 'Stored'
};
- await component.preview(file);
- expect(modalCtrlSpy.create.calls.count()).toBe(1);
+ filestackSpy.previewFile.and.returnValue(Promise.resolve({}));
+ component.preview(file);
+ expect(filestackSpy.previewFile.calls.count()).toBe(1);
});
-
- it(`should call modal controller when previewing file with mimetype`, async () => {
+ it(`should call modal controller if file have mimetype`, () => {
const file = {
filename: 'unnamed.jpg',
mimetype: 'image/jpeg',
url: 'https://cdn.filestackcontent.com/X8Cj0Y4QS2AmDUZX6LSq',
status: 'Stored'
};
- await component.preview(file);
+ component.preview(file);
expect(modalCtrlSpy.create.calls.count()).toBe(1);
});
});
@@ -549,11 +492,10 @@ describe('ChatRoomComponent', () => {
});
describe('when testing openChatInfo()', () => {
- it(`should call modal controller if app in mobile view`, async () => {
- modalCtrlSpy.create.calls.reset();
- component.isMobile = true;
- await component.openChatInfo();
- expect(modalCtrlSpy.create.calls.count()).toBe(1);
+ it(`should call modal controller if app in mobile view`, () => {
+ utils.isMobile = jasmine.createSpy('utils.isMobile').and.returnValue(true);
+ component.openChatInfo();
+ expect(modalCtrlSpy.create.calls.count()).toBe(2);
});
});
@@ -609,7 +551,7 @@ describe('ChatRoomComponent', () => {
describe('when testing isLastMessage()', () => {
it(`should assign correct value for 'noAvatar' variable`, () => {
- component.messageList = mockChatMessages.messages as Message[];
+ component.messageList = mockChatMessages.messages;
component.isLastMessage(mockChatMessages.messages[1]);
expect(component.messageList[1].noAvatar).toEqual(false);
});
@@ -633,7 +575,6 @@ describe('ChatRoomComponent', () => {
it('should return false for a message with only empty html tags', () => {
const message: any = { uuid: '1', message: '' };
- (utils as any).isQuillContentEmpty.and.returnValue(true);
expect(component.hasEditableText(message)).toBeFalse();
});
});
@@ -688,6 +629,7 @@ describe('ChatRoomComponent', () => {
});
it('should call notificationsService.alert for confirmation', () => {
+ spyOn(notificationsService, 'alert');
component.deleteMessage('msg-1');
expect(notificationsService.alert).toHaveBeenCalled();
});
diff --git a/projects/v3/src/app/pages/chat/chat-room/chat-room.component.ts b/projects/v3/src/app/pages/chat/chat-room/chat-room.component.ts
index 3d4bf1acd..a779a5a21 100644
--- a/projects/v3/src/app/pages/chat/chat-room/chat-room.component.ts
+++ b/projects/v3/src/app/pages/chat/chat-room/chat-room.component.ts
@@ -34,7 +34,6 @@ interface selectedAttachment {
}
@Component({
- standalone: false,
selector: "app-chat-room",
templateUrl: "./chat-room.component.html",
styleUrls: ["./chat-room.component.scss"],
diff --git a/projects/v3/src/app/pages/chat/chat-view/chat-view.component.spec.ts b/projects/v3/src/app/pages/chat/chat-view/chat-view.component.spec.ts
index 9fce1822b..4a8be47a1 100644
--- a/projects/v3/src/app/pages/chat/chat-view/chat-view.component.spec.ts
+++ b/projects/v3/src/app/pages/chat/chat-view/chat-view.component.spec.ts
@@ -1,15 +1,12 @@
-import { ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed, fakeAsync, tick, waitForAsync } from '@angular/core/testing';
import { CUSTOM_ELEMENTS_SCHEMA, Directive } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
-import { HttpClientTestingModule } from '@angular/common/http/testing';
-import { of } from 'rxjs';
import { ChatViewComponent } from './chat-view.component';
import { UtilsService } from '@v3/services/utils.service';
import { TestUtils } from '@testingv3/utils';
import { ActivatedRouteStub } from '@testingv3/activated-route-stub';
import { MockRouter } from '@testingv3/mocked.service';
-import { AuthService } from '@v3/app/services/auth.service';
describe('ChatViewComponent', () => {
let component: ChatViewComponent;
@@ -20,7 +17,6 @@ describe('ChatViewComponent', () => {
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ChatViewComponent],
- imports: [HttpClientTestingModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{
@@ -34,15 +30,7 @@ describe('ChatViewComponent', () => {
{
provide: ActivatedRoute,
useValue: new ActivatedRouteStub({})
- },
- {
- provide: AuthService,
- useValue: jasmine.createSpyObj('AuthService', {
- 'isAuthenticated': true,
- 'logout': of(true),
- 'authenticate': of({})
- }),
- },
+ }
]
})
.compileComponents();
diff --git a/projects/v3/src/app/pages/chat/chat-view/chat-view.component.ts b/projects/v3/src/app/pages/chat/chat-view/chat-view.component.ts
index 205cf675c..47c902e3d 100644
--- a/projects/v3/src/app/pages/chat/chat-view/chat-view.component.ts
+++ b/projects/v3/src/app/pages/chat/chat-view/chat-view.component.ts
@@ -6,7 +6,6 @@ import { DOCUMENT } from '@angular/common';
import { AuthService } from '@v3/app/services/auth.service';
@Component({
- standalone: false,
selector: 'app-chat-view',
templateUrl: './chat-view.component.html',
styleUrls: ['./chat-view.component.scss']
diff --git a/projects/v3/src/app/pages/chat/chat.page.ts b/projects/v3/src/app/pages/chat/chat.page.ts
index 919aa565e..80f0a2860 100644
--- a/projects/v3/src/app/pages/chat/chat.page.ts
+++ b/projects/v3/src/app/pages/chat/chat.page.ts
@@ -1,7 +1,6 @@
import { Component } from '@angular/core';
@Component({
- standalone: false,
template: '',
})
export class ChatPage {}
diff --git a/projects/v3/src/app/pages/chat/edit-message-popup/edit-message-popup.component.ts b/projects/v3/src/app/pages/chat/edit-message-popup/edit-message-popup.component.ts
index ca7b834a3..7560a0f81 100644
--- a/projects/v3/src/app/pages/chat/edit-message-popup/edit-message-popup.component.ts
+++ b/projects/v3/src/app/pages/chat/edit-message-popup/edit-message-popup.component.ts
@@ -8,7 +8,6 @@ import { QuillModules } from 'ngx-quill';
* displays a quill editor pre-populated with the message text.
*/
@Component({
- standalone: false,
selector: 'app-edit-message-popup',
templateUrl: './edit-message-popup.component.html',
styleUrls: ['./edit-message-popup.component.scss'],
diff --git a/projects/v3/src/app/pages/devtool/devtool.page.ts b/projects/v3/src/app/pages/devtool/devtool.page.ts
index e6fe857a2..face09a45 100644
--- a/projects/v3/src/app/pages/devtool/devtool.page.ts
+++ b/projects/v3/src/app/pages/devtool/devtool.page.ts
@@ -12,7 +12,6 @@ import { environment } from '../../../environments/environment';
import { FfmpegService } from '../../services/ffmpeg.service';
@Component({
- standalone: false,
selector: 'app-devtool',
templateUrl: './devtool.page.html',
styleUrls: ['./devtool.page.scss'],
diff --git a/projects/v3/src/app/pages/due-dates/due-dates.component.spec.ts b/projects/v3/src/app/pages/due-dates/due-dates.component.spec.ts
index c0dae7b6e..12decf234 100644
--- a/projects/v3/src/app/pages/due-dates/due-dates.component.spec.ts
+++ b/projects/v3/src/app/pages/due-dates/due-dates.component.spec.ts
@@ -1,216 +1,24 @@
-import { ComponentFixture, TestBed, waitForAsync, fakeAsync, tick } from '@angular/core/testing';
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { IonicModule } from '@ionic/angular';
-import { Router } from '@angular/router';
-import { of, Subject, throwError } from 'rxjs';
import { DueDatesComponent } from './due-dates.component';
-import { DueDatesService } from './due-dates.service';
-import { NotificationsService } from '@v3/app/services/notifications.service';
-import { AssessmentService } from '@v3/app/services/assessment.service';
-import { UtilsService } from '@v3/services/utils.service';
-import { TestUtils } from '@testingv3/utils';
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
describe('DueDatesComponent', () => {
let component: DueDatesComponent;
let fixture: ComponentFixture;
- let dueDatesService: jasmine.SpyObj;
- let assessmentService: jasmine.SpyObj;
- let notificationsService: jasmine.SpyObj;
- let router: jasmine.SpyObj;
-
- const dueAssessments = [
- {
- id: 1,
- name: 'Assessment A',
- description: 'Description A',
- dueDate: '2026-03-01 10:30:00',
- contextId: 10,
- activityId: 20,
- },
- {
- id: 2,
- name: 'Assessment B',
- description: 'Description B',
- dueDate: null,
- contextId: 11,
- activityId: 21,
- },
- ] as any;
beforeEach(waitForAsync(() => {
- const dueStatusSubject = new Subject();
-
TestBed.configureTestingModule({
declarations: [ DueDatesComponent ],
- imports: [IonicModule.forRoot()],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
- providers: [
- { provide: UtilsService, useClass: TestUtils },
- {
- provide: DueDatesService,
- useValue: jasmine.createSpyObj('DueDatesService', ['createCalendarEvent', 'generateGoogleCalendarUrl']),
- },
- {
- provide: NotificationsService,
- useValue: jasmine.createSpyObj('NotificationsService', ['alert']),
- },
- {
- provide: AssessmentService,
- useValue: jasmine.createSpyObj('AssessmentService', ['dueStatusAssessments']),
- },
- {
- provide: Router,
- useValue: jasmine.createSpyObj('Router', ['navigate']),
- },
- ],
+ imports: [IonicModule.forRoot()]
}).compileComponents();
fixture = TestBed.createComponent(DueDatesComponent);
component = fixture.componentInstance;
- dueDatesService = TestBed.inject(DueDatesService) as jasmine.SpyObj;
- assessmentService = TestBed.inject(AssessmentService) as jasmine.SpyObj;
- notificationsService = TestBed.inject(NotificationsService) as jasmine.SpyObj;
- router = TestBed.inject(Router) as jasmine.SpyObj;
-
- (assessmentService.dueStatusAssessments as jasmine.Spy).and.returnValue(dueStatusSubject.asObservable());
- dueStatusSubject.next([]);
- dueStatusSubject.complete();
-
- dueDatesService.generateGoogleCalendarUrl.and.returnValue('https://calendar.google.com/test-url');
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
-
- it('should initialize filteredAssessments$ in ngOnInit', fakeAsync(() => {
- component.ngOnInit();
- component.assessments$.next(component.groupByDate([dueAssessments[0]]));
- component.searchText$.next({ target: { value: 'assessment a' } });
- let result: any;
-
- component.filteredAssessments$.subscribe(res => result = res);
- tick(250);
-
- expect(result.length).toBe(1);
- expect(result[0].assessments.length).toBe(1);
- }));
-
- it('should keep all groups when search query is whitespace', fakeAsync(() => {
- component.ngOnInit();
- const groups = component.groupByDate(dueAssessments);
- component.assessments$.next(groups);
- component.searchText$.next({ target: { value: ' ' } });
- let result: any;
-
- component.filteredAssessments$.subscribe(res => result = res);
- tick(250);
-
- expect(result).toEqual(groups);
- }));
-
- it('should group assessments by month and place no due date last', () => {
- const grouped = component.groupByDate(dueAssessments);
-
- expect(grouped.length).toBe(2);
- expect(grouped[grouped.length - 1].month).toBe('No due date');
- });
-
- it('should convert datetime string to tuple array', () => {
- expect(component.convertDateTimeString('2026-03-01 10:30:00')).toEqual([2026, 3, 1, 10, 30]);
- });
-
- it('should load grouped assessments in ionViewDidEnter when data exists', () => {
- const subject = new Subject();
- (assessmentService.dueStatusAssessments as jasmine.Spy).and.returnValue(subject.asObservable());
-
- component.ionViewDidEnter();
- subject.next([dueAssessments[0]]);
- subject.complete();
-
- expect(component.isLoading).toBeFalse();
- expect(component.assessments$.value.length).toBe(1);
- });
-
- it('should set empty assessments when due list is empty', () => {
- (assessmentService.dueStatusAssessments as jasmine.Spy).and.returnValue(of([]));
-
- component.ionViewDidEnter();
-
- expect(component.assessments$.value).toEqual([]);
- expect(component.isLoading).toBeFalse();
- });
-
- it('should handle dueStatusAssessments error in ionViewDidEnter', () => {
- (assessmentService.dueStatusAssessments as jasmine.Spy).and.returnValue(throwError(() => new Error('boom')) as any);
-
- component.ionViewDidEnter();
-
- expect(component.isLoading).toBeFalse();
- });
-
- it('should call createCalendarEvent in downloadiCal success path', () => {
- component.downloadiCal(dueAssessments[0]);
-
- expect(dueDatesService.createCalendarEvent).toHaveBeenCalled();
- });
-
- it('should alert on downloadiCal failure', () => {
- dueDatesService.createCalendarEvent.and.callFake(() => {
- throw new Error('ical error');
- });
-
- component.downloadiCal(dueAssessments[0]);
-
- expect(notificationsService.alert).toHaveBeenCalled();
- });
-
- it('should open google calendar URL in new tab', () => {
- spyOn(window, 'open').and.returnValue({} as Window);
-
- component.downloadGoogleCalendar(dueAssessments[0]);
-
- expect(dueDatesService.generateGoogleCalendarUrl).toHaveBeenCalled();
- expect(window.open).toHaveBeenCalledWith('https://calendar.google.com/test-url', '_blank');
- });
-
- it('should alert when google calendar popup is blocked', () => {
- spyOn(window, 'open').and.returnValue(null);
-
- component.downloadGoogleCalendar(dueAssessments[0]);
-
- expect(notificationsService.alert).toHaveBeenCalledWith({
- message: 'Please allow pop-ups for this website',
- });
- });
-
- it('should alert when google calendar URL generation throws', () => {
- dueDatesService.generateGoogleCalendarUrl.and.callFake(() => {
- throw new Error('url error');
- });
-
- component.downloadGoogleCalendar(dueAssessments[0]);
-
- expect(notificationsService.alert).toHaveBeenCalledWith({
- message: 'Failed to generate Google calendar URL',
- });
- });
-
- it('should navigate to activity desktop route in goTo', () => {
- component.goTo(dueAssessments[0]);
-
- expect(router.navigate).toHaveBeenCalledWith(['v3', 'activity-desktop', 10, 20, 1]);
- });
-
- it('should complete unsubscribe subject on destroy', () => {
- const nextSpy = spyOn(component.unsubscribe$, 'next');
- const completeSpy = spyOn(component.unsubscribe$, 'complete');
-
- component.ngOnDestroy();
-
- expect(nextSpy).toHaveBeenCalled();
- expect(completeSpy).toHaveBeenCalled();
- });
});
diff --git a/projects/v3/src/app/pages/due-dates/due-dates.component.ts b/projects/v3/src/app/pages/due-dates/due-dates.component.ts
index f49b24c8f..902271396 100644
--- a/projects/v3/src/app/pages/due-dates/due-dates.component.ts
+++ b/projects/v3/src/app/pages/due-dates/due-dates.component.ts
@@ -14,7 +14,6 @@ interface GroupedAssessments {
}
@Component({
- standalone: false,
selector: 'app-due-dates',
templateUrl: './due-dates.component.html',
styleUrls: ['./due-dates.component.scss'],
diff --git a/projects/v3/src/app/pages/due-dates/due-dates.service.spec.ts b/projects/v3/src/app/pages/due-dates/due-dates.service.spec.ts
index 90a5906cd..7974b34c9 100644
--- a/projects/v3/src/app/pages/due-dates/due-dates.service.spec.ts
+++ b/projects/v3/src/app/pages/due-dates/due-dates.service.spec.ts
@@ -13,65 +13,4 @@ describe('DueDatesService', () => {
it('should be created', () => {
expect(service).toBeTruthy();
});
-
- it('should format date to compact google/ics style', () => {
- const date = new Date('2026-03-01T10:30:45.000Z');
- const formatted = service.formatDate(date);
-
- expect(formatted).toBe('20260301T103045Z');
- });
-
- it('should build google calendar URL with provided end date and location/reminder', () => {
- const url = service.generateGoogleCalendarUrl({
- start: new Date('2026-03-01T10:00:00'),
- end: new Date('2026-03-01T11:00:00'),
- title: 'Due Date',
- description: 'Assessment description',
- location: 'Online',
- reminder: 60,
- });
-
- expect(url).toContain('https://calendar.google.com/calendar/render?action=TEMPLATE');
- expect(url).toContain('text=Due%20Date');
- expect(url).toContain('location=Online');
- expect(url).toContain('reminders=reminder_60_minutes');
- });
-
- it('should default google calendar end time to +1 hour when no end date', () => {
- const url = service.generateGoogleCalendarUrl({
- start: new Date('2026-03-01T10:00:00'),
- title: 'Due Date',
- description: 'Assessment description',
- });
-
- expect(url).toContain('dates=');
- expect(url).toContain('/');
- });
-
- it('should call downloadCalendarEvent when createEvent succeeds', () => {
- const downloadSpy = spyOn(service, 'downloadCalendarEvent');
- service.icsCreateEvent = ((event: any, callback: any) => {
- callback(null, 'BEGIN:VCALENDAR...');
- return undefined as any;
- }) as any;
-
- service.createCalendarEvent({
- title: 'Assessment',
- start: [2026, 3, 1, 10, 0],
- } as any);
-
- expect(downloadSpy).toHaveBeenCalledWith('BEGIN:VCALENDAR...', 'Assessment');
- });
-
- it('should throw error when createEvent callback receives error', () => {
- service.icsCreateEvent = ((event: any, callback: any) => {
- callback({ message: 'failed' }, null);
- return undefined as any;
- }) as any;
-
- expect(() => service.createCalendarEvent({
- title: 'Assessment',
- start: [2026, 3, 1, 10, 0],
- } as any)).toThrowError('Failed to create event: failed');
- });
});
diff --git a/projects/v3/src/app/pages/due-dates/due-dates.service.ts b/projects/v3/src/app/pages/due-dates/due-dates.service.ts
index e16340071..5bf82419f 100644
--- a/projects/v3/src/app/pages/due-dates/due-dates.service.ts
+++ b/projects/v3/src/app/pages/due-dates/due-dates.service.ts
@@ -18,13 +18,10 @@ export interface GoogleCalendarParams {
providedIn: 'root'
})
export class DueDatesService {
- // wrapped for testability (esbuild freezes module namespaces)
- icsCreateEvent = createEvent;
-
constructor() { }
createCalendarEvent(eventData: EventAttributes): void {
- return this.icsCreateEvent(eventData, (error, value) => {
+ return createEvent(eventData, (error, value) => {
if (error) {
throw new Error('Failed to create event: ' + error.message);
}
diff --git a/projects/v3/src/app/pages/events/event-detail/event-detail.component.spec.ts b/projects/v3/src/app/pages/events/event-detail/event-detail.component.spec.ts
index 85027c561..41cf3f296 100644
--- a/projects/v3/src/app/pages/events/event-detail/event-detail.component.spec.ts
+++ b/projects/v3/src/app/pages/events/event-detail/event-detail.component.spec.ts
@@ -1,5 +1,5 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { EventDetailComponent } from './event-detail.component';
import { of } from 'rxjs';
import { Router } from '@angular/router';
@@ -62,7 +62,7 @@ describe('EventDetailComponent', () => {
let modalSpy: jasmine.SpyObj;
const testUtils = new TestUtils();
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ComponentsModule, BrowserAnimationsModule],
declarations: [EventDetailComponent],
@@ -159,18 +159,17 @@ describe('EventDetailComponent', () => {
fixture.detectChanges();
component.event = tmpEvent;
expect(component.buttonText.label).toEqual(expected);
- expect(page.eventName.innerHTML.trim()).toEqual(tmpEvent.name);
- expect(page.activityName.innerHTML.trim()).toEqual(tmpEvent.activityName);
+ expect(page.eventName.innerHTML).toEqual(tmpEvent.name);
+ expect(page.activityName.innerHTML).toEqual(tmpEvent.activityName);
if (expected === 'Expired') {
expect(page.expired).toBeTruthy();
} else {
expect(page.expired).toBeFalsy();
}
- expect(page.date.innerHTML.trim()).toEqual(`${utils.utcToLocal(tmpEvent.startTime, 'date')}, ${utils.utcToLocal(tmpEvent.startTime, 'time')} - ${utils.utcToLocal(tmpEvent.endTime, 'time')}`);
+ expect(page.date.innerHTML).toEqual(`${utils.utcToLocal(tmpEvent.startTime, 'date')}, ${utils.utcToLocal(tmpEvent.startTime, 'time')} - ${utils.utcToLocal(tmpEvent.endTime, 'time')}`);
// expect(page.time.innerHTML).toEqual(`${utils.utcToLocal(tmpEvent.startTime, 'time')} - ${utils.utcToLocal(tmpEvent.endTime, 'time')}`);
- expect(page.location.innerHTML.trim()).toEqual(tmpEvent.location);
- // normalize whitespace - template interpolation may add extra spaces
- expect(page.capacity.textContent.trim().replace(/\s+/g, ' ')).toEqual(`${tmpEvent.remainingCapacity} Seats Available Out of ${tmpEvent.capacity}`);
+ expect(page.location.innerHTML).toEqual(tmpEvent.location);
+ expect(page.capacity.innerHTML).toEqual(`${tmpEvent.remainingCapacity} Seats Available Out of ${tmpEvent.capacity}`);
if (expected) {
expect(page.button.innerHTML.trim()).toEqual(expected);
}
@@ -266,7 +265,7 @@ describe('EventDetailComponent', () => {
tmpEvent.isBooked = true;
tmpEvent.isPast = true;
tmpEvent.assessment = null;
- expected = undefined;
+ expected = false;
});
it(`should return 'View Check In' if the event's check in assessment is done`, () => {
diff --git a/projects/v3/src/app/pages/events/event-detail/event-detail.component.ts b/projects/v3/src/app/pages/events/event-detail/event-detail.component.ts
index 55eebb97f..a16020a0b 100644
--- a/projects/v3/src/app/pages/events/event-detail/event-detail.component.ts
+++ b/projects/v3/src/app/pages/events/event-detail/event-detail.component.ts
@@ -7,7 +7,6 @@ import { NotificationsService } from '@v3/services/notifications.service';
import { BrowserStorageService } from '@v3/services/storage.service';
@Component({
- standalone: false,
selector: 'app-event-detail',
templateUrl: 'event-detail.component.html',
styleUrls: ['event-detail.component.scss']
diff --git a/projects/v3/src/app/pages/events/event-list/event-list.component.html b/projects/v3/src/app/pages/events/event-list/event-list.component.html
index 727c292c8..80c86c7e1 100644
--- a/projects/v3/src/app/pages/events/event-list/event-list.component.html
+++ b/projects/v3/src/app/pages/events/event-list/event-list.component.html
@@ -23,7 +23,7 @@ Event categories
[value]="selectedActivities"
(ionChange)="onSelect(filterEle.value)"
#filterEle>
- {{ activity.name }}
+
diff --git a/projects/v3/src/app/pages/events/event-list/event-list.component.spec.ts b/projects/v3/src/app/pages/events/event-list/event-list.component.spec.ts
index 4d993df7c..5da0d9e3d 100644
--- a/projects/v3/src/app/pages/events/event-list/event-list.component.spec.ts
+++ b/projects/v3/src/app/pages/events/event-list/event-list.component.spec.ts
@@ -1,5 +1,5 @@
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { waitForAsync, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing';
+import { async, ComponentFixture, TestBed, tick, fakeAsync } from '@angular/core/testing';
import { EventListComponent } from './event-list.component';
import { EventService, Event } from '@v3/services/event.service';
import { Observable, of, pipe } from 'rxjs';
@@ -37,7 +37,7 @@ describe('EventListComponent', () => {
let utils: UtilsService;
const testUtils = new TestUtils();
- beforeEach(waitForAsync(() => {
+ beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ComponentsModule],
declarations: [EventListComponent],
diff --git a/projects/v3/src/app/pages/events/event-list/event-list.component.ts b/projects/v3/src/app/pages/events/event-list/event-list.component.ts
index 6e247aa9f..84a8a0410 100644
--- a/projects/v3/src/app/pages/events/event-list/event-list.component.ts
+++ b/projects/v3/src/app/pages/events/event-list/event-list.component.ts
@@ -6,7 +6,6 @@ import { UtilsService } from '@v3/services/utils.service';
import { EventDetailComponent } from '../event-detail/event-detail.component';
@Component({
- standalone: false,
selector: 'app-event-list',
templateUrl: 'event-list.component.html',
styleUrls: ['event-list.component.scss']
diff --git a/projects/v3/src/app/pages/events/events-routing.component.ts b/projects/v3/src/app/pages/events/events-routing.component.ts
index 3138ef25c..0b055e796 100644
--- a/projects/v3/src/app/pages/events/events-routing.component.ts
+++ b/projects/v3/src/app/pages/events/events-routing.component.ts
@@ -1,7 +1,6 @@
import { Component } from '@angular/core';
@Component({
- standalone: false,
template: ''
})
export class EventsRoutingComponent {}
diff --git a/projects/v3/src/app/pages/events/events.page.spec.ts b/projects/v3/src/app/pages/events/events.page.spec.ts
index 2afbfc299..c22f6fcb4 100644
--- a/projects/v3/src/app/pages/events/events.page.spec.ts
+++ b/projects/v3/src/app/pages/events/events.page.spec.ts
@@ -2,7 +2,6 @@ import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angul
import { ActivatedRoute } from '@angular/router';
import { UtilsService } from '@v3/services/utils.service';
import { IonicModule } from '@ionic/angular';
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { EventsPage } from './events.page';
import { ActivatedRouteStub } from '@testingv3/activated-route-stub';
@@ -16,7 +15,6 @@ describe('EventsPage', () => {
TestBed.configureTestingModule({
declarations: [ EventsPage ],
imports: [IonicModule.forRoot()],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{
provide: ActivatedRoute,
diff --git a/projects/v3/src/app/pages/events/events.page.ts b/projects/v3/src/app/pages/events/events.page.ts
index b73386cfd..333353802 100644
--- a/projects/v3/src/app/pages/events/events.page.ts
+++ b/projects/v3/src/app/pages/events/events.page.ts
@@ -5,7 +5,6 @@ import { Event } from '@v3/services/event.service';
import { DOCUMENT } from '@angular/common';
@Component({
- standalone: false,
selector: 'app-events',
templateUrl: './events.page.html',
styleUrls: ['./events.page.scss'],
diff --git a/projects/v3/src/app/pages/experiences/experiences.page.spec.ts b/projects/v3/src/app/pages/experiences/experiences.page.spec.ts
index fa9206329..fce9bc637 100644
--- a/projects/v3/src/app/pages/experiences/experiences.page.spec.ts
+++ b/projects/v3/src/app/pages/experiences/experiences.page.spec.ts
@@ -5,8 +5,6 @@ import { UtilsService } from '@v3/services/utils.service';
import { IonicModule, LoadingController } from '@ionic/angular';
import { ExperienceService } from '@v3/app/services/experience.service';
import { NotificationsService } from '@v3/app/services/notifications.service';
-import { UnlockIndicatorService } from '@v3/app/services/unlock-indicator.service';
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ExperiencesPage } from './experiences.page';
import { MockRouter } from '@testingv3/mocked.service';
@@ -26,7 +24,6 @@ describe('ExperiencesPage', () => {
TestBed.configureTestingModule({
declarations: [ ExperiencesPage ],
imports: [IonicModule.forRoot()],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{
provide: Router,
@@ -38,14 +35,11 @@ describe('ExperiencesPage', () => {
},
{
provide: ExperienceService,
- useValue: jasmine.createSpyObj('ExperienceService', {
- 'getPrograms': undefined,
- 'getExperiences': undefined,
- 'switchProgramAndNavigate': Promise.resolve(true),
- 'getProgresses': of([]),
- }, {
- 'programsWithProgress$': of([]),
- 'experiences$': of(null),
+ useValue: jasmine.createSpyObj('ExperienceService', [
+ 'getPrograms',
+ 'switchProgramAndNavigate',
+ ], {
+ 'programsWithProgress$': of(),
}),
},
{
@@ -64,16 +58,7 @@ describe('ExperiencesPage', () => {
},
{
provide: BrowserStorageService,
- useValue: jasmine.createSpyObj('BrowserStorageService', {
- 'getConfig': {},
- 'get': null,
- }),
- },
- {
- provide: UnlockIndicatorService,
- useValue: jasmine.createSpyObj('UnlockIndicatorService', ['clearAllTasks'], {
- 'unlockedTasks$': of([])
- })
+ useValue: jasmine.createSpyObj('BrowserStorageService', ['getConfig']),
},
],
}).compileComponents();
diff --git a/projects/v3/src/app/pages/experiences/experiences.page.ts b/projects/v3/src/app/pages/experiences/experiences.page.ts
index 2d5c3e29f..9108492ab 100644
--- a/projects/v3/src/app/pages/experiences/experiences.page.ts
+++ b/projects/v3/src/app/pages/experiences/experiences.page.ts
@@ -11,7 +11,6 @@ import { UnlockIndicatorService } from '@v3/app/services/unlock-indicator.servic
import { Subject, Observable } from 'rxjs';
@Component({
- standalone: false,
selector: 'app-experiences',
templateUrl: './experiences.page.html',
styleUrls: ['./experiences.page.scss'],
diff --git a/projects/v3/src/app/pages/home/home.page.spec.ts b/projects/v3/src/app/pages/home/home.page.spec.ts
index cbe4d57b5..e5fd6a3dd 100644
--- a/projects/v3/src/app/pages/home/home.page.spec.ts
+++ b/projects/v3/src/app/pages/home/home.page.spec.ts
@@ -3,7 +3,7 @@ import { ActivatedRoute, Router } from '@angular/router';
import { ActivityService } from '@v3/services/activity.service';
import { AssessmentService } from '@v3/services/assessment.service';
import { UtilsService } from '@v3/services/utils.service';
-import { AlertController, IonicModule, ModalController } from '@ionic/angular';
+import { IonicModule } from '@ionic/angular';
import { AchievementService } from '@v3/app/services/achievement.service';
import { HomeService } from '@v3/app/services/home.service';
import { NotificationsService } from '@v3/app/services/notifications.service';
@@ -11,9 +11,6 @@ import { SharedService } from '@v3/app/services/shared.service';
import { BrowserStorageService } from '@v3/app/services/storage.service';
import { FastFeedbackService } from '@v3/app/services/fast-feedback.service';
import { UnlockIndicatorService } from '@v3/app/services/unlock-indicator.service';
-import { PulsecheckService } from '@v3/app/services/pulsecheck.service';
-import { HttpClientTestingModule } from '@angular/common/http/testing';
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { HomePage } from './home.page';
import { of } from 'rxjs';
@@ -32,19 +29,19 @@ describe('HomePage', () => {
let utilsService: jasmine.SpyObj;
beforeEach(waitForAsync(() => {
- const homeServiceSpy = jasmine.createSpyObj('HomeService', {
- 'getExperience': undefined,
- 'getMilestones': undefined,
- 'getProjectProgress': undefined,
- 'getPulseCheckStatuses': of({ data: { pulseCheckStatus: {} } }),
- 'getPulseCheckSkills': of({ data: { pulseCheckSkills: [] } }),
- }, {
- 'experience$': of({ id: 1, name: 'Test Experience', cardUrl: 'test-card-url' }),
- 'experienceProgress$': of(0),
- 'activityCount$': of(0),
- 'milestonesWithProgress$': of([]),
- 'milestones$': of([]),
- 'projectProgress$': of(0),
+ const homeServiceSpy = jasmine.createSpyObj('HomeService', [
+ 'getExperience',
+ 'getMilestones',
+ 'getProjectProgress',
+ 'getPulseCheckStatuses',
+ 'getPulseCheckSkills',
+ ], {
+ 'experience$': of(),
+ 'experienceProgress$': of(),
+ 'activityCount$': of(),
+ 'milestonesWithProgress$': of(),
+ 'milestones$': of(),
+ 'projectProgress$': of(),
});
const achievementServiceSpy = jasmine.createSpyObj('AchievementService', [
@@ -55,38 +52,19 @@ describe('HomePage', () => {
'achievements$': of(),
});
- const sharedServiceSpy = jasmine.createSpyObj('SharedService', ['refreshJWT'], {
- 'team$': of(null),
- });
+ const sharedServiceSpy = jasmine.createSpyObj('SharedService', ['refreshJWT']);
const storageServiceSpy = jasmine.createSpyObj('BrowserStorageService', [
'get',
'lastVisited',
'getUser',
'getFeature',
]);
- // set up default return values for storage service
- storageServiceSpy.getUser.and.returnValue({
- role: 'participant',
- apikey: 'test-key',
- projectId: 1,
- teamId: 1,
- });
- storageServiceSpy.get.and.callFake((key: string) => {
- if (key === 'experience') {
- return { id: 1, name: 'Test Experience', cardUrl: 'test-card-url' };
- }
- return null;
- });
- storageServiceSpy.getFeature.and.returnValue(false);
- const fastFeedbackServiceSpy = jasmine.createSpyObj('FastFeedbackService', {
- 'pullFastFeedback': of(null),
- });
+ const fastFeedbackServiceSpy = jasmine.createSpyObj('FastFeedbackService', ['pullFastFeedback']);
const utilsServiceSpy = jasmine.createSpyObj('UtilsService', ['setPageTitle', 'isMobile']);
TestBed.configureTestingModule({
declarations: [ HomePage ],
- imports: [IonicModule.forRoot(), HttpClientTestingModule],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
+ imports: [IonicModule.forRoot()],
providers: [
{
provide: ActivatedRoute,
@@ -134,27 +112,7 @@ describe('HomePage', () => {
'unlockedTasks$': of([])
})
},
- {
- provide: AlertController,
- useValue: jasmine.createSpyObj('AlertController', ['create'])
- },
- {
- provide: PulsecheckService,
- useValue: jasmine.createSpyObj('PulsecheckService', ['getPulsecheckStatuses'])
- },
- {
- provide: NotificationsService,
- useValue: jasmine.createSpyObj('NotificationsService', [
- 'alert',
- 'popUp',
- 'getTodoItems',
- ])
- },
- {
- provide: ModalController,
- useValue: jasmine.createSpyObj('ModalController', ['create', 'dismiss'])
- },
- ],
+ ]
}).compileComponents();
fixture = TestBed.createComponent(HomePage);
@@ -205,12 +163,7 @@ describe('HomePage', () => {
it('should get experience from storage', async () => {
await component.updateDashboard();
expect(storageService.get).toHaveBeenCalledWith('experience');
- expect(component.experience).toEqual({ name: 'Test Experience', cardUrl: 'test-url' } as any);
- });
-
- it('should set project hub visibility from feature toggle', async () => {
- await component.updateDashboard();
- expect(storageService.getFeature).toHaveBeenCalledWith('pulseCheckIndicator');
+ expect(component.experience).toEqual({ name: 'Test Experience', cardUrl: 'test-url' });
});
it('should set project hub visibility from feature toggle', async () => {
@@ -244,7 +197,7 @@ describe('HomePage', () => {
component.pulseCheckIndicatorEnabled = true;
await component.updateDashboard();
expect(homeService.getPulseCheckStatuses).toHaveBeenCalled();
- expect(component.pulseCheckStatus).toEqual({ red: 1, orange: 2, green: 3 } as any);
+ expect(component.pulseCheckStatus).toEqual({ red: 1, orange: 2, green: 3 });
});
it('should not get pulse check statuses when pulse check indicator is disabled', async () => {
@@ -316,9 +269,7 @@ describe('HomePage', () => {
data: { pulseCheckSkills: null }
}));
await component.updateDashboard();
- // component defaults to [] when pulseCheckSkills is null or empty (see line 243: || [])
- // and only updates when newSkills.length > 0, so it stays as initial []
- expect(component.pulseCheckSkills).toEqual([]);
+ expect(component.pulseCheckSkills).toBeNull();
});
it('should handle empty pulse check skills response', async () => {
@@ -659,5 +610,3 @@ describe('HomePage', () => {
expect(component.getFilteredActivityCount()).toBe(0);
});
- });
-});
diff --git a/projects/v3/src/app/pages/home/home.page.ts b/projects/v3/src/app/pages/home/home.page.ts
index 213cabd36..8cfce4201 100644
--- a/projects/v3/src/app/pages/home/home.page.ts
+++ b/projects/v3/src/app/pages/home/home.page.ts
@@ -22,7 +22,6 @@ import { PulsecheckService } from '@v3/app/services/pulsecheck.service';
import { ProjectBriefModalComponent, ProjectBrief } from '@v3/app/components/project-brief-modal/project-brief-modal.component';
@Component({
- standalone: false,
selector: "app-home",
templateUrl: "./home.page.html",
styleUrls: ["./home.page.scss"],
diff --git a/projects/v3/src/app/pages/notifications/notifications.page.spec.ts b/projects/v3/src/app/pages/notifications/notifications.page.spec.ts
index c821dc4fd..6c936279a 100644
--- a/projects/v3/src/app/pages/notifications/notifications.page.spec.ts
+++ b/projects/v3/src/app/pages/notifications/notifications.page.spec.ts
@@ -4,8 +4,6 @@ import { UtilsService } from '@v3/services/utils.service';
import { IonicModule, ModalController } from '@ionic/angular';
import { TestUtils } from '@testingv3/utils';
import { NotificationsService } from '@v3/services/notifications.service';
-import { HomeService } from '@v3/services/home.service';
-import { UnlockIndicatorService } from '@v3/services/unlock-indicator.service';
import { NotificationsPage } from './notifications.page';
import { of } from 'rxjs';
@@ -34,12 +32,6 @@ describe('NotificationsPage', () => {
provide: NotificationsService,
useValue: jasmine.createSpyObj('NotificationsService', [
'modal',
- 'alert',
- 'presentToast',
- 'getCurrentTodoItems',
- 'getTodoItems',
- 'markTodoItemAsDone',
- 'markMultipleTodoItemsAsDone',
], {
'notification$': of(true),
'eventReminder$': of(true),
@@ -51,22 +43,7 @@ describe('NotificationsPage', () => {
},
{
provide: ModalController,
- useValue: jasmine.createSpyObj('ModalController', {
- 'dismiss': Promise.resolve(),
- 'getTop': Promise.resolve(true),
- }),
- },
- {
- provide: HomeService,
- useValue: jasmine.createSpyObj('HomeService', ['getMilestones'], {
- 'milestones$': of([]),
- }),
- },
- {
- provide: UnlockIndicatorService,
- useValue: jasmine.createSpyObj('UnlockIndicatorService', ['clearAllTasks'], {
- 'unlockedTasks$': of([]),
- }),
+ useValue: jasmine.createSpyObj('ModalController', ['dismiss']),
},
]
}).compileComponents();
@@ -77,10 +54,6 @@ describe('NotificationsPage', () => {
utilsSpy = TestBed.inject(UtilsService);
modalSpy = TestBed.inject(ModalController);
notificationSpy = TestBed.inject(NotificationsService);
-
- // reconfigure getTop to return truthy (global test.ts override sets it to null)
- (modalSpy.getTop as jasmine.Spy).and.returnValue(Promise.resolve(true));
-
fixture.detectChanges();
}));
@@ -91,12 +64,12 @@ describe('NotificationsPage', () => {
describe('ngOnInit()', () => {
it('should initiate subscriptions', () => {
utilsSpy.isEmpty = jasmine.createSpy('isEmpty').and.returnValue(false);
+ component['_addChatTodoItem'] = jasmine.createSpy('_addChatTodoItem');
component.ngOnInit();
- // notification$ emits true, which sets todoItems
expect(component.todoItems).toEqual(true as any);
- // eventReminder$ emits true, isEmpty returns false, so it gets pushed
expect(component.eventReminders).toContain(true);
+ expect(component['_addChatTodoItem']).toHaveBeenCalledWith(true);
});
});
@@ -306,7 +279,7 @@ describe('NotificationsPage', () => {
flushMicrotasks();
expect(showEventDetail).toBeUndefined();
- expect(keyboardEvent.preventDefault).toHaveBeenCalledTimes(3);
+ expect(keyboardEvent.preventDefault).toHaveBeenCalledTimes(4);
}));
});
});
diff --git a/projects/v3/src/app/pages/notifications/notifications.page.ts b/projects/v3/src/app/pages/notifications/notifications.page.ts
index cef85317b..352ee4d37 100644
--- a/projects/v3/src/app/pages/notifications/notifications.page.ts
+++ b/projects/v3/src/app/pages/notifications/notifications.page.ts
@@ -13,7 +13,6 @@ import { takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
@Component({
- standalone: false,
selector: 'app-notifications',
templateUrl: './notifications.page.html',
styleUrls: ['./notifications.page.scss'],
diff --git a/projects/v3/src/app/pages/page-not-found/page-not-found.page.ts b/projects/v3/src/app/pages/page-not-found/page-not-found.page.ts
index 72c96b9d0..1d9d2386d 100644
--- a/projects/v3/src/app/pages/page-not-found/page-not-found.page.ts
+++ b/projects/v3/src/app/pages/page-not-found/page-not-found.page.ts
@@ -4,7 +4,6 @@ import { UtilsService } from '@v3/services/utils.service';
import { Router } from '@angular/router';
@Component({
- standalone: false,
selector: 'app-page-not-found',
templateUrl: './page-not-found.page.html',
styleUrls: ['./page-not-found.page.scss']
diff --git a/projects/v3/src/app/pages/review-desktop/review-desktop.page.spec.ts b/projects/v3/src/app/pages/review-desktop/review-desktop.page.spec.ts
index d4dc410c2..03ae55e48 100644
--- a/projects/v3/src/app/pages/review-desktop/review-desktop.page.spec.ts
+++ b/projects/v3/src/app/pages/review-desktop/review-desktop.page.spec.ts
@@ -1,210 +1,55 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ActivatedRoute } from '@angular/router';
import { AssessmentService } from '@v3/services/assessment.service';
import { UtilsService } from '@v3/services/utils.service';
-import { NotificationsService } from '@v3/services/notifications.service';
+import { IonicModule } from '@ionic/angular';
+import { ActivatedRouteStub } from '@testingv3/activated-route-stub';
+import { TestUtils } from '@testingv3/utils';
import { ReviewService } from '@v3/app/services/review.service';
-import { of, Subject } from 'rxjs';
+import { of } from 'rxjs';
import { ReviewDesktopPage } from './review-desktop.page';
describe('ReviewDesktopPage', () => {
let component: ReviewDesktopPage;
- let assessmentService: jasmine.SpyObj;
- let reviewService: jasmine.SpyObj;
- let notificationsService: jasmine.SpyObj;
- let utilsService: jasmine.SpyObj;
-
- const createComponent = ({
- submissionId = 1,
- reviews = [] as any[],
- assessment = { id: 99, pulseCheck: false } as any,
- submission = { id: 100, status: 'pending review' } as any,
- review = { id: 101 } as any,
- } = {}) => {
- const paramMap$ = new Subject();
- const params$ = new Subject();
-
- assessmentService = jasmine.createSpyObj('AssessmentService', [
- 'getAssessment',
- 'fetchAssessment',
- 'submitReview',
- 'pullFastFeedback'
- ], {
- assessment$: of(assessment),
- submission$: of(submission),
- review$: of(review),
- });
-
- reviewService = jasmine.createSpyObj('ReviewService', ['getReviews'], {
- reviews$: of(reviews),
- });
-
- notificationsService = jasmine.createSpyObj('NotificationsService', [
- 'getTodoItems',
- 'assessmentSubmittedToast'
- ]);
- notificationsService.getTodoItems.and.returnValue(of([]) as any);
-
- utilsService = jasmine.createSpyObj('UtilsService', ['setPageTitle', 'isEmpty']);
- utilsService.isEmpty.and.callFake((value) => value === null || value === undefined || value === '');
-
- const activatedRoute = {
- paramMap: paramMap$.asObservable(),
- params: params$.asObservable(),
- } as ActivatedRoute;
-
- component = new ReviewDesktopPage(
- utilsService,
- activatedRoute,
- assessmentService,
- reviewService,
- notificationsService
- );
-
- component.ngOnInit();
- paramMap$.next({});
- params$.next({ submissionId });
- };
-
- beforeEach(() => {
- createComponent();
- });
+ let fixture: ComponentFixture;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ declarations: [ ReviewDesktopPage ],
+ imports: [IonicModule.forRoot()],
+ providers: [
+ {
+ provide: UtilsService,
+ useClass: TestUtils
+ },
+ {
+ provide: ActivatedRoute,
+ useValue: new ActivatedRouteStub({ submissionId: 1}),
+ },
+ {
+ provide: AssessmentService,
+ useValue: jasmine.createSpyObj('AssessmentService', ['saveAnswers'], {
+ 'assessment$': of(true),
+ 'submission$': of(true),
+ 'review$': of(true),
+ }),
+ },
+ {
+ provide: ReviewService,
+ useValue: jasmine.createSpyObj('ReviewService', ['getReviews'], {
+ reviews$: of(true),
+ }),
+ },
+ ],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(ReviewDesktopPage);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ }));
it('should create', () => {
expect(component).toBeTruthy();
});
-
- it('should mark noReview when goto receives undefined review', () => {
- component.goto(undefined as any);
-
- expect(component.noReview).toBeTrue();
- });
-
- it('should load assessment when goto receives review', () => {
- const review = { assessmentId: 11, contextId: 22, submissionId: 33, name: 'Review A' } as any;
-
- component.goto(review);
-
- expect(component.noReview).toBeFalse();
- expect(component.currentReview).toEqual(review);
- expect(assessmentService.getAssessment).toHaveBeenCalledWith(11, 'review', 0, 22, 33);
- });
-
- it('should return early in gotoFirstReview when reviews is falsy', () => {
- spyOn(component, 'goto');
-
- component.gotoFirstReview(undefined as any);
-
- expect(component.goto).not.toHaveBeenCalled();
- });
-
- it('should go to matching submission in gotoFirstReview', () => {
- const reviews = [
- { submissionId: 1, isDone: true },
- { submissionId: 2, isDone: false },
- ] as any;
- component.submissionId = 2;
- spyOn(component, 'goto');
-
- component.gotoFirstReview(reviews);
-
- expect(component.goto).toHaveBeenCalledWith(reviews[1]);
- });
-
- it('should go to first not-done review when no submission id', () => {
- const reviews = [
- { submissionId: 1, isDone: true },
- { submissionId: 2, isDone: false },
- ] as any;
- component.submissionId = 0;
- spyOn(component, 'goto');
-
- component.gotoFirstReview(reviews);
-
- expect(component.goto).toHaveBeenCalledWith(reviews[1]);
- });
-
- it('should return early in saveReview when autosave while loading', async () => {
- component.loading = true;
- const event = { autoSave: true };
-
- await component.saveReview(event as any);
-
- expect(assessmentService.fetchAssessment).not.toHaveBeenCalled();
- });
-
- it('should show duplicated toast when submission is not pending review', async () => {
- component.currentReview = { contextId: 2 } as any;
- component.submission = { id: 3, status: 'done' } as any;
- component.review = { id: 4 } as any;
- component.assessment = { id: 5, pulseCheck: false } as any;
- assessmentService.fetchAssessment.and.returnValue(of({ submission: { status: 'done' } }) as any);
-
- await component.saveReview({ autoSave: false, assessmentId: 5, answers: {} } as any);
-
- expect(notificationsService.assessmentSubmittedToast).toHaveBeenCalledWith({ isDuplicated: true });
- expect(component.loading).toBeFalse();
- });
-
- it('should handle submitReview false gracefully', async () => {
- component.currentReview = { contextId: 2 } as any;
- component.submission = { id: 3, status: 'pending review' } as any;
- component.review = { id: 4 } as any;
- component.assessment = { id: 5, pulseCheck: false } as any;
- assessmentService.fetchAssessment.and.returnValues(
- of({ submission: { status: 'pending review' } }) as any,
- of({ submission: { status: 'pending review' } }) as any
- );
- assessmentService.submitReview.and.returnValue(of({ data: { submitReview: false } }) as any);
-
- await component.saveReview({ autoSave: false, assessmentId: 5, answers: { q1: 'a' } } as any);
-
- expect(component.savingText$.value).toBe('Save failed.');
- expect(component.btnDisabled$.value).toBeFalse();
- expect(component.loading).toBeFalse();
- });
-
- it('should trigger pulse check and success toast on successful submit', async () => {
- component.currentReview = { contextId: 2 } as any;
- component.submission = { id: 3, status: 'pending review' } as any;
- component.review = { id: 4 } as any;
- component.assessment = { id: 5, pulseCheck: true } as any;
- assessmentService.fetchAssessment.and.returnValues(
- of({ submission: { status: 'pending review' } }) as any,
- of({ submission: { status: 'done' } }) as any
- );
- assessmentService.submitReview.and.returnValue(of({ data: { submitReview: true } }) as any);
- assessmentService.pullFastFeedback.and.returnValue(Promise.resolve() as any);
-
- await component.saveReview({ autoSave: false, assessmentId: 5, answers: { q1: 'a' } } as any);
-
- expect(assessmentService.pullFastFeedback).toHaveBeenCalled();
- expect(reviewService.getReviews).toHaveBeenCalled();
- expect(notificationsService.getTodoItems).toHaveBeenCalled();
- expect(notificationsService.assessmentSubmittedToast).toHaveBeenCalledWith({ isReview: true });
- expect(component.btnDisabled$.value).toBeFalse();
- expect(component.loading).toBeFalse();
- });
-
- it('should set failure states when saveReview throws', async () => {
- component.currentReview = { contextId: 2 } as any;
- component.submission = { id: 3, status: 'pending review' } as any;
- component.review = { id: 4 } as any;
- component.assessment = { id: 5, pulseCheck: false } as any;
- assessmentService.fetchAssessment.and.returnValue(of({ submission: { status: 'pending review' } }) as any);
- assessmentService.submitReview.and.returnValue(of({
- data: {
- get submitReview() {
- throw new Error('submit error');
- }
- }
- }) as any);
-
- await component.saveReview({ autoSave: false, assessmentId: 5, answers: {} } as any);
-
- expect(component.savingText$.value).toBe('Save Failed.');
- expect(component.loading).toBeFalse();
- expect(component.btnDisabled$.value).toBeFalse();
- expect(notificationsService.assessmentSubmittedToast).toHaveBeenCalledWith({ isFail: true });
- });
});
diff --git a/projects/v3/src/app/pages/review-desktop/review-desktop.page.ts b/projects/v3/src/app/pages/review-desktop/review-desktop.page.ts
index 78cf19bef..827d00b78 100644
--- a/projects/v3/src/app/pages/review-desktop/review-desktop.page.ts
+++ b/projects/v3/src/app/pages/review-desktop/review-desktop.page.ts
@@ -7,7 +7,6 @@ import { UtilsService } from '@v3/services/utils.service';
import { BehaviorSubject, firstValueFrom } from 'rxjs';
@Component({
- standalone: false,
selector: 'app-review-desktop',
templateUrl: './review-desktop.page.html',
styleUrls: ['./review-desktop.page.scss'],
diff --git a/projects/v3/src/app/pages/review-mobile/review-mobile.page.ts b/projects/v3/src/app/pages/review-mobile/review-mobile.page.ts
index 4035a9d11..4dfbf1504 100644
--- a/projects/v3/src/app/pages/review-mobile/review-mobile.page.ts
+++ b/projects/v3/src/app/pages/review-mobile/review-mobile.page.ts
@@ -4,7 +4,6 @@ import { Review, ReviewService } from '@v3/app/services/review.service';
import { UtilsService } from '@v3/services/utils.service';
@Component({
- standalone: false,
selector: 'app-review-mobile',
templateUrl: './review-mobile.page.html',
styleUrls: ['./review-mobile.page.scss'],
diff --git a/projects/v3/src/app/pages/settings/settings.page.spec.ts b/projects/v3/src/app/pages/settings/settings.page.spec.ts
index f79fe0b1c..0750340df 100644
--- a/projects/v3/src/app/pages/settings/settings.page.spec.ts
+++ b/projects/v3/src/app/pages/settings/settings.page.spec.ts
@@ -1,302 +1,89 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { Router, ActivatedRoute } from '@angular/router';
import { AuthService } from '@v3/services/auth.service';
import { BrowserStorageService } from '@v3/services/storage.service';
import { UtilsService } from '@v3/services/utils.service';
-import { of, Subject, throwError } from 'rxjs';
+import { IonicModule, ModalController } from '@ionic/angular';
+import { ActivatedRouteStub } from '@testingv3/activated-route-stub';
+import { MockRouter } from '@testingv3/mocked.service';
+import { TestUtils } from '@testingv3/utils';
+import { FilestackService } from '@v3/services/filestack.service';
import { NotificationsService } from '@v3/services/notifications.service';
import { SettingsPage } from './settings.page';
-import { ModalController } from '@ionic/angular';
-import { UppyUploaderService } from '../../components/uppy-uploader/uppy-uploader.service';
-import { SupportPopupComponent } from '../../components/support-popup/support-popup.component';
+import { HubspotService } from '../../services/hubspot.service';
describe('SettingsPage', () => {
let component: SettingsPage;
- let routerSpy: jasmine.SpyObj;
- let authSpy: jasmine.SpyObj;
- let storageSpy: jasmine.SpyObj;
+ let fixture: ComponentFixture;
let utilsSpy: jasmine.SpyObj;
- let notificationsServiceSpy: jasmine.SpyObj;
- let modalControllerSpy: jasmine.SpyObj;
- let uppyUploaderServiceSpy: jasmine.SpyObj;
- let queryParams$: Subject;
-
- const createComponent = () => {
- queryParams$ = new Subject();
-
- routerSpy = jasmine.createSpyObj('Router', ['navigate']);
- authSpy = jasmine.createSpyObj('AuthService', ['getMyInfo', 'logout', 'updateUserProfile']);
- storageSpy = jasmine.createSpyObj('BrowserStorageService', ['getUser', 'get', 'setUser']);
- utilsSpy = jasmine.createSpyObj('UtilsService', [
- 'setPageTitle',
- 'getEvent',
- 'checkIsPracteraSupportEmail',
- 'isMobile',
- 'redirectToUrl',
- 'isEmpty',
- 'openUrl',
- 'getSupportEmail'
- ]);
- notificationsServiceSpy = jasmine.createSpyObj('NotificationsService', ['alert', 'modal', 'dismiss']);
- modalControllerSpy = jasmine.createSpyObj('ModalController', ['create', 'dismiss', 'getTop']);
- uppyUploaderServiceSpy = jasmine.createSpyObj('UppyUploaderService', ['open']);
-
- authSpy.getMyInfo.and.returnValue(of({
- data: {
- user: {
- id: 1,
- uuid: 'uuid',
- name: 'User',
- firstName: 'First',
- lastName: 'Last',
- email: 'user@example.com',
- image: '',
- role: 'participant',
- contactNumber: '+61',
- userHash: 'hash',
- }
- }
- } as any));
- authSpy.logout.and.returnValue(Promise.resolve() as any);
- authSpy.updateUserProfile.and.returnValue(of({}) as any);
-
- storageSpy.getUser.and.returnValue({
- email: 'user@example.com',
- contactNumber: '+61',
- avatar: '',
- name: 'User',
- programName: 'Program',
- LtiReturnUrl: '',
- programImage: 'program.png',
- apikey: 'key-1',
- } as any);
- storageSpy.get.withArgs('experience').and.returnValue({ supportEmail: 'support@practera.com' } as any);
- storageSpy.get.withArgs('programs').and.returnValue([1, 2] as any);
-
- utilsSpy.getEvent.and.returnValue(of(false) as any);
- utilsSpy.checkIsPracteraSupportEmail.and.returnValue(true);
- utilsSpy.isEmpty.and.callFake((value) => value === null || value === undefined || value === '');
-
- const documentMock = {
- defaultView: {
- history: {
- back: jasmine.createSpy('back'),
- }
- }
- } as any;
-
- component = new SettingsPage(
- routerSpy,
- { queryParams: queryParams$.asObservable() } as ActivatedRoute,
- authSpy,
- storageSpy,
- utilsSpy,
- notificationsServiceSpy,
- modalControllerSpy,
- uppyUploaderServiceSpy,
- documentMock
- );
- };
-
- beforeEach(() => {
- createComponent();
- });
+ let hubspotServiceSpy: jasmine.SpyObj;
+
+ beforeEach(waitForAsync(() => {
+ TestBed.configureTestingModule({
+ declarations: [ SettingsPage ],
+ imports: [IonicModule.forRoot()],
+ providers: [
+ {
+ provide: HubspotService,
+ useValue: jasmine.createSpyObj('HubspotService', ['openSupportPopup']),
+ },
+ {
+ provide: Router,
+ useClass: MockRouter
+ },
+ {
+ provide: ActivatedRoute,
+ useValue: new ActivatedRouteStub({}),
+ },
+ {
+ provide: AuthService,
+ useValue: jasmine.createSpyObj('AuthService', ['logout', 'updateProfileImage']),
+ },
+ {
+ provide: BrowserStorageService,
+ useValue: jasmine.createSpyObj('BrowserStorageService', {
+ 'getUser': jasmine.createSpy('getUser'),
+ 'get': jasmine.createSpy('get'),
+ 'setUser': jasmine.createSpy('setUser'),
+ }),
+ },
+ {
+ provide: UtilsService,
+ useClass: TestUtils
+ },
+ {
+ provide: NotificationsService,
+ useValue: jasmine.createSpyObj('NotificationsService', ['alert']),
+ },
+ {
+ provide: FilestackService,
+ useValue: jasmine.createSpyObj('FilestackService', ['getFileTypes']),
+ },
+ {
+ provide: ModalController,
+ useValue: jasmine.createSpyObj('ModalController', ['dismiss']),
+ },
+ ],
+ }).compileComponents();
+
+ fixture = TestBed.createComponent(SettingsPage);
+ component = fixture.componentInstance;
+ fixture.detectChanges();
+ utilsSpy = TestBed.inject(UtilsService) as jasmine.SpyObj;
+ }));
it('should create', () => {
expect(component).toBeTruthy();
});
- it('should ignore openLink for unsupported keyboard key', () => {
- spyOn(window, 'open');
-
- component.openLink(new KeyboardEvent('keydown', { key: 'Escape' }));
-
- expect(window.open).not.toHaveBeenCalled();
+ it('should not call openSupportPopup on a KeyboardEvent that is not Enter or Space', () => {
+ component.openSupportPopup(new KeyboardEvent('keydown', { key: 'a' }));
+ expect(hubspotServiceSpy.openSupportPopup).not.toHaveBeenCalled();
});
- it('should open terms link for Enter key', () => {
- spyOn(window, 'open');
-
- component.openLink(new KeyboardEvent('keydown', { key: 'Enter' }));
-
- expect(window.open).toHaveBeenCalledWith(component.termsUrl, '_system');
- });
-
- it('should ignore switchProgram for unsupported keyboard key', () => {
- component.switchProgram(new KeyboardEvent('keydown', { key: 'a' }));
-
- expect(utilsSpy.redirectToUrl).not.toHaveBeenCalled();
- expect(routerSpy.navigate).not.toHaveBeenCalled();
- });
-
- it('should redirect to LTI URL when returnLtiUrl is set', () => {
- component.returnLtiUrl = 'https://example.com/lti';
-
- component.switchProgram(new Event('click'));
-
- expect(utilsSpy.redirectToUrl).toHaveBeenCalledWith('https://example.com/lti');
- });
-
- it('should navigate to switcher when returnLtiUrl is not set', () => {
- component.returnLtiUrl = '';
-
- component.switchProgram(new Event('click'));
-
- expect(routerSpy.navigate).toHaveBeenCalledWith(['switcher', 'switcher-program']);
- });
-
- it('should return true when user is in multiple programs', () => {
- storageSpy.get.withArgs('programs').and.returnValue([1, 2]);
-
- expect(component.isInMultiplePrograms()).toBeTrue();
- });
-
- it('should use experience support email when non-practera and non-empty', () => {
- spyOn(window, 'open');
- storageSpy.get.withArgs('experience').and.returnValue({ supportEmail: 'help@client.com' } as any);
- utilsSpy.getSupportEmail.and.returnValue('help@client.com');
- utilsSpy.checkIsPracteraSupportEmail.and.returnValue(false);
- utilsSpy.isEmpty.and.returnValue(false);
-
- component.mailTo(new Event('click'));
-
- expect(window.open).toHaveBeenCalledWith('mailto:help@client.com?subject=', '_self');
- });
-
- it('should fallback to helpline email when support email is practera/empty', () => {
- spyOn(window, 'open');
- storageSpy.get.withArgs('experience').and.returnValue({ supportEmail: 'support@practera.com' } as any);
- utilsSpy.getSupportEmail.and.returnValue('support@practera.com');
- utilsSpy.checkIsPracteraSupportEmail.and.returnValue(true);
-
- component.mailTo(new Event('click'));
-
- expect(window.open).toHaveBeenCalled();
- });
-
- it('should ignore logout for unsupported keyboard key', () => {
- component.logout(new KeyboardEvent('keydown', { key: 'a' }));
-
- expect(authSpy.logout).not.toHaveBeenCalled();
- });
-
- it('should dismiss and logout on valid logout action', async () => {
- await component.logout(new Event('click'));
-
- expect(modalControllerSpy.dismiss).toHaveBeenCalled();
- expect(authSpy.logout).toHaveBeenCalled();
- });
-
- it('should ignore support popup for unsupported keyboard key', async () => {
- await component.openSupportPopup(new KeyboardEvent('keydown', { key: 'a' }));
-
- expect(modalControllerSpy.create).not.toHaveBeenCalled();
- });
-
- it('should open support modal when hubspot is activated', async () => {
- const modal = { present: jasmine.createSpy('present').and.returnValue(Promise.resolve()) };
- modalControllerSpy.create.and.returnValue(Promise.resolve(modal as any));
+ it('should call openSupportPopup when hubspotActivated is true', () => {
component.hubspotActivated = true;
-
- await component.openSupportPopup(new Event('click'));
-
- expect(modalControllerSpy.create).toHaveBeenCalledWith(jasmine.objectContaining({
- component: SupportPopupComponent,
- cssClass: 'support-popup',
- backdropDismiss: false,
- }));
- expect(modal.present).toHaveBeenCalled();
- });
-
- it('should fallback to mailTo when hubspot is not activated', async () => {
- spyOn(component, 'mailTo');
- component.hubspotActivated = false;
-
- await component.openSupportPopup(new Event('click'));
-
- expect(component.mailTo).toHaveBeenCalled();
- });
-
- it('should return early in profileImage when modal dismiss has no data', async () => {
- uppyUploaderServiceSpy.open.and.returnValue(Promise.resolve({
- onDidDismiss: () => Promise.resolve({ data: null })
- } as any));
-
- await component.profileImage();
-
- expect(authSpy.updateUserProfile).not.toHaveBeenCalled();
- });
-
- it('should update profile image and notify on success', async () => {
- const uploaded = {
- tus: { uploadUrl: 'https://upload' },
- name: 'profile.png',
- extension: 'png',
- type: 'image/png',
- size: 10,
- bucket: 'bucket',
- path: '/uploads/profile',
- preview: 'https://cdn/profile.png',
- };
- uppyUploaderServiceSpy.open.and.returnValue(Promise.resolve({
- onDidDismiss: () => Promise.resolve({ data: uploaded })
- } as any));
-
- await component.profileImage();
-
- expect(authSpy.updateUserProfile).toHaveBeenCalled();
- expect(component.profile.avatar).toBe('https://cdn/profile.png');
- expect(storageSpy.setUser).toHaveBeenCalledWith({ image: 'https://cdn/profile.png' });
- expect(notificationsServiceSpy.alert).toHaveBeenCalled();
- });
-
- it('should show upload error subHeader when server returns message', async () => {
- uppyUploaderServiceSpy.open.and.returnValue(Promise.resolve({
- onDidDismiss: () => Promise.resolve({ data: { tus: { uploadUrl: 'u' } } })
- } as any));
- authSpy.updateUserProfile.and.returnValue(throwError(() => ({ error: { message: 'Upload denied' } })) as any);
-
- await component.profileImage();
-
- expect(notificationsServiceSpy.alert).toHaveBeenCalled();
- const alertArgs = notificationsServiceSpy.alert.calls.mostRecent().args[0];
- expect(alertArgs.subHeader).toBe('Upload denied');
- expect(component.imageUpdating).toBeFalse();
- });
-
- it('should go back using window history', () => {
- component.goBack();
-
- expect((component.window.history.back as any)).toHaveBeenCalled();
- });
-
- it('should open badge app url', () => {
- component.openBadgeApp(new Event('click'));
-
- expect(utilsSpy.openUrl).toHaveBeenCalled();
- });
-
- it('should handle retrieve user info failure with alert', async () => {
- authSpy.getMyInfo.and.returnValue(throwError(() => new Error('network')) as any);
-
- await (component as any)._retrieveUserInfo();
-
- expect(notificationsServiceSpy.alert).toHaveBeenCalled();
- });
-
- it('should initialize and trigger support email check on ngOnInit', () => {
- component.ngOnInit();
-
- expect(utilsSpy.setPageTitle).toHaveBeenCalledWith('Settings - Practera');
- expect(utilsSpy.checkIsPracteraSupportEmail).toHaveBeenCalled();
- });
-
- it('should complete unsubscribe subject on destroy', () => {
- const nextSpy = spyOn(component.unsubscribe$, 'next');
- const completeSpy = spyOn(component.unsubscribe$, 'complete');
-
- component.ngOnDestroy();
-
- expect(nextSpy).toHaveBeenCalled();
- expect(completeSpy).toHaveBeenCalled();
+ component.openSupportPopup(new Event('click'));
+ expect(hubspotServiceSpy.openSupportPopup).toHaveBeenCalledWith({ formOnly: true });
});
});
diff --git a/projects/v3/src/app/pages/settings/settings.page.ts b/projects/v3/src/app/pages/settings/settings.page.ts
index fff0999a3..cb506f674 100644
--- a/projects/v3/src/app/pages/settings/settings.page.ts
+++ b/projects/v3/src/app/pages/settings/settings.page.ts
@@ -13,7 +13,6 @@ import { first, takeUntil } from 'rxjs/operators';
import { SupportPopupComponent } from '../../components/support-popup/support-popup.component';
@Component({
- standalone: false,
selector: 'app-settings',
templateUrl: './settings.page.html',
styleUrls: ['./settings.page.scss'],
diff --git a/projects/v3/src/app/pages/tabs/tabs.page.spec.ts b/projects/v3/src/app/pages/tabs/tabs.page.spec.ts
index 37e742d94..ab5a75eaa 100644
--- a/projects/v3/src/app/pages/tabs/tabs.page.spec.ts
+++ b/projects/v3/src/app/pages/tabs/tabs.page.spec.ts
@@ -2,48 +2,20 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ChatService } from '@v3/services/chat.service';
import { BrowserStorageService } from '@v3/services/storage.service';
import { UtilsService } from '@v3/services/utils.service';
-import { IonicModule } from '@ionic/angular';
+import { IonicModule, Platform } from '@ionic/angular';
import { NotificationsService } from '@v3/services/notifications.service';
import { ReviewService } from '@v3/services/review.service';
-import { ActivityService } from '@v3/services/activity.service';
import { TabsPage } from './tabs.page';
import { RouterTestingModule } from '@angular/router/testing';
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { ActivatedRoute, Router } from '@angular/router';
-import { BehaviorSubject, of, Subject } from 'rxjs';
+import { of } from 'rxjs';
describe('TabsPage', () => {
let component: TabsPage;
let fixture: ComponentFixture;
- let reviewServiceSpy: jasmine.SpyObj;
- let storageServiceSpy: jasmine.SpyObj;
- let chatServiceSpy: jasmine.SpyObj;
- let utilsSpy: jasmine.SpyObj;
- let notificationsSpy: jasmine.SpyObj;
- let activityServiceSpy: jasmine.SpyObj;
- let routerSpy: jasmine.SpyObj;
-
- let reviews$: BehaviorSubject;
- let routeParams$: Subject;
- let screenStatus$: BehaviorSubject;
- let notification$: BehaviorSubject;
- let eventStreams: { [key: string]: Subject };
-
- const getEventStream = (key: string) => {
- if (!eventStreams[key]) {
- eventStreams[key] = new Subject();
- }
- return eventStreams[key];
- };
beforeEach(waitForAsync(() => {
- reviews$ = new BehaviorSubject([]);
- routeParams$ = new Subject();
- screenStatus$ = new BehaviorSubject({ leftSidebarExpanded: false });
- notification$ = new BehaviorSubject([]);
- eventStreams = {};
-
TestBed.configureTestingModule({
declarations: [ TabsPage ],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
@@ -52,13 +24,13 @@ describe('TabsPage', () => {
{
provide: ReviewService,
useValue: jasmine.createSpyObj('ReviewService', [], {
- reviews$: reviews$.asObservable(),
+ 'reviews$': of(),
}),
},
{
provide: BrowserStorageService,
useValue: jasmine.createSpyObj('BrowserStorageService', {
- getUser: jasmine.createSpy(),
+ 'getUser': jasmine.createSpy()
}),
},
{
@@ -67,45 +39,24 @@ describe('TabsPage', () => {
},
{
provide: UtilsService,
- useValue: jasmine.createSpyObj('UtilsService', ['setPageTitle', 'getEvent'], {
- screenStatus$: screenStatus$.asObservable(),
+ useValue: jasmine.createSpyObj('UtilsService', {
+ 'getEvent': of(true),
}),
},
{
provide: NotificationsService,
useValue: jasmine.createSpyObj('NotificationsService', {
- getTodoItemFromEvent: undefined,
- getReminderEvent: of(true),
- getChatMessage: of(true),
+ 'getTodoItemFromEvent': of(),
+ 'getReminderEvent': of(),
+ 'getTodoItems': of(),
+ 'getChatMessage': of(),
}, {
- notification$: notification$.asObservable(),
+ 'notification$': of(),
}),
},
- {
- provide: ActivityService,
- useValue: jasmine.createSpyObj('ActivityService', ['getActivity']),
- },
- {
- provide: ActivatedRoute,
- useValue: {
- params: routeParams$.asObservable(),
- },
- },
],
}).compileComponents();
- reviewServiceSpy = TestBed.inject(ReviewService) as jasmine.SpyObj;
- storageServiceSpy = TestBed.inject(BrowserStorageService) as jasmine.SpyObj;
- chatServiceSpy = TestBed.inject(ChatService) as jasmine.SpyObj;
- utilsSpy = TestBed.inject(UtilsService) as jasmine.SpyObj;
- notificationsSpy = TestBed.inject(NotificationsService) as jasmine.SpyObj;
- activityServiceSpy = TestBed.inject(ActivityService) as jasmine.SpyObj;
- routerSpy = TestBed.inject(Router) as jasmine.SpyObj;
-
- storageServiceSpy.getUser.and.returnValue({ role: 'participant', chatEnabled: true } as any);
- chatServiceSpy.getChatList.and.returnValue(of([{ uuid: 'chat-1' }] as any));
- utilsSpy.getEvent.and.callFake((key: string) => getEventStream(key).asObservable());
-
fixture = TestBed.createComponent(TabsPage);
component = fixture.componentInstance;
fixture.detectChanges();
@@ -114,122 +65,4 @@ describe('TabsPage', () => {
it('should create', () => {
expect(component).toBeTruthy();
});
-
- it('should initialize title and left sidebar state', () => {
- expect(utilsSpy.setPageTitle).toHaveBeenCalledWith('Practera');
-
- screenStatus$.next({ leftSidebarExpanded: true });
- expect(component.hasLeftSidebar).toBeTrue();
- });
-
- it('should keep chat tab hidden when chat is disabled for user', () => {
- storageServiceSpy.getUser.and.returnValue({ role: 'participant', chatEnabled: false } as any);
-
- component.ngOnInit();
-
- expect(component.showMessages).toBeFalse();
- });
-
- it('should show chat tab only when chat list has channels', () => {
- chatServiceSpy.getChatList.and.returnValue(of([] as any));
-
- component.ngOnInit();
- expect(component.showMessages).toBeFalse();
-
- chatServiceSpy.getChatList.and.returnValue(of([{ uuid: 'chat-1' }] as any));
- component.ngOnInit();
- expect(component.showMessages).toBeTrue();
- });
-
- it('should toggle events tab by user role', () => {
- storageServiceSpy.getUser.and.returnValue({ role: 'participant', chatEnabled: true } as any);
- routeParams$.next({});
- expect(component.showEvents).toBeTrue();
-
- storageServiceSpy.getUser.and.returnValue({ role: 'mentor', chatEnabled: true } as any);
- routeParams$.next({});
- expect(component.showEvents).toBeFalse();
- });
-
- it('should process notification events and trigger activity fetch when applicable', () => {
- const event = {
- type: 'assessment_review_published',
- meta: {
- AssessmentReview: {
- activity_id: 321,
- },
- },
- } as any;
-
- getEventStream('notification').next(event);
-
- expect(notificationsSpy.getTodoItemFromEvent).toHaveBeenCalledWith(event);
- expect(activityServiceSpy.getActivity).toHaveBeenCalledWith(321);
- });
-
- it('should process chat and reminder events', () => {
- getEventStream('chat:new-message').next({});
- getEventStream('chat:delete-message').next({});
- getEventStream('event-reminder').next({ id: 'reminder' });
-
- expect(notificationsSpy.getChatMessage).toHaveBeenCalledTimes(2);
- expect(notificationsSpy.getReminderEvent).toHaveBeenCalledWith({ id: 'reminder' });
- });
-
- it('should map notification badges by type', () => {
- notification$.next([
- { type: 'event-reminder' },
- { type: 'event-reminder' },
- { type: 'review_submission' },
- { type: 'chat', unreadMessages: 5 },
- ] as any);
-
- expect(component.badges.event).toBe(2);
- expect(component.badges.review).toBe(1);
- expect(component.badges.chat).toBe(5);
-
- notification$.next([{ type: 'chat' }] as any);
- expect(component.badges.chat).toBe(0);
- });
-
- it('should set selected tab from tabs component', () => {
- component.tabs = {
- getSelected: () => 'home',
- } as any;
-
- component.setCurrentTab();
-
- expect(component.selectedTab).toBe('home');
- });
-
- it('should handle keyboard navigation for tabs on enter and ignore unsupported keys', async () => {
- const preventDefault = jasmine.createSpy('preventDefault');
- const selectSpy = jasmine.createSpy('select');
- spyOn(routerSpy, 'navigateByUrl').and.returnValue(Promise.resolve(true));
- component.tabs = { select: selectSpy } as any;
-
- await component.keyboardNavigateTab('home', { code: 'Enter', preventDefault } as any);
- expect(preventDefault).toHaveBeenCalled();
- expect(selectSpy).toHaveBeenCalledWith('home');
- expect(routerSpy.navigateByUrl).toHaveBeenCalledWith('/v3/home');
-
- const noActionEvent = { code: 'KeyA', preventDefault: jasmine.createSpy('preventDefault') } as any;
- expect(component.keyboardNavigateTab('home', noActionEvent)).toBeUndefined();
- expect(noActionEvent.preventDefault).not.toHaveBeenCalled();
- });
-
- it('should return false for developer-only feature checks by default', () => {
- expect(component.forDeveloperMode('unknown-feature')).toBeFalse();
- });
-
- it('should unsubscribe open subscriptions on destroy', () => {
- const openSub = jasmine.createSpyObj('Subscription', ['unsubscribe'], { closed: false });
- const closedSub = jasmine.createSpyObj('Subscription', ['unsubscribe'], { closed: true });
- component.subscriptions = [openSub as any, closedSub as any];
-
- component.ngOnDestroy();
-
- expect(openSub.unsubscribe).toHaveBeenCalled();
- expect(closedSub.unsubscribe).not.toHaveBeenCalled();
- });
});
diff --git a/projects/v3/src/app/pages/tabs/tabs.page.ts b/projects/v3/src/app/pages/tabs/tabs.page.ts
index e71567de1..1200a7437 100644
--- a/projects/v3/src/app/pages/tabs/tabs.page.ts
+++ b/projects/v3/src/app/pages/tabs/tabs.page.ts
@@ -10,7 +10,6 @@ import { NotificationsService } from '@v3/services/notifications.service';
import { ActivityService } from '@v3/app/services/activity.service';
@Component({
- standalone: false,
selector: 'app-tabs',
templateUrl: './tabs.page.html',
styleUrls: ['./tabs.page.scss'],
diff --git a/projects/v3/src/app/pages/topic-mobile/topic-mobile.page.spec.ts b/projects/v3/src/app/pages/topic-mobile/topic-mobile.page.spec.ts
index b7153091a..e432e8161 100644
--- a/projects/v3/src/app/pages/topic-mobile/topic-mobile.page.spec.ts
+++ b/projects/v3/src/app/pages/topic-mobile/topic-mobile.page.spec.ts
@@ -2,132 +2,58 @@ import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivityService } from '@v3/services/activity.service';
import { TopicService } from '@v3/services/topic.service';
-import { UtilsService } from '@v3/services/utils.service';
import { IonicModule } from '@ionic/angular';
+import { MockRouter } from '@testingv3/mocked.service';
+import { ActivatedRouteStub } from '@testingv3/activated-route-stub';
import { TopicMobilePage } from './topic-mobile.page';
-import { BehaviorSubject, of, Subject } from 'rxjs';
+import { of } from 'rxjs';
describe('TopicMobilePage', () => {
let component: TopicMobilePage;
let fixture: ComponentFixture;
- let routeParams$: Subject;
- let topic$: BehaviorSubject;
- let currentTask$: BehaviorSubject;
- let topicServiceSpy: jasmine.SpyObj;
- let activityServiceSpy: jasmine.SpyObj;
- let routerSpy: jasmine.SpyObj;
- let utilsSpy: jasmine.SpyObj;
beforeEach(waitForAsync(() => {
- routeParams$ = new Subject();
- topic$ = new BehaviorSubject(null);
- currentTask$ = new BehaviorSubject(null);
-
TestBed.configureTestingModule({
declarations: [ TopicMobilePage ],
providers: [
{
provide: ActivatedRoute,
- useValue: {
- params: routeParams$.asObservable(),
- },
+ useValue: new ActivatedRouteStub({
+ id: 1, activityId: 1
+ }),
},
{
provide: Router,
- useValue: jasmine.createSpyObj('Router', ['navigate']),
+ useClass: MockRouter,
},
{
provide: TopicService,
- useValue: jasmine.createSpyObj('TopicService', ['getTopic', 'updateTopicProgress'], {
- topic$: topic$.asObservable(),
+ useValue: jasmine.createSpyObj('TopicService', [
+ 'getTopic',
+ 'updateTopicProgress',
+ ], {
+ topic$: of(true)
}),
},
{
provide: ActivityService,
- useValue: jasmine.createSpyObj('ActivityService', ['getActivity', 'goToNextTask'], {
- currentTask$: currentTask$.asObservable(),
+ useValue: jasmine.createSpyObj('ActivityService', [
+ 'getActivity', 'goToNextTask'
+ ], {
+ currentTask$: of(true)
}),
},
- {
- provide: UtilsService,
- useValue: jasmine.createSpyObj('UtilsService', ['setPageTitle']),
- }
],
imports: [IonicModule.forRoot()]
}).compileComponents();
fixture = TestBed.createComponent(TopicMobilePage);
component = fixture.componentInstance;
- topicServiceSpy = TestBed.inject(TopicService) as jasmine.SpyObj;
- activityServiceSpy = TestBed.inject(ActivityService) as jasmine.SpyObj;
- routerSpy = TestBed.inject(Router) as jasmine.SpyObj;
- utilsSpy = TestBed.inject(UtilsService) as jasmine.SpyObj;
-
- topicServiceSpy.updateTopicProgress.and.returnValue(of(true) as any);
- activityServiceSpy.getActivity.and.callFake((_activityId: number, _refresh: boolean, _task: any, callback: Function) => {
- callback();
- return Promise.resolve(true) as any;
- });
-
fixture.detectChanges();
}));
it('should create', () => {
expect(component).toBeTruthy();
});
-
- it('should initialise topic and current task from streams', () => {
- routeParams$.next({ id: 12, activityId: 44 });
- topic$.next({ id: 12, title: 'Topic A' } as any);
- currentTask$.next({ id: 999, type: 'Topic', status: 'in progress' } as any);
-
- expect(topicServiceSpy.getTopic).toHaveBeenCalledWith(12);
- expect(component.activityId).toBe(44);
- expect(component.topic).toEqual(jasmine.objectContaining({ id: 12, title: 'Topic A' }));
- expect(component.currentTask).toEqual(jasmine.objectContaining({ id: 999 }));
- expect(utilsSpy.setPageTitle).toHaveBeenCalledWith('Topic A - Practera');
- });
-
- it('should continue with done task by going directly to next task', async () => {
- component.topic = { id: 7, title: 'Done Topic' } as any;
- component.currentTask = { id: 7, type: 'Topic', status: 'done' } as any;
-
- await component.continue();
-
- expect(activityServiceSpy.goToNextTask).toHaveBeenCalledWith(component.currentTask);
- expect(topicServiceSpy.updateTopicProgress).not.toHaveBeenCalled();
- expect(component.btnDisabled$.value).toBeFalse();
- });
-
- it('should continue incomplete task by updating progress and refreshing activity', async () => {
- component.topic = { id: 9, title: 'In Progress Topic' } as any;
- component.activityId = 88;
- component.currentTask = { id: 9, type: 'Topic', status: 'in progress' } as any;
-
- await component.continue();
-
- expect(topicServiceSpy.updateTopicProgress).toHaveBeenCalledWith(9, 'completed');
- expect(activityServiceSpy.getActivity).toHaveBeenCalled();
- expect(component.btnDisabled$.value).toBeFalse();
- });
-
- it('should build fallback current task when missing', async () => {
- component.topic = { id: 11, title: 'Fallback Topic' } as any;
- component.activityId = 55;
- component.currentTask = null;
-
- await component.continue();
-
- expect(component.currentTask).toEqual(jasmine.objectContaining({ id: 11, type: 'Topic', name: 'Fallback Topic' }));
- expect(topicServiceSpy.updateTopicProgress).toHaveBeenCalledWith(11, 'completed');
- });
-
- it('should go back to activity-mobile page', () => {
- component.activityId = 123;
-
- component.goBack();
-
- expect(routerSpy.navigate).toHaveBeenCalledWith(['v3', 'activity-mobile', 123]);
- });
});
diff --git a/projects/v3/src/app/pages/topic-mobile/topic-mobile.page.ts b/projects/v3/src/app/pages/topic-mobile/topic-mobile.page.ts
index 23b2a0d3a..a2fe9ac59 100644
--- a/projects/v3/src/app/pages/topic-mobile/topic-mobile.page.ts
+++ b/projects/v3/src/app/pages/topic-mobile/topic-mobile.page.ts
@@ -3,16 +3,15 @@ import { ActivatedRoute, Router } from '@angular/router';
import { ActivityService, Task } from '@v3/app/services/activity.service';
import { TopicService, Topic } from '@v3/app/services/topic.service';
import { UtilsService } from '@v3/services/utils.service';
-import { BehaviorSubject, Observable, firstValueFrom } from 'rxjs';
+import { BehaviorSubject, firstValueFrom } from 'rxjs';
@Component({
- standalone: false,
selector: 'app-topic-mobile',
templateUrl: './topic-mobile.page.html',
styleUrls: ['./topic-mobile.page.scss'],
})
export class TopicMobilePage implements OnInit {
- topic$: Observable;
+ topic$ = this.topicService.topic$;
btnDisabled$: BehaviorSubject = new BehaviorSubject(false);
topic: Topic;
@@ -25,9 +24,7 @@ export class TopicMobilePage implements OnInit {
private topicService: TopicService,
private activityService: ActivityService,
private utils: UtilsService
- ) {
- this.topic$ = this.topicService.topic$;
- }
+ ) { }
ngOnInit() {
this.topic$.subscribe(res => {
diff --git a/projects/v3/src/app/pages/v3/v3.page.spec.ts b/projects/v3/src/app/pages/v3/v3.page.spec.ts
index 7efd81188..adb3c7125 100644
--- a/projects/v3/src/app/pages/v3/v3.page.spec.ts
+++ b/projects/v3/src/app/pages/v3/v3.page.spec.ts
@@ -16,7 +16,6 @@ import { TestUtils } from '@testingv3/utils';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { HomeService } from '@v3/app/services/home.service';
import { NotificationsService } from '@v3/app/services/notifications.service';
-import { UnlockIndicatorService } from '@v3/app/services/unlock-indicator.service';
describe('V3Page', () => {
let component: V3Page;
@@ -61,8 +60,7 @@ describe('V3Page', () => {
{
provide: BrowserStorageService,
useValue: jasmine.createSpyObj('BrowserStorageService', {
- getUser: jasmine.createSpy(),
- get: jasmine.createSpy().and.returnValue([]),
+ getUser: jasmine.createSpy()
}),
},
{
@@ -83,19 +81,10 @@ describe('V3Page', () => {
},
{
provide: NotificationsService,
- useValue: jasmine.createSpyObj('NotificationsService', {
- 'getTodoItems': of(),
- 'getChatMessage': of(),
- }, {
+ useValue: jasmine.createSpyObj('NotificationsService', ['getTodoItems', 'getChatMessage'], {
'notification$': of(),
}),
},
- {
- provide: UnlockIndicatorService,
- useValue: jasmine.createSpyObj('UnlockIndicatorService', [], {
- 'unlockedTasks$': of([]),
- }),
- },
]
}).compileComponents();
@@ -119,6 +108,7 @@ describe('V3Page', () => {
it('should call required methods and set component properties correctly', () => {
// Prepare data and spies
const getReviewsSpy = reviewSpy.getReviews;
+ const getExperienceSpy = homeSpy.getExperience;
utilsSpy.moveToNewLocale.and.stub();
const getTodoItemsSpy = notificationsSpy.getTodoItems.and.returnValue(of());
const getChatListSpy = chatSpy.getChatList.and.returnValue(of([]));
@@ -131,13 +121,14 @@ describe('V3Page', () => {
component.ngOnInit();
// Check if the required methods are called
- // Note: getExperience is only called on NavigationEnd events to /v3/home, not during ngOnInit
expect(getReviewsSpy).toHaveBeenCalled();
+ expect(getExperienceSpy).toHaveBeenCalled();
expect(getTodoItemsSpy).toHaveBeenCalled();
expect(getChatListSpy).toHaveBeenCalled();
// Check if component properties are set correctly
expect(component.showEvents).toBeTrue();
+ expect(component.openMenu).toBeFalse();
expect(component.showMessages).toBeFalse();
});
});
diff --git a/projects/v3/src/app/pages/v3/v3.page.ts b/projects/v3/src/app/pages/v3/v3.page.ts
index 3d7c2204a..5f4e9d3d1 100644
--- a/projects/v3/src/app/pages/v3/v3.page.ts
+++ b/projects/v3/src/app/pages/v3/v3.page.ts
@@ -16,7 +16,6 @@ import { environment } from '@v3/environments/environment';
import { UnlockIndicatorService } from '@v3/app/services/unlock-indicator.service';
@Component({
- standalone: false,
selector: 'app-v3',
templateUrl: './v3.page.html',
styleUrls: ['./v3.page.scss'],
diff --git a/projects/v3/src/app/personalised-header/personalised-header.component.spec.ts b/projects/v3/src/app/personalised-header/personalised-header.component.spec.ts
index 53cc0d96a..cdf87109d 100644
--- a/projects/v3/src/app/personalised-header/personalised-header.component.spec.ts
+++ b/projects/v3/src/app/personalised-header/personalised-header.component.spec.ts
@@ -1,8 +1,6 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { Router } from '@angular/router';
import { IonicModule, ModalController } from '@ionic/angular';
-import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
-import { of, Subject } from 'rxjs';
import { AnimationsService } from '../services/animations.service';
import { NotificationsService } from '../services/notifications.service';
import { BrowserStorageService } from '../services/storage.service';
@@ -14,54 +12,35 @@ describe('PersonalisedHeaderComponent', () => {
let component: PersonalisedHeaderComponent;
let fixture: ComponentFixture;
- const mockModalSpy = jasmine.createSpyObj('Modal', ['present', 'onDidDismiss']);
- mockModalSpy.onDidDismiss.and.returnValue(Promise.resolve({ data: {} }));
-
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [ PersonalisedHeaderComponent ],
- schemas: [CUSTOM_ELEMENTS_SCHEMA],
providers: [
{
provide: ModalController,
- useValue: jasmine.createSpyObj('ModalController', {
- 'create': Promise.resolve(mockModalSpy),
- 'dismiss': Promise.resolve()
- }),
+ useValue: jasmine.createSpyObj('ModalController', ['']),
},
{
provide: AnimationsService,
- useValue: {
- enterAnimation: jasmine.createSpy('enterAnimation'),
- leaveAnimation: jasmine.createSpy('leaveAnimation')
- },
+ useValue: jasmine.createSpyObj('AnimationsService', ['']),
},
{
provide: BrowserStorageService,
- useValue: jasmine.createSpyObj('BrowserStorageService', {
- 'getUser': { name: 'Test User', image: '' },
- 'get': { supportEmail: 'test@example.com' }
- }),
+ useValue: jasmine.createSpyObj('BrowserStorageService', [
+ 'getUser',
+ ]),
},
{
provide: UtilsService,
- useValue: jasmine.createSpyObj('UtilsService', {
- 'isMobile': false,
- 'getEvent': of({}),
- 'checkIsPracteraSupportEmail': undefined
- }),
+ useValue: jasmine.createSpyObj('UtilsService', ['']),
},
{
provide: Router,
- useValue: jasmine.createSpyObj('Router', {
- 'navigate': Promise.resolve(true)
- }),
+ useValue: jasmine.createSpyObj('Router', ['']),
},
{
provide: NotificationsService,
- useValue: {
- notification$: new Subject()
- },
+ useValue: jasmine.createSpyObj('NotificationsService', ['']),
},
],
imports: [IonicModule.forRoot()]
diff --git a/projects/v3/src/app/personalised-header/personalised-header.component.ts b/projects/v3/src/app/personalised-header/personalised-header.component.ts
index e2120d093..4c71e11b1 100644
--- a/projects/v3/src/app/personalised-header/personalised-header.component.ts
+++ b/projects/v3/src/app/personalised-header/personalised-header.component.ts
@@ -11,7 +11,6 @@ import { UtilsService } from '../services/utils.service';
import { SupportPopupComponent } from '../components/support-popup/support-popup.component';
@Component({
- standalone: false,
selector: 'app-personalised-header',
templateUrl: './personalised-header.component.html',
styleUrls: ['./personalised-header.component.scss'],
diff --git a/projects/v3/src/app/services/achievement.service.spec.ts b/projects/v3/src/app/services/achievement.service.spec.ts
index 47d9d6fb2..12ddc0422 100644
--- a/projects/v3/src/app/services/achievement.service.spec.ts
+++ b/projects/v3/src/app/services/achievement.service.spec.ts
@@ -5,16 +5,12 @@ import { RequestService } from 'request';
import { BrowserStorageService } from '@v3/services/storage.service';
import { UtilsService } from '@v3/services/utils.service';
import { TestUtils } from '@testingv3/utils';
-import { ApolloService } from './apollo.service';
-import { DemoService } from './demo.service';
describe('AchievementService', () => {
let service: AchievementService;
let requestSpy: jasmine.SpyObj;
- let apolloSpy: jasmine.SpyObj;
beforeEach(() => {
- apolloSpy = jasmine.createSpyObj('ApolloService', ['graphQLFetch', 'graphQLWatch']);
TestBed.configureTestingModule({
providers: [
{
@@ -34,14 +30,6 @@ describe('AchievementService', () => {
}
})
},
- {
- provide: ApolloService,
- useValue: apolloSpy,
- },
- {
- provide: DemoService,
- useValue: jasmine.createSpyObj('DemoService', ['normalResponse'])
- }
]
});
service = TestBed.inject(AchievementService) as jasmine.SpyObj;
@@ -53,54 +41,49 @@ describe('AchievementService', () => {
});
describe('when testing getAchievements()', () => {
- // graphql response format - achievements are in data.achievements
- const graphqlResponse = {
- data: {
- achievements: [
- {
- id: 1,
- name: 'achieve 1',
- description: 'des',
- badge: '',
- type: 'achievement',
- points: 100,
- isEarned: true,
- earnedDate: '2019-02-02'
- },
- {
- id: 2,
- name: 'achieve 2',
- description: 'des',
- badge: '',
- type: 'achievement',
- points: 200,
- isEarned: false,
- earnedDate: '2019-02-02'
- },
- {
- id: 3,
- name: 'achieve 3',
- description: 'des',
- badge: '',
- type: 'achievement',
- points: 300,
- isEarned: true,
- earnedDate: '2019-02-02'
- },
- {
- id: 4,
- name: 'achieve 4',
- description: 'des',
- badge: '',
- type: 'achievement',
- points: 0,
- isEarned: true,
- earnedDate: '2019-02-02'
- }
- ]
- }
+ const requestResponse = {
+ success: true,
+ data: [
+ {
+ id: 1,
+ name: 'achieve 1',
+ description: 'des',
+ badge: '',
+ points: 100,
+ isEarned: true,
+ earnedDate: '2019-02-02'
+ },
+ {
+ id: 2,
+ name: 'achieve 2',
+ description: 'des',
+ badge: '',
+ points: 200,
+ isEarned: false,
+ earnedDate: '2019-02-02'
+ },
+ {
+ id: 3,
+ name: 'achieve 3',
+ description: 'des',
+ badge: '',
+ points: 300,
+ isEarned: true,
+ earnedDate: '2019-02-02'
+ },
+ {
+ id: 4,
+ name: 'achieve 4',
+ description: 'des',
+ badge: '',
+ points: 0,
+ isEarned: true,
+ earnedDate: '2019-02-02'
+ }
+ ]
};
- const expected = JSON.parse(JSON.stringify(graphqlResponse.data.achievements)).map(res => {
+ const achievements = requestResponse.data[0];
+ const expected = JSON.parse(JSON.stringify(requestResponse.data)).map(res => {
return {
id: res.id,
name: res.name,
@@ -108,37 +91,35 @@ describe('AchievementService', () => {
image: res.badge,
points: res.points,
isEarned: res.isEarned,
- earnedDate: res.earnedDate,
- type: res.type,
- badge: res.badge
+ earnedDate: res.earnedDate
};
});
describe('should throw error', () => {
- let tmpAchievements;
+ let tmpRes;
let errMsg;
beforeEach(() => {
- tmpAchievements = JSON.parse(JSON.stringify(graphqlResponse.data.achievements));
+ tmpRes = JSON.parse(JSON.stringify(requestResponse));
});
afterEach(() => {
- apolloSpy.graphQLFetch.and.returnValue(of({ data: { achievements: tmpAchievements } }));
+ requestSpy.get.and.returnValue(of(tmpRes));
service.getAchievements();
service.achievements$.subscribe();
expect(requestSpy.apiResponseFormatError.calls.count()).toBe(1);
expect(requestSpy.apiResponseFormatError.calls.first().args[0]).toEqual(errMsg);
});
it('Achievement format error', () => {
- tmpAchievements = {}; // not an array
+ tmpRes.data = {};
errMsg = 'Achievement format error';
});
it('Achievement object format error', () => {
- tmpAchievements[0] = {}; // missing required fields
+ tmpRes.data[0] = {};
errMsg = 'Achievement object format error';
});
});
it('should get the correct data', () => {
- apolloSpy.graphQLFetch.and.returnValue(of(graphqlResponse));
+ requestSpy.get.and.returnValue(of(requestResponse));
service.getAchievements();
service.achievements$.subscribe(res => {
expect(res).toEqual(expected);
@@ -152,7 +133,7 @@ describe('AchievementService', () => {
it('should return an array of achievements', (done) => {
const mockResponse = {
data: {
- achievements: [
+ badges: [
{
id: 1,
name: 'Achievement 1',
@@ -185,13 +166,11 @@ describe('AchievementService', () => {
}
};
- // reset the spy for this describe block
- apolloSpy.graphQLFetch.calls.reset();
- apolloSpy.graphQLFetch.and.returnValue(of(mockResponse));
+ spyOn(service['apolloService'], 'graphQLFetch').and.returnValue(of(mockResponse));
service.graphQLGetAchievements().subscribe((achievements) => {
expect(achievements.length).toBe(2);
- expect(achievements).toEqual(mockResponse.data.achievements);
+ expect(achievements).toEqual(mockResponse.data.badges);
done();
});
});
@@ -199,12 +178,11 @@ describe('AchievementService', () => {
it('should return an empty array if no badges are returned', (done) => {
const mockResponse = {
data: {
- achievements: []
+ badges: []
}
};
- apolloSpy.graphQLFetch.calls.reset();
- apolloSpy.graphQLFetch.and.returnValue(of(mockResponse));
+ spyOn(service['apolloService'], 'graphQLFetch').and.returnValue(of(mockResponse));
service.graphQLGetAchievements().subscribe((achievements) => {
expect(achievements.length).toBe(0);
@@ -214,8 +192,7 @@ describe('AchievementService', () => {
});
it('should handle errors gracefully', (done) => {
- apolloSpy.graphQLFetch.calls.reset();
- apolloSpy.graphQLFetch.and.returnValue(of({ data: null }));
+ spyOn(service['apolloService'], 'graphQLFetch').and.returnValue(of({ data: null }));
service.graphQLGetAchievements().subscribe((achievements) => {
expect(achievements.length).toBe(0);
diff --git a/projects/v3/src/app/services/activity.service.spec.ts b/projects/v3/src/app/services/activity.service.spec.ts
index c2b00b795..ae6fdfdc8 100644
--- a/projects/v3/src/app/services/activity.service.spec.ts
+++ b/projects/v3/src/app/services/activity.service.spec.ts
@@ -1,5 +1,4 @@
import { TestBed, fakeAsync, tick, flushMicrotasks } from '@angular/core/testing';
-import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ActivityService } from './activity.service';
import { of, throwError } from 'rxjs';
import { RequestService } from 'request';
@@ -12,9 +11,6 @@ import { TestUtils } from '@testingv3/utils';
import { ApolloService } from './apollo.service';
import { AssessmentService } from './assessment.service';
import { TopicService } from './topic.service';
-import { DemoService } from './demo.service';
-import { SharedService } from './shared.service';
-import { UnlockIndicatorService } from './unlock-indicator.service';
describe('ActivityService', () => {
let service: ActivityService;
@@ -27,7 +23,6 @@ describe('ActivityService', () => {
beforeEach(() => {
TestBed.configureTestingModule({
- imports: [HttpClientTestingModule],
providers: [
ActivityService,
{
@@ -47,7 +42,7 @@ describe('ActivityService', () => {
},
{
provide: BrowserStorageService,
- useValue: jasmine.createSpyObj('BrowserStorageService', ['getUser', 'getReferrer', 'get', 'set'])
+ useValue: jasmine.createSpyObj('BrowserStorageService', ['getUser', 'getReferrer'])
},
{
provide: Router,
@@ -63,23 +58,11 @@ describe('ActivityService', () => {
},
{
provide: TopicService,
- useValue: jasmine.createSpyObj('TopicService', ['getTopic']),
+ useValue: jasmine.createSpyObj('TopicService', ['']),
},
{
provide: AssessmentService,
- useValue: jasmine.createSpyObj('AssessmentService', ['getAssessment']),
- },
- {
- provide: DemoService,
- useValue: jasmine.createSpyObj('DemoService', ['normalResponse'])
- },
- {
- provide: SharedService,
- useValue: jasmine.createSpyObj('SharedService', ['getTeamInfo', 'getTeamMembers'])
- },
- {
- provide: UnlockIndicatorService,
- useValue: jasmine.createSpyObj('UnlockIndicatorService', ['loadFromStorage', 'clearAllTasks', 'addTask', 'removeTask'])
+ useValue: jasmine.createSpyObj('AssessmentService', ['']),
},
]
});
@@ -103,7 +86,6 @@ describe('ActivityService', () => {
id: 1,
name: 'activity',
description: 'des',
- unlockConditions: [],
tasks: [
{
id: 1,
@@ -165,7 +147,6 @@ describe('ActivityService', () => {
id: activity.id,
name: activity.name,
description: activity.description,
- unlockConditions: [],
tasks: [
{
id: 0,
diff --git a/projects/v3/src/app/services/apollo.service.spec.ts b/projects/v3/src/app/services/apollo.service.spec.ts
index 66bf9645d..344b8f87d 100644
--- a/projects/v3/src/app/services/apollo.service.spec.ts
+++ b/projects/v3/src/app/services/apollo.service.spec.ts
@@ -13,7 +13,6 @@ describe('ApolloService', () => {
provide: Apollo,
useValue: jasmine.createSpyObj('Apollo', [
'create',
- 'createDefault',
'getClient',
'watchQuery',
'mutate',
@@ -37,21 +36,4 @@ describe('ApolloService', () => {
const service: ApolloService = TestBed.inject(ApolloService);
expect(service).toBeTruthy();
});
-
- describe('initiateCoreClient()', () => {
- it('should not throw when apollo client is not yet defined', () => {
- const service: ApolloService = TestBed.inject(ApolloService);
- const apollo: any = TestBed.inject(Apollo);
- // mock client getter to throw (as real Apollo does before initialization)
- apollo.client = undefined;
- Object.defineProperty(apollo, 'client', {
- get: () => { throw new Error('Client has not been defined yet'); },
- configurable: true,
- });
- // add createDefault as a simple spy since defineProperty may interfere with the original spy
- apollo.createDefault = jasmine.createSpy('createDefault');
- expect(() => service.initiateCoreClient()).not.toThrow();
- expect(apollo.createDefault).toHaveBeenCalled();
- });
- });
});
diff --git a/projects/v3/src/app/services/apollo.service.ts b/projects/v3/src/app/services/apollo.service.ts
index f6b784e45..9b5d745ef 100644
--- a/projects/v3/src/app/services/apollo.service.ts
+++ b/projects/v3/src/app/services/apollo.service.ts
@@ -62,13 +62,9 @@ export class ApolloService {
* @returns boolean
*/
private _hasInitiated(): boolean {
- try {
- if (this.apollo.client
- && this._url === environment.graphQL) {
- return true;
- }
- } catch {
- // apollo client not yet created
+ if (this.apollo.client
+ && this._url === environment.graphQL) {
+ return true;
}
return false;
}
diff --git a/projects/v3/src/app/services/assessment.service.spec.ts b/projects/v3/src/app/services/assessment.service.spec.ts
index cc300b789..564ae7cba 100644
--- a/projects/v3/src/app/services/assessment.service.spec.ts
+++ b/projects/v3/src/app/services/assessment.service.spec.ts
@@ -12,7 +12,6 @@ describe('AssessmentService', () => {
let service: AssessmentService;
let requestSpy: jasmine.SpyObj;
let notificationSpy: jasmine.SpyObj;
- let apolloSpy: jasmine.SpyObj;
let utils: UtilsService;
beforeEach(() => {
@@ -25,7 +24,7 @@ describe('AssessmentService', () => {
},
{
provide: NotificationsService,
- useValue: jasmine.createSpyObj('NotificationsService', ['modal', 'markTodoItemAsDone'])
+ useValue: jasmine.createSpyObj('NotificationsService', ['modal'])
},
{
provide: RequestService,
@@ -44,14 +43,13 @@ describe('AssessmentService', () => {
},
{
provide: ApolloService,
- useValue: jasmine.createSpyObj('ApolloService', ['graphQLMutate', 'graphQLWatch', 'graphQLFetch'])
+ useValue: jasmine.createSpyObj('ApolloService', ['graphQLMutate', 'graphQLWatch'])
},
]
});
service = TestBed.inject(AssessmentService);
requestSpy = TestBed.inject(RequestService) as jasmine.SpyObj;
notificationSpy = TestBed.inject(NotificationsService) as jasmine.SpyObj;
- apolloSpy = TestBed.inject(ApolloService) as jasmine.SpyObj;
utils = TestBed.inject(UtilsService);
});
@@ -74,7 +72,6 @@ describe('AssessmentService', () => {
isTeam: false,
dueDate: '2019-02-02',
pulseCheck: false,
- allowResubmit: false,
groups: [
{
name: 'g name',
@@ -170,7 +167,7 @@ describe('AssessmentService', () => {
submissions: [
{
id: 1,
- status: 'published',
+ status: 'feedback available',
modified: '2019-02-02',
locked: false,
completed: false,
@@ -265,8 +262,6 @@ describe('AssessmentService', () => {
dueDate: assessment.dueDate,
isOverdue: assessment.dueDate ? utils.timeComparer(assessment.dueDate) < 0 : false,
pulseCheck: assessment.pulseCheck,
- hasReviewRating: assessment.hasReviewRating,
- allowResubmit: assessment.allowResubmit,
groups: [
{
name: group0.name,
@@ -281,8 +276,6 @@ describe('AssessmentService', () => {
canComment: question0.hasComment,
canAnswer: question0.audience.includes('submitter'),
audience: question0.audience,
- min: undefined,
- max: undefined,
submitterOnly: true,
reviewerOnly: false
},
@@ -295,8 +288,6 @@ describe('AssessmentService', () => {
canComment: question1.hasComment,
canAnswer: question1.audience.includes('submitter'),
audience: question1.audience,
- min: undefined,
- max: undefined,
submitterOnly: false,
reviewerOnly: true,
info: '',
@@ -322,8 +313,6 @@ describe('AssessmentService', () => {
canComment: question2.hasComment,
canAnswer: question2.audience.includes('submitter'),
audience: question2.audience,
- min: undefined,
- max: undefined,
submitterOnly: false,
reviewerOnly: false,
info: `Choice Description:
${question2.choices[0].name} ` +
@@ -357,8 +346,6 @@ describe('AssessmentService', () => {
canComment: question3.hasComment,
canAnswer: question3.audience.includes('submitter'),
audience: question3.audience,
- min: undefined,
- max: undefined,
submitterOnly: false,
reviewerOnly: false,
fileType: question3.fileType
@@ -372,8 +359,6 @@ describe('AssessmentService', () => {
canComment: question4.hasComment,
canAnswer: question4.audience.includes('submitter'),
audience: question4.audience,
- min: undefined,
- max: undefined,
submitterOnly: false,
reviewerOnly: false,
teamMembers: [
@@ -394,7 +379,7 @@ describe('AssessmentService', () => {
submission = assessment.submissions[0];
expectedSubmission = {
id: submission.id,
- status: 'feedback available',
+ status: submission.status,
submitterName: submission.submitter.name,
submitterImage: submission.submitter.image,
modified: submission.modified,
@@ -412,8 +397,7 @@ describe('AssessmentService', () => {
answer: submission.answers[2].answer
},
11: {
- // file type answers normalize empty strings to null
- answer: null
+ answer: submission.answers[3].answer
},
12: {
answer: submission.answers[4].answer
@@ -426,7 +410,6 @@ describe('AssessmentService', () => {
status: review.status,
modified: review.modified,
teamName: submission.submitter.team.name,
- projectBrief: null,
answers: {
1: {
answer: review.answers[0].answer,
@@ -453,7 +436,7 @@ describe('AssessmentService', () => {
});
afterEach(() => {
- apolloSpy.graphQLFetch.and.returnValue(of(requestResponse));
+ apolloSpy.graphQLWatch.and.returnValue(of(requestResponse));
service.getAssessment(1, 'assessment', 2, 3);
service.assessment$.subscribe(assessment => {
expect(assessment).toEqual(expectedAssessment);
@@ -464,7 +447,7 @@ describe('AssessmentService', () => {
service.review$.subscribe(review => {
expect(review).toEqual(expectedReview);
});
- expect(apolloSpy.graphQLFetch.calls.count()).toBe(1);
+ expect(apolloSpy.graphQLWatch.calls.count()).toBe(1);
});
it(`should not include a question group if there's no question inside`, () => {
@@ -476,6 +459,9 @@ describe('AssessmentService', () => {
expectedAssessment.groups.splice(1, 1);
delete expectedSubmission.answers[11];
delete expectedSubmission.answers[12];
+ delete expectedReview.answers[1];
+ delete expectedReview.answers[2];
+ delete expectedReview.answers[3];
delete expectedReview.answers[11];
delete expectedReview.answers[12];
});
@@ -565,11 +551,12 @@ describe('AssessmentService', () => {
describe('when testing saveFeedbackReviewed()', () => {
it('should post correct data', () => {
- notificationSpy.markTodoItemAsDone.and.returnValue(of(true));
service.saveFeedbackReviewed(11);
- expect(notificationSpy.markTodoItemAsDone.calls.count()).toBe(1);
- expect(notificationSpy.markTodoItemAsDone.calls.first().args[0]).toEqual({
+ expect(requestSpy.post.calls.count()).toBe(1);
+ expect(requestSpy.post.calls.first().args[0].data).toEqual({
+ project_id: 1,
identifier: 'AssessmentSubmission-11',
+ is_done: true
});
});
});
@@ -609,8 +596,7 @@ describe('AssessmentService', () => {
it('should handle non-array string by wrapping it in an array for multiple question type', () => {
const result = service['_normaliseAnswer'](2, 'not an array');
- // non-numeric strings convert to NaN when the code attempts to convert to numbers
- expect(result).toEqual([NaN]);
+ expect(result).toEqual(['not an array']);
});
it('should parse string to array for multi team member selector question type', () => {
@@ -828,19 +814,16 @@ describe('AssessmentService', () => {
});
// Verify review answers normalization
- // Note: When answer is null and no file exists, the expression (answer || file) evaluates to undefined
- expect(result.review.answers[1].answer).toBeUndefined();
+ expect(result.review.answers[1].answer).toBeNull();
expect(result.review.answers[1].comment).toBe('Good answer');
expect(result.review.answers[2].answer).toBe(22);
expect(result.review.answers[2].comment).toBe('Consider the other option');
- // file is normalized and stored as answer, not as separate file property
- expect(result.review.answers[4].answer).toEqual({
+ expect(result.review.answers[4].file).toEqual({
name: 'feedback.jpg',
url: 'http://example.com/feedback.jpg',
type: 'image/jpeg',
size: 1024
});
- expect(result.review.answers[4].comment).toBe('Clear image');
done();
});
@@ -891,38 +874,34 @@ describe('AssessmentService', () => {
it('should handle different types of answers in _normaliseAnswer', (done) => {
// Modify the mock response to test various answer formats
- // Note: only one answer per questionId since the service uses questionId as key
- // Using question IDs from the mock: 1 (text), 2 (oneof), 3 (multiple), 11 (file)
mockResponse.data.assessment.submissions[0].answers = [
- { questionId: 1, answer: 'some text' }, // Non-empty text (empty string becomes undefined due to || logic)
+ { questionId: 1, answer: '' }, // Empty string for text
{ questionId: 2, answer: '22' }, // String that should be converted to number for oneof
- { questionId: 3, answer: '[31, 32]' }, // Multi-item array as string for multiple
- { questionId: 11, file: null } // Null file (question 11 is the file type)
+ { questionId: 3, answer: '[]' }, // Empty array as string for multiple
+ { questionId: 3, answer: '[31]' }, // Single item array as string
+ { questionId: 3, answer: '[31, 32]' }, // Multi-item array as string
+ { questionId: 4, file: null } // Null file
];
service.fetchAssessment(1, 'assessment', 5, 10).subscribe(result => {
- // Text question - answer should remain as is
- expect(result.submission.answers[1].answer).toBe('some text');
+ // Text question - empty answer should remain empty string
+ expect(result.submission.answers[1].answer).toBe('');
// Oneof question - string should be converted to number
expect(result.submission.answers[2].answer).toBe(22);
- // Multiple question - array string should be parsed to array of numbers
- expect(result.submission.answers[3].answer).toEqual([31, 32]);
-
- // File question - null file should result in null (question 11 is file type)
- expect(result.submission.answers[11].answer).toBeNull();
+ // Multiple question - empty array string should be parsed to empty array
+ expect(result.submission.answers[3].answer).toEqual([]);
done();
});
});
it('should handle file answers correctly', (done) => {
- // Modify the mock to include a file answer in the submission
- // Using question ID 11 which is the file type question
+ // Modify the mock to include a file answer in the review
mockResponse.data.assessment.submissions[0].answers = [
{
- questionId: 11,
+ questionId: 4,
file: {
name: 'submission.pdf',
url: 'http://example.com/submission.pdf',
@@ -932,8 +911,8 @@ describe('AssessmentService', () => {
];
service.fetchAssessment(1, 'assessment', 5, 10).subscribe(result => {
- // File should be normalized properly in submission (question 11 is file type)
- expect(result.submission.answers[11].answer).toEqual({
+ // File should be normalized properly in submission
+ expect(result.submission.answers[4].answer).toEqual({
name: 'submission.pdf',
url: 'http://example.com/submission.pdf',
type: 'application/pdf'
diff --git a/projects/v3/src/app/services/auth.service.spec.ts b/projects/v3/src/app/services/auth.service.spec.ts
index cabc7d17f..c80b544b3 100644
--- a/projects/v3/src/app/services/auth.service.spec.ts
+++ b/projects/v3/src/app/services/auth.service.spec.ts
@@ -9,8 +9,6 @@ import { UtilsService } from '@v3/services/utils.service';
import { NotificationsService } from './notifications.service';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { ApolloService } from './apollo.service';
-import { DemoService } from './demo.service';
-import { UnlockIndicatorService } from './unlock-indicator.service';
describe('AuthService', () => {
@@ -29,10 +27,6 @@ describe('AuthService', () => {
imports: [HttpClientTestingModule],
providers: [
AuthService,
- {
- provide: DemoService,
- useValue: jasmine.createSpyObj('DemoService', { 'isDemoMode': false }),
- },
{
provide: RequestService,
useValue: jasmine.createSpyObj('RequestService', [
@@ -65,7 +59,7 @@ describe('AuthService', () => {
'setUser', 'getUser',
'set', 'getConfig',
'setConfig', 'get',
- 'clear', 'remove',
+ 'clear',
]),
},
{
@@ -77,10 +71,6 @@ describe('AuthService', () => {
useValue: jasmine.createSpyObj('PusherService', ['unsubscribeChannels', 'disconnect'])
},
{ provide: NotificationsService, useValue: notificationsSpy },
- {
- provide: UnlockIndicatorService,
- useValue: jasmine.createSpyObj('UnlockIndicatorService', ['clearAllTasks', 'loadFromStorage']),
- },
]
});
service = TestBed.inject(AuthService);
@@ -102,95 +92,57 @@ describe('AuthService', () => {
});
it('when testing directLogin(), it should pass the correct data to API', () => {
- const apolloSpy = TestBed.inject(ApolloService) as jasmine.SpyObj;
- apolloSpy.graphQLFetch.and.returnValue(of({
+ requestSpy.post.and.returnValue(of({
+ success: true,
data: {
- auth: {
- apikey: '123456',
- experience: {
- id: 1,
- uuid: 'test-uuid',
- timelineId: 1,
- projectId: 1,
- name: 'Test Experience',
- description: 'Test',
- type: 'normal',
- leadImage: '',
- status: 'active',
- setupStep: '',
- color: '#abc',
- secondaryColor: '#def',
- role: 'participant',
- isLast: false,
- locale: 'en',
- supportName: '',
- supportEmail: '',
- cardUrl: '',
- bannerUrl: '',
- logoUrl: '',
- iconUrl: '',
- reviewRating: false,
- truncateDescription: false,
- team: { id: 1 },
- featureToggle: { pulseCheckIndicator: false }
- },
- email: 'test@test.com',
- unregistered: false,
- activationCode: null
- }
+ tutorial: null,
+ apikey: '123456',
+ Timelines: [
+ {
+ Program: {
+ config: {
+ theme_color: 'abc'
+ }
+ },
+ Enrolment: {},
+ Project: {},
+ Timeline: {}
+ }
+ ]
}
}));
storageSpy.getConfig.and.returnValue(true);
service.authenticate({ authToken: 'abcd' }).subscribe();
- expect(apolloSpy.graphQLFetch.calls.count()).toBe(1);
- expect(apolloSpy.graphQLFetch.calls.first().args[1]?.variables?.authToken).toEqual('abcd');
+ expect(requestSpy.post.calls.count()).toBe(1);
+ expect(requestSpy.post.calls.first().args[0].data).toContain('abcd');
+ expect(storageSpy.setUser.calls.first().args[0]).toEqual({ apikey: '123456' });
});
it('when testing globalLogin(), it should pass the correct data to API', () => {
- const apolloSpy = TestBed.inject(ApolloService) as jasmine.SpyObj;
- apolloSpy.graphQLFetch.and.returnValue(of({
+ requestSpy.post.and.returnValue(of({
+ success: true,
data: {
- auth: {
- apikey: '123456',
- experience: {
- id: 1,
- uuid: 'test-uuid',
- timelineId: 1,
- projectId: 1,
- name: 'Test Experience',
- description: 'Test',
- type: 'normal',
- leadImage: '',
- status: 'active',
- setupStep: '',
- color: '#abc',
- secondaryColor: '#def',
- role: 'participant',
- isLast: false,
- locale: 'en',
- supportName: '',
- supportEmail: '',
- cardUrl: '',
- bannerUrl: '',
- logoUrl: '',
- iconUrl: '',
- reviewRating: false,
- truncateDescription: false,
- team: { id: 1 },
- featureToggle: { pulseCheckIndicator: false }
- },
- email: 'test@test.com',
- unregistered: false,
- activationCode: null
- }
+ tutorial: null,
+ apikey: '123456',
+ Timelines: [
+ {
+ Program: {
+ config: {
+ theme_color: 'abc'
+ }
+ },
+ Enrolment: {},
+ Project: {},
+ Timeline: {}
+ }
+ ]
}
}));
storageSpy.getConfig.and.returnValue(true);
service.authenticate({ apikey: 'abcd', service: 'LOGIN' }).subscribe();
- expect(apolloSpy.graphQLFetch.calls.count()).toBe(1);
- expect(apolloSpy.graphQLFetch.calls.first().args[1]?.context?.headers?.apikey).toEqual('abcd');
- expect(apolloSpy.graphQLFetch.calls.first().args[1]?.context?.headers?.service).toEqual('LOGIN');
- expect(storageSpy.setUser.calls.first().args[0]).toEqual({ apikey: 'abcd' });
+ expect(requestSpy.post.calls.count()).toBe(1);
+ expect(requestSpy.post.calls.first().args[0].data).toContain('abcd');
+ expect(storageSpy.setUser.calls.first().args[0]).toEqual({ apikey: '123456' });
});
describe('when testing isAuthenticated()', () => {
@@ -280,16 +232,19 @@ describe('AuthService', () => {
{ id: 2, name: 'Experience 2' },
],
};
- requestSpy.get.and.returnValue(of(responseData));
spyOn(service, 'isAuthenticated').and.returnValue(true);
service.getConfig(configParams).subscribe(response => {
expect(response).toEqual(responseData);
});
- expect(requestSpy.get.calls.count()).toBe(1);
- expect(requestSpy.get.calls.first().args[0]).toEqual('api/v2/plan/experience/list');
- expect(requestSpy.get.calls.first().args[1]).toEqual({ params: configParams });
+ const req = httpTestingController.expectOne('api/v2/plan/experience/list');
+ expect(req.request.method).toEqual('GET');
+
+ req.flush(responseData);
+
+ expect(service.isAuthenticated).not.toHaveBeenCalled();
+ expect(notificationsService.alert).not.toHaveBeenCalled();
});
it('when testing checkDomain()', () => {
diff --git a/projects/v3/src/app/services/chat.service.spec.ts b/projects/v3/src/app/services/chat.service.spec.ts
index 5d1d1caaa..34cc8c402 100644
--- a/projects/v3/src/app/services/chat.service.spec.ts
+++ b/projects/v3/src/app/services/chat.service.spec.ts
@@ -231,11 +231,11 @@ describe('ChatService', () => {
expect(message.message).toEqual(messageListRequestResponse.data.channel.chatLogsConnection.chatLogs[i].message);
expect(message.created).toEqual(messageListRequestResponse.data.channel.chatLogsConnection.chatLogs[i].created);
expect(message.file).toEqual(messageListRequestResponse.data.channel.chatLogsConnection.chatLogs[i].file);
- expect((message as any).fileObject).toBeDefined();
+ expect(message.fileObject).toBeDefined();
if ((typeof messageListRequestResponse.data.channel.chatLogsConnection.chatLogs[i].file) === 'string') {
- expect((message as any).fileObject).toEqual(fileJson);
+ expect(message.fileObject).toEqual(fileJson);
} else {
- expect((message as any).fileObject).toEqual(messageListRequestResponse.data.channel.chatLogsConnection.chatLogs[i].file);
+ expect(message.fileObject).toEqual(messageListRequestResponse.data.channel.chatLogsConnection.chatLogs[i].file);
}
});
}
@@ -348,7 +348,7 @@ describe('ChatService', () => {
{
message: 'test message',
channelUuid: '10',
- fileObj: undefined
+ file: undefined
}
));
});
@@ -357,15 +357,12 @@ describe('ChatService', () => {
const attachmentMessageParam = {
message: 'test message',
channelUuid: '10',
- file: {
- path: '/path/to/file',
- bucket: 'file-bucket',
- name: 'unnamed.jpg',
+ file: JSON.stringify({
+ filename: 'unnamed.jpg',
+ mimetype: 'image/jpeg',
url: 'https://cdn.filestackcontent.com/X8Cj0Y4QS2AmDUZX6LSq',
- extension: 'jpg',
- type: 'image/jpeg',
- size: 12345
- }
+ status: 'Stored'
+ })
};
const newMessageRes = {
data: {
@@ -396,13 +393,19 @@ describe('ChatService', () => {
status: 'Stored'
};
apolloSpy.graphQLMutate.and.returnValue(of(newMessageRes));
- service.postNewMessage(attachmentMessageParam).subscribe(
+ service.postAttachmentMessage(attachmentMessageParam).subscribe(
message => {
expect(message.uuid).toEqual(newMessageRes.data.createChatLog.uuid);
expect(message.isSender).toEqual(newMessageRes.data.createChatLog.isSender);
expect(message.message).toEqual(newMessageRes.data.createChatLog.message);
expect(message.created).toEqual(newMessageRes.data.createChatLog.created);
expect(message.file).toEqual(newMessageRes.data.createChatLog.file);
+ expect(message.fileObject).toBeDefined();
+ if ((typeof newMessageRes.data.createChatLog.file) === 'string') {
+ expect(message.fileObject).toEqual(fileJson);
+ } else {
+ expect(message.fileObject).toEqual(newMessageRes.data.createChatLog.file);
+ }
}
);
expect(apolloSpy.graphQLMutate.calls.count()).toBe(1);
@@ -410,7 +413,12 @@ describe('ChatService', () => {
{
message: 'test message',
channelUuid: '10',
- fileObj: attachmentMessageParam.file
+ file: JSON.stringify({
+ filename: 'unnamed.jpg',
+ mimetype: 'image/jpeg',
+ url: 'https://cdn.filestackcontent.com/X8Cj0Y4QS2AmDUZX6LSq',
+ status: 'Stored'
+ })
}
));
});
diff --git a/projects/v3/src/app/services/event.service.spec.ts b/projects/v3/src/app/services/event.service.spec.ts
index 866371c8a..6038b87e8 100644
--- a/projects/v3/src/app/services/event.service.spec.ts
+++ b/projects/v3/src/app/services/event.service.spec.ts
@@ -6,7 +6,7 @@ import { UtilsService } from '@v3/services/utils.service';
import { NotificationsService } from '@v3/services/notifications.service';
import { TestUtils } from '@testingv3/utils';
import { BrowserStorageService } from '@v3/services/storage.service';
-import moment from 'moment';
+import * as moment from 'moment';
describe('EventService', () => {
moment.updateLocale('en', {
diff --git a/projects/v3/src/app/services/experience.service.spec.ts b/projects/v3/src/app/services/experience.service.spec.ts
index 8792559e3..62bea2f23 100644
--- a/projects/v3/src/app/services/experience.service.spec.ts
+++ b/projects/v3/src/app/services/experience.service.spec.ts
@@ -2,7 +2,6 @@ import { TestBed } from '@angular/core/testing';
import { TestUtils } from '@testingv3/utils';
import { RequestService } from 'request';
import { ApolloService } from './apollo.service';
-import { AuthService } from './auth.service';
import { DemoService } from './demo.service';
import { EventService } from './event.service';
@@ -29,35 +28,31 @@ describe('ExperienceService', () => {
},
{
provide: ApolloService,
- useValue: jasmine.createSpyObj('ApolloService', ['graphQLFetch', 'graphQLMutate', 'graphQLWatch']),
+ useValue: jasmine.createSpyObj('ApolloService', ['']),
},
{
provide: SharedService,
- useValue: jasmine.createSpyObj('SharedService', ['getConfig']),
+ useValue: jasmine.createSpyObj('SharedService', ['']),
},
{
provide: BrowserStorageService,
- useValue: jasmine.createSpyObj('BrowserStorageService', ['get', 'set', 'getUser', 'getConfig']),
+ useValue: jasmine.createSpyObj('BrowserStorageService', ['']),
},
{
provide: RequestService,
- useValue: jasmine.createSpyObj('RequestService', ['get', 'post']),
+ useValue: jasmine.createSpyObj('RequestService', ['']),
},
{
provide: EventService,
- useValue: jasmine.createSpyObj('EventService', ['trigger', 'listen']),
+ useValue: jasmine.createSpyObj('EventService', ['']),
},
{
provide: ReviewService,
- useValue: jasmine.createSpyObj('ReviewService', ['getReviews']),
+ useValue: jasmine.createSpyObj('ReviewService', ['']),
},
{
provide: HomeService,
- useValue: jasmine.createSpyObj('HomeService', ['getTodoItems']),
- },
- {
- provide: AuthService,
- useValue: jasmine.createSpyObj('AuthService', ['getConfig']),
+ useValue: jasmine.createSpyObj('HomeService', ['']),
},
],
});
diff --git a/projects/v3/src/app/services/fast-feedback.service.spec.ts b/projects/v3/src/app/services/fast-feedback.service.spec.ts
index 53418ca22..2d70f40ef 100644
--- a/projects/v3/src/app/services/fast-feedback.service.spec.ts
+++ b/projects/v3/src/app/services/fast-feedback.service.spec.ts
@@ -7,7 +7,6 @@ import { BrowserStorageService } from '@v3/services/storage.service';
import { UtilsService } from '@v3/services/utils.service';
import { DemoService } from './demo.service';
import { ApolloService } from './apollo.service';
-import { AlertController } from '@ionic/angular';
// helper to build a valid pulse check API response
function makePulseCheckResponse(questions: any[] = [], meta: any = null) {
@@ -63,10 +62,6 @@ describe('FastFeedbackService', () => {
provide: DemoService,
useValue: jasmine.createSpyObj('DemoService', ['fastFeedback', 'normalResponse']),
},
- {
- provide: AlertController,
- useValue: jasmine.createSpyObj('AlertController', ['create']),
- },
]
});
service = TestBed.inject(FastFeedbackService);
@@ -88,10 +83,12 @@ describe('FastFeedbackService', () => {
describe('when testing pullFastFeedback()', () => {
it('should open modal and set lock when pulse check data is valid', () => {
apolloSpy.graphQLFetch.and.returnValue(of(makePulseCheckResponse(VALID_QUESTIONS, VALID_META)));
- storageSpy.get.and.returnValue(false);
+ storageSpy.get.and.returnValue(false); // fastFeedbackOpening = false
service.pullFastFeedback().subscribe(() => {
+ // should set fastFeedbackOpening = true
expect(storageSpy.set).toHaveBeenCalledWith('fastFeedbackOpening', true);
+ // should call fastFeedbackModal
expect(notificationSpy.fastFeedbackModal).toHaveBeenCalledTimes(1);
});
});
@@ -103,6 +100,7 @@ describe('FastFeedbackService', () => {
service.pullFastFeedback().subscribe();
tick();
+ // lock is set to true and never released by the service
const setCalls = storageSpy.set.calls.allArgs();
const lockCalls = setCalls.filter(args => args[0] === 'fastFeedbackOpening');
expect(lockCalls.length).toBe(1);
@@ -111,7 +109,7 @@ describe('FastFeedbackService', () => {
it('should not open modal when fastFeedbackOpening is already true', () => {
apolloSpy.graphQLFetch.and.returnValue(of(makePulseCheckResponse(VALID_QUESTIONS, VALID_META)));
- storageSpy.get.and.returnValue(true);
+ storageSpy.get.and.returnValue(true); // lock already held
service.pullFastFeedback().subscribe(() => {
expect(notificationSpy.fastFeedbackModal).not.toHaveBeenCalled();
@@ -151,10 +149,11 @@ describe('FastFeedbackService', () => {
notificationSpy.fastFeedbackModal.and.returnValue(Promise.reject('modal error'));
service.pullFastFeedback().subscribe();
- tick();
+ tick(); // resolve rejected promise
const setCalls = storageSpy.set.calls.allArgs();
const lockCalls = setCalls.filter(args => args[0] === 'fastFeedbackOpening');
+ // first set to true, then released to false on error
expect(lockCalls).toEqual([
['fastFeedbackOpening', true],
['fastFeedbackOpening', false],
diff --git a/projects/v3/src/app/services/fast-feedback.service.ts b/projects/v3/src/app/services/fast-feedback.service.ts
index 0d18d2140..1b6d02032 100644
--- a/projects/v3/src/app/services/fast-feedback.service.ts
+++ b/projects/v3/src/app/services/fast-feedback.service.ts
@@ -1,4 +1,4 @@
-import { Inject, Injectable, forwardRef } from '@angular/core';
+import { Injectable } from '@angular/core';
import { NotificationsService } from './notifications.service';
import { BrowserStorageService } from '@v3/services/storage.service';
import { UtilsService } from '@v3/services/utils.service';
@@ -18,8 +18,7 @@ export class FastFeedbackService {
private currentPulseCheckId: string = null; // temporary store active pulse check ID
constructor(
- // type is 'any' to prevent design:paramtypes metadata from accessing NotificationsService during module evaluation (circular dependency)
- @Inject(forwardRef(() => NotificationsService)) private notificationsService: any,
+ private notificationsService: NotificationsService,
private storage: BrowserStorageService,
private utils: UtilsService,
private demo: DemoService,
diff --git a/projects/v3/src/app/services/filestack.service.spec.ts b/projects/v3/src/app/services/filestack.service.spec.ts
index 136d1dd9b..f64393d02 100644
--- a/projects/v3/src/app/services/filestack.service.spec.ts
+++ b/projects/v3/src/app/services/filestack.service.spec.ts
@@ -130,13 +130,6 @@ describe('FilestackService', () => {
it('should popup file preview', fakeAsync(() => {
spyOn(service, 'metadata').and.returnValue(Promise.resolve({ mimetype: 'testing/format' }));
service.previewFile({
- bucket: 'test-bucket',
- path: 'test-path',
- name: 'test-file',
- url: 'https://example.com/test.jpg',
- extension: 'jpg',
- type: 'image/jpeg',
- size: 1000,
handle: 'testingHandleValue'
}).then();
flushMicrotasks();
@@ -146,13 +139,7 @@ describe('FilestackService', () => {
it('should popup file preview (support older URL format)', fakeAsync(() => {
spyOn(service, 'metadata').and.returnValue(Promise.resolve({ mimetype: 'testing/format' }));
service.previewFile({
- bucket: 'test-bucket',
- path: 'test-path',
- name: 'test-file',
url: 'www.filepicker.io/api/file',
- extension: 'jpg',
- type: 'image/jpeg',
- size: 1000,
handle: 'testingHandleValue'
}).then();
flushMicrotasks();
@@ -162,13 +149,7 @@ describe('FilestackService', () => {
it('should popup file preview (support older URL format 2)', fakeAsync(() => {
spyOn(service, 'metadata').and.returnValue(Promise.resolve({ mimetype: 'testing/format' }));
service.previewFile({
- bucket: 'test-bucket',
- path: 'test-path',
- name: 'test-file',
url: 'filestackcontent.com',
- extension: 'jpg',
- type: 'image/jpeg',
- size: 1000,
handle: 'testingHandleValue'
}).then();
flushMicrotasks();
@@ -182,13 +163,7 @@ describe('FilestackService', () => {
}));
service.previewFile({
- bucket: 'test-bucket',
- path: 'test-path',
- name: 'test-file',
url: 'filestackcontent.com',
- extension: 'pdf',
- type: 'application/pdf',
- size: 11 * 1000 * 1000, // 11mb
handle: 'testingHandleValue'
}).then();
flushMicrotasks();
@@ -266,10 +241,14 @@ describe('FilestackService', () => {
describe('previewModal()', () => {
it('should pop up modal for provided filestack link', fakeAsync(() => {
- service.previewModal('test.com');
+ let result;
+ service.previewModal('test.com').then(res => {
+ result = res;
+ });
flushMicrotasks();
expect(modalctrlSpy.create).toHaveBeenCalled();
+ expect(result).toEqual(MODAL_SAMPLE);
}));
});
@@ -319,30 +298,23 @@ describe('FilestackService', () => {
describe('onFileSelectedRename()', () => {
it('should rename file with spacing', fakeAsync(() => {
+ let result: any;
const currentFile = {
- bucket: 'test-bucket',
- path: 'test-path',
- name: 'a b c',
- url: 'http://example.com/a-b-c',
- extension: 'jpg',
- type: 'image/jpeg',
- size: 1000,
filename: 'a b c',
handle: 'a-b-c',
mimetype: 'mimetype',
originalPath: 'here',
+ size: 1,
source: 'earth',
uploadId: '12345',
- alt: ''
+ url: 'https://test.com',
};
-
- // Since onFileSelectedRename is always returning a Promise now
- let result;
service['onFileSelectedRename'](currentFile).then(res => {
- result = res;
- expect(result.filename).toEqual('a_b_c');
+ result = res.filename;
});
+
flushMicrotasks();
+ expect(result).toEqual('a_b_c');
}));
});
});
diff --git a/projects/v3/src/app/services/home.service.spec.ts b/projects/v3/src/app/services/home.service.spec.ts
index 27f048f77..1cf80c11e 100644
--- a/projects/v3/src/app/services/home.service.spec.ts
+++ b/projects/v3/src/app/services/home.service.spec.ts
@@ -1,46 +1,20 @@
import { TestBed } from '@angular/core/testing';
import { of } from 'rxjs';
import { ApolloService } from './apollo.service';
+
import { HomeService } from './home.service';
-import { NotificationsService } from './notifications.service';
-import { AuthService } from './auth.service';
-import { BrowserStorageService } from './storage.service';
-import { UtilsService } from './utils.service';
-import { DemoService } from './demo.service';
-import { TestUtils } from '@testingv3/utils';
describe('HomeService', () => {
let service: HomeService;
let apolloService: jasmine.SpyObj;
beforeEach(() => {
- apolloService = jasmine.createSpyObj('ApolloService', ['graphQLWatch', 'graphQLFetch']);
+ apolloService = jasmine.createSpyObj('ApolloService', ['graphQLWatch']);
TestBed.configureTestingModule({
providers: [
- HomeService,
{
provide: ApolloService,
useValue: apolloService,
- },
- {
- provide: NotificationsService,
- useValue: jasmine.createSpyObj('NotificationsService', ['presentToast', 'alert', 'modal'])
- },
- {
- provide: AuthService,
- useValue: jasmine.createSpyObj('AuthService', ['getConfig'])
- },
- {
- provide: BrowserStorageService,
- useValue: jasmine.createSpyObj('BrowserStorageService', ['getUser', 'get', 'set'])
- },
- {
- provide: UtilsService,
- useClass: TestUtils
- },
- {
- provide: DemoService,
- useValue: jasmine.createSpyObj('DemoService', ['normalResponse'])
}
]
});
@@ -69,9 +43,6 @@ describe('HomeService', () => {
it('should return an observable with pulseCheckSkills data', (done) => {
const mockResponse = {
- success: true,
- status: 'success',
- cache: false,
data: {
pulseCheckSkills: [
{ id: 1, name: 'Skill A', value: 5 },
diff --git a/projects/v3/src/app/services/hubspot.service.spec.ts b/projects/v3/src/app/services/hubspot.service.spec.ts
index 751f1a2ec..b36082566 100644
--- a/projects/v3/src/app/services/hubspot.service.spec.ts
+++ b/projects/v3/src/app/services/hubspot.service.spec.ts
@@ -1,11 +1,12 @@
import { TestBed } from '@angular/core/testing';
-import { of } from 'rxjs';
import { HubspotService } from './hubspot.service';
import { RequestService } from 'request';
-import { UtilsService } from '@v3/services/utils.service';
+import { map } from 'rxjs/operators';
+import { Observable, of } from 'rxjs';
+import { environment } from '@v3/environments/environment';
+import { TestUtils } from '@testingv3/utils';
import { BrowserStorageService } from '@v3/services/storage.service';
-import { ModalController } from '@ionic/angular';
-import { DemoService } from './demo.service';
+import { UtilsService } from './utils.service';
describe('HubspotService', () => {
let service: HubspotService;
@@ -19,7 +20,8 @@ describe('HubspotService', () => {
HubspotService,
{
provide: UtilsService,
- useValue: jasmine.createSpyObj('UtilsService', ['isEmpty'])
+ useValue: jasmine.createSpyObj('UtilsService', ['isEmpty']),
+ // useClass: TestUtils,
},
{
provide: RequestService,
@@ -28,14 +30,6 @@ describe('HubspotService', () => {
{
provide: BrowserStorageService,
useValue: jasmine.createSpyObj('BrowserStorageService', ['getUser', 'getReferrer', 'get'])
- },
- {
- provide: ModalController,
- useValue: jasmine.createSpyObj('ModalController', ['create', 'dismiss', 'getTop'])
- },
- {
- provide: DemoService,
- useValue: jasmine.createSpyObj('DemoService', ['normalResponse'])
}
]
});
@@ -50,8 +44,8 @@ describe('HubspotService', () => {
});
- // user without uuid - the service only generates params when uuid is NOT present
const tempUser = {
+ uuid: 'uuid-1',
name: 'test user',
firstName: 'test',
lastName: 'user',
@@ -65,22 +59,25 @@ describe('HubspotService', () => {
experienceId: 1234
}
- // experience object - the service calls storage.get('experience') expecting an Experience, not an array
- const tempExperience = {
- id: 1234,
- name: 'Global Trade Accelerator - 01',
- config: {
- primary_color: '#2bc1d9',
- secondary_color: '#9fc5e8',
- email_template: 'email_1',
- card_url: 'https://cdn.filestackcontent.com/uYxes8YBS2elXV0m2yjA',
- manual_url: 'https://www.filepicker.io/api/file/lNQp4sFcTjGj2ojOm1fR',
- design_url: 'https://www.filepicker.io/api/file/VuL71nOUSiM9NoNuEIhS',
- overview_url: 'https://vimeo.com/325554048'
- },
- lead_image: 'https://cdn.filestackcontent.com/urFIZW6TuC9lujp0N3PD',
- support_email: 'help@practera.com'
- }
+ const tempPrograms = [
+ {
+ experience: {
+ id: 1234,
+ name: 'Global Trade Accelerator - 01',
+ config: {
+ primary_color: '#2bc1d9',
+ secondary_color: '#9fc5e8',
+ email_template: 'email_1',
+ card_url: 'https://cdn.filestackcontent.com/uYxes8YBS2elXV0m2yjA',
+ manual_url: 'https://www.filepicker.io/api/file/lNQp4sFcTjGj2ojOm1fR',
+ design_url: 'https://www.filepicker.io/api/file/VuL71nOUSiM9NoNuEIhS',
+ overview_url: 'https://vimeo.com/325554048'
+ },
+ lead_image: 'https://cdn.filestackcontent.com/urFIZW6TuC9lujp0N3PD',
+ support_email: 'help@practera.com'
+ }
+ }
+ ]
const params = {
subject: 'test',
@@ -161,13 +158,9 @@ describe('HubspotService', () => {
describe('when testing submitDataToHubspot()', () => {
- beforeEach(() => {
- requestSpy.post.and.returnValue(of({}));
- });
-
it('should call hubspot API with correct data', () => {
storageSpy.getUser.and.returnValue(tempUser);
- storageSpy.get.and.returnValue(tempExperience);
+ storageSpy.get.and.returnValue(tempPrograms);
service.submitDataToHubspot(params);
expect(requestSpy.post.calls.count()).toBe(1);
});
@@ -175,7 +168,7 @@ describe('HubspotService', () => {
it('should return correct user role "Learner"', () => {
const hubspotFields = [ ...hubspotSubmitData.fields ];
storageSpy.getUser.and.returnValue(tempUser);
- storageSpy.get.and.returnValue(tempExperience);
+ storageSpy.get.and.returnValue(tempPrograms);
service.submitDataToHubspot(params);
expect(requestSpy.post.calls.first().args[0].data.fields).toEqual(hubspotFields);
});
@@ -186,7 +179,7 @@ describe('HubspotService', () => {
const user = { ... tempUser };
user.role = 'mentor';
storageSpy.getUser.and.returnValue(user);
- storageSpy.get.and.returnValue(tempExperience);
+ storageSpy.get.and.returnValue(tempPrograms);
service.submitDataToHubspot(params);
expect(requestSpy.post.calls.first().args[0].data.fields).toEqual(hubspotFields);
});
@@ -197,7 +190,7 @@ describe('HubspotService', () => {
const user = { ... tempUser };
user.role = 'admin';
storageSpy.getUser.and.returnValue(user);
- storageSpy.get.and.returnValue(tempExperience);
+ storageSpy.get.and.returnValue(tempPrograms);
service.submitDataToHubspot(params);
expect(requestSpy.post.calls.first().args[0].data.fields).toEqual(hubspotFields);
});
@@ -209,7 +202,7 @@ describe('HubspotService', () => {
const user = { ... tempUser };
user.firstName = null;
storageSpy.getUser.and.returnValue(user);
- storageSpy.get.and.returnValue(tempExperience);
+ storageSpy.get.and.returnValue(tempPrograms);
service.submitDataToHubspot(params);
expect(requestSpy.post.calls.first().args[0].data.fields).toEqual(hubspotFields);
});
@@ -222,7 +215,7 @@ describe('HubspotService', () => {
user.firstName = 'test';
user.lastName = null;
storageSpy.getUser.and.returnValue(user);
- storageSpy.get.and.returnValue(tempExperience);
+ storageSpy.get.and.returnValue(tempPrograms);
service.submitDataToHubspot(params);
expect(requestSpy.post.calls.first().args[0].data.fields).toEqual(hubspotFields);
});
@@ -235,7 +228,7 @@ describe('HubspotService', () => {
user.lastName = 'user';
user.contactNumber = null;
storageSpy.getUser.and.returnValue(user);
- storageSpy.get.and.returnValue(tempExperience);
+ storageSpy.get.and.returnValue(tempPrograms);
service.submitDataToHubspot(params);
expect(requestSpy.post.calls.first().args[0].data.fields).toEqual(hubspotFields);
});
@@ -248,7 +241,7 @@ describe('HubspotService', () => {
user.contactNumber = '1212121212';
user.teamName = null;
storageSpy.getUser.and.returnValue(user);
- storageSpy.get.and.returnValue(tempExperience);
+ storageSpy.get.and.returnValue(tempPrograms);
service.submitDataToHubspot(params);
expect(requestSpy.post.calls.first().args[0].data.fields).toEqual(hubspotFields);
});
@@ -262,24 +255,56 @@ describe('HubspotService', () => {
const tempPram = { ...params };
tempPram.file = null;
storageSpy.getUser.and.returnValue(user);
- storageSpy.get.and.returnValue(tempExperience);
+ storageSpy.get.and.returnValue(tempPrograms);
service.submitDataToHubspot(params);
expect(requestSpy.post.calls.first().args[0].data.fields).toEqual(hubspotFields);
});
- describe('if user has uuid (service returns null for users with uuid)', () => {
+ describe('if no user data in storage', () => {
+ it('should not call Post request', () => {
+ storageSpy.getUser.and.returnValue({});
+ service.submitDataToHubspot(params);
+ expect(requestSpy.post.calls.count()).toBe(0);
+ });
+ });
+
+ describe('if experienceId is missing', () => {
it('should not call Post request', () => {
- storageSpy.getUser.and.returnValue({ uuid: 'some-uuid' });
+ const user = tempUser;
+ delete user.experienceId;
+ storageSpy.getUser.and.returnValue(user);
+ service.submitDataToHubspot(params);
+ expect(requestSpy.post.calls.count()).toBe(0);
+ });
+ });
+
+ describe('if programList is empty', () => {
+ it('should not call Post request', () => {
+ storageSpy.getUser.and.returnValue(tempUser);
+ storageSpy.get.and.returnValue({});
+ service.submitDataToHubspot(params);
+ expect(requestSpy.post.calls.count()).toBe(0);
+ });
+ });
+
+ describe('if no program match the program ID', () => {
+ it('should not call Post request', () => {
+ const program = tempPrograms;
+ program[0].experience.id = 4334;
+ storageSpy.getUser.and.returnValue(tempUser);
+ storageSpy.get.and.returnValue(program);
service.submitDataToHubspot(params);
expect(requestSpy.post.calls.count()).toBe(0);
});
});
- describe('if experience is missing from storage', () => {
+ describe('if no program match the program ID', () => {
it('should not call Post request', () => {
+ const program = tempPrograms;
+ program[0].experience.id = 4334;
storageSpy.getUser.and.returnValue(tempUser);
- storageSpy.get.and.returnValue(null);
+ storageSpy.get.and.returnValue(program);
service.submitDataToHubspot(params);
expect(requestSpy.post.calls.count()).toBe(0);
});
diff --git a/projects/v3/src/app/services/modal.service.spec.ts b/projects/v3/src/app/services/modal.service.spec.ts
index 349bc075e..bafdef1ff 100644
--- a/projects/v3/src/app/services/modal.service.spec.ts
+++ b/projects/v3/src/app/services/modal.service.spec.ts
@@ -1,5 +1,6 @@
-import { TestBed, fakeAsync, tick, flush } from '@angular/core/testing';
+import { TestBed } from '@angular/core/testing';
import { ModalController } from '@ionic/angular';
+import { of } from 'rxjs';
import { ModalService } from './modal.service';
describe('ModalService', () => {
@@ -26,8 +27,7 @@ describe('ModalService', () => {
it('should add a modal to the queue and show it', async () => {
const modalSpy = jasmine.createSpyObj('Modal', ['present', 'onDidDismiss']);
- // onDidDismiss returns a Promise, not an Observable
- modalSpy.onDidDismiss.and.returnValue(Promise.resolve({}));
+ modalSpy.onDidDismiss.and.returnValue(of({}));
modalControllerSpy.create.and.returnValue(Promise.resolve(modalSpy));
await service.addModal({}, () => {});
@@ -38,7 +38,6 @@ describe('ModalService', () => {
it('should not show a new modal while another one is showing', async () => {
const modalSpy = jasmine.createSpyObj('Modal', ['present', 'onDidDismiss']);
- // never-resolving promise to simulate modal staying open
modalSpy.onDidDismiss.and.returnValue(new Promise(() => {}));
modalControllerSpy.create.and.returnValue(Promise.resolve(modalSpy));
@@ -49,22 +48,15 @@ describe('ModalService', () => {
expect(modalSpy.present.calls.count()).toEqual(1);
});
- it('should show the next modal after the current one is dismissed', fakeAsync(() => {
+ it('should show the next modal after the current one is dismissed', async () => {
const modalSpy = jasmine.createSpyObj('Modal', ['present', 'onDidDismiss']);
- // onDidDismiss returns a Promise, not an Observable
- modalSpy.onDidDismiss.and.returnValue(Promise.resolve({}));
+ modalSpy.onDidDismiss.and.returnValue(of({}));
modalControllerSpy.create.and.returnValue(Promise.resolve(modalSpy));
- service.addModal({}, () => {});
- tick(); // let first modal be created
- service.addModal({}, () => {});
- tick(); // let second modal be added to queue
-
- // flush all pending async operations
- flush();
+ await service.addModal({}, () => {});
+ await service.addModal({}, () => {});
expect(modalControllerSpy.create.calls.count()).toEqual(2);
expect(modalSpy.present.calls.count()).toEqual(2);
- }));
-
+ });
});
diff --git a/projects/v3/src/app/services/network.service.spec.ts b/projects/v3/src/app/services/network.service.spec.ts
index f38844d3d..74ff24189 100644
--- a/projects/v3/src/app/services/network.service.spec.ts
+++ b/projects/v3/src/app/services/network.service.spec.ts
@@ -1,23 +1,12 @@
import { TestBed } from '@angular/core/testing';
-import { RequestService } from 'request';
-import { of } from 'rxjs';
import { NetworkService } from './network.service';
describe('NetworkService', () => {
let service: NetworkService;
- let requestServiceSpy: jasmine.SpyObj;
beforeEach(() => {
- requestServiceSpy = jasmine.createSpyObj('RequestService', ['get']);
- requestServiceSpy.get.and.returnValue(of({ status: 200 }));
-
- TestBed.configureTestingModule({
- providers: [
- NetworkService,
- { provide: RequestService, useValue: requestServiceSpy }
- ]
- });
+ TestBed.configureTestingModule({});
service = TestBed.inject(NetworkService);
});
diff --git a/projects/v3/src/app/services/ngx-embed-video.service.spec.ts b/projects/v3/src/app/services/ngx-embed-video.service.spec.ts
index 7a6ae2cd6..24212fdd3 100644
--- a/projects/v3/src/app/services/ngx-embed-video.service.spec.ts
+++ b/projects/v3/src/app/services/ngx-embed-video.service.spec.ts
@@ -44,74 +44,63 @@ describe('EmbedVideoService', () => {
});
it('converts vimeo.com url', () => {
- const target = service.embed('http://vimeo.com/19339941') as string;
- expect(target).toMatch(/^sanitized: