diff --git a/src/app/auth/switch-org/switch-org.page.spec.ts b/src/app/auth/switch-org/switch-org.page.spec.ts index 88bd205f07..173e75219b 100644 --- a/src/app/auth/switch-org/switch-org.page.spec.ts +++ b/src/app/auth/switch-org/switch-org.page.spec.ts @@ -54,7 +54,7 @@ const roles = ['OWNER', 'USER', 'FYLER']; const email = 'ajain@fyle.in'; const org_id = 'orNVthTo2Zyo'; -describe('SwitchOrgPage', () => { +fdescribe('SwitchOrgPage', () => { let component: SwitchOrgPage; let fixture: ComponentFixture; let loaderService: jasmine.SpyObj; @@ -118,7 +118,7 @@ describe('SwitchOrgPage', () => { const deepLinkServiceSpy = jasmine.createSpyObj('DeepLinkService', ['getExpenseRoute']); const ldSpy = jasmine.createSpyObj('LaunchDarklyService', ['initializeUser']); const orgSettingsServiceSpy = jasmine.createSpyObj('OrgSettingsService', ['get']); - const spenderOnboardingServiceSpy = jasmine.createSpyObj('SpenderOnboardingSettings', ['getOnboardingSettings']); + const spenderOnboardingServiceSpy = jasmine.createSpyObj('SpenderOnboardingSettings', ['getOnboardingStatus']); TestBed.configureTestingModule({ declarations: [SwitchOrgPage, ActiveOrgCardComponent, OrgCardComponent, FyZeroStateComponent], @@ -280,7 +280,10 @@ describe('SwitchOrgPage', () => { component.searchOrgsInput = fixture.debugElement.query(By.css('.smartlook-show')); component.contentRef = fixture.debugElement.query(By.css('.switch-org__content-container__content-block')); fixture.detectChanges(); - spyOn(component, 'navigateToDashboard').and.callThrough(); + spenderOnboardingService.getOnboardingStatus.and.returnValue( + of({ ...onboardingStatusData, state: OnboardingState.COMPLETED }) + ); + orgSettingsService.get.and.returnValue(of(orgSettingsData)); })); it('should create', () => { @@ -568,6 +571,16 @@ describe('SwitchOrgPage', () => { })); }); + it('navigateToDashboard(): should navigate to spender onboarding when onboarding status is not complete', fakeAsync(() => { + spenderOnboardingService.getOnboardingStatus.and.returnValue( + of({ ...onboardingStatusData, state: OnboardingState.YET_TO_START }) + ); + orgSettingsService.get.and.returnValue(of(orgSettingsData)); + component.navigateToDashboard(); + tick(); + expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'enterprise', 'spender_onboarding']); + })); + describe('navigateToSetupPage():', () => { it('should navigate to setup page if org the roles has OWNER', () => { component.navigateToSetupPage(['OWNER']); @@ -592,7 +605,12 @@ describe('SwitchOrgPage', () => { .pipe( finalize(() => { expect(loaderService.hideLoader).toHaveBeenCalledTimes(1); - expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'enterprise', 'my_dashboard']); + expect(router.navigate).toHaveBeenCalledOnceWith([ + '/', + 'enterprise', + 'my_dashboard', + { openSMSOptInDialog: undefined }, + ]); }) ) .subscribe((res) => { @@ -675,7 +693,12 @@ describe('SwitchOrgPage', () => { tick(); component.navigateBasedOnUserStatus(config).subscribe((res) => { expect(res).toBeNull(); - expect(router.navigate).toHaveBeenCalledOnceWith(['/', 'enterprise', 'my_dashboard']); + expect(router.navigate).toHaveBeenCalledOnceWith([ + '/', + 'enterprise', + 'my_dashboard', + { openSMSOptInDialog: undefined }, + ]); }); })); diff --git a/src/app/auth/switch-org/switch-org.page.ts b/src/app/auth/switch-org/switch-org.page.ts index 226de20f0f..9ca8e30b4a 100644 --- a/src/app/auth/switch-org/switch-org.page.ts +++ b/src/app/auth/switch-org/switch-org.page.ts @@ -321,7 +321,8 @@ export class SwitchOrgPage implements OnInit, AfterViewChecked { forkJoin([this.orgSettingsService.get(), this.spenderOnboardingService.getOnboardingStatus()]).subscribe( ([orgSettings, onboardingStatus]) => { if ( - (orgSettings.visa_enrollment_settings.enabled || + (orgSettings.corporate_credit_card_settings.enabled || + orgSettings.visa_enrollment_settings.enabled || orgSettings.mastercard_enrollment_settings.enabled || orgSettings.amex_feed_enrollment_settings.enabled) && onboardingStatus.state !== OnboardingState.COMPLETED diff --git a/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.spec.ts b/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.spec.ts index 65822a59d9..38b177ed27 100644 --- a/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.spec.ts +++ b/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.spec.ts @@ -58,18 +58,56 @@ describe('SpenderOnboardingConnectCardStepComponent', () => { realTimeFeedService.getCardTypeFromNumber.and.returnValue(CardNetworkType.VISA); })); - it('ngOnInit(): should setup card form controls', () => { - component.enrollableCards = [{ ...statementUploadedCard, card_number: '1111' }]; - corporateCreditCardExpenseService.getCorporateCards.and.returnValue( - of([{ ...statementUploadedCard, card_number: '1111' }]) - ); - realTimeFeedService.isCardNumberValid.and.returnValue(false); - realTimeFeedService.getCardTypeFromNumber.and.returnValue(CardNetworkType.OTHERS); - component.ngOnInit(); - component.fg.controls.card_number_bacc15bbrRGWzf.setValue('4111111111'); - fixture.detectChanges(); - - expect(component.fg.controls.card_number_bacc15bbrRGWzf.errors.invalidCardNumber).toBeTrue(); + describe('ngOnInit(): ', () => { + beforeEach(() => { + component.enrollableCards = [{ ...statementUploadedCard, card_number: '1111' }]; + corporateCreditCardExpenseService.getCorporateCards.and.returnValue( + of([{ ...statementUploadedCard, card_number: '1111' }]) + ); + }); + + it('should setup card form controls', () => { + realTimeFeedService.isCardNumberValid.and.returnValue(false); + realTimeFeedService.getCardTypeFromNumber.and.returnValue(CardNetworkType.OTHERS); + component.ngOnInit(); + component.fg.controls.card_number_bacc15bbrRGWzf.setValue('4111111111'); + fixture.detectChanges(); + + expect(component.fg.controls.card_number_bacc15bbrRGWzf.errors.invalidCardNumber).toBeTrue(); + }); + + it('should run validator for invalid card network', () => { + realTimeFeedService.isCardNumberValid.and.returnValue(false); + realTimeFeedService.getCardTypeFromNumber.and.returnValue(CardNetworkType.VISA); + component.isVisaRTFEnabled = false; + component.ngOnInit(); + component.fg.controls.card_number_bacc15bbrRGWzf.setValue('4111111111'); + fixture.detectChanges(); + + expect(component.fg.controls.card_number_bacc15bbrRGWzf.errors.invalidCardNetwork).toBeTrue(); + }); + + it('should run validator for valid card and valid network', () => { + spyOn(component, 'getFullCardNumber').and.returnValue('4111111111111111'); + realTimeFeedService.isCardNumberValid.and.returnValue(true); + realTimeFeedService.getCardTypeFromNumber.and.returnValue(CardNetworkType.VISA); + component.isVisaRTFEnabled = true; + component.ngOnInit(); + fixture.detectChanges(); + + expect(component.fg.controls.card_number_bacc15bbrRGWzf.errors.invalidCardNetwork).toBeUndefined(); + }); + + it('should run validator for valid card and invalid network', () => { + spyOn(component, 'getFullCardNumber').and.returnValue('41111111111111'); + realTimeFeedService.isCardNumberValid.and.returnValue(true); + realTimeFeedService.getCardTypeFromNumber.and.returnValue(CardNetworkType.OTHERS); + component.isVisaRTFEnabled = false; + component.ngOnInit(); + fixture.detectChanges(); + + expect(component.fg.controls.card_number_bacc15bbrRGWzf.errors.invalidCardNetwork).toBeUndefined(); + }); }); it('ngOnChanges(): should update isVisaRTFEnabled and isMastercardRTFEnabled when orgSettings changes', () => { @@ -136,8 +174,9 @@ describe('SpenderOnboardingConnectCardStepComponent', () => { const mockError = { status: 404 }; component.enrollableCards = []; - component.ngOnInit(); - fixture.detectChanges(); + component.fg = fb.group({ + card_number: ['', Validators.required], + }); //@ts-ignore component.handleEnrollmentFailures(mockError); @@ -158,16 +197,30 @@ describe('SpenderOnboardingConnectCardStepComponent', () => { expect(component.cardsEnrolling).toBeTrue(); }); + it('should call enrollMultipleCards if enrollableCards has items', () => { + component.enrollableCards = [statementUploadedCard, { ...statementUploadedCard, id: 'bacc15bbrRGWzg' }]; + const enrollSingularCardSpy = spyOn(component, 'enrollSingularCard'); + const enrollMultipleCardsSpy = spyOn(component, 'enrollMultipleCards'); + component.cardValuesMap.bacc15bbrRGWzf = { + last_four: '1111', + enrollment_success: true, + card_type: CardNetworkType.VISA, + }; + + component.enrollCards(); + + expect(enrollMultipleCardsSpy).toHaveBeenCalledWith([component.enrollableCards[1]]); + expect(enrollSingularCardSpy).not.toHaveBeenCalled(); + expect(component.cardsEnrolling).toBeTrue(); + }); + it('should call enrollSingularCard if enrollableCards is empty', () => { - // Arrange component.enrollableCards = []; const enrollSingularCardSpy = spyOn(component, 'enrollSingularCard'); const enrollMultipleCardsSpy = spyOn(component, 'enrollMultipleCards'); - // Act component.enrollCards(); - // Assert expect(enrollSingularCardSpy).toHaveBeenCalled(); expect(enrollMultipleCardsSpy).not.toHaveBeenCalled(); expect(component.cardsEnrolling).toBeTrue(); @@ -183,9 +236,8 @@ describe('SpenderOnboardingConnectCardStepComponent', () => { }); it('should return the message for successful cards with failed cards present', () => { - component.cardsList.successfulCards = ['**** 5678']; - component.cardsList.failedCards = ['**** 1234']; - + component.cardsList.successfulCards.push('**** 5678'); + component.cardsList.failedCards.push('**** 1234'); const message = component.generateMessage(); expect(message).toBe( @@ -204,6 +256,7 @@ describe('SpenderOnboardingConnectCardStepComponent', () => { it('should return the message for a single failed card', () => { component.cardsList.failedCards = ['**** 1234']; + component.cardsList.successfulCards = []; fixture.detectChanges(); const message = component.generateMessage(); @@ -215,11 +268,26 @@ describe('SpenderOnboardingConnectCardStepComponent', () => { }); describe('enrollMultipleCards(): ', () => { - it('should handle successful card enrollment', fakeAsync(() => { + it('should handle successful card enrollment - existing card', fakeAsync(() => { corporateCreditCardExpenseService.getCorporateCards.and.returnValue( of([statementUploadedCard, { ...statementUploadedCard, id: 'bacc15bbrRGWzg' }]) ); - component.ngOnInit(); + component.enrollableCards = [statementUploadedCard, { ...statementUploadedCard, id: 'bacc15bbrRGWzg' }]; + component.fg = fb.group({ + card_number_bacc15bbrRGWzf: ['', Validators.required], + card_number_bacc15bbrRGWzg: ['', Validators.required], + }); + + component.cardValuesMap = { + bacc15bbrRGWzf: { + last_four: '5555', + card_type: CardNetworkType.OTHERS, + }, + bacc15bbrRGWzg: { + last_four: '5555', + card_type: CardNetworkType.OTHERS, + }, + }; const stepCompleteSpy = spyOn(component.isStepComplete, 'emit'); const showErrorPopoverSpy = spyOn(component, 'showErrorPopover'); @@ -234,13 +302,26 @@ describe('SpenderOnboardingConnectCardStepComponent', () => { expect(setupErrorMessagesSpy).not.toHaveBeenCalled(); })); - it('should handle unsuccessful card enrollment', fakeAsync(() => { + it('should handle unsuccessful card enrollment - new card', fakeAsync(() => { corporateCreditCardExpenseService.getCorporateCards.and.returnValue( of([statementUploadedCard, { ...statementUploadedCard, id: 'bacc15bbrRGWzg' }]) ); - component.ngOnInit(); + component.enrollableCards = [statementUploadedCard, { ...statementUploadedCard, id: 'bacc15bbrRGWzg' }]; + component.cardValuesMap = { + bacc15bbrRGWzf: { + last_four: '5555', + card_type: CardNetworkType.OTHERS, + }, + bacc15bbrRGWzg: { + last_four: '5555', + card_type: CardNetworkType.OTHERS, + }, + }; + component.fg = fb.group({ + card_number_bacc15bbrRGWzf: ['', Validators.required], + card_number_bacc15bbrRGWzg: ['', Validators.required], + }); - const stepCompleteSpy = spyOn(component.isStepComplete, 'emit'); const showErrorPopoverSpy = spyOn(component, 'showErrorPopover'); realTimeFeedService.enroll.and.returnValues( of(statementUploadedCard), @@ -251,7 +332,6 @@ describe('SpenderOnboardingConnectCardStepComponent', () => { expect(component.cardsList.successfulCards).toEqual(['**** 5555']); expect(component.cardsList.failedCards).toEqual(['**** 5555']); expect(component.cardsEnrolling).toBeFalse(); - expect(stepCompleteSpy).toHaveBeenCalledWith(true); expect(showErrorPopoverSpy).toHaveBeenCalledTimes(1); })); }); @@ -259,8 +339,9 @@ describe('SpenderOnboardingConnectCardStepComponent', () => { describe('enrollSingularCard(): ', () => { it('should handle successful card enrollment', fakeAsync(() => { corporateCreditCardExpenseService.getCorporateCards.and.returnValue(of([])); - component.ngOnInit(); - + component.fg = fb.group({ + card_number: ['', Validators.required], + }); component.fg.controls.card_number.setValue('41111111111111111'); const stepCompleteSpy = spyOn(component.isStepComplete, 'emit'); const showErrorPopoverSpy = spyOn(component, 'showErrorPopover'); @@ -277,10 +358,10 @@ describe('SpenderOnboardingConnectCardStepComponent', () => { it('should handle unsuccessful card enrollment', fakeAsync(() => { corporateCreditCardExpenseService.getCorporateCards.and.returnValue(of([])); - component.ngOnInit(); - + component.fg = fb.group({ + card_number: ['', Validators.required], + }); component.fg.controls.card_number.setValue('41111111111111111'); - const stepCompleteSpy = spyOn(component.isStepComplete, 'emit'); const showErrorPopoverSpy = spyOn(component, 'showErrorPopover'); realTimeFeedService.enroll.and.returnValues( throwError(() => new Error('This card already exists in the system')) @@ -293,53 +374,93 @@ describe('SpenderOnboardingConnectCardStepComponent', () => { })); }); - it('showErrorPopover(): should display a popover and handle its actions', () => { - const popoverSpy = jasmine.createSpyObj('popover', ['present', 'onWillDismiss']); - spyOn(component, 'generateMessage').and.returnValue('Error message'); - popoverSpy.onWillDismiss.and.resolveTo({ - data: { - action: 'close', - }, + describe('showErrorPopover(): ', () => { + it('should display a popover and handle its actions', () => { + const popoverSpy = jasmine.createSpyObj('popover', ['present', 'onWillDismiss']); + spyOn(component, 'generateMessage').and.returnValue('Error message'); + popoverSpy.onWillDismiss.and.resolveTo({ + data: { + action: 'close', + }, + }); + popoverController.create.and.resolveTo(popoverSpy); + component.cardsList = { + successfulCards: [], + failedCards: ['**** 1111'], + }; + fixture.detectChanges(); + component.showErrorPopover(); + + expect(popoverController.create).toHaveBeenCalledOnceWith({ + componentProps: { + title: 'Failed connecting', + message: 'Error message', + primaryCta: { + text: 'Proceed anyway', + action: 'close', + }, + secondaryCta: { + text: 'Cancel', + action: 'cancel', + }, + cardsList: {}, + }, + component: PopupAlertComponent, + cssClass: 'pop-up-in-center', + }); }); - popoverController.create.and.resolveTo(popoverSpy); - component.cardsList = { - successfulCards: [], - failedCards: ['**** 1111'], - }; - fixture.detectChanges(); - component.showErrorPopover(); - - expect(popoverController.create).toHaveBeenCalledOnceWith({ - componentProps: { - title: 'Failed connecting', - message: 'Error message', - primaryCta: { - text: 'Proceed anyway', + + it('should display a popover when successful cards are present and handle its actions', () => { + const popoverSpy = jasmine.createSpyObj('popover', ['present', 'onWillDismiss']); + spyOn(component, 'generateMessage').and.returnValue('Error message'); + popoverSpy.onWillDismiss.and.resolveTo({ + data: { action: 'close', }, - secondaryCta: { - text: 'Cancel', - action: 'cancel', + }); + popoverController.create.and.resolveTo(popoverSpy); + component.cardsList = { + successfulCards: ['**** 1111'], + failedCards: ['**** 1111'], + }; + fixture.detectChanges(); + component.showErrorPopover(); + + expect(popoverController.create).toHaveBeenCalledOnceWith({ + componentProps: { + title: 'Status summary', + message: 'Error message', + primaryCta: { + text: 'Proceed anyway', + action: 'close', + }, + secondaryCta: { + text: 'Cancel', + action: 'cancel', + }, + cardsList: component.cardsList, }, - cardsList: {}, - }, - component: PopupAlertComponent, - cssClass: 'pop-up-in-center', + component: PopupAlertComponent, + cssClass: 'pop-up-in-center', + }); }); }); describe('onCardNumberUpdate(): ', () => { - it('should update card_type for the given card or singleEnrollableCardDetails', () => { + it('should update card_type for the given card or singleEnrollableCardDetails - new card', () => { realTimeFeedService.getCardTypeFromNumber.and.returnValue(CardNetworkType.VISA); + corporateCreditCardExpenseService.getCorporateCards.and.returnValue(of([])); component.enrollableCards = []; - component.ngOnInit(); - component.fg.controls.card_number.setValue('41111111111111111'); + component.fg = fb.group({ + card_number: ['', Validators.required], + }); + component.fg.controls.card_number.setValue('4111111111111111'); component.onCardNumberUpdate(); expect(component.singleEnrollableCardDetails.card_type).toBe(CardNetworkType.VISA); }); - it('should update card_type for the given card or singleEnrollableCardDetails', () => { + it('should update card_type for the given card or singleEnrollableCardDetails - existing card', () => { realTimeFeedService.getCardTypeFromNumber.and.returnValue(CardNetworkType.VISA); component.cardValuesMap.bacc15bbrRGWzf = { last_four: '1111', @@ -349,7 +470,7 @@ describe('SpenderOnboardingConnectCardStepComponent', () => { component.fg = fb.group({ card_number_bacc15bbrRGWzf: ['', Validators.required], }); - component.fg.controls.card_number_bacc15bbrRGWzf.setValue('41111111111111111'); + component.fg.controls.card_number_bacc15bbrRGWzf.setValue('4111111111111111'); component.onCardNumberUpdate(statementUploadedCard); expect(component.cardValuesMap.bacc15bbrRGWzf.card_type).toBe(CardNetworkType.VISA); diff --git a/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts b/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts index f420fc211a..973314b9a9 100644 --- a/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts +++ b/src/app/fyle/spender-onboarding/spender-onboarding-connect-card-step/spender-onboarding-connect-card-step.component.ts @@ -186,7 +186,7 @@ export class SpenderOnboardingConnectCardStepComponent implements OnInit, OnChan action: string; }>; - if (data?.action === 'close') { + if (data.action === 'close') { this.isStepSkipped.emit(true); } } @@ -256,7 +256,7 @@ export class SpenderOnboardingConnectCardStepComponent implements OnInit, OnChan ); } else { this.singleEnrollableCardDetails.card_type = this.realTimeFeedService.getCardTypeFromNumber( - this.fg.controls.card_number?.value as string + this.fg.controls.card_number.value as string ); } } @@ -276,7 +276,7 @@ export class SpenderOnboardingConnectCardStepComponent implements OnInit, OnChan return (): ValidationErrors | null => { // Reactive forms are not strongly typed in Angular 13, so we need to cast the value to string // TODO (Angular 14 >): Remove the type casting and directly use string type for the form control - const cardNumber = cardId ? this.getFullCardNumber(cardId) : (this.fg.controls.card_number?.value as string); + const cardNumber = cardId ? this.getFullCardNumber(cardId) : (this.fg.controls.card_number.value as string); const isValid = this.realTimeFeedService.isCardNumberValid(cardNumber); const cardType = this.realTimeFeedService.getCardTypeFromNumber(cardNumber); @@ -291,7 +291,7 @@ export class SpenderOnboardingConnectCardStepComponent implements OnInit, OnChan private cardNetworkValidator(cardId?: string): ValidatorFn { return (): ValidationErrors | null => { - const cardNumber = cardId ? this.getFullCardNumber(cardId) : (this.fg.controls.card_number?.value as string); + const cardNumber = cardId ? this.getFullCardNumber(cardId) : (this.fg.controls.card_number.value as string); const cardType = this.realTimeFeedService.getCardTypeFromNumber(cardNumber); if ( diff --git a/src/app/fyle/spender-onboarding/spender-onboarding.page.spec.ts b/src/app/fyle/spender-onboarding/spender-onboarding.page.spec.ts index 8e69123985..8728d62b73 100644 --- a/src/app/fyle/spender-onboarding/spender-onboarding.page.spec.ts +++ b/src/app/fyle/spender-onboarding/spender-onboarding.page.spec.ts @@ -13,7 +13,7 @@ import { onboardingStatusData } from 'src/app/core/mock-data/onboarding-status.d import { extendedOrgUserResponse } from 'src/app/core/test-data/tasks.service.spec.data'; import { OnboardingStepStatus } from 'src/app/core/models/onboarding-step-status.model'; -describe('SpenderOnboardingPage', () => { +fdescribe('SpenderOnboardingPage', () => { let component: SpenderOnboardingPage; let fixture: ComponentFixture; let loaderService: jasmine.SpyObj; diff --git a/src/app/post-verification/invited-user/invited-user.page.spec.ts b/src/app/post-verification/invited-user/invited-user.page.spec.ts index 927d44ff4f..71a6d4ca3e 100644 --- a/src/app/post-verification/invited-user/invited-user.page.spec.ts +++ b/src/app/post-verification/invited-user/invited-user.page.spec.ts @@ -25,7 +25,7 @@ import { OrgService } from 'src/app/core/services/org.service'; import { ToastMessageComponent } from 'src/app/shared/components/toast-message/toast-message.component'; import { RouterTestingModule } from '@angular/router/testing'; -describe('InvitedUserPage', () => { +fdescribe('InvitedUserPage', () => { let component: InvitedUserPage; let fixture: ComponentFixture; let networkService: jasmine.SpyObj; diff --git a/src/app/post-verification/invited-user/invited-user.page.ts b/src/app/post-verification/invited-user/invited-user.page.ts index 427e61a4d7..aa7baabe3f 100644 --- a/src/app/post-verification/invited-user/invited-user.page.ts +++ b/src/app/post-verification/invited-user/invited-user.page.ts @@ -99,7 +99,8 @@ export class InvitedUserPage implements OnInit { forkJoin([this.orgSettingsService.get(), this.spenderOnboardingService.getOnboardingStatus()]).subscribe( ([orgSettings, onboardingStatus]) => { if ( - (orgSettings.visa_enrollment_settings.enabled || + (orgSettings.corporate_credit_card_settings.enabled || + orgSettings.visa_enrollment_settings.enabled || orgSettings.mastercard_enrollment_settings.enabled || orgSettings.amex_feed_enrollment_settings.enabled) && onboardingStatus.state !== OnboardingState.COMPLETED