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 @@
+
+
+
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,
},
],
};