Merge branch 'develop' into CSR-1382-order-quote-initial-setup
This commit is contained in:
commit
6085cd1b13
26 changed files with 1123 additions and 680 deletions
|
|
@ -4,7 +4,7 @@ const AppointmentTypeStrings = {
|
|||
DROP_OFF: "Dropoff",
|
||||
};
|
||||
|
||||
const PREMIUM_TIME_SLOT_ID_FLAG = "-premium";
|
||||
const PREMIUM_TIME_SLOT_ID_FLAG = "-PREMIUM";
|
||||
|
||||
const PREMIUM_FEE_PART_TYPE = "EARLY BIRD";
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
{{ value }}
|
||||
</option>
|
||||
</select>
|
||||
<div v-show="errorMessage" class="row mt-2 form-test-error">
|
||||
<div v-show="errorMessage && this.disableValidation" class="row mt-2 form-test-error">
|
||||
<span aria-atomic="true" aria-live="polite">{{ errorMessage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -56,6 +56,11 @@ export default {
|
|||
required: false,
|
||||
default: false,
|
||||
},
|
||||
disableValidationIfElementDisabled: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
const uuid = uuidv4();
|
||||
|
|
@ -110,6 +115,9 @@ export default {
|
|||
this.$emit("update:modelValue", newValue);
|
||||
},
|
||||
},
|
||||
disableValidation() {
|
||||
return this.disableValidationIfElementDisabled ? !this.isDisabled : true;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedOption(newValue) {
|
||||
|
|
|
|||
|
|
@ -2,13 +2,12 @@
|
|||
<div class="text-block w-100" :class="[justifyText, typeStyle, fontWeight, marginTopClass]">
|
||||
<span v-for="copy in splitCopyOnCMSPlaceHolder(this.textBlockCopy)" :key="copy">
|
||||
<span v-if="doesCopyContainRouterLink(copy)">
|
||||
<router-link
|
||||
:to="{
|
||||
query: { [pageQueryString]: `${getRouterLinkRouteFromCopy(copy)}` },
|
||||
name: 'root',
|
||||
}"
|
||||
>{{ getRouterLinkDisplayTextFromCopy(copy) }}</router-link
|
||||
>
|
||||
<textLink
|
||||
linkType="text"
|
||||
:text="getRouterLinkDisplayTextFromCopy(copy)"
|
||||
href="javascript:void(0)"
|
||||
useLoadingModal
|
||||
@click-event="navigateWithScenario(getRouterLinkRouteFromCopy(copy))" />
|
||||
</span>
|
||||
<span v-else-if="doesCopyContainTextLink(copy)">
|
||||
<textLink
|
||||
|
|
@ -55,6 +54,9 @@ export default {
|
|||
getRouterLinkRouteFromCopy,
|
||||
getRouterLinkDisplayTextFromCopy,
|
||||
getExternalLink,
|
||||
navigateWithScenario(scenarioName) {
|
||||
this.$router.navigateWithoutSaving(scenarioName, this.$route);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
pageQueryString() {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
<textLink
|
||||
linkType="navigation"
|
||||
:text="backLink"
|
||||
useLoadingModal
|
||||
@click-event="linkClick"
|
||||
href="javascript:void(0)"
|
||||
data-bs-target="#footerModal"
|
||||
|
|
|
|||
|
|
@ -1,47 +1,50 @@
|
|||
<template>
|
||||
<div v-if="isModalVisible" class="container-fluid modal-loader">
|
||||
<div class="row g-2 h-100 d-flex align-items-center">
|
||||
<div class="container-fluid overflow-hidden">
|
||||
<div class="row h-100">
|
||||
<div class="col text-center tagbg mb-4">
|
||||
<div class="spinner-border text-danger" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
<div v-if="isModalVisible">
|
||||
<div class="dimmer-overlay"></div>
|
||||
<div class="container-fluid modal-loader" :class="layoutClass">
|
||||
<div class="row g-2 h-100 d-flex align-items-center">
|
||||
<div class="container-fluid overflow-hidden">
|
||||
<div class="row h-100">
|
||||
<div class="col text-center tagbg mb-4">
|
||||
<div class="spinner-border text-danger" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="text-container slide">
|
||||
<p>
|
||||
Tidying up the shop
|
||||
<span class="dot-1">.</span>
|
||||
<span class="dot-2">.</span>
|
||||
<span class="dot-3">.</span>
|
||||
</p>
|
||||
<p>
|
||||
Getting all the glass shined up
|
||||
<span class="dot-1">.</span>
|
||||
<span class="dot-2">.</span>
|
||||
<span class="dot-3">.</span>
|
||||
</p>
|
||||
<p>
|
||||
Planning your new view of the road
|
||||
<span class="dot-1">.</span>
|
||||
<span class="dot-2">.</span>
|
||||
<span class="dot-3">.</span>
|
||||
</p>
|
||||
<p>
|
||||
Nearly there
|
||||
<span class="dot-1">.</span>
|
||||
<span class="dot-2">.</span>
|
||||
<span class="dot-3">.</span>
|
||||
</p>
|
||||
<p>
|
||||
Finishing up
|
||||
<span class="dot-1">.</span>
|
||||
<span class="dot-2">.</span>
|
||||
<span class="dot-3">.</span>
|
||||
</p>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="text-container slide">
|
||||
<p>
|
||||
Tidying up the shop
|
||||
<span class="dot-1">.</span>
|
||||
<span class="dot-2">.</span>
|
||||
<span class="dot-3">.</span>
|
||||
</p>
|
||||
<p>
|
||||
Getting all the glass shined up
|
||||
<span class="dot-1">.</span>
|
||||
<span class="dot-2">.</span>
|
||||
<span class="dot-3">.</span>
|
||||
</p>
|
||||
<p>
|
||||
Planning your new view of the road
|
||||
<span class="dot-1">.</span>
|
||||
<span class="dot-2">.</span>
|
||||
<span class="dot-3">.</span>
|
||||
</p>
|
||||
<p>
|
||||
Nearly there
|
||||
<span class="dot-1">.</span>
|
||||
<span class="dot-2">.</span>
|
||||
<span class="dot-3">.</span>
|
||||
</p>
|
||||
<p>
|
||||
Finishing up
|
||||
<span class="dot-1">.</span>
|
||||
<span class="dot-2">.</span>
|
||||
<span class="dot-3">.</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -58,6 +61,17 @@ export default {
|
|||
isModalVisible: false,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
notFullScreen: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
layoutClass() {
|
||||
return this.notFullScreen ? "card-layout" : "full-screen";
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
showModal() {
|
||||
//Display modal
|
||||
|
|
@ -80,20 +94,43 @@ export default {
|
|||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.modal-loader {
|
||||
.dimmer-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 1057;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: $black;
|
||||
opacity: 0.4;
|
||||
z-index: 1056;
|
||||
}
|
||||
|
||||
.modal-loader {
|
||||
position: fixed;
|
||||
z-index: 1057;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
outline: 0;
|
||||
background: $gray-100;
|
||||
|
||||
&.full-screen {
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&.card-layout {
|
||||
height: 186px;
|
||||
width: calc(100% - 3rem);
|
||||
border-radius: 0.5rem;
|
||||
position: absolute;
|
||||
top: calc(50% - 93px);
|
||||
left: 1.5rem;
|
||||
}
|
||||
|
||||
.tagbg {
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg fill='none' xmlns='http://www.w3.org/2000/svg' viewBox='0 0 84 32'%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' fill='%23fff'/%3e%3cpath d='M22.39 27.313a3.53 3.53 0 0 0 7.058 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' fill='%23fff'/%3e%3cpath d='M54.459 27.313a3.527 3.527 0 0 0 7.054 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06.8c8.08 0 11.89.935 11.89.935 3.58.576 5.696 7.207 5.696 7.207h.727c0-2.427 1.302-2.397 2.341-2 .723.292 1.363.76 1.86 1.361 1.319 1.547.465 1.674.465 1.674h-5.067l3.846 3.01v12.016c0 2.41-2.029 2.31-2.029 2.31H22.338s-2.029.1-2.029-2.31V12.996l3.84-3.009H19.07s-.853-.127.466-1.674a4.693 4.693 0 0 1 1.86-1.361c1.032-.397 2.341-.427 2.341 2h.736s2.117-6.64 5.696-7.21c0 0 3.81-.942 11.89-.942Z' fill='%23fff' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M42.06 8.847c7.924 0 14.685.511 14.685.511 0-2.585-2.38-6.011-2.38-6.011S50.4 2.519 42.06 2.519s-12.303.828-12.303.828-2.38 3.426-2.38 6.011c0 0 6.76-.51 14.683-.51Z' fill='%23DA291C' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3cpath d='M35.277 16.523a62.558 62.558 0 0 1 13.277 0m3.276-.439s2.384-2.257 8.608-2.257c0 0 1.179 2.657-2.54 3.37m-25.894-1.113s-2.387-2.257-8.611-2.257c0 0-1.16 2.579 2.543 3.37m-3.546 5.856s21.52 3.647 39.123 0' stroke='%23000' stroke-width='.75' stroke-linecap='round' stroke-linejoin='round'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
linkType="textSmall"
|
||||
text="Edit"
|
||||
class="ml-auto"
|
||||
useLoadingModal
|
||||
@click-event="linkClicked"
|
||||
href="javascript:void(0)" />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,150 +0,0 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
|
||||
import customerDetailsModalQuestion from "@/layouts/review/review-sections/customer-review/customer-details-modal-question/customer-details-modal-question";
|
||||
|
||||
const testConstants = {
|
||||
previousCustomerValues: {
|
||||
firstName: "First",
|
||||
lastName: "Last",
|
||||
emailAddress: "builddigitaltest@safelite.com",
|
||||
phoneNumber: "111-111-1111",
|
||||
isSmsOptIn: false,
|
||||
},
|
||||
newValues: {
|
||||
firstName: "New First",
|
||||
lastName: "New Last",
|
||||
emailAddress: "builddigitaltest2@safelite.com",
|
||||
phoneNumber: "222-222-2222",
|
||||
isSmsOptIn: true,
|
||||
},
|
||||
};
|
||||
|
||||
let cmsContent;
|
||||
|
||||
describe("Customer Details Modal", () => {
|
||||
beforeEach(() => {
|
||||
cmsContent = {};
|
||||
});
|
||||
|
||||
describe("Submit", () => {
|
||||
test("Should push to store if a field has changed", async () => {
|
||||
// Arrange
|
||||
let props = generateDefaultProps();
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
propsData: props,
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.onModalOpened();
|
||||
|
||||
wrapper.vm.firstName = testConstants.newValues.firstName;
|
||||
|
||||
await wrapper.vm.setContactDetails();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalled();
|
||||
expect(wrapper.vm.closeModal).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("Should not push to store if no fields have changed", async () => {
|
||||
// Arrange
|
||||
let props = generateDefaultProps();
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
propsData: props,
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.onModalOpened();
|
||||
|
||||
await wrapper.vm.setContactDetails();
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.dispatchStoreAction).not.toHaveBeenCalled();
|
||||
expect(wrapper.vm.closeModal).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe("Default values", () => {
|
||||
test("Should populate on open", () => {
|
||||
// Arrange
|
||||
let props = generateDefaultProps();
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
propsData: props,
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.onModalOpened();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.firstName).toBe(testConstants.previousCustomerValues.firstName);
|
||||
expect(wrapper.vm.lastName).toBe(testConstants.previousCustomerValues.lastName);
|
||||
expect(wrapper.vm.emailAddress).toBe(testConstants.previousCustomerValues.emailAddress);
|
||||
expect(wrapper.vm.phoneNumber).toBe(testConstants.previousCustomerValues.phoneNumber);
|
||||
expect(wrapper.vm.isSmsOptIn).toBe(testConstants.previousCustomerValues.isSmsOptIn);
|
||||
});
|
||||
|
||||
test("Should replace old values on open", async () => {
|
||||
// Arrange
|
||||
let props = generateDefaultProps();
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
propsData: props,
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.firstName = testConstants.newValues.firstName;
|
||||
wrapper.vm.lastName = testConstants.newValues.lastName;
|
||||
wrapper.vm.emailAddress = testConstants.newValues.emailAddress;
|
||||
wrapper.vm.phoneNumber = testConstants.newValues.phoneNumber;
|
||||
wrapper.vm.isSmsOptIn = testConstants.newValues.isSmsOptIn;
|
||||
|
||||
wrapper.vm.onModalOpened();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.firstName).toBe(testConstants.previousCustomerValues.firstName);
|
||||
expect(wrapper.vm.lastName).toBe(testConstants.previousCustomerValues.lastName);
|
||||
expect(wrapper.vm.emailAddress).toBe(testConstants.previousCustomerValues.emailAddress);
|
||||
expect(wrapper.vm.phoneNumber).toBe(testConstants.previousCustomerValues.phoneNumber);
|
||||
expect(wrapper.vm.isSmsOptIn).toBe(testConstants.previousCustomerValues.isSmsOptIn);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function generateDefaultProps() {
|
||||
return {
|
||||
previousCustomerValues: {
|
||||
firstName: testConstants.previousCustomerValues.firstName,
|
||||
lastName: testConstants.previousCustomerValues.lastName,
|
||||
emailAddress: testConstants.previousCustomerValues.emailAddress,
|
||||
phoneNumber: testConstants.previousCustomerValues.phoneNumber,
|
||||
isSmsOptIn: testConstants.previousCustomerValues.isSmsOptIn,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function setupMocks(customMountOptions) {
|
||||
const mountOptions = getMountOptions(customMountOptions);
|
||||
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||
return cmsContent?.[widgetName]?.[cmsFieldName] ?? "";
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
mountOptions.global.mixins = [mockMixin];
|
||||
|
||||
const wrapper = shallowMount(customerDetailsModalQuestion, mountOptions);
|
||||
wrapper.vm.setCmsContent = jest.fn();
|
||||
wrapper.vm.openModal = jest.fn();
|
||||
wrapper.vm.closeModal = jest.fn();
|
||||
wrapper.vm.dispatchStoreAction = jest.fn();
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
@ -1,148 +0,0 @@
|
|||
<template>
|
||||
<transition name="fade" mode="out-in">
|
||||
<div class="customer-details-question">
|
||||
<modal
|
||||
ref="CustomerDetailsModal"
|
||||
headerText="Contact details"
|
||||
footerButtonText="Save contact details"
|
||||
:onModalOpenedCallback="onModalOpened"
|
||||
:onModalClosedCallback="onModalClosed"
|
||||
@isModalOpened="setModalStatus"
|
||||
@footer-button-event="setContactDetails">
|
||||
<template v-if="isModalOpened">
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="CustomerFirstNameWidget"
|
||||
v-model="firstName"
|
||||
ref="firstName"
|
||||
customInputId="firstName"
|
||||
validation-rules="first-name-required" />
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="CustomerLastNameWidget"
|
||||
v-model="lastName"
|
||||
ref="lastName"
|
||||
customInputId="lastName"
|
||||
validation-rules="last-name-required" />
|
||||
<textboxQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="CustomerEmailWidget"
|
||||
v-model="emailAddress"
|
||||
ref="emailAddress"
|
||||
customInputId="emailAddress"
|
||||
validation-rules="email-address-required|email-address-format" />
|
||||
<phoneNumberQuestion
|
||||
class="mb-4"
|
||||
cmsWidgetName="CustomerPhoneWidget"
|
||||
v-model="phoneNumber"
|
||||
ref="phoneNumber"
|
||||
isRequired
|
||||
validation-rules="phone-number-required" />
|
||||
<checkboxQuestion
|
||||
class="mb-5"
|
||||
cmsWidgetName="CustomerTextMeWidget"
|
||||
v-model="isSmsOptIn" />
|
||||
<textBlock cmsWidgetName="CustomerTextDisclaimerWidget" typeStyle="caption" />
|
||||
</template>
|
||||
</modal>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import modal from "@/digital-components/modal/modal";
|
||||
import textboxQuestion from "@/digital-components/textbox-question/textbox-question";
|
||||
import phoneNumberQuestion from "@/digital-components/phone-number-question/phone-number-question";
|
||||
import checkboxQuestion from "@/digital-components/checkbox-question/checkbox-question";
|
||||
import textBlock from "@/digital-components/text-block/text-block";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
import { required, regex } from "@/helpers/validation-rules";
|
||||
import { defineRule } from "vee-validate";
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("first-name-required", required(errorMessages.FIRST_NAME_REQUIRED));
|
||||
defineRule("last-name-required", required(errorMessages.LAST_NAME_REQUIRED));
|
||||
defineRule("phone-number-required", required(errorMessages.PHONE_REQUIRED));
|
||||
defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIRED));
|
||||
defineRule(
|
||||
"email-address-format",
|
||||
regex(
|
||||
/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9-]+)\.([a-zA-Z]{2,})$/,
|
||||
errorMessages.EMAIL_ADDRESS_FORMAT
|
||||
)
|
||||
);
|
||||
|
||||
export default {
|
||||
name: "customer-details-modal-question",
|
||||
data() {
|
||||
return {
|
||||
isModalOpened: false,
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
emailAddress: "",
|
||||
phoneNumber: "",
|
||||
isSmsOptIn: false,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
previousCustomerValues: Object,
|
||||
},
|
||||
computed: {
|
||||
modal() {
|
||||
return this.$refs.CustomerDetailsModal;
|
||||
},
|
||||
haveCustomerDetailsChanged() {
|
||||
return (
|
||||
this.firstName !== this.previousCustomerValues?.firstName ||
|
||||
this.lastName !== this.previousCustomerValues?.lastName ||
|
||||
this.emailAddress !== this.previousCustomerValues?.emailAddress ||
|
||||
this.phoneNumber !== this.previousCustomerValues?.phoneNumber ||
|
||||
this.isSmsOptIn !== this.previousCustomerValues?.isSmsOptIn
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
this.modal.openModal();
|
||||
},
|
||||
closeModal() {
|
||||
this.modal.closeModal();
|
||||
},
|
||||
onModalOpened() {
|
||||
this.firstName = this.previousCustomerValues?.firstName ?? "";
|
||||
this.lastName = this.previousCustomerValues?.lastName ?? "";
|
||||
this.emailAddress = this.previousCustomerValues?.emailAddress ?? "";
|
||||
this.phoneNumber = this.previousCustomerValues?.phoneNumber ?? "";
|
||||
this.isSmsOptIn = this.previousCustomerValues?.isSmsOptIn ?? false;
|
||||
},
|
||||
onModalClosed() {},
|
||||
setModalStatus(isOpened) {
|
||||
this.isModalOpened = isOpened;
|
||||
},
|
||||
async setContactDetails() {
|
||||
if (this.haveCustomerDetailsChanged) {
|
||||
await this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_CUSTOMER_DETAILS,
|
||||
{
|
||||
firstName: this.firstName,
|
||||
lastName: this.lastName,
|
||||
emailAddress: this.emailAddress,
|
||||
phoneNumber: this.phoneNumber,
|
||||
isSmsOptIn: this.isSmsOptIn,
|
||||
},
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
this.closeModal();
|
||||
},
|
||||
},
|
||||
components: {
|
||||
modal,
|
||||
textboxQuestion,
|
||||
phoneNumberQuestion,
|
||||
checkboxQuestion,
|
||||
textBlock,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -9,11 +9,7 @@ const testConstants = {
|
|||
text: "Header",
|
||||
},
|
||||
sms: {
|
||||
template: "Test {custom:smsOptInJoiner}",
|
||||
expected: {
|
||||
ifTrue: "Test in to",
|
||||
ifFalse: "Test out of",
|
||||
},
|
||||
text: "Sms",
|
||||
},
|
||||
},
|
||||
customer: {
|
||||
|
|
@ -27,7 +23,7 @@ const testConstants = {
|
|||
fullName: "First Last",
|
||||
phoneNumber: "111-111-1111",
|
||||
emailAddress: "builddigitaltest@safelite.com",
|
||||
smsOptIn: "Test out of",
|
||||
smsOptIn: "Sms",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -38,7 +34,7 @@ describe("Customer Review Block", () => {
|
|||
cmsContent = {
|
||||
CustomerWidget: {
|
||||
HeaderText: testConstants.cms.header.text,
|
||||
SubheaderText: testConstants.cms.sms.template,
|
||||
SubheaderText: testConstants.cms.sms.text,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
@ -58,52 +54,19 @@ describe("Customer Review Block", () => {
|
|||
expect(wrapper.vm.header).toEqual(testConstants.cms.header.text);
|
||||
});
|
||||
|
||||
describe("SMS Opt In Text", () => {
|
||||
test("Should render correctly when opt in is true:", async () => {
|
||||
// Arrange
|
||||
let props = generateDefaultProps();
|
||||
props.customer.isSmsOptIn = true;
|
||||
test("Should display sms text from cms", async () => {
|
||||
// Arrange
|
||||
let props = generateDefaultProps();
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
propsData: props,
|
||||
});
|
||||
|
||||
// Act
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.smsOptIn).toEqual(testConstants.cms.sms.expected.ifTrue);
|
||||
const { wrapper } = setupMocks({
|
||||
propsData: props,
|
||||
});
|
||||
test("Should render correctly when opt in is false:", async () => {
|
||||
// Arrange
|
||||
let props = generateDefaultProps();
|
||||
props.customer.isSmsOptIn = false;
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
propsData: props,
|
||||
});
|
||||
// Act
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Act
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.smsOptIn).toEqual(testConstants.cms.sms.expected.ifFalse);
|
||||
});
|
||||
test("Should render correctly when opt in is null:", async () => {
|
||||
// Arrange
|
||||
let props = generateDefaultProps();
|
||||
props.customer.isSmsOptIn = null;
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
propsData: props,
|
||||
});
|
||||
|
||||
// Act
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.smsOptIn).toEqual(testConstants.cms.sms.expected.ifFalse);
|
||||
});
|
||||
// Assert
|
||||
expect(wrapper.vm.smsOptIn).toEqual(testConstants.cms.sms.text);
|
||||
});
|
||||
|
||||
test("Should render correct display content", async () => {
|
||||
|
|
|
|||
|
|
@ -36,10 +36,7 @@ export default {
|
|||
return this.customer?.phoneNumber;
|
||||
},
|
||||
smsOptIn() {
|
||||
const rawCmsText = this.getCmsContent(this.cmsWidgetName, "SubheaderText");
|
||||
const joinerText = this.customer?.isSmsOptIn ? "in to" : "out of";
|
||||
|
||||
return rawCmsText.replace("{custom:smsOptInJoiner}", joinerText);
|
||||
return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,362 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import store from "@/store";
|
||||
|
||||
import review from "@/layouts/review/review";
|
||||
|
||||
const testConstants = {};
|
||||
|
||||
jest.mock("@/store", () => ({
|
||||
commit: jest.fn(),
|
||||
dispatch: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("Review Page", () => {
|
||||
test.todo("Add more tests as specific functionality is added.");
|
||||
beforeEach(() => {
|
||||
store.getters = {
|
||||
order: {
|
||||
vehicle: {
|
||||
year: "2020",
|
||||
make: "Acura",
|
||||
model: "MDX",
|
||||
style: "4 door sedan",
|
||||
},
|
||||
damage: {
|
||||
isRepair: false,
|
||||
numberOfChips: 2,
|
||||
glassToReplace: ["dummy location value"],
|
||||
},
|
||||
lineItems: {
|
||||
glassParts: ["dummy part value"],
|
||||
supportingItems: ["dummy supporting item"],
|
||||
vaps: ["dummy vap"],
|
||||
},
|
||||
serviceLocation: {
|
||||
address: "address 1",
|
||||
address2: "address 2",
|
||||
city: "city",
|
||||
state: "state",
|
||||
zipCode: "zip code",
|
||||
appointmentType: "Mobile",
|
||||
provider: {
|
||||
providerNumber: 1,
|
||||
address: {
|
||||
streetAddress: "provider address 1",
|
||||
city: "provider city",
|
||||
state: "provider state",
|
||||
zipCode: "provider zip code",
|
||||
},
|
||||
},
|
||||
},
|
||||
schedule: {
|
||||
date: "date",
|
||||
startTime: "start",
|
||||
endTime: "end",
|
||||
jobMinMinutes: "30",
|
||||
jobMaxMinutes: "45",
|
||||
},
|
||||
customer: {
|
||||
firstName: "first name",
|
||||
lastName: "last name",
|
||||
emailAddress: "builddigitaltest@safelite.com",
|
||||
phoneNumber: "555-555-5555",
|
||||
isSmsOptIn: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
describe("arePagePrerequisitesValid", () => {
|
||||
test("Returns true for baseline valid state", () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const isValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
// Assert
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
test("Returns false for empty state", () => {
|
||||
// Arrange
|
||||
store.getters.order = {
|
||||
vehicle: {
|
||||
year: null,
|
||||
make: null,
|
||||
model: null,
|
||||
style: null,
|
||||
carId: null,
|
||||
category: null,
|
||||
vin: null,
|
||||
imageUrl: null,
|
||||
imageVifNumber: null,
|
||||
imageColor: null,
|
||||
registration: {
|
||||
licensePlate: null,
|
||||
},
|
||||
},
|
||||
serviceLocation: {
|
||||
address: null,
|
||||
address2: null,
|
||||
city: null,
|
||||
state: null,
|
||||
zipCode: null,
|
||||
zipCodeCtu: null,
|
||||
appointmentType: null,
|
||||
isVehicleProtected: null,
|
||||
provider: {
|
||||
providerNumber: null,
|
||||
address: {
|
||||
streetAddress: null,
|
||||
city: null,
|
||||
state: null,
|
||||
zipCode: null,
|
||||
zipCodeCtu: null,
|
||||
},
|
||||
},
|
||||
techNotes: null,
|
||||
},
|
||||
customer: {
|
||||
firstName: null,
|
||||
lastName: null,
|
||||
emailAddress: null,
|
||||
phoneNumber: null,
|
||||
isSmsOptIn: null,
|
||||
},
|
||||
damage: {
|
||||
isRepair: null,
|
||||
numberOfChips: null,
|
||||
glassToReplace: null,
|
||||
partQuestionAnswers: null,
|
||||
moldingQuestionAnswers: null,
|
||||
capabilityQuestionAnswers: null,
|
||||
},
|
||||
lineItems: {
|
||||
glassParts: null,
|
||||
supportingItems: null,
|
||||
vaps: null,
|
||||
serverData: null,
|
||||
},
|
||||
payment: {
|
||||
isInsurance: null,
|
||||
insuranceCoverage: {
|
||||
isVerified: null,
|
||||
coverageStatus: null,
|
||||
},
|
||||
parentAccountNumber: 0,
|
||||
},
|
||||
schedule: {
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
routeCode: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
referralNumber: null,
|
||||
referralSequenceNumber: null,
|
||||
referralDate: null,
|
||||
referralCorrelationId: null,
|
||||
eon: null,
|
||||
};
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const isValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
// Assert
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
describe("Damage requirements", () => {
|
||||
test("Accepts null glassToReplace when is repair", () => {
|
||||
// Arrange
|
||||
store.getters.order.damage.isRepair = true;
|
||||
store.getters.order.damage.glassToReplace = null;
|
||||
store.getters.order.damage.numberOfChips = 1;
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const isValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
// Assert
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
test("Rejects 0 chips when repair", () => {
|
||||
// Arrange
|
||||
store.getters.order.damage.isRepair = true;
|
||||
store.getters.order.damage.numberOfChips = 0;
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const isValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
// Assert
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
test("Rejects null chips when repair", () => {
|
||||
// Arrange
|
||||
store.getters.order.damage.isRepair = true;
|
||||
store.getters.order.damage.numberOfChips = null;
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const isValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
// Assert
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
test("Accepts null chips when not repair", () => {
|
||||
// Arrange
|
||||
store.getters.order.damage.isRepair = false;
|
||||
store.getters.order.damage.numberOfChips = null;
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const isValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
// Assert
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
test("Rejects empty glassToReplace when not repair", () => {
|
||||
// Arrange
|
||||
store.getters.order.damage.isRepair = false;
|
||||
store.getters.order.damage.glassToReplace = null;
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const isValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
// Assert
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
test("Rejects null glassToReplace when not repair", () => {
|
||||
// Arrange
|
||||
store.getters.order.damage.isRepair = false;
|
||||
store.getters.order.damage.glassToReplace = [];
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const isValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
// Assert
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
});
|
||||
describe("Package requirements", () => {
|
||||
test("Accepts null glassParts when is repair", () => {
|
||||
// Arrange
|
||||
store.getters.order.damage.isRepair = true;
|
||||
store.getters.order.lineItems.glassParts = null;
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const isValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
// Assert
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
test("Rejects null glassParts when not repair", () => {
|
||||
// Arrange
|
||||
store.getters.order.damage.isRepair = false;
|
||||
store.getters.order.lineItems.glassParts = null;
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const isValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
// Assert
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
});
|
||||
describe("Service Location requirements", () => {
|
||||
test("Accepts null provider address when mobile appointment", () => {
|
||||
// Arrange
|
||||
store.getters.order.serviceLocation.appointmentType = "Mobile";
|
||||
store.getters.order.serviceLocation.provider.address = {};
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const isValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
// Assert
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
test("Reject null provider address when non-mobile appointment", () => {
|
||||
// Arrange
|
||||
store.getters.order.serviceLocation.appointmentType = "Inshop";
|
||||
store.getters.order.serviceLocation.provider.address = {};
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const isValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
// Assert
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
test("Accepts null service location address when non-mobile appointment", () => {
|
||||
// Arrange
|
||||
store.getters.order.serviceLocation.appointmentType = "Inshop";
|
||||
store.getters.order.serviceLocation.address = null;
|
||||
store.getters.order.serviceLocation.address2 = null;
|
||||
store.getters.order.serviceLocation.zipCode = null;
|
||||
store.getters.order.serviceLocation.city = null;
|
||||
store.getters.order.serviceLocation.state = null;
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const isValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
// Assert
|
||||
expect(isValid).toBe(true);
|
||||
});
|
||||
test("Rejects null service location address when mobile appointment", () => {
|
||||
// Arrange
|
||||
store.getters.order.serviceLocation.appointmentType = "Mobile";
|
||||
store.getters.order.serviceLocation.address = null;
|
||||
store.getters.order.serviceLocation.address2 = null;
|
||||
store.getters.order.serviceLocation.zipCode = null;
|
||||
store.getters.order.serviceLocation.city = null;
|
||||
store.getters.order.serviceLocation.state = null;
|
||||
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
const isValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
// Assert
|
||||
expect(isValid).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks(customMountOptions) {
|
||||
customMountOptions.store = store;
|
||||
|
||||
const mountOptions = getMountOptions(customMountOptions);
|
||||
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
|
||||
return `${widgetName} ${cmsFieldName}`;
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
mountOptions.global.mixins = [mockMixin];
|
||||
|
||||
const wrapper = shallowMount(review, mountOptions);
|
||||
wrapper.vm.setCmsContent = jest.fn();
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,10 +89,6 @@
|
|||
<hr class="my-0" />
|
||||
</div>
|
||||
|
||||
<customerDetailsModalQuestion
|
||||
ref="customerDetailsModalQuestion"
|
||||
:previousCustomerValues="customerInfo" />
|
||||
|
||||
<funnelFooter
|
||||
cmsWidgetName="FunnelFooterWidget"
|
||||
@back-clicked="backButtonAction"
|
||||
|
|
@ -115,10 +111,11 @@ import serviceLocationReview from "@/layouts/review/review-sections/service-loca
|
|||
import scheduleReview from "@/layouts/review/review-sections/schedule-review/schedule-review";
|
||||
import customerReview from "@/layouts/review/review-sections/customer-review/customer-review";
|
||||
|
||||
import customerDetailsModalQuestion from "@/layouts/review/review-sections/customer-review/customer-details-modal-question/customer-details-modal-question.vue";
|
||||
import { AppointmentTypeStrings } from "@/constants/schedule-constants";
|
||||
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import store from "@/store";
|
||||
|
||||
export default {
|
||||
name: "review",
|
||||
|
|
@ -153,7 +150,75 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
return true;
|
||||
// Vehicle
|
||||
const vehicle = store.getters.order.vehicle;
|
||||
const vehicleReqs = !!(vehicle.year && vehicle.make && vehicle.model && vehicle.style);
|
||||
|
||||
// Damage
|
||||
const damage = store.getters.order.damage;
|
||||
const damageReqs = !!(
|
||||
(damage.isRepair && damage.numberOfChips) ||
|
||||
(!damage.isRepair && damage.glassToReplace?.length)
|
||||
);
|
||||
|
||||
// Service Package
|
||||
const lineItems = store.getters.order.lineItems;
|
||||
// damageReqs handles checking for damage, even though it is also required for this section.
|
||||
const packageReqs = !!(
|
||||
(damage.isRepair || lineItems.glassParts) &&
|
||||
lineItems.supportingItems &&
|
||||
lineItems.vaps
|
||||
);
|
||||
|
||||
// Service Location
|
||||
const serviceLocation = store.getters.order.serviceLocation;
|
||||
const mobileReqs = !!(
|
||||
serviceLocation.address &&
|
||||
serviceLocation.city &&
|
||||
serviceLocation.state &&
|
||||
serviceLocation.zipCode
|
||||
);
|
||||
|
||||
const providerLocation = serviceLocation.provider.address;
|
||||
const dropOffInshopReqs = !!(
|
||||
providerLocation.streetAddress &&
|
||||
providerLocation.city &&
|
||||
providerLocation.state &&
|
||||
providerLocation.zipCode
|
||||
);
|
||||
|
||||
const isMobile = serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE;
|
||||
|
||||
const serviceLocationReqs =
|
||||
(isMobile && mobileReqs) || (!isMobile && dropOffInshopReqs);
|
||||
|
||||
// Schedule
|
||||
const schedule = store.getters.order.schedule;
|
||||
const scheduleReqs = !!(
|
||||
schedule.date &&
|
||||
schedule.startTime &&
|
||||
schedule.endTime &&
|
||||
schedule.jobMaxMinutes &&
|
||||
schedule.jobMinMinutes
|
||||
);
|
||||
|
||||
// Customer
|
||||
const customer = store.getters.order.customer;
|
||||
const customerReqs = !!(
|
||||
customer.firstName &&
|
||||
customer.lastName &&
|
||||
customer.phoneNumber &&
|
||||
customer.emailAddress
|
||||
);
|
||||
|
||||
return (
|
||||
vehicleReqs &&
|
||||
damageReqs &&
|
||||
packageReqs &&
|
||||
serviceLocationReqs &&
|
||||
scheduleReqs &&
|
||||
customerReqs
|
||||
);
|
||||
},
|
||||
backButtonAction() {
|
||||
this.$router.navigateWithoutSaving(this.navigationScenarios.CLICKED_BACK, this.$route);
|
||||
|
|
@ -190,7 +255,10 @@ export default {
|
|||
);
|
||||
},
|
||||
editCustomerDetails() {
|
||||
this.$refs.customerDetailsModalQuestion.openModal();
|
||||
this.$router.navigateWithoutSaving(
|
||||
this.navigationScenarios.CLICKED_CUSTOMER_EDIT,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -234,7 +302,6 @@ export default {
|
|||
serviceLocationReview,
|
||||
scheduleReview,
|
||||
customerReview,
|
||||
customerDetailsModalQuestion,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import { shallowMount } from "@vue/test-utils";
|
|||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import store from "@/store";
|
||||
import router from "@/router";
|
||||
import { nextTick } from "vue";
|
||||
import baseMixin from "../../mixins/base-mixin";
|
||||
|
||||
// Mock basemixin
|
||||
|
|
@ -130,64 +129,67 @@ afterEach(() => {
|
|||
describe("schedule.vue...", () => {
|
||||
describe("initial load", () => {
|
||||
test("should pass arePagePrerequisitesValid with a mobile order and no providerNumber", () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
store.getters.order.serviceLocation.appointmentType = "Mobile";
|
||||
store.getters.order.serviceLocation.provider = null;
|
||||
|
||||
//Act
|
||||
// Act
|
||||
const arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(arePagePrerequisitesValid).toBe(true);
|
||||
});
|
||||
|
||||
test("should pass arePagePrerequisitesValid with a inshop order and providerNumber", () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
// Act
|
||||
const arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(arePagePrerequisitesValid).toBe(true);
|
||||
});
|
||||
|
||||
test("should fail arePagePrerequisitesValid with a replace with no glass parts", async () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
store.getters.order.lineItems.glassParts = [];
|
||||
|
||||
//Act
|
||||
// Act
|
||||
const arePagePrerequisitesValid2 = await wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(arePagePrerequisitesValid2).toBe(false);
|
||||
});
|
||||
|
||||
test("should fail arePagePrerequisitesValid without isInsurance", () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
store.getters.payment.isInsurance = null;
|
||||
|
||||
//Act
|
||||
// Act
|
||||
const arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(arePagePrerequisitesValid).toBe(false);
|
||||
});
|
||||
|
||||
test("should return newShopTimeSlots when getAvailableDatesMethod is called", async () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.selectableDatesData = {
|
||||
days: [],
|
||||
};
|
||||
|
||||
//Act
|
||||
// Act
|
||||
const newShopTimeSlots = await wrapper.vm.getAvailableDatesMethod(
|
||||
"2023-01-01",
|
||||
"2023-01-31"
|
||||
);
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(newShopTimeSlots).toStrictEqual({
|
||||
days: [
|
||||
{
|
||||
|
|
@ -208,13 +210,13 @@ describe("schedule.vue...", () => {
|
|||
});
|
||||
|
||||
test("should call API service in day ranges of 34 or less when getAvailableDatesMethod is called with large date ranges", async () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.selectableDatesData = {
|
||||
days: [],
|
||||
};
|
||||
|
||||
//Act
|
||||
// Act
|
||||
await wrapper.vm.getAvailableDates.call(
|
||||
wrapper.vm,
|
||||
"2023-01-01",
|
||||
|
|
@ -223,7 +225,7 @@ describe("schedule.vue...", () => {
|
|||
"123"
|
||||
);
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalledTimes(3);
|
||||
expect(baseMixin.methods.dispatchStoreAction).toHaveBeenCalledWith(
|
||||
"getShopTimeSlots",
|
||||
|
|
@ -234,7 +236,7 @@ describe("schedule.vue...", () => {
|
|||
|
||||
describe("beforeRouteEnter function... ", () => {
|
||||
test("should call next() and call all functions within next", async () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.selectableDatesData = {
|
||||
days: [],
|
||||
|
|
@ -244,7 +246,7 @@ describe("schedule.vue...", () => {
|
|||
c(wrapper.vm);
|
||||
});
|
||||
|
||||
//Act
|
||||
// Act
|
||||
await schedule.beforeRouteEnter.call(
|
||||
wrapper.vm,
|
||||
{ query: { fmgPage: "schedule" } },
|
||||
|
|
@ -252,7 +254,7 @@ describe("schedule.vue...", () => {
|
|||
nextFunction
|
||||
);
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(nextFunction).toHaveBeenCalled();
|
||||
expect(wrapper.vm.setCmsContent).toHaveBeenCalledWith("content");
|
||||
expect(wrapper.vm.$refs.datePicker.initializeComponent).toHaveBeenCalledWith(
|
||||
|
|
@ -279,7 +281,7 @@ describe("schedule.vue...", () => {
|
|||
|
||||
describe("computed properties...", () => {
|
||||
test("timeSlotsForSelectedDate should return timeslots if selected date is available", () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.selectableDatesData = {
|
||||
days: [
|
||||
|
|
@ -306,10 +308,10 @@ describe("schedule.vue...", () => {
|
|||
selectedDate: "2022-11-11",
|
||||
});
|
||||
|
||||
//Act
|
||||
// Act
|
||||
const testValue = wrapper.vm.timeSlotsForSelectedDate;
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(testValue).toStrictEqual(
|
||||
expect.objectContaining({
|
||||
date: "2022-11-11",
|
||||
|
|
@ -318,7 +320,7 @@ describe("schedule.vue...", () => {
|
|||
});
|
||||
|
||||
test("timeSlotsForSelectedDate should be null if no date has been selected", () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.selectableDatesData = {
|
||||
days: [
|
||||
|
|
@ -345,10 +347,10 @@ describe("schedule.vue...", () => {
|
|||
selectedDate: undefined,
|
||||
});
|
||||
|
||||
//Act
|
||||
// Act
|
||||
const testValue = wrapper.vm.timeSlotsForSelectedDate;
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(testValue).toBe(null);
|
||||
});
|
||||
});
|
||||
|
|
@ -356,75 +358,67 @@ describe("schedule.vue...", () => {
|
|||
|
||||
describe("schedule page methods...", () => {
|
||||
test("getServiceZipCtuCodeFromStore should return zipCodeCtu", () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.selectableDatesData = {
|
||||
days: [],
|
||||
};
|
||||
|
||||
//Act
|
||||
// Act
|
||||
const testValue = wrapper.vm.getServiceZipCtuCodeFromStore();
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(testValue).toStrictEqual("01234");
|
||||
});
|
||||
|
||||
test("openInshopTimeSlotsModal should trigger openModal method", () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.selectableDatesData = {
|
||||
days: [],
|
||||
};
|
||||
|
||||
//Act
|
||||
// Act
|
||||
wrapper.vm.openInshopTimeSlotsModal();
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(wrapper.vm.$refs.timeSlotModalQuestion.openModal).toBeCalled();
|
||||
});
|
||||
|
||||
test("getSelectedRouteCode should return schedule routeCode", () => {
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
wrapper.vm.selectableDatesData = {
|
||||
days: [],
|
||||
};
|
||||
|
||||
//Act
|
||||
const testValue = wrapper.vm.getSelectedRouteCode();
|
||||
|
||||
//Assert
|
||||
expect(testValue).toStrictEqual("000");
|
||||
});
|
||||
|
||||
test("timeSlotModalClosed should null any selected date when there's no route code", () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.selectableDatesData = {
|
||||
days: [],
|
||||
};
|
||||
|
||||
wrapper.setData({
|
||||
selectedDate: "1980-05-05",
|
||||
});
|
||||
wrapper.setData({
|
||||
selectedTimeSlot: {
|
||||
|
||||
const selectedTimeSlotInfo = {
|
||||
timeSlot: {
|
||||
date: "2019-01-01",
|
||||
startTime: "09:00",
|
||||
endTime: "10:00",
|
||||
routeCode: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
};
|
||||
|
||||
wrapper.setData({
|
||||
selectedTimeSlotInfo: selectedTimeSlotInfo,
|
||||
});
|
||||
|
||||
//Act
|
||||
// Act
|
||||
wrapper.vm.timeSlotModalClosed();
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(wrapper.vm.selectedDate).toBe(null);
|
||||
});
|
||||
|
||||
test("getDisplayTextForMilitaryTime should return the correctly formatted string", () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.selectableDatesData = {
|
||||
days: [],
|
||||
|
|
@ -432,13 +426,13 @@ describe("schedule.vue...", () => {
|
|||
const timeInput1 = "15:00";
|
||||
const timeInput2 = "15:30";
|
||||
|
||||
//Act
|
||||
// Act
|
||||
const testOutput1 = wrapper.vm.getDisplayTextForMilitaryTime(timeInput1);
|
||||
const testOutput2 = wrapper.vm.getDisplayTextForMilitaryTime(timeInput2);
|
||||
const testOutput3 = wrapper.vm.getDisplayTextForMilitaryTime(timeInput1, true);
|
||||
const testOutput4 = wrapper.vm.getDisplayTextForMilitaryTime(timeInput2, true);
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(testOutput1).toBe("3:00 PM");
|
||||
expect(testOutput2).toBe("3:30 PM");
|
||||
expect(testOutput3).toBe("3 PM");
|
||||
|
|
@ -446,7 +440,7 @@ describe("schedule.vue...", () => {
|
|||
});
|
||||
|
||||
test("getDisplayTextForMilitaryTime should return the correctly formatted string", () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.selectableDatesData = {
|
||||
days: [],
|
||||
|
|
@ -454,10 +448,10 @@ describe("schedule.vue...", () => {
|
|||
wrapper.vm.$router.navigateWithoutSaving = jest.fn();
|
||||
wrapper.vm.$route = "testRoute";
|
||||
|
||||
//Act
|
||||
// Act
|
||||
wrapper.vm.backButtonAction();
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(wrapper.vm.$router.navigateWithoutSaving).toBeCalledWith(
|
||||
"CLICKED_BACK",
|
||||
"testRoute"
|
||||
|
|
@ -466,7 +460,7 @@ describe("schedule.vue...", () => {
|
|||
});
|
||||
|
||||
test("forwardButtonAction should call route method navigateWithoutSaving", async () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.dispatchStoreAction = jest.fn(() => {
|
||||
return {
|
||||
|
|
@ -477,15 +471,15 @@ describe("schedule.vue...", () => {
|
|||
return {};
|
||||
});
|
||||
|
||||
//Act
|
||||
// Act
|
||||
await wrapper.vm.forwardButtonAction();
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("for mobile appts, updateSupportingItems should call store action to save supporting items", async () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
store.getters.order.serviceLocation.appointmentType = "Mobile";
|
||||
store.getters.lineItems.supportingItems = [
|
||||
{
|
||||
|
|
@ -514,10 +508,10 @@ describe("schedule.vue...", () => {
|
|||
},
|
||||
});
|
||||
|
||||
//Act
|
||||
// Act
|
||||
await wrapper.vm.updateSupportingItems();
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(wrapper.vm.dispatchStoreAction).toBeCalledWith(
|
||||
"saveSupportingItemsSuppressingStateResetting",
|
||||
expect.arrayContaining([
|
||||
|
|
@ -530,7 +524,7 @@ describe("schedule.vue...", () => {
|
|||
});
|
||||
|
||||
test("for Inshop appts, updateSupportingItems should call store action to save supporting items WITHOUT the EARLY BIRD supporting item", async () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
store.getters.order.serviceLocation.appointmentType = "Inshop";
|
||||
store.getters.lineItems.supportingItems = [
|
||||
{
|
||||
|
|
@ -559,10 +553,10 @@ describe("schedule.vue...", () => {
|
|||
},
|
||||
});
|
||||
|
||||
//Act
|
||||
// Act
|
||||
await wrapper.vm.updateSupportingItems();
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(wrapper.vm.dispatchStoreAction).toBeCalledWith(
|
||||
"saveSupportingItemsSuppressingStateResetting",
|
||||
expect.not.arrayContaining([
|
||||
|
|
@ -575,7 +569,7 @@ describe("schedule.vue...", () => {
|
|||
});
|
||||
|
||||
test("if no EARLY BIRD supporting item, then updateSupportingItems should NOT call store action", async () => {
|
||||
//Arrange
|
||||
// Arrange
|
||||
store.getters.order.serviceLocation.appointmentType = "Mobile";
|
||||
store.getters.lineItems.supportingItems = [];
|
||||
const { wrapper } = setupMocks({});
|
||||
|
|
@ -595,10 +589,10 @@ describe("schedule.vue...", () => {
|
|||
},
|
||||
});
|
||||
|
||||
//Act
|
||||
// Act
|
||||
await wrapper.vm.updateSupportingItems();
|
||||
|
||||
//Assert
|
||||
// Assert
|
||||
expect(wrapper.vm.dispatchStoreAction).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -24,17 +24,17 @@
|
|||
<timeSlotModalQuestion
|
||||
ref="timeSlotModalQuestion"
|
||||
customComponentId="timeSlotModalQuestion"
|
||||
v-model="selectedTimeSlotInfo"
|
||||
cmsWidgetName="TimeSlotModalQuestion"
|
||||
mobilePremiumCmsWidgetName="MobilePremiumTimeSlotModal"
|
||||
mobileCmsWidgetName="MobileTimeSlotModal"
|
||||
dropoffCmsWidgetName="DropOffTimeSlotModal"
|
||||
sameDayDropOffCmsWidgetName="SameDayDropOffTimeSlotModal"
|
||||
overnightDropOffCmsWidgetName="OvernightDropOffTimeSlotModal"
|
||||
v-model="selectedTimeSlot"
|
||||
:selectedDate="selectedDate"
|
||||
:appointmentType="appointmentType"
|
||||
:premiumAppointmentFee="mobilePremiumAppointmentFee"
|
||||
:dateAndTimeSlotData="timeSlotsForSelectedDate"
|
||||
:timeSlotsForSelectedDate="timeSlotsForSelectedDate"
|
||||
:estimatedServiceMinutesMinimum="selectableDatesData.estimatedServiceMinutesMinimum"
|
||||
:estimatedServiceMinutesMaximum="selectableDatesData.estimatedServiceMinutesMaximum"
|
||||
@time-slot-modal-closed="timeSlotModalClosed"
|
||||
|
|
@ -81,12 +81,7 @@ import store from "@/store";
|
|||
// DEFINE VALIDATION RULES
|
||||
defineRule("date-required", required(errorMessages.DATE_REQUIRED));
|
||||
defineRule("time-slot-selection-required", (value) => {
|
||||
if (
|
||||
value.date == null ||
|
||||
value.startTime == null ||
|
||||
value.routeCode == null ||
|
||||
value.estimatedServiceMinutesMaximum
|
||||
) {
|
||||
if (value?.timeSlot?.routeCode == null) {
|
||||
return errorMessages.DATE_REQUIRED;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -190,7 +185,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
selectedDate: this.getSelectedDate(),
|
||||
selectedTimeSlot: this.getSelectedTimeSlot(),
|
||||
selectedTimeSlotInfo: this.getSelectedTimeSlotInfo(),
|
||||
selectableDatesData: [],
|
||||
mobilePremiumAppointmentFee: null,
|
||||
};
|
||||
|
|
@ -265,7 +260,7 @@ export default {
|
|||
vm.mobilePremiumAppointmentFee = resultMap.premiumFeeWithPrice
|
||||
? resultMap.premiumFeeWithPrice[0]
|
||||
: null;
|
||||
vm.updateFooterButtonText(vm.selectedTimeSlot);
|
||||
vm.updateFooterButtonText(vm.selectedTimeSlotInfo);
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -293,18 +288,22 @@ export default {
|
|||
splitCopyOnCMSPlaceHolder,
|
||||
arePagePrerequisitesValid() {
|
||||
const serviceLocation = store.getters.order.serviceLocation;
|
||||
|
||||
const serviceLocationPreReqs =
|
||||
serviceLocation.zipCode &&
|
||||
serviceLocation.zipCodeCtu &&
|
||||
serviceLocation.appointmentType &&
|
||||
(serviceLocation.appointmentType === AppointmentTypeStrings.MOBILE ||
|
||||
serviceLocation.provider.providerNumber);
|
||||
|
||||
const paymentInfo = store.getters.payment.isInsurance !== null;
|
||||
const supportingItems = store.getters.lineItems.supportingItems !== null;
|
||||
|
||||
const damageInfo =
|
||||
store.getters.order.damage.isRepair ||
|
||||
(store.getters.order.lineItems?.glassParts != null &&
|
||||
store.getters.order.lineItems.glassParts.length > 0);
|
||||
|
||||
return serviceLocationPreReqs && paymentInfo && supportingItems && damageInfo;
|
||||
},
|
||||
async getAvailableDatesMethod(startDate, endDate) {
|
||||
|
|
@ -327,45 +326,55 @@ export default {
|
|||
openInshopTimeSlotsModal() {
|
||||
this.$refs["timeSlotModalQuestion"].openModal();
|
||||
},
|
||||
getSelectedTimeSlot() {
|
||||
return store.getters.order.schedule;
|
||||
},
|
||||
getSelectedDate() {
|
||||
return store.getters.order.schedule.date;
|
||||
},
|
||||
getSelectedRouteCode() {
|
||||
return store.getters.order.schedule.routeCode;
|
||||
getSelectedTimeSlotInfo() {
|
||||
const supportingItems = this.getSupportingItems();
|
||||
const isPremiumAppointment =
|
||||
!!supportingItems.filter((lineItem) => lineItem.partType === PREMIUM_FEE_PART_TYPE)
|
||||
.length > 0;
|
||||
|
||||
const selectedTimeSlotInfo = {
|
||||
timeSlot: store.getters.order.schedule,
|
||||
isPremiumAppointment: isPremiumAppointment,
|
||||
};
|
||||
|
||||
return selectedTimeSlotInfo;
|
||||
},
|
||||
getSupportingItems() {
|
||||
return store.getters.lineItems.supportingItems;
|
||||
},
|
||||
timeSlotModalClosed() {
|
||||
// Clear the selectedDate if no timeSlot has been selected
|
||||
if (this.selectedTimeSlot.routeCode == null) {
|
||||
if (this.selectedTimeSlotInfo.timeSlot.routeCode == null) {
|
||||
this.selectedDate = null;
|
||||
}
|
||||
},
|
||||
updateFooterButtonText(timeSlot) {
|
||||
updateFooterButtonText(timeSlotInfo) {
|
||||
let funnelFooterButtonText;
|
||||
if (!timeSlot || !timeSlot.date) {
|
||||
if (!timeSlotInfo || !timeSlotInfo.timeSlot.date) {
|
||||
funnelFooterButtonText = "Continue";
|
||||
} else {
|
||||
funnelFooterButtonText = `Select ${this.convertSelectedDateToShortMonthAndDay(
|
||||
timeSlot.date
|
||||
timeSlotInfo.timeSlot.date
|
||||
)}`;
|
||||
|
||||
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
|
||||
funnelFooterButtonText += ` at ${this.getDisplayTextForMilitaryTime(
|
||||
timeSlot.startTime
|
||||
timeSlotInfo.timeSlot.startTime
|
||||
)}`;
|
||||
} else if (
|
||||
this.appointmentType === AppointmentTypeStrings.MOBILE &&
|
||||
!timeSlot.isPremiumAppointment
|
||||
!timeSlotInfo.isPremiumAppointment
|
||||
) {
|
||||
funnelFooterButtonText += ` at ${this.getDisplayTextForMilitaryTime(
|
||||
timeSlot.startTime,
|
||||
timeSlotInfo.timeSlot.startTime,
|
||||
true
|
||||
)} - ${this.getDisplayTextForMilitaryTime(timeSlot.endTime, true)}`;
|
||||
)} - ${this.getDisplayTextForMilitaryTime(
|
||||
timeSlotInfo.timeSlot.endTime,
|
||||
true
|
||||
)}`;
|
||||
}
|
||||
}
|
||||
this.$refs.funnelFooter.updateButtonText(funnelFooterButtonText);
|
||||
|
|
@ -376,14 +385,16 @@ export default {
|
|||
// Ex: April 25
|
||||
return dateObject.toLocaleDateString("en-us", { month: "short", day: "numeric" });
|
||||
},
|
||||
// Expected input: "HH:MM"
|
||||
getDisplayTextForMilitaryTime(militaryTimeInput, shouldTrimMinutesIfEmpty = false) {
|
||||
// Expected input: "HH:MM"
|
||||
let hours = parseInt(militaryTimeInput.split(":")[0]);
|
||||
const minutes = militaryTimeInput.split(":")[1];
|
||||
const meridianNotation = hours > 11 ? "PM" : "AM";
|
||||
|
||||
if (hours > 12) {
|
||||
hours -= 12;
|
||||
}
|
||||
|
||||
if (shouldTrimMinutesIfEmpty && minutes === "00") {
|
||||
return `${hours} ${meridianNotation}`;
|
||||
} else {
|
||||
|
|
@ -395,7 +406,12 @@ export default {
|
|||
},
|
||||
forwardButtonAction() {
|
||||
this.updateSupportingItems();
|
||||
this.dispatchStoreAction(this.storeActions.SAVE_SCHEDULE, this.selectedTimeSlot, false);
|
||||
|
||||
this.dispatchStoreAction(
|
||||
this.storeActions.SAVE_SCHEDULE,
|
||||
this.selectedTimeSlotInfo.timeSlot,
|
||||
false
|
||||
);
|
||||
|
||||
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD, this.$route);
|
||||
},
|
||||
|
|
@ -405,7 +421,7 @@ export default {
|
|||
// if we have a premium fee(early bird), then save/update supporting items
|
||||
if (
|
||||
this.appointmentType === AppointmentTypeStrings.MOBILE &&
|
||||
this.selectedTimeSlot?.isPremiumAppointment
|
||||
this.selectedTimeSlotInfo?.isPremiumAppointment
|
||||
) {
|
||||
const earlyBirdIndex = supportingItems.findIndex(
|
||||
(item) => item.partType == PREMIUM_FEE_PART_TYPE
|
||||
|
|
@ -448,17 +464,20 @@ export default {
|
|||
selectedDate(newValue, oldValue) {
|
||||
// Clear time slot selection if date selected changes
|
||||
if (newValue !== oldValue) {
|
||||
this.selectedTimeSlot = {
|
||||
date: null,
|
||||
routeCode: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
this.selectedTimeSlotInfo = {
|
||||
timeSlot: {
|
||||
date: null,
|
||||
routeCode: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
};
|
||||
}
|
||||
},
|
||||
selectedTimeSlot(newValue) {
|
||||
selectedTimeSlotInfo(newValue) {
|
||||
this.updateFooterButtonText(newValue);
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2,30 +2,76 @@ import { shallowMount, flushPromises } from "@vue/test-utils";
|
|||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import timeSlotModalQuestion from "@/layouts/schedule/time-slot-modal-question/time-slot-modal-question.vue";
|
||||
import store from "@/store";
|
||||
|
||||
import {
|
||||
RouteCodeFlags,
|
||||
PREMIUM_TIME_SLOT_ID_FLAG,
|
||||
PREMIUM_FEE_PART_TYPE,
|
||||
} from "@/constants/schedule-constants";
|
||||
|
||||
describe("time-slot-modal-list-button-question.vue", () => {
|
||||
describe("time-slot-modal-question.vue", () => {
|
||||
const timeSlotsForSelectedDate = {
|
||||
date: "2023-09-02",
|
||||
timeSlots: [
|
||||
{
|
||||
id: "03335-01820-S-B*20334*8 AM",
|
||||
startTime: "08:00",
|
||||
endTime: "09:00",
|
||||
offerPremium: false,
|
||||
},
|
||||
{
|
||||
id: "03335-01820-S-B*20334*830 AM",
|
||||
startTime: "08:30",
|
||||
endTime: "09:30",
|
||||
offerPremium: false,
|
||||
},
|
||||
{
|
||||
id: "03335-01820-S-B*20334*9 AM",
|
||||
startTime: "09:00",
|
||||
endTime: "10:00",
|
||||
offerPremium: false,
|
||||
},
|
||||
{
|
||||
id: "03335-01820-S-B*20334*930 AM",
|
||||
startTime: "09:30",
|
||||
endTime: "10:30",
|
||||
offerPremium: false,
|
||||
},
|
||||
{
|
||||
id: "03335-01820-S-B*20334*10 AM",
|
||||
startTime: "10:00",
|
||||
endTime: "11:00",
|
||||
offerPremium: false,
|
||||
},
|
||||
{
|
||||
id: "03335-01820-S-B*20334*1030 AM",
|
||||
startTime: "10:30",
|
||||
endTime: "11:30",
|
||||
offerPremium: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
test("When selected time slot is changed, a correctly formatted selectedTimeSlot should be emitted", async () => {
|
||||
//Arrange
|
||||
const date = "2023-07-01";
|
||||
const timeSlotId = "testRouteCodeId";
|
||||
const startTime = "8:00 AM";
|
||||
const endTime = "8:30 AM";
|
||||
const date = "2023-09-02";
|
||||
const routeCode = "03335-01820-S-B*20334*830 AM";
|
||||
const startTime = "08:30";
|
||||
const endTime = "09:30";
|
||||
const jobMaxMinutes = 100;
|
||||
const jobMinMinutes = 50;
|
||||
const expectedEmit = [
|
||||
[
|
||||
{
|
||||
date: date,
|
||||
routeCode: timeSlotId,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
jobMaxMinutes: jobMaxMinutes.toString(),
|
||||
jobMinMinutes: jobMinMinutes.toString(),
|
||||
timeSlot: {
|
||||
date: date,
|
||||
routeCode: routeCode,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
jobMaxMinutes: jobMaxMinutes.toString(),
|
||||
jobMinMinutes: jobMinMinutes.toString(),
|
||||
},
|
||||
isPremiumAppointment: false,
|
||||
},
|
||||
],
|
||||
];
|
||||
|
|
@ -35,22 +81,16 @@ describe("time-slot-modal-list-button-question.vue", () => {
|
|||
propsData: {
|
||||
estimatedServiceMinutesMaximum: jobMaxMinutes,
|
||||
estimatedServiceMinutesMinimum: jobMinMinutes,
|
||||
dateAndTimeSlotData: {
|
||||
date: date,
|
||||
timeSlots: [
|
||||
{
|
||||
id: timeSlotId,
|
||||
startTime: startTime,
|
||||
endTime: endTime,
|
||||
},
|
||||
],
|
||||
},
|
||||
timeSlotsForSelectedDate: timeSlotsForSelectedDate,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.vm.$refs.timeSlots.closeModal = jest.fn();
|
||||
wrapper.vm.selectedTimeSlotId = timeSlotId;
|
||||
|
||||
await wrapper.setData({
|
||||
selectedRouteCode: routeCode,
|
||||
});
|
||||
|
||||
//Act
|
||||
wrapper.vm.setSelectedTimeSlot();
|
||||
|
|
@ -59,7 +99,8 @@ describe("time-slot-modal-list-button-question.vue", () => {
|
|||
expect(wrapper.emitted()["update:modelValue"]).toEqual(expectedEmit);
|
||||
});
|
||||
});
|
||||
describe("time-slot-modal-list-button-question.vue Supplemental information", () => {
|
||||
|
||||
describe("time-slot-modal-question.vue Supplemental information", () => {
|
||||
test("The correct supplemental information should be obtained from CMS - Mobile - Not Premium ", async () => {
|
||||
//Arrange
|
||||
const mobileCmsWidgetName = "MobileNotPremium";
|
||||
|
|
@ -72,11 +113,13 @@ describe("time-slot-modal-list-button-question.vue Supplemental information", ()
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.supplementalInformationBlock).toEqual(
|
||||
mockCmsContent[mobileCmsWidgetName]["BodyText"]
|
||||
);
|
||||
});
|
||||
|
||||
test("The correct supplemental information should be obtained from CMS - Mobile - Premium", async () => {
|
||||
//Arrange
|
||||
const mobileCmsWidgetName = "MobilePremium";
|
||||
|
|
@ -89,11 +132,13 @@ describe("time-slot-modal-list-button-question.vue Supplemental information", ()
|
|||
},
|
||||
},
|
||||
});
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.supplementalInformationBlock).toEqual(
|
||||
mockCmsContent[mobileCmsWidgetName]["BodyText"]
|
||||
);
|
||||
});
|
||||
|
||||
test("No supplemental information should be obtained from CMS - Inshop", async () => {
|
||||
//Arrange
|
||||
const inshopWidgetName = "Inshop";
|
||||
|
|
@ -109,6 +154,7 @@ describe("time-slot-modal-list-button-question.vue Supplemental information", ()
|
|||
//Assert
|
||||
expect(wrapper.vm.supplementalInformationBlock).toBeNull();
|
||||
});
|
||||
|
||||
test("No supplemental information should be obtained from CMS - Drop off, nothing selected", async () => {
|
||||
//Arrange
|
||||
const dropOffWidgetName = "Dropoff";
|
||||
|
|
@ -124,40 +170,69 @@ describe("time-slot-modal-list-button-question.vue Supplemental information", ()
|
|||
//Assert
|
||||
expect(wrapper.vm.supplementalInformationBlock).toBeNull();
|
||||
});
|
||||
|
||||
test("Correct supplemental information should be retrieved, Dropoff regular", async () => {
|
||||
//Arrange
|
||||
const dropOffWidgetName = "Dropoff";
|
||||
const modelValue = {
|
||||
timeSlot: {
|
||||
routeCode: `test${RouteCodeFlags.ALL_DAY_DROP_OFF}`,
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
};
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
customMountOptions: {
|
||||
propsData: {
|
||||
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
|
||||
modelValue: modelValue,
|
||||
appointmentType: "Dropoff",
|
||||
dropoffCmsWidgetName: dropOffWidgetName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.supplementalInformationBlock).toEqual(
|
||||
mockCmsContent[dropOffWidgetName]["BodyText"]
|
||||
);
|
||||
});
|
||||
|
||||
test("Correct supplemental information should be retrieved, Dropoff Overnight", async () => {
|
||||
//Arrange
|
||||
const overnightDropOffCmsWidgetName = "Overnight";
|
||||
const modelValue = {
|
||||
timeSlot: {
|
||||
routeCode: `test${RouteCodeFlags.OVERNIGHT_DROP_OFF}`,
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
};
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
customMountOptions: {
|
||||
propsData: {
|
||||
modelValue: { routeCode: "Test" + RouteCodeFlags.OVERNIGHT_DROP_OFF },
|
||||
modelValue: modelValue,
|
||||
appointmentType: "Dropoff",
|
||||
overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.supplementalInformationBlock).toEqual(
|
||||
mockCmsContent[overnightDropOffCmsWidgetName]["BodyText"]
|
||||
);
|
||||
});
|
||||
|
||||
test("Correct supplemental information should be retrieved, Dropoff Sameday", async () => {
|
||||
//Arrange
|
||||
const sameDayDropOffCmsWidgetName = "Sameday";
|
||||
|
|
@ -167,13 +242,24 @@ describe("time-slot-modal-list-button-question.vue Supplemental information", ()
|
|||
const endTime = "8:30 AM";
|
||||
const jobMaxMinutes = 100;
|
||||
const jobMinMinutes = 50;
|
||||
const modelValue = {
|
||||
timeSlot: {
|
||||
routeCode: `test${RouteCodeFlags.ALL_DAY_DROP_OFF}`,
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
};
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
customMountOptions: {
|
||||
propsData: {
|
||||
estimatedServiceMinutesMaximum: jobMaxMinutes,
|
||||
estimatedServiceMinutesMinimum: jobMinMinutes,
|
||||
dateAndTimeSlotData: {
|
||||
timeSlotsForSelectedDate: {
|
||||
date: date,
|
||||
timeSlots: [
|
||||
{
|
||||
|
|
@ -183,7 +269,7 @@ describe("time-slot-modal-list-button-question.vue Supplemental information", ()
|
|||
},
|
||||
],
|
||||
},
|
||||
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
|
||||
modelValue: modelValue,
|
||||
appointmentType: "Dropoff",
|
||||
sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
|
||||
},
|
||||
|
|
@ -208,12 +294,24 @@ describe("time-slot-modal-list-button-question.vue Disclaimer", () => {
|
|||
const jobMaxMinutes = 100;
|
||||
const jobMinMinutes = 50;
|
||||
|
||||
const modelValue = {
|
||||
timeSlot: {
|
||||
routeCode: `Test${RouteCodeFlags.ALL_DAY_DROP_OFF}`,
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
};
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
customMountOptions: {
|
||||
propsData: {
|
||||
estimatedServiceMinutesMaximum: jobMaxMinutes,
|
||||
estimatedServiceMinutesMinimum: jobMinMinutes,
|
||||
dateAndTimeSlotData: {
|
||||
timeSlotsForSelectedDate: {
|
||||
date: date,
|
||||
timeSlots: [
|
||||
{
|
||||
|
|
@ -223,7 +321,7 @@ describe("time-slot-modal-list-button-question.vue Disclaimer", () => {
|
|||
},
|
||||
],
|
||||
},
|
||||
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
|
||||
modelValue: modelValue,
|
||||
appointmentType: "Dropoff",
|
||||
sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
|
||||
},
|
||||
|
|
@ -235,47 +333,88 @@ describe("time-slot-modal-list-button-question.vue Disclaimer", () => {
|
|||
mockCmsContent[sameDayDropOffCmsWidgetName]["FooterText"]
|
||||
);
|
||||
});
|
||||
|
||||
test("Correct disclaimer information should be retrieved, Dropoff regular", async () => {
|
||||
//Arrange
|
||||
const dropOffWidgetName = "Dropoff";
|
||||
const modelValue = {
|
||||
timeSlot: {
|
||||
routeCode: `Test${RouteCodeFlags.ALL_DAY_DROP_OFF}`,
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
};
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
customMountOptions: {
|
||||
propsData: {
|
||||
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
|
||||
modelValue: modelValue,
|
||||
appointmentType: "Dropoff",
|
||||
dropoffCmsWidgetName: dropOffWidgetName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.disclaimerTextBlockCopy).toEqual(
|
||||
mockCmsContent[dropOffWidgetName]["FooterText"]
|
||||
);
|
||||
});
|
||||
|
||||
test("Correct disclaimer information should be retrieved, Dropoff overnight", async () => {
|
||||
//Arrange
|
||||
const overnightDropOffCmsWidgetName = "Overnight";
|
||||
const modelValue = {
|
||||
timeSlot: {
|
||||
routeCode: `Test${RouteCodeFlags.OVERNIGHT_DROP_OFF}`,
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
};
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
customMountOptions: {
|
||||
propsData: {
|
||||
modelValue: { routeCode: "Test" + RouteCodeFlags.OVERNIGHT_DROP_OFF },
|
||||
modelValue: modelValue,
|
||||
appointmentType: "Dropoff",
|
||||
overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.disclaimerTextBlockCopy).toEqual(
|
||||
mockCmsContent[overnightDropOffCmsWidgetName]["FooterText"]
|
||||
);
|
||||
});
|
||||
|
||||
test("No disclaimer information should be retrieved, non-dropoff", async () => {
|
||||
//Arrange
|
||||
const inshopWidgetName = "Inshop";
|
||||
const modelValue = {
|
||||
timeSlot: {
|
||||
routeCode: `test`,
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
};
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
customMountOptions: {
|
||||
propsData: {
|
||||
modelValue: { routeCode: "test" },
|
||||
modelValue: modelValue,
|
||||
appointmentType: "Inshop",
|
||||
cmsWidgetName: inshopWidgetName,
|
||||
},
|
||||
|
|
@ -286,7 +425,7 @@ describe("time-slot-modal-list-button-question.vue Disclaimer", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("time-slot-modal-list-button-question.vue Duration", () => {
|
||||
describe("time-slot-modal-question.vue Duration", () => {
|
||||
test("The correct duration information should be displayed - Dropoff - Sameday ", async () => {
|
||||
//Arrange
|
||||
const sameDayDropOffCmsWidgetName = "Sameday";
|
||||
|
|
@ -297,12 +436,24 @@ describe("time-slot-modal-list-button-question.vue Duration", () => {
|
|||
const jobMaxMinutes = 100;
|
||||
const jobMinMinutes = 50;
|
||||
|
||||
const modelValue = {
|
||||
timeSlot: {
|
||||
routeCode: `test` + RouteCodeFlags.ALL_DAY_DROP_OFF,
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
};
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
customMountOptions: {
|
||||
propsData: {
|
||||
estimatedServiceMinutesMaximum: jobMaxMinutes,
|
||||
estimatedServiceMinutesMinimum: jobMinMinutes,
|
||||
dateAndTimeSlotData: {
|
||||
timeSlotsForSelectedDate: {
|
||||
date: date,
|
||||
timeSlots: [
|
||||
{
|
||||
|
|
@ -312,7 +463,7 @@ describe("time-slot-modal-list-button-question.vue Duration", () => {
|
|||
},
|
||||
],
|
||||
},
|
||||
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
|
||||
modelValue: modelValue,
|
||||
appointmentType: "Dropoff",
|
||||
sameDayDropOffCmsWidgetName: sameDayDropOffCmsWidgetName,
|
||||
},
|
||||
|
|
@ -324,30 +475,59 @@ describe("time-slot-modal-list-button-question.vue Duration", () => {
|
|||
mockCmsContent[sameDayDropOffCmsWidgetName]["SubheaderText"]
|
||||
);
|
||||
});
|
||||
|
||||
test("Correct duration information should be retrieved, Dropoff overnight", async () => {
|
||||
//Arrange
|
||||
const overnightDropOffCmsWidgetName = "Overnight";
|
||||
|
||||
const modelValue = {
|
||||
timeSlot: {
|
||||
routeCode: `test` + RouteCodeFlags.OVERNIGHT_DROP_OFF,
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
};
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
customMountOptions: {
|
||||
propsData: {
|
||||
modelValue: { routeCode: "Test" + RouteCodeFlags.OVERNIGHT_DROP_OFF },
|
||||
modelValue: modelValue,
|
||||
appointmentType: "Dropoff",
|
||||
overnightDropOffCmsWidgetName: overnightDropOffCmsWidgetName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.durationTextBlockCopy).toEqual(
|
||||
mockCmsContent[overnightDropOffCmsWidgetName]["SubheaderText"]
|
||||
);
|
||||
});
|
||||
|
||||
test("Correct duration information should be retrieved, Dropoff", async () => {
|
||||
//Arrange
|
||||
const dropoffCmsWidgetName = "Dropoff";
|
||||
|
||||
const modelValue = {
|
||||
timeSlot: {
|
||||
routeCode: `test` + RouteCodeFlags.ALL_DAY_DROP_OFF,
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
};
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
customMountOptions: {
|
||||
propsData: {
|
||||
modelValue: { routeCode: "Test" + RouteCodeFlags.ALL_DAY_DROP_OFF },
|
||||
modelValue: modelValue,
|
||||
appointmentType: "Dropoff",
|
||||
dropoffCmsWidgetName: dropoffCmsWidgetName,
|
||||
},
|
||||
|
|
@ -358,25 +538,39 @@ describe("time-slot-modal-list-button-question.vue Duration", () => {
|
|||
mockCmsContent[dropoffCmsWidgetName]["SubheaderText"]
|
||||
);
|
||||
});
|
||||
|
||||
test("Correct duration information should be retrieved, Inshop - convert to hours", async () => {
|
||||
//Arrange
|
||||
const cmsWidgetName = "Inshop";
|
||||
|
||||
const modelValue = {
|
||||
timeSlot: {
|
||||
routeCode: `test`,
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
};
|
||||
|
||||
const { wrapper } = setupMocks({
|
||||
customMountOptions: {
|
||||
propsData: {
|
||||
estimatedServiceMinutesMaximum: 180,
|
||||
estimatedServiceMinutesMinimum: 120,
|
||||
modelValue: { routeCode: "Test" },
|
||||
modelValue: modelValue,
|
||||
appointmentType: "Inshop",
|
||||
cmsWidgetName: cmsWidgetName,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.durationTextBlockCopy).toEqual(
|
||||
mockCmsContent[cmsWidgetName]["SubheaderText"] + " 2 - 3 hours"
|
||||
);
|
||||
});
|
||||
|
||||
test("Correct duration information should be retrieved, Inshop - convert to minutes", async () => {
|
||||
//Arrange
|
||||
const cmsWidgetName = "Inshop";
|
||||
|
|
@ -396,6 +590,7 @@ describe("time-slot-modal-list-button-question.vue Duration", () => {
|
|||
mockCmsContent[cmsWidgetName]["SubheaderText"] + " 60 - 90 minutes"
|
||||
);
|
||||
});
|
||||
|
||||
test("Correct duration information should be retrieved, Inshop - min = maximum time", async () => {
|
||||
//Arrange
|
||||
const cmsWidgetName = "Inshop";
|
||||
|
|
@ -415,6 +610,7 @@ describe("time-slot-modal-list-button-question.vue Duration", () => {
|
|||
mockCmsContent[cmsWidgetName]["SubheaderText"] + " 4 hours"
|
||||
);
|
||||
});
|
||||
|
||||
test("No duration information should be displayed, Mobile", async () => {
|
||||
//Arrange
|
||||
const mobileCmsWidgetName = "Mobile";
|
||||
|
|
@ -450,7 +646,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
|
|||
propsData: {
|
||||
estimatedServiceMinutesMaximum: jobMaxMinutes,
|
||||
estimatedServiceMinutesMinimum: jobMinMinutes,
|
||||
dateAndTimeSlotData: {
|
||||
timeSlotsForSelectedDate: {
|
||||
date: date,
|
||||
timeSlots: [
|
||||
{
|
||||
|
|
@ -473,6 +669,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
|
|||
};
|
||||
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
|
||||
});
|
||||
|
||||
test("Available Timeslots should be formatted correctly - Dropoff - Overnight ", async () => {
|
||||
//Arrange
|
||||
const overnightDropOffCmsWidgetName = "Overnight";
|
||||
|
|
@ -488,7 +685,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
|
|||
propsData: {
|
||||
estimatedServiceMinutesMaximum: jobMaxMinutes,
|
||||
estimatedServiceMinutesMinimum: jobMinMinutes,
|
||||
dateAndTimeSlotData: {
|
||||
timeSlotsForSelectedDate: {
|
||||
date: date,
|
||||
timeSlots: [
|
||||
{
|
||||
|
|
@ -511,6 +708,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
|
|||
};
|
||||
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
|
||||
});
|
||||
|
||||
test("Available Timeslots should be formatted correctly - Dropoff", async () => {
|
||||
//Arrange
|
||||
const dropoffCmsWidgetName = "Dropoff";
|
||||
|
|
@ -526,7 +724,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
|
|||
propsData: {
|
||||
estimatedServiceMinutesMaximum: jobMaxMinutes,
|
||||
estimatedServiceMinutesMinimum: jobMinMinutes,
|
||||
dateAndTimeSlotData: {
|
||||
timeSlotsForSelectedDate: {
|
||||
date: date,
|
||||
timeSlots: [
|
||||
{
|
||||
|
|
@ -549,6 +747,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
|
|||
};
|
||||
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
|
||||
});
|
||||
|
||||
test("Available Timeslots should be formatted correctly - Mobile not premium", async () => {
|
||||
//Arrange
|
||||
const mobileCmsWidgetName = "MobileNotPremium";
|
||||
|
|
@ -564,7 +763,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
|
|||
propsData: {
|
||||
estimatedServiceMinutesMaximum: jobMaxMinutes,
|
||||
estimatedServiceMinutesMinimum: jobMinMinutes,
|
||||
dateAndTimeSlotData: {
|
||||
timeSlotsForSelectedDate: {
|
||||
date: date,
|
||||
timeSlots: [
|
||||
{
|
||||
|
|
@ -587,6 +786,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
|
|||
};
|
||||
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
|
||||
});
|
||||
|
||||
test("Available Timeslots should be formatted correctly - Mobile with premium", async () => {
|
||||
//Arrange
|
||||
const mobilePremiumCmsWidgetName = "MobilePremium";
|
||||
|
|
@ -605,7 +805,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
|
|||
premiumAppointmentFee: {
|
||||
partType: PREMIUM_FEE_PART_TYPE,
|
||||
},
|
||||
dateAndTimeSlotData: {
|
||||
timeSlotsForSelectedDate: {
|
||||
date: date,
|
||||
timeSlots: [
|
||||
{
|
||||
|
|
@ -631,8 +831,10 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
|
|||
buttonLabelSubCopy: "+$15.99",
|
||||
value: timeSlotId + PREMIUM_TIME_SLOT_ID_FLAG,
|
||||
};
|
||||
|
||||
expect(wrapper.vm.availableTimeSlots[0]).toEqual(expectedTimeSlotObject);
|
||||
});
|
||||
|
||||
test("Available Timeslots should be formatted correctly - Inshop", async () => {
|
||||
//Arrange
|
||||
const cmsWidgetName = "Inshop";
|
||||
|
|
@ -648,7 +850,7 @@ describe("time-slot-modal-list-button-question.vue Available Timeslots", () => {
|
|||
propsData: {
|
||||
estimatedServiceMinutesMaximum: jobMaxMinutes,
|
||||
estimatedServiceMinutesMinimum: jobMinMinutes,
|
||||
dateAndTimeSlotData: {
|
||||
timeSlotsForSelectedDate: {
|
||||
date: date,
|
||||
timeSlots: [
|
||||
{
|
||||
|
|
@ -678,7 +880,7 @@ const mockCmsContent = {
|
|||
HeaderText: "Mobile, not premium HeaderText",
|
||||
},
|
||||
MobilePremium: {
|
||||
"BodyText:": "Mobile, with premium BodyText",
|
||||
BodyText: "Mobile, with premium BodyText",
|
||||
FooterText: "Mobile, with premium FooterText",
|
||||
SubheaderText: "Mobile, with premium SubheaderText",
|
||||
HeaderText: "Mobile, with premium HeaderText",
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
:answers="availableTimeSlots"
|
||||
groupName="chooseTimeSlot"
|
||||
textPosition="text-center"
|
||||
v-model="selectedTimeSlotId"
|
||||
v-model="selectedRouteCode"
|
||||
isRequired
|
||||
validationRules="time-slot-required" />
|
||||
<div
|
||||
|
|
@ -47,12 +47,12 @@ import buttonQuestion from "@/digital-components/button-question/button-question
|
|||
import timeSlotModalListButton from "./time-slot-modal-list-button/time-slot-modal-list-button";
|
||||
|
||||
// Helpers
|
||||
import { deepClone } from "@/helpers/object-helper";
|
||||
import {
|
||||
convertDateStringToDate,
|
||||
militaryToTwelveHourTime,
|
||||
getDisplayTextForDurationLength,
|
||||
} from "@/layouts/schedule/helpers/schedule-helper";
|
||||
import { deepClone } from "@/helpers/object-helper";
|
||||
|
||||
// Validation - TODO: Move this somewhere more global?
|
||||
import { defineRule, useField } from "vee-validate";
|
||||
|
|
@ -87,12 +87,15 @@ export default {
|
|||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
routeCode: null,
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
timeSlot: {
|
||||
routeCode: null,
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
}),
|
||||
},
|
||||
cmsWidgetName: String,
|
||||
|
|
@ -102,7 +105,7 @@ export default {
|
|||
sameDayDropOffCmsWidgetName: String,
|
||||
overnightDropOffCmsWidgetName: String,
|
||||
appointmentType: String,
|
||||
dateAndTimeSlotData: Object,
|
||||
timeSlotsForSelectedDate: Object,
|
||||
premiumAppointmentFee: Object,
|
||||
estimatedServiceMinutesMinimum: Number,
|
||||
estimatedServiceMinutesMaximum: Number,
|
||||
|
|
@ -113,7 +116,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
isModalOpened: false,
|
||||
selectedValue: null,
|
||||
selectedRouteCode: this.getSelectedRouteCode(),
|
||||
timeSlotModalListButton: timeSlotModalListButton,
|
||||
};
|
||||
},
|
||||
|
|
@ -150,36 +153,31 @@ export default {
|
|||
modalName() {
|
||||
return "timeSlots";
|
||||
},
|
||||
selectedTimeSlotId: {
|
||||
get: function () {
|
||||
return this.modelValue?.routeCode;
|
||||
},
|
||||
set: function (newValue) {
|
||||
// Button Question only supports Number, or String data types so we must convert to the full object before emitting
|
||||
this.selectedValue = this.getSelectedTimeSlotObject(newValue);
|
||||
},
|
||||
modal() {
|
||||
return this.$refs[this.modalName];
|
||||
},
|
||||
supplementalInformationBlock() {
|
||||
let appointmentTypeCmsWidgetName;
|
||||
if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
|
||||
return null;
|
||||
} else if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
appointmentTypeCmsWidgetName = this.selectedTimeSlotId?.includes(
|
||||
appointmentTypeCmsWidgetName = this.selectedRouteCode?.includes(
|
||||
PREMIUM_TIME_SLOT_ID_FLAG
|
||||
)
|
||||
? this.mobilePremiumCmsWidgetName
|
||||
: this.mobileCmsWidgetName;
|
||||
} else {
|
||||
if (!this.selectedTimeSlotId) {
|
||||
if (!this.selectedRouteCode) {
|
||||
return null;
|
||||
} else {
|
||||
appointmentTypeCmsWidgetName =
|
||||
this.getRelevantDropOffCmsWidgetNameForSelectedTimeSlot(
|
||||
this.selectedTimeSlotId,
|
||||
this.selectedRouteCode,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return this.getCmsContent(
|
||||
appointmentTypeCmsWidgetName,
|
||||
cmsWidgetFieldMappings.SUPPLEMENTAL_INFORMATION
|
||||
|
|
@ -232,13 +230,13 @@ export default {
|
|||
},
|
||||
disclaimerTextBlockCopy() {
|
||||
if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) {
|
||||
if (this.selectedTimeSlotId?.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
|
||||
if (this.selectedRouteCode?.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
|
||||
if (this.isSameDay) {
|
||||
return this.sameDayDropOffDisclaimerText;
|
||||
} else {
|
||||
return this.dropoffDisclaimerText;
|
||||
}
|
||||
} else if (this.selectedTimeSlotId?.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
|
||||
} else if (this.selectedRouteCode?.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
|
||||
return this.overnightDropOffDisclaimerText;
|
||||
} else {
|
||||
return null;
|
||||
|
|
@ -281,9 +279,9 @@ export default {
|
|||
} else if (this.appointmentType === AppointmentTypeStrings.IN_SHOP) {
|
||||
return this.inshopDurationText;
|
||||
} else {
|
||||
if (this.selectedTimeSlotId?.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
|
||||
if (this.selectedRouteCode?.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
|
||||
return this.overnightDropoffDurationText;
|
||||
} else if (this.selectedTimeSlotId?.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
|
||||
} else if (this.selectedRouteCode?.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
|
||||
if (this.isSameDay) {
|
||||
return this.sameDayDropoffDurationText;
|
||||
} else {
|
||||
|
|
@ -295,21 +293,21 @@ export default {
|
|||
}
|
||||
},
|
||||
isSameDay() {
|
||||
if (!this.dateAndTimeSlotData) {
|
||||
if (!this.timeSlotsForSelectedDate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const selectedDate = this.dateAndTimeSlotData.date;
|
||||
const selectedDate = this.timeSlotsForSelectedDate.date;
|
||||
const todaysDate = new Date().toISOString().split("T")[0];
|
||||
return selectedDate === todaysDate;
|
||||
},
|
||||
dateSelectedReadableDate() {
|
||||
if (!this.dateAndTimeSlotData) {
|
||||
if (!this.timeSlotsForSelectedDate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// This conversion ensures we don't get get GMT induced date changes
|
||||
const dateObject = convertDateStringToDate(this.dateAndTimeSlotData.date);
|
||||
const dateObject = convertDateStringToDate(this.timeSlotsForSelectedDate.date);
|
||||
// Ex: Tuesday, April 22
|
||||
return dateObject.toLocaleDateString("en-us", {
|
||||
weekday: "long",
|
||||
|
|
@ -318,21 +316,20 @@ export default {
|
|||
});
|
||||
},
|
||||
availableTimeSlots() {
|
||||
if (!this.dateAndTimeSlotData) {
|
||||
if (!this.timeSlotsForSelectedDate) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.appointmentType === AppointmentTypeStrings.DROP_OFF) {
|
||||
return this.getAvailableTimeSlotsForDropOff(this.dateAndTimeSlotData.timeSlots);
|
||||
return this.getAvailableTimeSlotsForDropOff(
|
||||
this.timeSlotsForSelectedDate.timeSlots
|
||||
);
|
||||
} else if (this.appointmentType === AppointmentTypeStrings.MOBILE) {
|
||||
return this.getAvailableTimeSlotsForMobile(this.dateAndTimeSlotData.timeSlots);
|
||||
return this.getAvailableTimeSlotsForMobile(this.timeSlotsForSelectedDate.timeSlots);
|
||||
} else {
|
||||
return this.getAvailableTimeSlotsForInshop(this.dateAndTimeSlotData.timeSlots);
|
||||
return this.getAvailableTimeSlotsForInshop(this.timeSlotsForSelectedDate.timeSlots);
|
||||
}
|
||||
},
|
||||
modal() {
|
||||
return this.$refs[this.modalName];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openModal() {
|
||||
|
|
@ -348,16 +345,19 @@ export default {
|
|||
this.$emit("time-slot-modal-closed");
|
||||
},
|
||||
async setSelectedTimeSlot() {
|
||||
this.$emit("update:modelValue", this.selectedValue);
|
||||
this.$emit(
|
||||
"update:modelValue",
|
||||
this.getSelectedTimeSlotInfoObject(this.selectedRouteCode)
|
||||
);
|
||||
this.closeModal();
|
||||
},
|
||||
getRelevantDropOffCmsWidgetNameForSelectedTimeSlot(
|
||||
selectedTimeSlotId,
|
||||
selectedRouteCode,
|
||||
isSameDayRelevant = false
|
||||
) {
|
||||
if (selectedTimeSlotId.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
|
||||
if (selectedRouteCode.includes(RouteCodeFlags.OVERNIGHT_DROP_OFF)) {
|
||||
return this.overnightDropOffCmsWidgetName;
|
||||
} else if (selectedTimeSlotId.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
|
||||
} else if (selectedRouteCode.includes(RouteCodeFlags.ALL_DAY_DROP_OFF)) {
|
||||
return this.isSameDay && isSameDayRelevant
|
||||
? this.sameDayDropOffCmsWidgetName
|
||||
: this.dropoffCmsWidgetName;
|
||||
|
|
@ -416,8 +416,9 @@ export default {
|
|||
getPremiumAppointmentTimeSlot(timeSlotData) {
|
||||
const formattedPrice =
|
||||
"+$" + this.getTotalLineItemPrice(this.premiumAppointmentFee).toFixed(2);
|
||||
|
||||
return {
|
||||
// Unique value is required for each <input> and the premium appoinment shares a timeSlot ID
|
||||
// Unique value is required for each <input> and the premium appoinment shares an id
|
||||
value: this.addPremiumFlagToInput(timeSlotData.id),
|
||||
buttonLabel: this.premiumAppointmentButtonText,
|
||||
buttonLabelSubCopy: formattedPrice,
|
||||
|
|
@ -426,41 +427,68 @@ export default {
|
|||
},
|
||||
};
|
||||
},
|
||||
autoSelectTimeSlotIfOnlyOneIsAvailable(newAvailableTimeSlotsValue) {
|
||||
const numberOfOptions = newAvailableTimeSlotsValue?.length;
|
||||
getSelectedRouteCode() {
|
||||
let selectedRouteCode;
|
||||
if (!this.modelValue?.timeSlot) {
|
||||
selectedRouteCode = null;
|
||||
}
|
||||
|
||||
if (this.modelValue?.isPremiumAppointment) {
|
||||
selectedRouteCode = this.addPremiumFlagToInput(
|
||||
this.modelValue?.timeSlot?.routeCode
|
||||
);
|
||||
} else {
|
||||
selectedRouteCode = this.modelValue?.timeSlot?.routeCode;
|
||||
}
|
||||
|
||||
return selectedRouteCode;
|
||||
},
|
||||
autoSelectTimeSlotIfOnlyOneIsAvailable() {
|
||||
const numberOfOptions = this.availableTimeSlots?.length;
|
||||
if (numberOfOptions === 1) {
|
||||
this.selectedTimeSlotId = newAvailableTimeSlotsValue[0].value;
|
||||
this.selectedRouteCode = this.availableTimeSlots[0].value;
|
||||
}
|
||||
},
|
||||
addPremiumFlagToInput(timeSlotId) {
|
||||
return (timeSlotId += PREMIUM_TIME_SLOT_ID_FLAG);
|
||||
addPremiumFlagToInput(routeCode) {
|
||||
return (routeCode += PREMIUM_TIME_SLOT_ID_FLAG);
|
||||
},
|
||||
removePremiumFlagFromInput(timeSlotId) {
|
||||
return timeSlotId.substring(0, timeSlotId.length - PREMIUM_TIME_SLOT_ID_FLAG.length);
|
||||
removePremiumFlagFromInput(routeCode) {
|
||||
return routeCode.replace(PREMIUM_TIME_SLOT_ID_FLAG, "");
|
||||
},
|
||||
getSelectedTimeSlotObject(timeSlotId) {
|
||||
const timeSlot = this.dateAndTimeSlotData?.timeSlots?.find(
|
||||
(timeSlot) => timeSlot.id == timeSlotId
|
||||
getSelectedTimeSlotInfoObject(routeCode) {
|
||||
const routeCodeIncludesPremium = routeCode?.includes(PREMIUM_TIME_SLOT_ID_FLAG);
|
||||
if (routeCodeIncludesPremium) {
|
||||
routeCode = this.removePremiumFlagFromInput(routeCode);
|
||||
}
|
||||
|
||||
const timeSlot = this.timeSlotsForSelectedDate?.timeSlots?.find(
|
||||
(timeSlot) => timeSlot.id == routeCode
|
||||
);
|
||||
|
||||
if (timeSlot) {
|
||||
return {
|
||||
date: this.dateAndTimeSlotData.date,
|
||||
routeCode: timeSlot.id,
|
||||
startTime: timeSlot.startTime,
|
||||
endTime: timeSlot.endTime,
|
||||
jobMaxMinutes: this.estimatedServiceMinutesMaximum.toString(),
|
||||
jobMinMinutes: this.estimatedServiceMinutesMinimum.toString(),
|
||||
timeSlot: {
|
||||
date: this.timeSlotsForSelectedDate.date,
|
||||
routeCode: timeSlot.id,
|
||||
startTime: timeSlot.startTime,
|
||||
endTime: timeSlot.endTime,
|
||||
jobMaxMinutes: this.estimatedServiceMinutesMaximum.toString(),
|
||||
jobMinMinutes: this.estimatedServiceMinutesMinimum.toString(),
|
||||
},
|
||||
isPremiumAppointment: routeCodeIncludesPremium ? true : false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
routeCode: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
timeSlot: {
|
||||
date: null,
|
||||
startTime: null,
|
||||
endTime: null,
|
||||
routeCode: null,
|
||||
jobMaxMinutes: null,
|
||||
jobMinMinutes: null,
|
||||
},
|
||||
isPremiumAppointment: null,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
|
@ -473,12 +501,10 @@ export default {
|
|||
},
|
||||
selectedDate: {
|
||||
handler() {
|
||||
this.selectedTimeSlotId = null;
|
||||
this.selectedRouteCode = null;
|
||||
this.autoSelectTimeSlotIfOnlyOneIsAvailable();
|
||||
},
|
||||
},
|
||||
availableTimeSlots(newValue) {
|
||||
this.autoSelectTimeSlotIfOnlyOneIsAvailable(newValue);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
modal,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<label
|
||||
for="mobileLocationLinkPromptId"
|
||||
:aria-label="mobileLocationLinkPromptText"
|
||||
class="form-label fw-bold w-100 ps-4 pe-4 pt-4"
|
||||
class="form-label fw-bold w-100 ps-4 pe-4 pt-4 text-black"
|
||||
v-html="mobileLocationLinkPromptText"></label>
|
||||
<div class="update-mobile-location-text-link">
|
||||
<textLink
|
||||
|
|
@ -309,4 +309,7 @@ export default {
|
|||
#mobileLocationLinkPromptId {
|
||||
white-space: pre-line;
|
||||
}
|
||||
.text-black {
|
||||
color: $black;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -430,6 +430,7 @@ export default {
|
|||
.question-text {
|
||||
& > span {
|
||||
text-align: center;
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@
|
|||
<div
|
||||
v-if="!isLoaderDisplayed"
|
||||
class="availability-badge"
|
||||
:class="availabilityRating == 'high' ? 'green' : 'red'"></div>
|
||||
:class="availabilityRating == 'high' ? 'green' : 'orange'"></div>
|
||||
<span v-if="!isLoaderDisplayed" class="m-0 button-auxillary-copy">{{
|
||||
badgeText
|
||||
}}</span>
|
||||
|
|
@ -88,7 +88,7 @@ export default {
|
|||
if (this.availabilityRating == null) {
|
||||
return "gray";
|
||||
} else {
|
||||
return this.availabilityRating == "high" ? "green" : "red";
|
||||
return this.availabilityRating == "high" ? "green" : "orange";
|
||||
}
|
||||
},
|
||||
badgeText() {
|
||||
|
|
@ -187,11 +187,10 @@ export default {
|
|||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 13 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.5 12C3.19159 12 0.5 9.30841 0.5 6C0.5 2.69159 3.19159 0 6.5 0C9.80841 0 12.5 2.69159 12.5 6C12.5 9.30841 9.80841 12 6.5 12ZM6.5 0.785047C3.62449 0.785047 1.28505 3.12449 1.28505 6C1.28505 8.87551 3.62449 11.215 6.5 11.215C9.37551 11.215 11.715 8.87551 11.715 6C11.715 3.12449 9.37551 0.785047 6.5 0.785047Z' fill='%23006A36'/%3E%3Cpath d='M5.697 7.95252C5.5927 7.95252 5.49289 7.91102 5.41999 7.837L3.90597 6.32299C3.75233 6.16934 3.75233 5.92149 3.90597 5.76785C4.05962 5.6142 4.30747 5.6142 4.46111 5.76785L5.69812 7.00373L8.53999 4.16186C8.69364 4.00822 8.94149 4.00822 9.09513 4.16186C9.24878 4.31551 9.24878 4.56336 9.09513 4.717L5.97626 7.83588C5.90224 7.9099 5.80242 7.9514 5.69925 7.9514L5.697 7.95252Z' fill='%23006A36'/%3E%3C/svg%3E%0A");
|
||||
}
|
||||
|
||||
&.red {
|
||||
background-image: url("data:image/svg+xml,%3Csvg width='13' height='12' viewBox='0 0 13 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.5 12C3.19159 12 0.5 9.30841 0.5 6C0.5 2.69159 3.19159 0 6.5 0C9.80841 0 12.5 2.69159 12.5 6C12.5 9.30841 9.80841 12 6.5 12ZM6.5 0.785047C3.62449 0.785047 1.28505 3.12449 1.28505 6C1.28505 8.87551 3.62449 11.215 6.5 11.215C9.37551 11.215 11.715 8.87551 11.715 6C11.715 3.12449 9.37551 0.785047 6.5 0.785047Z' fill='%23006A36'/%3E%3Csvg width='13' height='12' viewBox='0 0 13 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.5 0C9.81368 0 12.5 2.68632 12.5 6C12.5 9.31368 9.81368 12 6.5 12C3.18632 12 0.5 9.31368 0.5 6C0.5 2.68632 3.18632 0 6.5 0ZM6.5 0.84C3.6548 0.84 1.34 3.1548 1.34 6C1.34 8.8452 3.6548 11.16 6.5 11.16C9.3452 11.16 11.66 8.8452 11.66 6C11.66 3.1548 9.3452 0.84 6.5 0.84ZM7.90018 4.00596C8.06422 3.84204 8.33002 3.84192 8.49406 4.00596C8.6581 4.17 8.6581 4.43592 8.49406 4.59996L7.0939 6L8.49406 7.40004C8.6581 7.56408 8.6581 7.83 8.49406 7.99404C8.4121 8.076 8.30458 8.11704 8.19706 8.11704C8.08966 8.11704 7.98214 8.076 7.90018 7.99404L6.50002 6.594L5.09986 7.99404C5.01778 8.076 4.91038 8.11704 4.80286 8.11704C4.69546 8.11704 4.58794 8.076 4.50598 7.99404C4.34182 7.83 4.34182 7.56408 4.50598 7.40004L5.90614 6L4.50598 4.59996C4.34182 4.43592 4.34182 4.17 4.50598 4.00596C4.66978 3.84192 4.93582 3.84204 5.09986 4.00596L6.50002 5.406L7.90018 4.00596Z' fill='%23AC160B'/%3E%3C/svg%3E%3C/svg%3E%0A");
|
||||
&.orange {
|
||||
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 13 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.5 0C9.81368 0 12.5 2.68632 12.5 6C12.5 9.31368 9.81368 12 6.5 12C3.18632 12 0.5 9.31368 0.5 6C0.5 2.68632 3.18632 0 6.5 0ZM6.5 0.84C3.6548 0.84 1.34 3.1548 1.34 6C1.34 8.8452 3.6548 11.16 6.5 11.16C9.3452 11.16 11.66 8.8452 11.66 6C11.66 3.1548 9.3452 0.84 6.5 0.84ZM7.90018 4.00596C8.06422 3.84204 8.33002 3.84192 8.49406 4.00596C8.6581 4.17 8.6581 4.43592 8.49406 4.59996L7.0939 6L8.49406 7.40004C8.6581 7.56408 8.6581 7.83 8.49406 7.99404C8.4121 8.076 8.30458 8.11704 8.19706 8.11704C8.08966 8.11704 7.98214 8.076 7.90018 7.99404L6.50002 6.594L5.09986 7.99404C5.01778 8.076 4.91038 8.11704 4.80286 8.11704C4.69546 8.11704 4.58794 8.076 4.50598 7.99404C4.34182 7.83 4.34182 7.56408 4.50598 7.40004L5.90614 6L4.50598 4.59996C4.34182 4.43592 4.34182 4.17 4.50598 4.00596C4.66978 3.84192 4.93582 3.84204 5.09986 4.00596L6.50002 5.406L7.90018 4.00596Z' fill='%23E86421'/%3E%3C/svg%3E%0A");
|
||||
}
|
||||
}
|
||||
|
||||
.button-auxillary-copy {
|
||||
box-sizing: border-box;
|
||||
justify-content: right;
|
||||
|
|
@ -218,9 +217,9 @@ export default {
|
|||
background-color: $green-100;
|
||||
}
|
||||
|
||||
&.red {
|
||||
color: $red-600;
|
||||
background-color: $red-100;
|
||||
&.orange {
|
||||
color: $orange-600;
|
||||
background-color: $orange-100;
|
||||
}
|
||||
|
||||
&.gray {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
<template>
|
||||
<dropdownQuestion disableAutoFill v-model="selectedValue" :valueAsKey="true" />
|
||||
<dropdownQuestion
|
||||
disableAutoFill
|
||||
v-model="selectedValue"
|
||||
:valueAsKey="true"
|
||||
:disableValidationIfElementDisabled="true" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
|
|||
|
|
@ -112,17 +112,15 @@ export default {
|
|||
selectedMake: this.selectedMakefromStore(),
|
||||
selectedModel: this.selectedModelfromStore(),
|
||||
selectedStyle: this.selectedStylefromStore(),
|
||||
carId: null,
|
||||
category: null,
|
||||
imageUrl: null,
|
||||
imageVifNumber: null,
|
||||
imageVifColor: null,
|
||||
carId: this.getCarIdfromStore(),
|
||||
category: this.getCategoryfromStore(),
|
||||
imageUrl: this.getImagefromStore(),
|
||||
imageVifNumber: this.getImageVifNumberfromStore(),
|
||||
imageVifColor: this.getImageVifColorfromStore(),
|
||||
yearOptions: [],
|
||||
makeOptions: [],
|
||||
modelOptions: [],
|
||||
styleOptions: [],
|
||||
displayGeneric: this.displayGenericFromStore(),
|
||||
unmatchedVehicleIcon: false,
|
||||
};
|
||||
},
|
||||
|
||||
|
|
@ -242,19 +240,24 @@ export default {
|
|||
}
|
||||
} else {
|
||||
this.makeOptions = [];
|
||||
this.selectedMake = null;
|
||||
this.selectedModel = null;
|
||||
this.selectedStyle = null;
|
||||
}
|
||||
},
|
||||
async selectedMake(make) {
|
||||
if (make) {
|
||||
const result = await this.getModelOptions(this.selectedYear, make);
|
||||
this.modelOptions = result?.data;
|
||||
if (this.selectedMakefromStore() !== make) {
|
||||
if (this.modelOptions.length === 1) this.selectedModel = this.modelOptions[0];
|
||||
else this.selectedModel = null;
|
||||
if (this.modelOptions.length === 1) this.selectedModel = this.modelOptions[0];
|
||||
else {
|
||||
this.selectedModel = null;
|
||||
this.selectedStyle = null;
|
||||
}
|
||||
} else {
|
||||
this.modelOptions = [];
|
||||
this.selectedModel = null;
|
||||
this.selectedStyle = null;
|
||||
}
|
||||
},
|
||||
async selectedModel(model) {
|
||||
|
|
@ -265,16 +268,16 @@ export default {
|
|||
model
|
||||
);
|
||||
this.styleOptions = result?.data;
|
||||
if (this.selectedModelfromStore() !== model) {
|
||||
if (this.styleOptions.length === 1) this.selectedStyle = this.styleOptions[0];
|
||||
else this.selectedStyle = null;
|
||||
}
|
||||
if (this.styleOptions.length === 1) this.selectedStyle = this.styleOptions[0];
|
||||
else this.selectedStyle = null;
|
||||
} else {
|
||||
this.styleOptions = [];
|
||||
this.selectedStyle = null;
|
||||
}
|
||||
},
|
||||
async selectedStyle(style) {
|
||||
if (style) {
|
||||
this.carId = null;
|
||||
const result = await this.getVehicle(
|
||||
this.selectedYear,
|
||||
this.selectedMake,
|
||||
|
|
@ -286,17 +289,23 @@ export default {
|
|||
this.imageUrl = result?.data.imageUrl;
|
||||
this.imageVifNumber = result?.data.imageVifNumber;
|
||||
this.imageVifColor = result?.data.imageVifColor;
|
||||
if (this.imageUrl == null) {
|
||||
this.displayGeneric = false;
|
||||
this.unmatchedVehicleIcon = true;
|
||||
} else this.unmatchedVehicleIcon = false;
|
||||
} else {
|
||||
this.imageUrl = null;
|
||||
this.displayGeneric = true;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
computed: {
|
||||
unmatchedVehicleIcon() {
|
||||
if (this.imageUrl == null && this.carId !== null) return true;
|
||||
else return false;
|
||||
},
|
||||
displayGeneric() {
|
||||
if (!this.selectedStyle) return true;
|
||||
else if (this.carId == null) return true;
|
||||
else if (this.imageUrl == null && this.carId !== null) return false;
|
||||
else return true;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getVehicle(year, make, model, style) {
|
||||
return this.dispatchStoreAction(this.storeActions.GET_VEHICLE, {
|
||||
|
|
@ -306,15 +315,6 @@ export default {
|
|||
style: style,
|
||||
});
|
||||
},
|
||||
displayGenericFromStore() {
|
||||
if (store.getters.vehicle.imageUrl !== null) return false;
|
||||
else if (
|
||||
store.getters.vehicle.imageUrl === null &&
|
||||
store.getters.vehicle.carId !== null
|
||||
)
|
||||
return false;
|
||||
else return true;
|
||||
},
|
||||
arePagePrerequisitesValid() {
|
||||
return true;
|
||||
},
|
||||
|
|
@ -377,6 +377,21 @@ export default {
|
|||
model: model,
|
||||
});
|
||||
},
|
||||
getImagefromStore() {
|
||||
return store.getters.vehicle.imageUrl;
|
||||
},
|
||||
getCarIdfromStore() {
|
||||
return store.getters.vehicle.carId;
|
||||
},
|
||||
getCategoryfromStore() {
|
||||
return store.getters.vehicle.category;
|
||||
},
|
||||
getImageVifNumberfromStore() {
|
||||
return store.getters.vehicle.imageVifNumber;
|
||||
},
|
||||
getImageVifColorfromStore() {
|
||||
return store.getters.vehicle.imageVifColor;
|
||||
},
|
||||
},
|
||||
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ const navigationScenarios = {
|
|||
SELECTED_LICENSE_PLATE: "SELECTED_LICENSE_PLATE",
|
||||
SELECTED_HOME_ADDRESS: "SELECTED_HOME_ADDRESS",
|
||||
CLICKED_FORWARD_WITH_NO_QUESTIONS: "CLICKED_FORWARD_WITH_NO_QUESTIONS",
|
||||
CLICKED_VIN_RETRY: "CLICKED_VIN_RETRY",
|
||||
|
||||
// Part selection
|
||||
CLICKED_FORWARD_WITH_PART_QUESTIONS: "CLICKED_FORWARD_WITH_PART_QUESTIONS",
|
||||
|
|
@ -40,12 +41,16 @@ const navigationScenarios = {
|
|||
CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS: "CLICKED_BACK_WITH_VIN_AND_NO_MORE_QUESTIONS",
|
||||
CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS: "CLICKED_BACK_WITH_NO_VIN_NOR_QUESTIONS",
|
||||
|
||||
// Schedule
|
||||
CLICKED_CHANGE_LOCATION: "CLICKED_CHANGE_LOCATION",
|
||||
|
||||
// Review
|
||||
CLICKED_VEHICLE_EDIT: "CLICKED_VEHICLE_EDIT",
|
||||
CLICKED_DAMAGE_EDIT: "CLICKED_DAMAGE_EDIT",
|
||||
CLICKED_SERVICE_PACKAGE_EDIT: "CLICKED_SERVICE_PACKAGE_EDIT",
|
||||
CLICKED_SERVICE_LOCATION_EDIT: "CLICKED_SERVICE_LOCATION_EDIT",
|
||||
CLICKED_SCHEDULE_EDIT: "CLICKED_SCHEDULE_EDIT",
|
||||
CLICKED_CUSTOMER_EDIT: "CLICKED_CUSTOMER_EDIT",
|
||||
};
|
||||
|
||||
export { navigationScenarios };
|
||||
|
|
|
|||
|
|
@ -76,6 +76,10 @@ const routingTable = function (store) {
|
|||
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
|
||||
destinationFmgPageValue: fmgPageValues.QUOTE,
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_VIN_RETRY,
|
||||
destinationFmgPageValue: fmgPageValues.ESTIMATE,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -109,6 +113,10 @@ const routingTable = function (store) {
|
|||
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
|
||||
destinationFmgPageValue: fmgPageValues.QUOTE,
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_VIN_RETRY,
|
||||
destinationFmgPageValue: fmgPageValues.ESTIMATE,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -146,6 +154,10 @@ const routingTable = function (store) {
|
|||
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
|
||||
destinationFmgPageValue: fmgPageValues.QUOTE,
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_VIN_RETRY,
|
||||
destinationFmgPageValue: fmgPageValues.ESTIMATE,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -179,6 +191,10 @@ const routingTable = function (store) {
|
|||
scenario: navigationScenarios.CLICKED_FORWARD_WITH_NO_MORE_QUESTIONS,
|
||||
destinationFmgPageValue: fmgPageValues.QUOTE,
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_VIN_RETRY,
|
||||
destinationFmgPageValue: fmgPageValues.ESTIMATE,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -399,6 +415,10 @@ const routingTable = function (store) {
|
|||
scenario: navigationScenarios.CLICKED_FORWARD,
|
||||
destinationFmgPageValue: fmgPageValues.CUSTOMER_DETAILS,
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_CHANGE_LOCATION,
|
||||
destinationFmgPageValue: fmgPageValues.SERVICE_LOCATION,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -441,6 +461,10 @@ const routingTable = function (store) {
|
|||
scenario: navigationScenarios.CLICKED_SCHEDULE_EDIT,
|
||||
destinationFmgPageValue: fmgPageValues.SCHEDULE,
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_CUSTOMER_EDIT,
|
||||
destinationFmgPageValue: fmgPageValues.CUSTOMER_DETAILS,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -336,8 +336,6 @@ export const mutations = {
|
|||
},
|
||||
resetRegistrationState(state) {
|
||||
state.order.vehicle.registration.licensePlate = null;
|
||||
state.order.customer.firstName = null;
|
||||
state.order.customer.lastName = null;
|
||||
},
|
||||
resetGlassPartsState(state) {
|
||||
state.order.lineItems.glassParts = null;
|
||||
|
|
@ -481,7 +479,7 @@ export const mutations = {
|
|||
state.order.customer.emailAddress = sessionInformation.order.customer.emailAddress;
|
||||
state.order.customer.firstName = sessionInformation.order.customer.firstName;
|
||||
state.order.customer.lastName = sessionInformation.order.customer.lastName;
|
||||
state.order.customer.phoneNumber = sessionInformation.order.customer.servicePhoneNumber;
|
||||
state.order.customer.phoneNumber = sessionInformation.order.customer.phoneNumber;
|
||||
state.order.customer.isSmsOptIn = sessionInformation.order.customer.isSmsOptIn;
|
||||
|
||||
state.order.existingPromoCode = sessionInformation.order.existingPromoCode;
|
||||
|
|
@ -1436,7 +1434,7 @@ export const actions = {
|
|||
firstName: order.customer.firstName,
|
||||
lastName: order.customer.lastName,
|
||||
isSmsOptIn: order.customer.isSmsOptIn,
|
||||
servicePhoneNumber: order.customer.phoneNumber,
|
||||
phoneNumber: order.customer.phoneNumber,
|
||||
},
|
||||
damage: {
|
||||
numberOfChips: damage.numberOfChips,
|
||||
|
|
@ -1675,10 +1673,7 @@ export const actions = {
|
|||
{ isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }
|
||||
) {
|
||||
//Reset dependent state when changing
|
||||
if (
|
||||
registrationInfo?.firstName !== context.state.order.customer?.firstName ||
|
||||
registrationInfo?.lastName !== context.state.order.customer?.lastName
|
||||
) {
|
||||
if (vehicleInfo.vin !== context.state.order.vehicle.vin) {
|
||||
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
||||
|
||||
if (!isSelectedGlassAvailableForVehicle) {
|
||||
|
|
|
|||
|
|
@ -60,6 +60,10 @@ $gray-700: #303333; // Used in theme
|
|||
$gray-800: #222424; // Used in theme
|
||||
$gray-900: #181a1a; // Used in theme
|
||||
|
||||
//orange
|
||||
$orange-600: #e86421;
|
||||
$orange-100: #fff5eb;
|
||||
|
||||
// Miscellaneous Colors
|
||||
$indigo: #6610f2;
|
||||
$purple: #6f42c1;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<template>
|
||||
<loadingModal v-if="useLoadingModal" notFullScreen ref="loadingModal" />
|
||||
<a v-if="linkType === 'navigation'" @click="handleClick" class="navigation-link" :href="href"
|
||||
>{{ text }}<slot name="after-text"></slot
|
||||
></a>
|
||||
|
|
@ -19,6 +20,8 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import loadingModal from "@/fmg-components/loading-modal/loading-modal";
|
||||
|
||||
export default {
|
||||
name: "textLink",
|
||||
props: {
|
||||
|
|
@ -27,6 +30,10 @@ export default {
|
|||
href: {
|
||||
type: String,
|
||||
},
|
||||
useLoadingModal: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleClick(event) {
|
||||
|
|
@ -36,9 +43,17 @@ export default {
|
|||
this.text,
|
||||
true
|
||||
);
|
||||
|
||||
if (this.useLoadingModal) {
|
||||
this.$refs.loadingModal.showModal();
|
||||
}
|
||||
|
||||
this.$emit("click-event");
|
||||
},
|
||||
},
|
||||
components: {
|
||||
loadingModal,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue