Merge remote-tracking branch 'origin/develop' into feature/skiener/INSR-2124
This commit is contained in:
commit
6e2e7d8ee3
13 changed files with 676 additions and 148 deletions
|
|
@ -342,6 +342,7 @@ stages:
|
||||||
- template: templates/digital/auto-tag.yml@AzureDevOps
|
- template: templates/digital/auto-tag.yml@AzureDevOps
|
||||||
parameters:
|
parameters:
|
||||||
dependsOn: prodBuildDeployment
|
dependsOn: prodBuildDeployment
|
||||||
|
environment: digitalCloud-prod
|
||||||
userName: SafeliteAzureDevops
|
userName: SafeliteAzureDevops
|
||||||
userEmail: githubazuredevops@safelite.com
|
userEmail: githubazuredevops@safelite.com
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -73,3 +73,22 @@ export function containsRecalParts(lineItems) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function anyPartWithRequiresRecalFlag(lineItems) {
|
||||||
|
if (!lineItems) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(lineItems)) {
|
||||||
|
return lineItems.some((li) => li.requiresRecalibration === true);
|
||||||
|
}
|
||||||
|
|
||||||
|
const flattened = [
|
||||||
|
...(lineItems.glassParts ?? []),
|
||||||
|
...(lineItems.supportingItems ?? []),
|
||||||
|
...(lineItems.otherParts ?? []),
|
||||||
|
...(lineItems.feeItems ?? []),
|
||||||
|
...(lineItems.vaps ?? []),
|
||||||
|
];
|
||||||
|
|
||||||
|
return flattened.some((li) => li.requiresRecalibration === true);
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ export default {
|
||||||
default: 'RecalModal'
|
default: 'RecalModal'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
emits: ['recal-modal-opened'],
|
||||||
computed: {
|
computed: {
|
||||||
modalBodyText() {
|
modalBodyText() {
|
||||||
return this.getCmsContent(this.cmsWidgetName, 'BodyText').replaceAll('{custom:vehicleMake}', this.vehicleMake);
|
return this.getCmsContent(this.cmsWidgetName, 'BodyText').replaceAll('{custom:vehicleMake}', this.vehicleMake);
|
||||||
|
|
@ -50,6 +51,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
openModal() {
|
openModal() {
|
||||||
this.$refs.recalModal.openModal();
|
this.$refs.recalModal.openModal();
|
||||||
|
this.$emit('recal-modal-opened');
|
||||||
},
|
},
|
||||||
footerButtonClick() {
|
footerButtonClick() {
|
||||||
this.$refs.recalModal.closeModal();
|
this.$refs.recalModal.closeModal();
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import { useMainStore } from '@/store';
|
||||||
import { getPriceOfLineItems } from '@/helpers/price-calculator.js';
|
import { getPriceOfLineItems } from '@/helpers/price-calculator.js';
|
||||||
import coverageStatuses from '@/constants/coverage-statuses';
|
import coverageStatuses from '@/constants/coverage-statuses';
|
||||||
import coverageType from '@/constants/coverage-type';
|
import coverageType from '@/constants/coverage-type';
|
||||||
|
import { containsRecalParts } from '@/helpers/recal-helper';
|
||||||
|
|
||||||
jest.mock('@/helpers/layout-helper.js', () => jest.fn());
|
jest.mock('@/helpers/layout-helper.js', () => jest.fn());
|
||||||
jest.mock('@/helpers/price-calculator.js', () => ({
|
jest.mock('@/helpers/price-calculator.js', () => ({
|
||||||
|
|
@ -36,8 +37,6 @@ jest.mock('@/helpers/recal-helper.js', () => ({
|
||||||
containsRecalParts: jest.fn()
|
containsRecalParts: jest.fn()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
import { containsRecalParts } from '@/helpers/recal-helper.js';
|
|
||||||
|
|
||||||
const SAFELITE_PROVIDER = 'Safelite';
|
const SAFELITE_PROVIDER = 'Safelite';
|
||||||
const CANCEL_CLAIM_REF_NAME = 'CancelClaimModal';
|
const CANCEL_CLAIM_REF_NAME = 'CancelClaimModal';
|
||||||
|
|
||||||
|
|
@ -115,7 +114,8 @@ describe('coverageStatement.vue', () => {
|
||||||
const mainInitialState = {
|
const mainInitialState = {
|
||||||
order: {
|
order: {
|
||||||
damage: {
|
damage: {
|
||||||
isRepair: true
|
isRepair: true,
|
||||||
|
numberOfChips: 2
|
||||||
},
|
},
|
||||||
insuranceCoverage: {
|
insuranceCoverage: {
|
||||||
coverageStatus: coverageStatuses.PENDING,
|
coverageStatus: coverageStatuses.PENDING,
|
||||||
|
|
@ -343,7 +343,7 @@ describe('coverageStatement.vue', () => {
|
||||||
// Assert
|
// Assert
|
||||||
expect(result).toBeFalsy();
|
expect(result).toBeFalsy();
|
||||||
});
|
});
|
||||||
test('returns true when a part in glassParts require recalibration and recalibration has been added to order', () => {
|
test('returns true when a part in glassParts requires recalibration and recalibration has been added to order', () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
containsRecalParts.mockReturnValue(true);
|
containsRecalParts.mockReturnValue(true);
|
||||||
const mainInitialState = {
|
const mainInitialState = {
|
||||||
|
|
@ -614,7 +614,8 @@ describe('coverageStatement.vue', () => {
|
||||||
const mainInitialState = {
|
const mainInitialState = {
|
||||||
order: {
|
order: {
|
||||||
damage: {
|
damage: {
|
||||||
isRepair: true
|
isRepair: true,
|
||||||
|
numberOfChips: 2
|
||||||
},
|
},
|
||||||
insuranceCoverage: {
|
insuranceCoverage: {
|
||||||
coverageStatus: coverageStatuses.PENDING,
|
coverageStatus: coverageStatuses.PENDING,
|
||||||
|
|
@ -644,7 +645,8 @@ describe('coverageStatement.vue', () => {
|
||||||
const mainInitialState = {
|
const mainInitialState = {
|
||||||
order: {
|
order: {
|
||||||
damage: {
|
damage: {
|
||||||
isRepair: true
|
isRepair: true,
|
||||||
|
numberOfChips: 2
|
||||||
},
|
},
|
||||||
insuranceCoverage: {
|
insuranceCoverage: {
|
||||||
coverageStatus: coverageStatuses.VERIFIED,
|
coverageStatus: coverageStatuses.VERIFIED,
|
||||||
|
|
@ -673,7 +675,8 @@ describe('coverageStatement.vue', () => {
|
||||||
const mainInitialState = {
|
const mainInitialState = {
|
||||||
order: {
|
order: {
|
||||||
damage: {
|
damage: {
|
||||||
isRepair: true
|
isRepair: true,
|
||||||
|
numberOfChips: 2
|
||||||
},
|
},
|
||||||
insuranceCoverage: {
|
insuranceCoverage: {
|
||||||
coverageStatus: coverageStatuses.VERIFIED,
|
coverageStatus: coverageStatuses.VERIFIED,
|
||||||
|
|
@ -705,7 +708,8 @@ describe('coverageStatement.vue', () => {
|
||||||
const mainInitialState = {
|
const mainInitialState = {
|
||||||
order: {
|
order: {
|
||||||
damage: {
|
damage: {
|
||||||
isRepair: true
|
isRepair: true,
|
||||||
|
numberOfChips: 2
|
||||||
},
|
},
|
||||||
insuranceCoverage: {
|
insuranceCoverage: {
|
||||||
coverageStatus: coverageStatuses.VERIFIED,
|
coverageStatus: coverageStatuses.VERIFIED,
|
||||||
|
|
@ -732,7 +736,8 @@ describe('coverageStatement.vue', () => {
|
||||||
const mainInitialState = {
|
const mainInitialState = {
|
||||||
order: {
|
order: {
|
||||||
damage: {
|
damage: {
|
||||||
isRepair: true
|
isRepair: true,
|
||||||
|
numberOfChips: 2
|
||||||
},
|
},
|
||||||
insuranceCoverage: {
|
insuranceCoverage: {
|
||||||
coverageStatus: coverageStatuses.VERIFIED,
|
coverageStatus: coverageStatuses.VERIFIED,
|
||||||
|
|
@ -763,7 +768,8 @@ describe('coverageStatement.vue', () => {
|
||||||
const mainInitialState = {
|
const mainInitialState = {
|
||||||
order: {
|
order: {
|
||||||
damage: {
|
damage: {
|
||||||
isRepair: true
|
isRepair: true,
|
||||||
|
numberOfChips: 2
|
||||||
},
|
},
|
||||||
insuranceCoverage: {
|
insuranceCoverage: {
|
||||||
coverageStatus: coverageStatuses.VERIFIED,
|
coverageStatus: coverageStatuses.VERIFIED,
|
||||||
|
|
|
||||||
|
|
@ -103,14 +103,16 @@
|
||||||
</div>
|
</div>
|
||||||
<recalModal
|
<recalModal
|
||||||
:ref="RECAL_MODAL_REF_NAME"
|
:ref="RECAL_MODAL_REF_NAME"
|
||||||
cmsWidgetName="RecalModal" />
|
cmsWidgetName="RecalModal"
|
||||||
|
@recalModalOpened="handleRecalModalOpened" />
|
||||||
<cancelClaimModal
|
<cancelClaimModal
|
||||||
:ref="CANCEL_CLAIM_REF_NAME"
|
:ref="CANCEL_CLAIM_REF_NAME"
|
||||||
@cancelClaimConfirmation="cancelClaim"
|
@cancelClaimConfirmation="cancelClaim"
|
||||||
@returnToClaim="navigateForward" />
|
@returnToClaim="navigateForward" />
|
||||||
<oemEndorsementModal
|
<oemEndorsementModal
|
||||||
:ref="OEM_ENDORSEMENT_REF_NAME"
|
:ref="OEM_ENDORSEMENT_REF_NAME"
|
||||||
cmsWidgetName="OEMEndorsementModal" />
|
cmsWidgetName="OEMEndorsementModal"
|
||||||
|
@oemEndorsementModalOpened="handleOemEndorsementModalOpened" />
|
||||||
</Form>
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -377,6 +379,71 @@ export default {
|
||||||
setupModalLinks(this);
|
setupModalLinks(this);
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
logEventsToGA() {
|
||||||
|
const clientName = this.mainStore.accountNameForEvents;
|
||||||
|
|
||||||
|
if (this.isDeductibleVisible || this.isITACQuoteVisible) {
|
||||||
|
this.pushEventToGA('coverage', 'Yes', clientName, true, null, '1');
|
||||||
|
if (this.isITACQuoteVisible) {
|
||||||
|
this.pushEventToGA('Coverage', 'ITAC', `${this.deductibleValue}_${this.totalServicePrice}`, true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.pushEventToGA('coverage', 'full_comprehensive', clientName, true);
|
||||||
|
this.pushEventToGA('coverage', 'deductible', this.deductibleValue.toString(), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.pushEventToGA('coverage', 'No', clientName, true, null, '0');
|
||||||
|
if (this.isNoCompQuoteVisible) {
|
||||||
|
this.pushEventToGA('coverage', 'no_comprehensive', `${this.totalServicePrice}`, true);
|
||||||
|
this.pushEventToGA('coverage', 'deductible', '9999', true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.pushEventToGA('coverage', 'deductible', '7777', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.mainStore.hasOemEndorsement) {
|
||||||
|
this.pushEventToGA('oem_endorsement', 'displayed', 'Yes', true, null, '1');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.pushEventToGA('oem_endorsement', 'displayed', 'No', true, null, '0');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isADAS) {
|
||||||
|
if (this.mainStore.vehicle.canSafeliteService) {
|
||||||
|
this.pushEventToGA('ADAS_self_service', 'ADAS_servicable_status', 'yes', true);
|
||||||
|
if (this.mainStore.hasRecalibrationPart) {
|
||||||
|
if (this.isDeductibleVisible) {
|
||||||
|
const recalibrationType = this.mainStore.lineItems.glassParts.find((part) => part.requiresRecalibration).recalibrationType;
|
||||||
|
if (this.deductibleValue <= 0) {
|
||||||
|
this.pushEventToGA('recalibration_opportunity_verified_0_deductible', this.mainStore.vehicle.carId, `recal_type_${recalibrationType}`, true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.pushEventToGA('recalibration_opportunity_verified_over_0_deductible', this.mainStore.vehicle.carId, `recal_type_${recalibrationType}`, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
let appointmentType = '';
|
||||||
|
if (this.isITACQuoteVisible) {
|
||||||
|
appointmentType = 'ITAC';
|
||||||
|
}
|
||||||
|
else if (this.isNoCompQuoteVisible) {
|
||||||
|
appointmentType = 'no_comp';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
appointmentType = 'unverified';
|
||||||
|
}
|
||||||
|
this.pushEventToGA(`recalibration_opportunity_${appointmentType}`, this.mainStore.vehicle.carId, `recal_type_${this.mainStore.lineItems.glassParts.find((part) => part.requiresRecalibration).recalibrationType}`, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.pushEventToGA('ADAS_self_service', 'ADAS_servicable_status', 'no', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
arePagePrerequisitesValid() {
|
arePagePrerequisitesValid() {
|
||||||
return !!useMainStore().vehicle.carId;
|
return !!useMainStore().vehicle.carId;
|
||||||
},
|
},
|
||||||
|
|
@ -396,6 +463,8 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logEventsToGA();
|
||||||
|
|
||||||
showIssLoadingModal(false);
|
showIssLoadingModal(false);
|
||||||
this.isPageLoading = false;
|
this.isPageLoading = false;
|
||||||
},
|
},
|
||||||
|
|
@ -414,7 +483,13 @@ export default {
|
||||||
},
|
},
|
||||||
async navigateForward() {
|
async navigateForward() {
|
||||||
showIssLoadingModal(true);
|
showIssLoadingModal(true);
|
||||||
if (this.isUnverifiedVisible || this.isDeductibleVisible) {
|
if (this.isUnverifiedVisible || this.isDeductibleVisible) {
|
||||||
|
if (this.isRepair) {
|
||||||
|
this.pushEventToGA('RepairDamage', 'Repair', this.mainStore.damage.numberOfChips.toString(), true, null, '1');
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.pushEventToGA('RepairDamage', 'Replacement', 'Replacement', true, null, '1');
|
||||||
|
}
|
||||||
this.navigateWithScenario(navigationScenarios.CLICKED_FORWARD);
|
this.navigateWithScenario(navigationScenarios.CLICKED_FORWARD);
|
||||||
} else if (this.isITACQuoteVisible || this.isNoCompQuoteVisible) {
|
} else if (this.isITACQuoteVisible || this.isNoCompQuoteVisible) {
|
||||||
this.mainStore.updateIsSafeliteProvider(true);
|
this.mainStore.updateIsSafeliteProvider(true);
|
||||||
|
|
@ -478,7 +553,13 @@ export default {
|
||||||
cancelClaim() {
|
cancelClaim() {
|
||||||
this.mainStore.updateIsSafeliteProvider(false);
|
this.mainStore.updateIsSafeliteProvider(false);
|
||||||
this.$router.navigateBailout(bailoutMessage.CoverageCancelledByUser());
|
this.$router.navigateBailout(bailoutMessage.CoverageCancelledByUser());
|
||||||
}
|
},
|
||||||
|
handleRecalModalOpened() {
|
||||||
|
this.pushEventToGA('recalibration_modal', 'learn_more_clicked', this.mainStore.vehicle.carId, true);
|
||||||
|
},
|
||||||
|
handleOemEndorsementModalOpened() {
|
||||||
|
this.pushEventToGA('oem_endorsement_modal', 'clicked', 'Yes', true, null, '1');
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ export default {
|
||||||
default: 'OEMEndorsementModal'
|
default: 'OEMEndorsementModal'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
emits: ['oem-endorsement-modal-opened'],
|
||||||
computed: {
|
computed: {
|
||||||
modalBodyText() {
|
modalBodyText() {
|
||||||
return this.getCmsContent(this.cmsWidgetName, 'BodyText');
|
return this.getCmsContent(this.cmsWidgetName, 'BodyText');
|
||||||
|
|
@ -40,6 +41,7 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
openModal() {
|
openModal() {
|
||||||
this.$refs.oemEndorsementModal.openModal();
|
this.$refs.oemEndorsementModal.openModal();
|
||||||
|
this.$emit('oem-endorsement-modal-opened');
|
||||||
},
|
},
|
||||||
footerButtonClick() {
|
footerButtonClick() {
|
||||||
this.$refs.oemEndorsementModal.closeModal();
|
this.$refs.oemEndorsementModal.closeModal();
|
||||||
|
|
|
||||||
|
|
@ -193,7 +193,7 @@ export default {
|
||||||
appointmentType: serviceLocation.appointmentType,
|
appointmentType: serviceLocation.appointmentType,
|
||||||
providerAddress: serviceLocation?.provider?.address,
|
providerAddress: serviceLocation?.provider?.address,
|
||||||
customerPortalLoginToken,
|
customerPortalLoginToken,
|
||||||
carrierName: issConfig.clientName,
|
carrierName: issConfig.clientDisplayName,
|
||||||
carrierUrl: issConfig.successReturnURL,
|
carrierUrl: issConfig.successReturnURL,
|
||||||
isRepair: damage.isRepair,
|
isRepair: damage.isRepair,
|
||||||
hasRecalibrationPart,
|
hasRecalibrationPart,
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@
|
||||||
linkType="text"
|
linkType="text"
|
||||||
href="#!"
|
href="#!"
|
||||||
:text="textForToggle"
|
:text="textForToggle"
|
||||||
@click="handleClickToggle" />
|
@click-event="handleClickToggle" />
|
||||||
<div
|
<button
|
||||||
|
type="button"
|
||||||
class="tpa-toggle d-inline-block"
|
class="tpa-toggle d-inline-block"
|
||||||
:class="[isDetailVisible ? 'active' : '']"
|
:class="[isDetailVisible ? 'active' : '']"
|
||||||
@click="handleClickToggle"></div>
|
@click="handleClickToggle"></button>
|
||||||
<Transition>
|
<Transition>
|
||||||
<div
|
<div
|
||||||
v-show="isDetailVisible"
|
v-show="isDetailVisible"
|
||||||
|
|
@ -60,6 +61,8 @@ export default {
|
||||||
.tpa-recal-toggle {
|
.tpa-recal-toggle {
|
||||||
.tpa-toggle {
|
.tpa-toggle {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: "";
|
content: "";
|
||||||
|
|
|
||||||
151
src/layouts/schedule-page/recal-ack-modal/recal-ack-modal.vue
Normal file
151
src/layouts/schedule-page/recal-ack-modal/recal-ack-modal.vue
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
<template>
|
||||||
|
<div id="recal-ack-modal-container">
|
||||||
|
<modal
|
||||||
|
:ref="modalName"
|
||||||
|
:modalId="modalName"
|
||||||
|
:footerButtonText="modalCloseButtonText"
|
||||||
|
@footerButtonEvent="footerButtonClick"
|
||||||
|
:displayCloseButton="false"
|
||||||
|
:displayCloseLink="true">
|
||||||
|
<div class="form-test-invalid">
|
||||||
|
<h5 class="modal-headline">{{ modalHeadline }}</h5>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
class="mb-4 modal-body-text"
|
||||||
|
v-html="modalBodyText"></div>
|
||||||
|
<recalAckToggle
|
||||||
|
class="mb-3"
|
||||||
|
cmsWidgetName="RecalAckModalToggle" />
|
||||||
|
<checkBox
|
||||||
|
ref="recalAcknowledgement"
|
||||||
|
v-model="isAcknowledged"
|
||||||
|
class="mb-4 mt-4 acknowledgement-checkbox"
|
||||||
|
:class="showAcknowledgementError && ' has-error'"
|
||||||
|
:validationRules="rules.optionRequired"
|
||||||
|
checkboxName="recalAcknowledgement"
|
||||||
|
buttonID="recalAcknowledgement"
|
||||||
|
:tabIndex="0"
|
||||||
|
:checkboxLabel="modalSubBodyText"
|
||||||
|
:screenReaderOnlyText="modalSubBodyText"
|
||||||
|
isRequired />
|
||||||
|
<div
|
||||||
|
v-if="showAcknowledgementError"
|
||||||
|
class="row form-test-error mt-1">
|
||||||
|
<p>{{ errorMessage }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import modal from "@/digital-components/modal/modal";
|
||||||
|
import recalAckToggle from '@/layouts/schedule-page/recal-ack-modal/recal-ack-toggle/recal-ack-toggle.vue';
|
||||||
|
import checkBox from '@/ux-components/checkbox/checkbox.vue';
|
||||||
|
import globalRules from '@/constants/global-rules';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "recal-ack-modal",
|
||||||
|
components: {
|
||||||
|
modal,
|
||||||
|
checkBox,
|
||||||
|
recalAckToggle
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
cmsWidgetName: String,
|
||||||
|
ackError: String
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isAcknowledged: false,
|
||||||
|
showAcknowledgementError: false,
|
||||||
|
rules: {
|
||||||
|
optionRequired: globalRules.OPTION_REQUIRED
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
modalName() {
|
||||||
|
return this.cmsWidgetName;
|
||||||
|
},
|
||||||
|
modalHeadline() {
|
||||||
|
return this.getCmsContent(this.cmsWidgetName, 'HeaderText');
|
||||||
|
},
|
||||||
|
modalBodyText() {
|
||||||
|
return this.getCmsContent(this.cmsWidgetName, 'BodyText');
|
||||||
|
},
|
||||||
|
modalSubBodyText() {
|
||||||
|
return this.getCmsContent(this.cmsWidgetName, 'BodyText2');
|
||||||
|
},
|
||||||
|
modalCloseButtonText() {
|
||||||
|
return this.getCmsContent(this.cmsWidgetName, 'FooterText');
|
||||||
|
},
|
||||||
|
errorMessage() {
|
||||||
|
if (this.showAcknowledgementError) {
|
||||||
|
return this.ackError;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
isAcknowledged() {
|
||||||
|
this.showAcknowledgementError = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openModal() {
|
||||||
|
this.$refs[this.modalName].openModal();
|
||||||
|
},
|
||||||
|
footerButtonClick() {
|
||||||
|
if (this.isAcknowledged) {
|
||||||
|
this.$emit('recalAcknowledged', this.isAcknowledged);
|
||||||
|
this.$refs[this.modalName]?.closeModal();
|
||||||
|
} else {
|
||||||
|
this.showAcknowledgementError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.modal-headline {
|
||||||
|
font-size: 1.625rem;
|
||||||
|
font-weight: 300;
|
||||||
|
}
|
||||||
|
.modal-body-text {
|
||||||
|
color: #4d4e53;
|
||||||
|
:deep(strong) {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
:deep(p:first-of-type) {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.acknowledgement-checkbox {
|
||||||
|
:deep(p) {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #4d4e53;
|
||||||
|
}
|
||||||
|
:deep(.form-check-input) {
|
||||||
|
&:checked + label p {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #4d4e53;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.modal) {
|
||||||
|
.modal-footer {
|
||||||
|
button {
|
||||||
|
background-color: $green;
|
||||||
|
color: $white;
|
||||||
|
font-weight: $font-weight-bold;
|
||||||
|
&:hover, &:focus {
|
||||||
|
background-color: $green;
|
||||||
|
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $green;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,115 @@
|
||||||
|
<template>
|
||||||
|
<div class="recal-ack-toggle">
|
||||||
|
<textLink
|
||||||
|
id="moreDetails"
|
||||||
|
class="d-inline-block"
|
||||||
|
linkType="text"
|
||||||
|
href="#!"
|
||||||
|
:text="textForToggle"
|
||||||
|
:preventDefaultOnClick="true"
|
||||||
|
@click-event="handleClickToggle" />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="recal-ack-toggle d-inline-block"
|
||||||
|
:class="[isDetailVisible ? 'active' : '']"
|
||||||
|
@click="handleClickToggle"></button>
|
||||||
|
<Transition>
|
||||||
|
<div
|
||||||
|
v-show="isDetailVisible"
|
||||||
|
id="recal-ack-detail-wrapper"
|
||||||
|
class="mt-2">
|
||||||
|
<div
|
||||||
|
id="recal-ack-detail-content"
|
||||||
|
class="mb-2"
|
||||||
|
v-html="detailContent" />
|
||||||
|
</div>
|
||||||
|
</Transition>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import textLink from '@/ux-components/text-link/text-link.vue';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'recal-ack-toggle',
|
||||||
|
components: {
|
||||||
|
textLink
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
cmsWidgetName: String
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
isDetailVisible: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
textForToggle() {
|
||||||
|
return this.getCmsContent(this.cmsWidgetName, 'HeaderText');
|
||||||
|
},
|
||||||
|
detailContent() {
|
||||||
|
return this.getCmsContent(this.cmsWidgetName, 'BodyText');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClickToggle() {
|
||||||
|
this.isDetailVisible = !this.isDetailVisible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import "@/styles/ux-variables-svg-strings.scss";
|
||||||
|
.recal-ack-toggle {
|
||||||
|
.recal-ack-toggle {
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
transition: all 0.5s ease;
|
||||||
|
background-image: url($svg-tpa-recal-modal-toggle);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
margin-left: 0.5rem;
|
||||||
|
width: 16px;
|
||||||
|
height: 9px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
&.active::after {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#recal-ack-detail-wrapper {
|
||||||
|
// START - Vue Transition
|
||||||
|
&.v-enter-active,
|
||||||
|
&.v-leave-active {
|
||||||
|
transition: opacity 250ms ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.v-enter-from,
|
||||||
|
&.v-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
// END - Vue Transition
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#recal-ack-detail-content {
|
||||||
|
font-size: 1rem;
|
||||||
|
p {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
color: #4d4e53;
|
||||||
|
}
|
||||||
|
ol {
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -12,6 +12,8 @@ jest.mock('@/helpers/cms-content-helper', () => ({
|
||||||
fetchCmsContentForPage: jest.fn()
|
fetchCmsContentForPage: jest.fn()
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
const RECAL_ACK_MODAL_REF_NAME = 'recalAckModal';
|
||||||
|
|
||||||
const mockMixin = {
|
const mockMixin = {
|
||||||
methods: {
|
methods: {
|
||||||
getCmsContent: jest.fn().mockImplementation(() => ''),
|
getCmsContent: jest.fn().mockImplementation(() => ''),
|
||||||
|
|
@ -55,6 +57,22 @@ const loadingModalStub = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const serviceLocationStub = {
|
||||||
|
render: () => {},
|
||||||
|
methods: {
|
||||||
|
forwardButtonAction: jest.fn(),
|
||||||
|
initializeComponent: jest.fn()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const recalAckModalStub = {
|
||||||
|
template: '<div></div>',
|
||||||
|
methods: {
|
||||||
|
openModal: jest.fn(),
|
||||||
|
footerButtonClick: jest.fn()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
function getShallowMountedComponent(initialData = {}, methodToRun = () => {}) {
|
function getShallowMountedComponent(initialData = {}, methodToRun = () => {}) {
|
||||||
const mountOptions = getMountOptions({
|
const mountOptions = getMountOptions({
|
||||||
router: {
|
router: {
|
||||||
|
|
@ -64,7 +82,9 @@ function getShallowMountedComponent(initialData = {}, methodToRun = () => {}) {
|
||||||
|
|
||||||
mountOptions.global.stubs = {
|
mountOptions.global.stubs = {
|
||||||
siteFooter: footerStub,
|
siteFooter: footerStub,
|
||||||
loadingModal: loadingModalStub
|
loadingModal: loadingModalStub,
|
||||||
|
serviceLocation: serviceLocationStub,
|
||||||
|
recalAckModal: recalAckModalStub
|
||||||
};
|
};
|
||||||
|
|
||||||
methodToRun();
|
methodToRun();
|
||||||
|
|
@ -75,6 +95,27 @@ function getShallowMountedComponent(initialData = {}, methodToRun = () => {}) {
|
||||||
);
|
);
|
||||||
|
|
||||||
const wrapper = shallowMount(schedule, mountOptions);
|
const wrapper = shallowMount(schedule, mountOptions);
|
||||||
|
|
||||||
|
// Manually create the ref for recalAckModal since shallowMount doesn't populate it
|
||||||
|
Object.defineProperty(wrapper.vm.$refs, RECAL_ACK_MODAL_REF_NAME, {
|
||||||
|
value: {
|
||||||
|
openModal: jest.fn(),
|
||||||
|
footerButtonClick: jest.fn()
|
||||||
|
},
|
||||||
|
writable: false,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Manually create the ref for serviceLocation since shallowMount doesn't populate it
|
||||||
|
Object.defineProperty(wrapper.vm.$refs, 'serviceLocation', {
|
||||||
|
value: {
|
||||||
|
forwardButtonAction: jest.fn(),
|
||||||
|
initializeComponent: jest.fn()
|
||||||
|
},
|
||||||
|
writable: false,
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
|
||||||
return { wrapper };
|
return { wrapper };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,152 +167,163 @@ afterEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('schedule-page.vue', () => {
|
beforeAll(() => {
|
||||||
beforeAll(() => {
|
Object.defineProperty(window, 'matchMedia', { value: jest.fn().mockImplementation((query) => ({
|
||||||
Object.defineProperty(window, 'matchMedia', { value: jest.fn().mockImplementation((query) => ({
|
matches: false,
|
||||||
matches: false,
|
media: query,
|
||||||
media: query,
|
onchange: null,
|
||||||
onchange: null,
|
addEventListener: jest.fn(),
|
||||||
addEventListener: jest.fn(),
|
removeEventListener: jest.fn(),
|
||||||
removeEventListener: jest.fn(),
|
dispatchEvent: jest.fn()
|
||||||
dispatchEvent: jest.fn()
|
})),
|
||||||
})),
|
writable: true });
|
||||||
writable: true });
|
});
|
||||||
|
describe('Initial Load', () => {
|
||||||
|
test('Should pass arePagePrerequisitesValid with a serviceLocation zipcode [in beforeEach]', () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = getShallowMountedComponent();
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(arePagePrerequisitesValid).toBe(true);
|
||||||
});
|
});
|
||||||
describe('Initial Load', () => {
|
test('Should fail arePagePrerequisitesValid with no serviceLocation zipcode', () => {
|
||||||
test('Should pass arePagePrerequisitesValid with a serviceLocation zipcode [in beforeEach]', () => {
|
// Arrange
|
||||||
// Arrange
|
const { wrapper } = getShallowMountedComponent();
|
||||||
const { wrapper } = getShallowMountedComponent();
|
wrapper.vm.mainStore.order.serviceLocation.zipCode = null;
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
const arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(arePagePrerequisitesValid).toBe(true);
|
expect(arePagePrerequisitesValid).toBeFalsy();
|
||||||
});
|
});
|
||||||
test('Should fail arePagePrerequisitesValid with no serviceLocation zipcode', () => {
|
test('should return newShopTimeSlots when getAvailableDatesMethod is called', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { wrapper } = getShallowMountedComponent();
|
const { wrapper } = getShallowMountedComponent();
|
||||||
wrapper.vm.mainStore.order.serviceLocation.zipCode = null;
|
wrapper.vm.selectableDatesData = {
|
||||||
|
days: []
|
||||||
// Act
|
};
|
||||||
const arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
const store = useMainStore();
|
||||||
|
store.getShopTimeSlots.mockImplementation(() => Promise.resolve({
|
||||||
// Assert
|
data: {
|
||||||
expect(arePagePrerequisitesValid).toBeFalsy();
|
estimatedServiceMinutesMinimum: 90,
|
||||||
});
|
estimatedServiceMinutesMaximum: 120,
|
||||||
test('should return newShopTimeSlots when getAvailableDatesMethod is called', async () => {
|
|
||||||
// Arrange
|
|
||||||
const { wrapper } = getShallowMountedComponent();
|
|
||||||
wrapper.vm.selectableDatesData = {
|
|
||||||
days: []
|
|
||||||
};
|
|
||||||
const store = useMainStore();
|
|
||||||
store.getShopTimeSlots.mockImplementation(() => Promise.resolve({
|
|
||||||
data: {
|
|
||||||
estimatedServiceMinutesMinimum: 90,
|
|
||||||
estimatedServiceMinutesMaximum: 120,
|
|
||||||
days: [
|
|
||||||
{
|
|
||||||
date: '2023-12-01',
|
|
||||||
timeSlots: [
|
|
||||||
{
|
|
||||||
id: '06747-01820-S-B*20424*7 AM',
|
|
||||||
startTime: '07:00',
|
|
||||||
endTime: '08:00',
|
|
||||||
offerPremium: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const newShopTimeSlots = await wrapper.vm.getAvailableDatesMethod(
|
|
||||||
'2023-01-01',
|
|
||||||
'2023-01-15'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(newShopTimeSlots).toStrictEqual({
|
|
||||||
days: [
|
days: [
|
||||||
{
|
{
|
||||||
date: '2023-12-01',
|
date: '2023-12-01',
|
||||||
timeSlots: [
|
timeSlots: [
|
||||||
{
|
{
|
||||||
endTime: '08:00',
|
|
||||||
id: '06747-01820-S-B*20424*7 AM',
|
id: '06747-01820-S-B*20424*7 AM',
|
||||||
offerPremium: false,
|
startTime: '07:00',
|
||||||
startTime: '07:00'
|
endTime: '08:00',
|
||||||
|
offerPremium: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
]
|
||||||
estimatedServiceMinutesMinimum: 90,
|
}
|
||||||
estimatedServiceMinutesMaximum: 120
|
}));
|
||||||
});
|
|
||||||
});
|
// Act
|
||||||
test('Should call API service in days of 15 or less when getAvailableDatesMethod is called with large date ranges', async () => {
|
const newShopTimeSlots = await wrapper.vm.getAvailableDatesMethod(
|
||||||
// Arrange
|
'2023-01-01',
|
||||||
const { wrapper } = getShallowMountedComponent();
|
'2023-01-15'
|
||||||
wrapper.vm.selectableDatesData = {
|
);
|
||||||
days: []
|
|
||||||
};
|
// Assert
|
||||||
const store = useMainStore();
|
expect(newShopTimeSlots).toStrictEqual({
|
||||||
store.getShopTimeSlots.mockImplementation(() => Promise.resolve({
|
days: [
|
||||||
data: {
|
{
|
||||||
estimatedServiceMinutesMinimum: 90,
|
date: '2023-12-01',
|
||||||
estimatedServiceMinutesMaximum: 120,
|
timeSlots: [
|
||||||
days: []
|
{
|
||||||
|
endTime: '08:00',
|
||||||
|
id: '06747-01820-S-B*20424*7 AM',
|
||||||
|
offerPremium: false,
|
||||||
|
startTime: '07:00'
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}));
|
],
|
||||||
|
estimatedServiceMinutesMinimum: 90,
|
||||||
// Act
|
estimatedServiceMinutesMaximum: 120
|
||||||
await wrapper.vm.getAvailableDates.call(
|
|
||||||
wrapper.vm,
|
|
||||||
'2023-01-01',
|
|
||||||
'2023-03-31',
|
|
||||||
'Inshop',
|
|
||||||
'123',
|
|
||||||
'43228'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
// 2023-01-01 --> 2023-02-05
|
|
||||||
// 2023-02-06 --> 2023-03-12
|
|
||||||
// 2023-03-13 --> 2023-03-31
|
|
||||||
expect(store.getShopTimeSlots).toHaveBeenCalledTimes(6);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('Rendering', () => {
|
test('Should call API service in days of 15 or less when getAvailableDatesMethod is called with large date ranges', async () => {
|
||||||
test('Schedule page loads', () => {
|
// Arrange
|
||||||
// Arrange
|
const { wrapper } = getShallowMountedComponent();
|
||||||
const { wrapper } = getShallowMountedComponent();
|
wrapper.vm.selectableDatesData = {
|
||||||
|
days: []
|
||||||
// Assert
|
};
|
||||||
expect(wrapper).toBeTruthy();
|
const store = useMainStore();
|
||||||
});
|
store.getShopTimeSlots.mockImplementation(() => Promise.resolve({
|
||||||
});
|
data: {
|
||||||
describe('schedule page methods...', () => {
|
estimatedServiceMinutesMinimum: 90,
|
||||||
test('getServiceZipCtuCodeFromStore should return zipCodeCtu', () => {
|
estimatedServiceMinutesMaximum: 120,
|
||||||
// Arrange
|
|
||||||
const { wrapper } = getShallowMountedComponent();
|
|
||||||
wrapper.vm.selectableDatesData = {
|
|
||||||
days: []
|
days: []
|
||||||
};
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const testValue = wrapper.vm.getServiceZipCtuCodeFromStore();
|
await wrapper.vm.getAvailableDates.call(
|
||||||
|
wrapper.vm,
|
||||||
|
'2023-01-01',
|
||||||
|
'2023-03-31',
|
||||||
|
'Inshop',
|
||||||
|
'123',
|
||||||
|
'43228'
|
||||||
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(testValue).toStrictEqual('01234');
|
// 2023-01-01 --> 2023-02-05
|
||||||
});
|
// 2023-02-06 --> 2023-03-12
|
||||||
|
// 2023-03-13 --> 2023-03-31
|
||||||
|
expect(store.getShopTimeSlots).toHaveBeenCalledTimes(6);
|
||||||
});
|
});
|
||||||
test.skip('forwardButtonAction should call route method navigateWithoutSaving', async () => {
|
});
|
||||||
|
describe('Rendering', () => {
|
||||||
|
test('Schedule page loads', () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = getShallowMountedComponent();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('schedule page methods...', () => {
|
||||||
|
test('getServiceZipCtuCodeFromStore should return zipCodeCtu', () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = getShallowMountedComponent();
|
||||||
|
wrapper.vm.selectableDatesData = {
|
||||||
|
days: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const testValue = wrapper.vm.getServiceZipCtuCodeFromStore();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(testValue).toStrictEqual('01234');
|
||||||
|
});
|
||||||
|
test('forwardButtonAction should call route method navigate', async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const { wrapper } = getShallowMountedComponent();
|
const { wrapper } = getShallowMountedComponent();
|
||||||
wrapper.vm.$router.navigate = jest.fn(() => ({}));
|
wrapper.vm.$router.navigate = jest.fn(() => ({}));
|
||||||
|
wrapper.vm.mainStore.saveSchedule = jest.fn();
|
||||||
|
wrapper.vm.navigationScenarios = {
|
||||||
|
CLICKED_FORWARD: 'CLICKED_FORWARD'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock the serviceLocation ref using Object.defineProperty to bypass readonly
|
||||||
|
Object.defineProperty(wrapper.vm.$refs, 'serviceLocation', {
|
||||||
|
value: {
|
||||||
|
forwardButtonAction: jest.fn()
|
||||||
|
},
|
||||||
|
configurable: true
|
||||||
|
});
|
||||||
|
|
||||||
wrapper.vm.selectedTimeSlotInfo = {
|
wrapper.vm.selectedTimeSlotInfo = {
|
||||||
timeSlot: {
|
timeSlot: {
|
||||||
routeCode: 'test-id'
|
routeCode: 'test-id'
|
||||||
|
|
@ -281,6 +333,59 @@ describe('schedule-page.vue', () => {
|
||||||
// Act
|
// Act
|
||||||
await wrapper.vm.forwardButtonAction();
|
await wrapper.vm.forwardButtonAction();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
test('recalAckModal should open when part requires recal, but recal not added to order', async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = getShallowMountedComponent();
|
||||||
|
wrapper.vm.mainStore.lineItems.glassParts = [
|
||||||
|
{
|
||||||
|
partNumber: 'TEST123',
|
||||||
|
requiresRecalibration: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await wrapper.vm.forwardButtonAction();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.$refs[RECAL_ACK_MODAL_REF_NAME].openModal).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
test('navigation forward is blocked when recalAckModal is displayed, but checkbox has not been acknowledged', async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = getShallowMountedComponent();
|
||||||
|
wrapper.vm.mainStore.lineItems.glassParts = [
|
||||||
|
{
|
||||||
|
partNumber: 'TEST123',
|
||||||
|
requiresRecalibration: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await wrapper.vm.forwardButtonAction();
|
||||||
|
await wrapper.vm.$refs[RECAL_ACK_MODAL_REF_NAME].openModal();
|
||||||
|
await wrapper.vm.$refs[RECAL_ACK_MODAL_REF_NAME].footerButtonClick();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.$router.navigate).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
test('navigation forward is allowed when checkbox has been acknowledged on recalAckModal', async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = getShallowMountedComponent();
|
||||||
|
wrapper.vm.mainStore.lineItems.glassParts = [
|
||||||
|
{
|
||||||
|
partNumber: 'TEST123',
|
||||||
|
requiresRecalibration: true
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await wrapper.vm.$refs[RECAL_ACK_MODAL_REF_NAME].openModal();
|
||||||
|
wrapper.vm.updateIsRecalAcknowledged(true);
|
||||||
|
await wrapper.vm.$refs[RECAL_ACK_MODAL_REF_NAME].footerButtonClick();
|
||||||
|
await wrapper.vm.forwardButtonAction();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
|
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,11 @@
|
||||||
:ref="SUGGEST_TIMESLOT_MODAL_REF_NAME"
|
:ref="SUGGEST_TIMESLOT_MODAL_REF_NAME"
|
||||||
cmsWidgetName="SuggestTimeslotModal"
|
cmsWidgetName="SuggestTimeslotModal"
|
||||||
@confirmAppointmentClicked="autoSelectTimeslotConfirmed" />
|
@confirmAppointmentClicked="autoSelectTimeslotConfirmed" />
|
||||||
|
<recalAckModal
|
||||||
|
:ref="RECAL_ACK_MODAL_REF_NAME"
|
||||||
|
cmsWidgetName="RecalAckModalWidget"
|
||||||
|
:ackError="ackError"
|
||||||
|
@recalAcknowledged="updateIsRecalAcknowledged"/>
|
||||||
</Form>
|
</Form>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -75,6 +80,7 @@ import datePicker from '@/digital-components/date-picker/date-picker.vue';
|
||||||
import serviceLocation from '@/layouts/schedule-page/service-location/service-location.vue';
|
import serviceLocation from '@/layouts/schedule-page/service-location/service-location.vue';
|
||||||
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
import siteFooter from '@/iss-components/site-footer/site-footer.vue';
|
||||||
import suggestTimeslotModal from '@/layouts/schedule-page/suggest-timeslot-modal/suggest-timeslot-modal.vue';
|
import suggestTimeslotModal from '@/layouts/schedule-page/suggest-timeslot-modal/suggest-timeslot-modal.vue';
|
||||||
|
import recalAckModal from '@/layouts/schedule-page/recal-ack-modal/recal-ack-modal.vue';
|
||||||
|
|
||||||
// Supporting files
|
// Supporting files
|
||||||
import { experimentSettings, experimentTest, experimentVariation, experimentUniverses } from '@/constants/experiments';
|
import { experimentSettings, experimentTest, experimentVariation, experimentUniverses } from '@/constants/experiments';
|
||||||
|
|
@ -105,10 +111,13 @@ import {
|
||||||
import { Form } from 'vee-validate';
|
import { Form } from 'vee-validate';
|
||||||
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
import BaseFormMixin from '@/mixins/base-form-mixin.js';
|
||||||
import { useMainStore } from '@/store';
|
import { useMainStore } from '@/store';
|
||||||
|
import { containsRecalParts, anyPartWithRequiresRecalFlag } from '@/helpers/recal-helper';
|
||||||
|
import errorMessages from '@/constants/error-messages';
|
||||||
|
|
||||||
// Define constants
|
// Define constants
|
||||||
const SUGGEST_TIMESLOT_MODAL_REF_NAME = 'SuggestTimeslotModal';
|
const SUGGEST_TIMESLOT_MODAL_REF_NAME = 'SuggestTimeslotModal';
|
||||||
const TIME_SLOTS_CALL_DAYS_LIMIT = 15;
|
const TIME_SLOTS_CALL_DAYS_LIMIT = 15;
|
||||||
|
const RECAL_ACK_MODAL_REF_NAME = 'recalAckModal';
|
||||||
|
|
||||||
const getAvailableDates = async (
|
const getAvailableDates = async (
|
||||||
startDateString,
|
startDateString,
|
||||||
|
|
@ -230,7 +239,8 @@ export default {
|
||||||
serviceLocation,
|
serviceLocation,
|
||||||
siteFooter,
|
siteFooter,
|
||||||
suggestTimeslotModal,
|
suggestTimeslotModal,
|
||||||
Form
|
Form,
|
||||||
|
recalAckModal
|
||||||
},
|
},
|
||||||
mixins: [BaseFormMixin],
|
mixins: [BaseFormMixin],
|
||||||
async beforeRouteEnter(to, from, next) {
|
async beforeRouteEnter(to, from, next) {
|
||||||
|
|
@ -370,7 +380,9 @@ export default {
|
||||||
selectedServiceLocationCity: this.getServiceLocationCity(),
|
selectedServiceLocationCity: this.getServiceLocationCity(),
|
||||||
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
|
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
|
||||||
showDatePickerError: false,
|
showDatePickerError: false,
|
||||||
SUGGEST_TIMESLOT_MODAL_REF_NAME
|
SUGGEST_TIMESLOT_MODAL_REF_NAME,
|
||||||
|
RECAL_ACK_MODAL_REF_NAME,
|
||||||
|
isRecalAcknowledged: this.getIsRecalAcknowledgedForScheduling()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -412,6 +424,21 @@ export default {
|
||||||
},
|
},
|
||||||
supportingItems() {
|
supportingItems() {
|
||||||
return useMainStore().lineItems.supportingItems;
|
return useMainStore().lineItems.supportingItems;
|
||||||
|
},
|
||||||
|
displayRecalAckModal() {
|
||||||
|
// if any of the glass parts contain an item with the requiresRecalibration flag set to true
|
||||||
|
// and the order does not contain a recalibration part, show the recalibration acknowledgement modal
|
||||||
|
const { glassParts } = useMainStore().order.lineItems;
|
||||||
|
const containsRecalPart = containsRecalParts(glassParts);
|
||||||
|
const hasPartWithRecalRequirement = anyPartWithRequiresRecalFlag(glassParts);
|
||||||
|
|
||||||
|
if (!containsRecalPart && hasPartWithRecalRequirement) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
ackError() {
|
||||||
|
return errorMessages.ACKNOWLEDGEMENT_REQUIRED;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
@ -440,6 +467,8 @@ export default {
|
||||||
this.mql = window.matchMedia('(min-width: 1200px)');
|
this.mql = window.matchMedia('(min-width: 1200px)');
|
||||||
this.isMobileView = !this.mql.matches;
|
this.isMobileView = !this.mql.matches;
|
||||||
this.mql.addEventListener('change', this.handleMqlChange);
|
this.mql.addEventListener('change', this.handleMqlChange);
|
||||||
|
this.mainStore.order.isRecalAcknowledgedForScheduling = false;
|
||||||
|
this.isRecalAcknowledged = false;
|
||||||
},
|
},
|
||||||
unmounted() {
|
unmounted() {
|
||||||
if (this.mql) {
|
if (this.mql) {
|
||||||
|
|
@ -931,6 +960,12 @@ export default {
|
||||||
this.showDatePickerError = false;
|
this.showDatePickerError = false;
|
||||||
},
|
},
|
||||||
async forwardButtonAction() {
|
async forwardButtonAction() {
|
||||||
|
if (this.displayRecalAckModal && this.isRecalAcknowledged === false) {
|
||||||
|
showIssLoadingModal(false);
|
||||||
|
this.$refs[RECAL_ACK_MODAL_REF_NAME].openModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!this.isFormValid) {
|
if (!this.isFormValid) {
|
||||||
this.showDatePickerError = true;
|
this.showDatePickerError = true;
|
||||||
return;
|
return;
|
||||||
|
|
@ -947,7 +982,14 @@ export default {
|
||||||
this.navigationScenarios.CLICKED_FORWARD,
|
this.navigationScenarios.CLICKED_FORWARD,
|
||||||
this.$route
|
this.$route
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
updateIsRecalAcknowledged(isAcknowledged) {
|
||||||
|
this.isRecalAcknowledged = isAcknowledged;
|
||||||
|
this.mainStore.order.isRecalAcknowledgedForScheduling = isAcknowledged;
|
||||||
|
},
|
||||||
|
getIsRecalAcknowledgedForScheduling() {
|
||||||
|
return this.mainStore.order.isRecalAcknowledgedForScheduling;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,8 @@ export const getDefaultState = () => ({
|
||||||
loadedFromDupeCheck: null,
|
loadedFromDupeCheck: null,
|
||||||
loadedSessionClearedPreviousData: null,
|
loadedSessionClearedPreviousData: null,
|
||||||
availableVaps: null,
|
availableVaps: null,
|
||||||
visitedDuplicateCheckPage: false
|
visitedDuplicateCheckPage: false,
|
||||||
|
isRecalAcknowledgedForScheduling: false
|
||||||
},
|
},
|
||||||
applicationUser: {
|
applicationUser: {
|
||||||
experiments: [],
|
experiments: [],
|
||||||
|
|
@ -3163,7 +3164,7 @@ export const useMainStore = defineStore({
|
||||||
resetSubmittedOrder() {
|
resetSubmittedOrder() {
|
||||||
// clear from sessionStorage
|
// clear from sessionStorage
|
||||||
window.sessionStorage.removeItem(webStorageConstants.SUBMITTED_ORDER);
|
window.sessionStorage.removeItem(webStorageConstants.SUBMITTED_ORDER);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
persist: {
|
persist: {
|
||||||
// Default is localStorage, but sessionStorage is used here to ensure state is cleared when the browser tab is closed, preventing potential issues with stale data on return visits.
|
// Default is localStorage, but sessionStorage is used here to ensure state is cleared when the browser tab is closed, preventing potential issues with stale data on return visits.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue