diff --git a/package-lock.json b/package-lock.json index 94ccaaadb..a1c4d9d4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38,7 +38,7 @@ "@dvsa/mes-config-schema": "1.7.0", "@dvsa/mes-driver-schema": "^0.0.2", "@dvsa/mes-journal-schema": "1.3.1", - "@dvsa/mes-search-schema": "1.2.0", + "@dvsa/mes-search-schema": "1.3.0", "@dvsa/mes-test-schema": "3.42.5", "@ionic-enterprise/auth": "3.9.5", "@ionic/angular": "~7.5.6", @@ -2734,9 +2734,9 @@ "license": "MIT" }, "node_modules/@dvsa/mes-search-schema": { - "version": "1.2.0", - "resolved": "https://npm.pkg.github.com/download/@dvsa/mes-search-schema/1.2.0/4787b4a5f3aed319d1def12e358a4d51d36e0429", - "integrity": "sha512-QXiev07hH7W9p/4nKqP5HxF/S/sTUBwuDO0NE5uQRfyzbzF/2CtTTaupjZTnit73tLLU0XE4SJG2LFt1hNTcNQ==", + "version": "1.3.0", + "resolved": "https://npm.pkg.github.com/download/@dvsa/mes-search-schema/1.3.0/4196119777d66906ef3bde0b05e133d6d31693cb", + "integrity": "sha512-wQE8qB4UFVXqgngUyjE79+SH1+UstWITieDFaqsC6KZ0mmFGYYOBp/+Y+EWS93KYIST0YlURegfh8rfiTWouZA==", "license": "MIT" }, "node_modules/@dvsa/mes-test-schema": { diff --git a/package.json b/package.json index 614d30557..b1ba5defa 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "@dvsa/mes-config-schema": "1.7.0", "@dvsa/mes-driver-schema": "^0.0.2", "@dvsa/mes-journal-schema": "1.3.1", - "@dvsa/mes-search-schema": "1.2.0", + "@dvsa/mes-search-schema": "1.3.0", "@dvsa/mes-test-schema": "3.42.5", "@ionic-enterprise/auth": "3.9.5", "@ionic/angular": "~7.5.6", @@ -189,4 +189,4 @@ "npm run format" ] } -} \ No newline at end of file +} diff --git a/src/app/pages/candidate-details/candidate-details.page.html b/src/app/pages/candidate-details/candidate-details.page.html index e48062960..d4e6d92cb 100644 --- a/src/app/pages/candidate-details/candidate-details.page.html +++ b/src/app/pages/candidate-details/candidate-details.page.html @@ -51,6 +51,7 @@

Candidate details

