import { Injectable } from '@angular/core'; import { GCMockModule } from '@core/mocks/gc-module.mock'; import { TimeZoneService } from '@core/services/time-zone.service'; import { ClientSettings } from '@core/typings/api/admin-client.typing'; import { ProcessingTypes } from '@core/typings/payment.typing'; import { Program, ProgramApplicantType, ProgramDetailFromApi, ProgramTypes, WorkflowFormMap } from '@core/typings/program.typing'; import { ClientSettingsService } from '@features/client-settings/client-settings.service'; import { AvailabilityOptions, CompletionRequirementType, RelativeFormDueDateFields, ResponseVisibilityOptions, WorkflowLevelFormApi } from '@features/configure-forms/form.typing'; import { UserService } from '@features/users/user.service'; import { WorkflowResources } from '@features/workflow/workflow.resources'; import { WorkflowService } from '@features/workflow/workflow.service'; import { BeforeEach, Spec, TestCase } from '@yourcause/test-decorators'; import { DescribeAngularService } from '@yourcause/test-decorators/angular'; import { expect } from 'chai'; import { ProgramResources } from './program.resources'; import { ProgramService } from './program.service'; @Injectable({ providedIn: 'root' }) @DescribeAngularService(ProgramService, { imports: [ GCMockModule ] }) @Injectable() export class ProgramServiceSpec implements Spec { programId = '7831'; program: ProgramDetailFromApi = { daysBeforeEndDateReminders: 0, defaultLanguageId: 'en-US', additionalForms: [], useProgramLogo: false, timezoneId: '1', allowAddOrg: false, allowCollaboration: true, canChangeDefaultForm: false, charityBucketDescription: '', charityBucketId: null, deadlines: [], defaultFormId: 835, defaultWorkflowLevelId: 23514, description: 'LS_Healthy_Water', eligibilityFailMessage: '', eligibilityPassMessage: '', grantProgramsForNominationProgram: [], hasApplications: true, id: 7831, imageName: '49bddf64-b334-47d1-9657-123cc44a7df4', imageUrl: 'https://grantsconnect-qa.imgix.net/management/6/images/49bddf64-b334-47d1-9657-123cc44a7df4', logoName: null, logoUrl: null, maskApplicantInfo: false, maxAwardsAllowed: null, maxValueForSingleAward: null, name: 'LS_Healthy_Water', notifyNominators: true, programApplicantType: 2, sendEmailsToCollaborators: false, workflowId: 18148, isArchived: false, cycles: [], inviteOnly: true, formDueReminderFrequencyForApplicant: 5, formDueReminderFrequencyForReviewer: 5, formOverDueReminderFrequencyForApplicant: 5, formOverDueReminderFrequencyForReviewer: 5, reserveFunds: true, autoDeclineBudgetAssignment: true, autoDeclineBudgetAssignmentSendEmail: true, autoDeclineBudgetAssignmentDeclinationComment: 'test string', hidePaymentStatus: true, alternatePaymentStatusText: '', clientId: 1 }; individualProgramId = 658; orgProgramId = 4645; orgProgram: Program = { defaultLanguageId: 'en-US', grantProgramId: '' + this.orgProgramId, timeZone: { id: 'timeZone', displayName: 'timeZone', offset: 1 }, timezoneId: 'UTC', grantProgramGuid: 'asdf', grantProgramName: 'Test Program', grantProgramDescription: 'this is a test', startDate: '2018-10-29T14:42:51.389Z', endDate: '2019-10-29T14:42:51.39Z', active: true, isDraft: false, programApplicantType: ProgramApplicantType.ORGS, charityBucketId: '', allowCollaboration: true, notifyNominators: false, allowAddOrg: true, maskApplicantInfo: true, programType: ProgramTypes.GRANT, isTiedToNominationPrograms: false, isArchived: false, numberOfGrantProgramCycles: 1, senderDisplayName: '', daysBeforeEndDateReminders: 2, grantProgramCycles: [], workflowId: 1, defaultFormId: 1, defaultWorkflowLevelId: 1, clientId: 1 }; individualProgram: Program = { defaultLanguageId: 'en-US', grantProgramId: '' + this.individualProgramId, timeZone: { id: 'timeZone', displayName: 'timeZone', offset: 1 }, timezoneId: 'UTC', grantProgramGuid: 'asdf', grantProgramName: 'Individual Program', grantProgramDescription: 'this is a test', startDate: '2018-10-29T14:42:51.389Z', endDate: '2019-10-29T14:42:51.39Z', active: true, isDraft: false, programApplicantType: ProgramApplicantType.INDIVIDUAL, charityBucketId: '', allowCollaboration: true, notifyNominators: false, allowAddOrg: true, maskApplicantInfo: true, programType: ProgramTypes.GRANT, isTiedToNominationPrograms: false, isArchived: false, numberOfGrantProgramCycles: 1, senderDisplayName: '', daysBeforeEndDateReminders: 2, grantProgramCycles: [], workflowId: 1, defaultFormId: 1, defaultWorkflowLevelId: 1, clientId: 1 }; programs: Program[] = [ this.orgProgram, this.individualProgram ]; programStats = { amountRequestedInProgressApplications: { count: 2, amount: 299 }, amountFromApprovedApplications: { count: 3, amount: 400 }, amountRequestedDeclinedApplications: { count: 1, amount: 500 }, amountRequestedDraftApplications: { count: 34, amount: 8949 }, totalAmountRequestedAllApplications: 50, totalNumberOfApplications: 23 }; paymentStats = { amountNoAwards: 3, remainingAward: 8, pending: 5, paid: 1, scheduled: 2, processing: 6, outstanding: 8, voided: 1 }; constructor ( private programResources: ProgramResources, private workflowResources: WorkflowResources, private workflowService: WorkflowService ) { } @BeforeEach() async mock (service: ProgramService) { const clientSettingsService: ClientSettingsService = (service as any).clientSettingsService; clientSettingsService['set']('clientSettings', { hasInternational: true } as ClientSettings); const timeZoneService: TimeZoneService = (service as any).timeZoneService; timeZoneService['set']('timeZones', [{ id: 'UTC', displayName: 'UTC', offset: 0 }]); const userService: UserService = (service as any).userService; userService.setUser({ isRootUser: true, firstName: '', lastName: '' } as any); clientSettingsService.setClientBranding({ hasImportedFromUAT: false } as any); (service as any).isLocalHost = () => { return false; }; service['programResources'] = this.programResources; (this.workflowService as any).set('workflowMap', { 18148: { levels: [], id: 18148, name: 'test', description: '', active: true, hasApplications: false, workflowLevelManagers: [] } }); this.programResources.getProgramFromApi = async () => { return this.program; }; this.programResources.getProgramsForManager = async () => { return this.programs; }; this.programResources.getProgramStats = async () => { return this.programStats; }; this.programResources.getPaymentStats = async () => { return this.paymentStats; }; this.programResources.getMasterFormResponse = async () => { return []; }; this.programResources.getAllProgramsDraftAndPublished = async () => { return this.programs; }; this.programResources.getProgramEmailSettings = async () => { return { disabledEmails: [], senderDisplayName: '' }; }; this.workflowResources.getWorkflowForms = async () => { return []; }; await service.setAllPrograms(); } @TestCase('should be able get program and set programMap') async getProgramTest ( service: ProgramService ) { await service.getProgram(this.programId); const program = service.get('programMap')[this.programId]; expect(program.name).to.be.equal(this.program.name); expect(program.workflowId).to.be.equal(this.program.workflowId); } @TestCase('should be able to get and set program dashboard stats') async getProgramForDashboardTest ( service: ProgramService ) { await service.getProgramForDashboard(+this.programId, []); const stats = service.get('programDashboardMap')[this.programId]; expect(stats.programStats.totalNumberOfApplications).to.be.equal(23); expect(stats.paymentStats.amountNoAwards).to.be.equal(3); } @TestCase('should be able to get program for edit') async getProgramForEditTest ( service: ProgramService ) { service.getMasterResponse = () => null; const program = await service.getProgramForEdit(+this.programId, ProcessingTypes.Client, []); expect(program.name).to.be.equal(this.program.name); expect(program.applicantType).to.be.equal(this.program.programApplicantType); } @TestCase('should be able to get pass fail message for program') async getPassFailMessageTest ( service: ProgramService ) { const translationArray = [{ language: 'en-US', translation: 'You passed!' }]; const message = service.getPassFailMessage( translationArray, 'pass' ); expect(message).to.be.equal('You passed!'); } @TestCase('should return correct value for programExportAllowed') programExportAllowedTest ( service: ProgramService ) { expect(service.programExportAllowed()).to.be.equal(true); (service as any).userService.user.isRootUser = false; expect(service.programExportAllowed()).to.be.equal(false); (service as any).environment = { locationBase: 'prod' }; expect(service.programExportAllowed()).to.be.equal(false); } @TestCase('should return correct value for programImportAllowed') programImportAllowedTest ( service: ProgramService ) { expect(service.programImportAllowed()).to.be.equal(true); (service as any).userService.user.isRootUser = false; expect(service.programImportAllowed()).to.be.equal(false); (service as any).userService.user.isRootUser = true; (service as any).clientSettingsService.get('clientBranding').hasImportedFromUAT = true; expect(service.programImportAllowed()).to.be.equal(true); } @TestCase('should be able to construct workflow level forms') constructWorkflowLevelForms (service: ProgramService) { const workflowFormMap: WorkflowFormMap = { 1: [{ formId: 1, workflowLevelId: 2, completionRequirementType: CompletionRequirementType.MAJORITY, specificNumberForCompletion: null, managerActionType: ResponseVisibilityOptions.VIEW_NONE, portalAvailability: AvailabilityOptions.AUTO, portalAvailabilityDetails: null, notifyApplicantOfFormAvailability: true, dueDateDetails: { relativeDateField: RelativeFormDueDateFields.ENTERED_WFL_DATE, dateOffset: 30 }, grantProgramId: 3, clientEmailTemplateId: null, sortOrder: 1 }], 2: [{ formId: 2, workflowLevelId: 2, completionRequirementType: CompletionRequirementType.ALL_USERS, specificNumberForCompletion: null, managerActionType: ResponseVisibilityOptions.VIEW_AT_THIS_WORKFLOW, portalAvailability: AvailabilityOptions.AUTO, portalAvailabilityDetails: null, notifyApplicantOfFormAvailability: true, dueDateDetails: null, grantProgramId: 3, clientEmailTemplateId: null, sortOrder: 2 }] }; const forms = service.constructWorkflowLevelForms(workflowFormMap); expect(forms.length).to.be.equal(2); expect(forms[0].formId).to.be.equal(1); } @TestCase('should be able to update the sort order of workflow level forms') testUpdatingTheSortOrderOfWorkflowLevelForms (service: ProgramService) { const formIdOfSecondItem = 2; const formIdOfThirdItem = 3; const initialList = [ { formId: 1, sortOrder: 1 }, { formId: formIdOfSecondItem, sortOrder: 2 }, { formId: formIdOfThirdItem, sortOrder: 3 } ] as WorkflowLevelFormApi[]; // should be able to move item up in the list const listAfterMovingItem2Up = service.updateFormsListSortOrder( initialList, 1, true ); expect(listAfterMovingItem2Up[0].sortOrder).to.equal(1); expect(listAfterMovingItem2Up[0].formId).to.equal(formIdOfSecondItem); // should be able to move item down in the list const listAfterMovingItem2Down = service.updateFormsListSortOrder( initialList, 1, false ); expect(listAfterMovingItem2Down[2].sortOrder).to.equal(3); expect(listAfterMovingItem2Down[2].formId).to.equal(formIdOfSecondItem); } @TestCase('should be able to get published active program options - all') async getPublishedActiveProgramOptionsAll (service: ProgramService) { const results = service.getPublishedActiveProgramOptions(); const activePublishedLength = service.allPublishedActivePrograms.length; expect(results.length).to.be.equal(activePublishedLength); } @TestCase('should be able to get published active program options - filtered') async getPublishedActiveProgramOptionsFiltered (service: ProgramService) { const results = service.getPublishedActiveProgramOptions(ProgramApplicantType.ORGS); const activePublishedOrgLength = service.allPublishedActivePrograms.filter((prog) => { return prog.programApplicantType === ProgramApplicantType.ORGS; }).length; expect(results.length).to.be.equal(activePublishedOrgLength); const orgProgs = service.allPublishedActivePrograms.filter((prog) => { return prog.programApplicantType === ProgramApplicantType.ORGS; }).map((prog) => prog.grantProgramId); const mappedResult = results.map((result) => result.value); expect(orgProgs).to.deep.equal(mappedResult); } }