+ diff --git a/src/app/pages/candidate-details/candidate-details.page.ts b/src/app/pages/candidate-details/candidate-details.page.ts index cd32c49c9..9c1b1f090 100644 --- a/src/app/pages/candidate-details/candidate-details.page.ts +++ b/src/app/pages/candidate-details/candidate-details.page.ts @@ -55,6 +55,7 @@ export class CandidateDetailsPage implements OnInit, OnDestroy, ViewDidEnter { slots: TestSlot[]; slotChanged: boolean = false; isTeamJournal: boolean = false; + isRecovered: boolean = false; testCategory: TestCategory = null; idPrefix: string = 'candidate-details'; prevSlot: TestSlot; @@ -81,6 +82,7 @@ export class CandidateDetailsPage implements OnInit, OnDestroy, ViewDidEnter { const navSlots = this.navParams.get('slots'); this.slotChanged = this.navParams.get('slotChanged'); this.isTeamJournal = this.navParams.get('isTeamJournal'); + this.isRecovered = this.navParams.get('isRecovered'); // if `slot` is not defined, then use the slot value from `navParams` // it will be undefined, when using the next/prev buttons as the value wouldn't be set via the Journal navigation diff --git a/src/app/pages/journal/components/journal-slot/__tests__/journal-slot.spec.ts b/src/app/pages/journal/components/journal-slot/__tests__/journal-slot.spec.ts index adbc448b0..0c2f86061 100644 --- a/src/app/pages/journal/components/journal-slot/__tests__/journal-slot.spec.ts +++ b/src/app/pages/journal/components/journal-slot/__tests__/journal-slot.spec.ts @@ -42,6 +42,16 @@ describe('JournalSlotComponent', () => { expect(component.derivedTestStatus({} as TestSlot, [])).toEqual(TestStatus.Submitted); }); }); + describe('derivedAutosaveStatus', () => { + it('should return null when slot not completed', () => { + spyOn(slotSelector, 'hasSlotBeenPartiallyCompleted').and.returnValue(null); + expect(component.derivedAutosaveStatus({} as TestSlot, [])).toEqual(null); + }); + it('should return true when remote slot status is autosaved', () => { + spyOn(slotSelector, 'hasSlotBeenPartiallyCompleted').and.returnValue(1); + expect(component.derivedAutosaveStatus({} as TestSlot, [])).toEqual(true); + }); + }); describe('hasSlotBeenTested', () => { it('should return null when hasSlotBeenTested returns null', () => { spyOn(slotSelector, 'hasSlotBeenTested').and.returnValue(null); diff --git a/src/app/pages/journal/components/journal-slot/journal-slot.html b/src/app/pages/journal/components/journal-slot/journal-slot.html index a231397dd..9dc2a83d6 100644 --- a/src/app/pages/journal/components/journal-slot/journal-slot.html +++ b/src/app/pages/journal/components/journal-slot/journal-slot.html @@ -32,6 +32,7 @@ [derivedActivityCode]="hasSlotBeenTested(slot.slotData, completedTests)" [derivedPassCertificate]="didSlotPass(slot.slotData, completedTests)" [derivedTestStatus]="derivedTestStatus(slot?.slotData, completedTests)" + [derivedAutosaveStatus]="derivedAutosaveStatus(slot?.slotData, completedTests)" [hasSeenCandidateDetails]="slot?.hasSeenCandidateDetails" [hasSlotChanged]="slot?.hasSlotChanged" [isPortrait]="isPortrait" diff --git a/src/app/pages/journal/components/journal-slot/journal-slot.ts b/src/app/pages/journal/components/journal-slot/journal-slot.ts index c59fa3005..c2e207845 100644 --- a/src/app/pages/journal/components/journal-slot/journal-slot.ts +++ b/src/app/pages/journal/components/journal-slot/journal-slot.ts @@ -34,6 +34,12 @@ export class JournalSlotComponent { completedTests: SearchResultTestSchema[], ): TestStatus | null => this.slotSelector.hasSlotBeenTested(slotData, completedTests) ? TestStatus.Submitted : null; + derivedAutosaveStatus = ( + slotData: TestSlot, + completedTests: SearchResultTestSchema[], + ): boolean | null => + this.slotSelector.hasSlotBeenPartiallyCompleted(slotData, completedTests) ? true : null; + hasSlotBeenTested = ( slotData: TestSlot, completedTests: SearchResultTestSchema[], diff --git a/src/app/providers/search/__mocks__/search-results.mock.ts b/src/app/providers/search/__mocks__/search-results.mock.ts index 56b11c0f8..3f5c973b5 100644 --- a/src/app/providers/search/__mocks__/search-results.mock.ts +++ b/src/app/providers/search/__mocks__/search-results.mock.ts @@ -13,6 +13,7 @@ export const searchResultsMock: SearchResultTestSchema [] = [ lastName: 'Blogs', title: 'Mr', }, + autosave: 1, }, { activityCode: '2', @@ -26,5 +27,6 @@ export const searchResultsMock: SearchResultTestSchema [] = [ lastName: 'Smith', title: 'Mr', }, + autosave: 1, }, ]; diff --git a/src/app/providers/slot-selector/__mocks__/slot-selector.mock.ts b/src/app/providers/slot-selector/__mocks__/slot-selector.mock.ts index af30e6b33..de3f854fe 100644 --- a/src/app/providers/slot-selector/__mocks__/slot-selector.mock.ts +++ b/src/app/providers/slot-selector/__mocks__/slot-selector.mock.ts @@ -36,6 +36,10 @@ export class SlotSelectorProviderMock { return '1'; } + hasSlotBeenPartiallyCompleted(): number | null { + return 1; + } + createSlots = (): void => { }; } diff --git a/src/app/providers/slot-selector/__tests__/slot-selector.spec.ts b/src/app/providers/slot-selector/__tests__/slot-selector.spec.ts index 3b1d7c14e..5fb2235f9 100644 --- a/src/app/providers/slot-selector/__tests__/slot-selector.spec.ts +++ b/src/app/providers/slot-selector/__tests__/slot-selector.spec.ts @@ -36,11 +36,63 @@ describe('SlotSelectorProvider', () => { applicationReference: 111222333, category: 'A', activityCode: '4', + autosave: 1, }])) .toEqual('4'); }); }); + describe('hasSlotBeenPartiallyCompleted', () => { + it('should return null if completedTest is empty', () => { + expect(slotSelector.hasSlotBeenPartiallyCompleted(null, null)) + .toEqual(null); + }); + + it('should return autosave status if the searched test entry exists', () => { + expect(slotSelector.hasSlotBeenPartiallyCompleted({ + booking: { + application: { + applicationId: 111, + bookingSequence: 222, + checkDigit: 333, + }, + }, + }, [{ + costCode: '123456', + testDate: '11/1/11', + driverNumber: '1234', + candidateName: { firstName: 'name' }, + applicationReference: 111222333, + category: 'A', + activityCode: '4', + autosave: 1, + }])) + .toEqual(1); + }); + + it('should return null if the searched test entry does not exist', () => { + expect(slotSelector.hasSlotBeenPartiallyCompleted({ + booking: { + application: { + applicationId: 111, + bookingSequence: 222, + checkDigit: 333, + }, + }, + }, [{ + costCode: '123456', + testDate: '11/1/11', + driverNumber: '1234', + candidateName: { firstName: 'name' }, + applicationReference: 999999999, + category: 'A', + activityCode: '4', + autosave: 1, + }])) + .toEqual(null); + }); + }); + describe('didSlotPass', () => { it('should return null if completedTest is empty', () => { expect(slotSelector.didSlotPass(null, null)) @@ -63,6 +115,7 @@ describe('SlotSelectorProvider', () => { applicationReference: 111222333, category: 'A', activityCode: '4', + autosave: 1, }])) .toEqual(undefined); }); @@ -84,6 +137,7 @@ describe('SlotSelectorProvider', () => { category: 'A', activityCode: '4', passCertificateNumber: '123456789', + autosave: 1, }])) .toEqual('123456789'); }); diff --git a/src/app/providers/slot-selector/slot-selector.ts b/src/app/providers/slot-selector/slot-selector.ts index 7c3b79f87..82858e6f1 100644 --- a/src/app/providers/slot-selector/slot-selector.ts +++ b/src/app/providers/slot-selector/slot-selector.ts @@ -59,6 +59,19 @@ export class SlotSelectorProvider { return completedTest.activityCode; } + /** + * Returns the autosave status of a test if held remotely + * @param slotData + * @param completedTests + */ + public hasSlotBeenPartiallyCompleted(slotData: TestSlot, completedTests: SearchResultTestSchema[]): number | null { + const completedTest = this.getCompletedTest(slotData, completedTests); + if (!completedTest) { + return null; + } + return completedTest.autosave; + } + private getCompletedTest = (slotData: TestSlot, completedTests: SearchResultTestSchema[]) => { if (isEmpty(completedTests)) { return null; diff --git a/src/components/common/common-components.module.ts b/src/components/common/common-components.module.ts index 6e35c4bab..e8773b4f8 100644 --- a/src/components/common/common-components.module.ts +++ b/src/components/common/common-components.module.ts @@ -47,6 +47,7 @@ import { DrivingFaultsBadgeComponent } from './driving-faults-badge/driving-faul import { EndTestLinkComponent } from './end-test-link/end-test-link'; import { VRNCaptureModalModule } from './vrn-capture-modal/vrn-capture-modal.module'; import { DirectivesModule } from '@directives/directives.module'; +import { TestRecoveredBannerComponent } from '@components/common/test-recovered-banner/test-recovered-banner'; @NgModule({ declarations: [ @@ -73,6 +74,7 @@ import { DirectivesModule } from '@directives/directives.module'; PracticeModeBanner, LockScreenIndicator, SeriousFaultBadgeComponent, + TestRecoveredBannerComponent, TickIndicatorComponent, TransmissionComponent, SignatureComponent, @@ -121,6 +123,7 @@ import { DirectivesModule } from '@directives/directives.module'; PracticeModeBanner, LockScreenIndicator, SeriousFaultBadgeComponent, + TestRecoveredBannerComponent, TickIndicatorComponent, TransmissionComponent, SignatureComponent, diff --git a/src/components/common/test-recovered-banner/__tests__/test-recovered-banner.spec.ts b/src/components/common/test-recovered-banner/__tests__/test-recovered-banner.spec.ts new file mode 100644 index 000000000..b253219dc --- /dev/null +++ b/src/components/common/test-recovered-banner/__tests__/test-recovered-banner.spec.ts @@ -0,0 +1,23 @@ +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; +import { TestRecoveredBannerComponent } from '@components/common/test-recovered-banner/test-recovered-banner'; + +describe('TestRecoveredBannerComponent', () => { + let component: TestRecoveredBannerComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [TestRecoveredBannerComponent], + imports: [IonicModule], + }); + + fixture = TestBed.createComponent(TestRecoveredBannerComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/components/common/test-recovered-banner/test-recovered-banner.html b/src/components/common/test-recovered-banner/test-recovered-banner.html new file mode 100644 index 000000000..151834d86 --- /dev/null +++ b/src/components/common/test-recovered-banner/test-recovered-banner.html @@ -0,0 +1,16 @@ +
+ + + + + + + + This test has been successfully recorded. +
No further action is required. +
+
+
+
+
+
diff --git a/src/components/common/test-recovered-banner/test-recovered-banner.scss b/src/components/common/test-recovered-banner/test-recovered-banner.scss new file mode 100644 index 000000000..8b5facce5 --- /dev/null +++ b/src/components/common/test-recovered-banner/test-recovered-banner.scss @@ -0,0 +1,29 @@ +:host { + .alert-icon { + color: var(--mes-white); + font-size: 50px; + } + + div { + background: var(--gds-dark-green); + border-left: 10px solid var(--gds-black); + } + + .center-icon { + text-align: center; + } + + .modal-alert-header { + margin: 0 0 24px; + } + + .recovered-centre { + display: flex; + align-items: center; + } + + .recovered-text { + color: var(--mes-white); + } +} + diff --git a/src/components/common/test-recovered-banner/test-recovered-banner.ts b/src/components/common/test-recovered-banner/test-recovered-banner.ts new file mode 100644 index 000000000..ed09baba4 --- /dev/null +++ b/src/components/common/test-recovered-banner/test-recovered-banner.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'test-recovered-banner', + templateUrl: 'test-recovered-banner.html', + styleUrls: ['test-recovered-banner.scss'], +}) +export class TestRecoveredBannerComponent { +} diff --git a/src/components/test-slot/autosave-status/__tests__/autosave-status.spec.ts b/src/components/test-slot/autosave-status/__tests__/autosave-status.spec.ts new file mode 100644 index 000000000..e178afcd2 --- /dev/null +++ b/src/components/test-slot/autosave-status/__tests__/autosave-status.spec.ts @@ -0,0 +1,23 @@ +import { waitForAsync, ComponentFixture, TestBed } from '@angular/core/testing'; +import { IonicModule } from '@ionic/angular'; +import { AutosaveStatusComponent } from '@components/test-slot/autosave-status/autosave-status'; + +describe('AutosaveStatusComponent', () => { + let component: AutosaveStatusComponent; + let fixture: ComponentFixture; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [AutosaveStatusComponent], + imports: [IonicModule], + }); + + fixture = TestBed.createComponent(AutosaveStatusComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + })); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/components/test-slot/autosave-status/autosave-status.html b/src/components/test-slot/autosave-status/autosave-status.html new file mode 100644 index 000000000..781d48fd8 --- /dev/null +++ b/src/components/test-slot/autosave-status/autosave-status.html @@ -0,0 +1,3 @@ +
+ RECOVERED +
diff --git a/src/components/test-slot/autosave-status/autosave-status.scss b/src/components/test-slot/autosave-status/autosave-status.scss new file mode 100644 index 000000000..3d371fb82 --- /dev/null +++ b/src/components/test-slot/autosave-status/autosave-status.scss @@ -0,0 +1,8 @@ +.recovered-text { + font-size: 15px !important; + background: var(--gds-dark-green); + color: var(--mes-white) !important; + display: flex; + align-items: center; + justify-content: center; +} diff --git a/src/components/test-slot/autosave-status/autosave-status.ts b/src/components/test-slot/autosave-status/autosave-status.ts new file mode 100644 index 000000000..8a700e4d7 --- /dev/null +++ b/src/components/test-slot/autosave-status/autosave-status.ts @@ -0,0 +1,9 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'autosave-status', + templateUrl: 'autosave-status.html', + styleUrls: ['autosave-status.scss'], +}) +export class AutosaveStatusComponent { +} diff --git a/src/components/test-slot/candidate-link/candidate-link.html b/src/components/test-slot/candidate-link/candidate-link.html index 0542752aa..916b657a6 100644 --- a/src/components/test-slot/candidate-link/candidate-link.html +++ b/src/components/test-slot/candidate-link/candidate-link.html @@ -12,6 +12,7 @@ 'candidate-name-portrait': isPortrait, 'candidate-name-landscape': !isPortrait, 'team-journal': isTeamJournal, + 'recovered': isRecovered, 'extra-large-mode': accessibilityService.getTextZoomClass() === 'text-zoom-x-large' }" >{{name.title}} {{name.firstName}} {{name.lastName}} { }); }); + describe('isAutosavedTest', () => { + it('should return true when remoteAutosaved is true and testStatus is not Autosaved', () => { + const result = component.isAutosavedTest(true, TestStatus.Completed); + expect(result).toEqual(true); + }); + + it('should return false when remoteAutosaved is true and testStatus is Autosaved', () => { + const result = component.isAutosavedTest(true, TestStatus.Autosaved); + expect(result).toEqual(false); + }); + + it('should return false when remoteAutosaved is false regardless of testStatus', () => { + const resultWithAutosavedStatus = component.isAutosavedTest(false, TestStatus.Autosaved); + const resultWithCompletedStatus = component.isAutosavedTest(false, TestStatus.Completed); + + expect(resultWithAutosavedStatus).toEqual(false); + expect(resultWithCompletedStatus).toEqual(false); + }); + }); + describe('DOM', () => { describe('Component Interaction', () => { it('should pass the special needs status to a indicator component', () => { diff --git a/src/components/test-slot/test-slot/test-slot.html b/src/components/test-slot/test-slot/test-slot.html index afc5a1401..3f09d0823 100644 --- a/src/components/test-slot/test-slot/test-slot.html +++ b/src/components/test-slot/test-slot/test-slot.html @@ -29,6 +29,11 @@ class="full-width" > + + @@ -157,6 +162,7 @@ [isTeamJournal]="isTeamJournal" [name]="slot.booking.candidate.candidateName" [slotChanged]="hasSlotChanged" + [isRecovered]="isAutosavedTest(derivedAutosaveStatus, componentState.testStatus$ | async)" [slot]="slot" [slots]="slots" > diff --git a/src/components/test-slot/test-slot/test-slot.ts b/src/components/test-slot/test-slot/test-slot.ts index f7ab96870..9f2f576f7 100644 --- a/src/components/test-slot/test-slot/test-slot.ts +++ b/src/components/test-slot/test-slot/test-slot.ts @@ -70,6 +70,9 @@ export class TestSlotComponent implements SlotComponent, OnInit { @Input() derivedTestStatus: TestStatus | null = null; + @Input() + derivedAutosaveStatus: boolean | null = null; + @Input() derivedActivityCode: ActivityCode | null = null; @@ -205,6 +208,16 @@ export class TestSlotComponent implements SlotComponent, OnInit { isCompletedTest = (testStatus: TestStatus): boolean => testStatus === TestStatus.Completed; + /** + * Determines if the test held locally in an autosaved state + * and exists remotely as autosaved + * @param autosaved + * @param testStatus + */ + isAutosavedTest = (remoteAutosaved: boolean, testStatus: TestStatus): boolean => { + return remoteAutosaved && testStatus !== TestStatus.Autosaved; + } + showOutcome(status: TestStatus): boolean { return [TestStatus.Completed, TestStatus.Submitted].includes(status); } diff --git a/src/index.html b/src/index.html index 66dca0a36..c259ac11d 100644 --- a/src/index.html +++ b/src/index.html @@ -1,37 +1,37 @@ - - - DVSA DES + + + DVSA DES - + - - - - + + + + - + - + gtag('js', new Date()); + - - - - + + + + - - - + + + diff --git a/src/store/journal/__tests__/journal.selector.spec.ts b/src/store/journal/__tests__/journal.selector.spec.ts index 9fd3a7b6c..a447c20ea 100644 --- a/src/store/journal/__tests__/journal.selector.spec.ts +++ b/src/store/journal/__tests__/journal.selector.spec.ts @@ -412,6 +412,7 @@ describe('JournalSelector', () => { applicationReference: 1234561014, category: 'B', activityCode: '1', + autosave: 1, }, ], }; @@ -444,6 +445,7 @@ describe('JournalSelector', () => { applicationReference: 1234567031, category: 'category', activityCode: '2', + autosave: 1, }, { costCode: 'costCode', @@ -453,6 +455,7 @@ describe('JournalSelector', () => { applicationReference: 1234569019, category: 'category', activityCode: '11', + autosave: 1, }, ], };