Before merging from develop

This commit is contained in:
Leah Schumann 2023-03-27 09:47:30 -04:00
parent d704eee8e8
commit 2fd0ce5f21
8 changed files with 379 additions and 55 deletions

View file

@ -79,7 +79,7 @@ const endpoints = {
method: "GET", method: "GET",
}, },
GetServiceabilityDetails: { GetServiceabilityDetails: {
url: "/location/serviceability-details", url: "/location/api/v1/location/serviceability-details",
method: "GET", method: "GET",
}, },
GetSupportingItems: { GetSupportingItems: {

View file

@ -26,7 +26,7 @@
<slot></slot> <slot></slot>
</div> </div>
<div class="modal-footer px-5 py-4"> <div class="modal-footer px-5 py-4">
<buttonMain <modalButtonMain
isPrimary isPrimary
class="w-100" class="w-100"
ref="buttonMain" ref="buttonMain"
@ -41,7 +41,7 @@
</template> </template>
<script> <script>
import buttonMain from "@/ux-components/button-main/button-main"; import modalButtonMain from "@/ux-components/modal-button-main/modal-button-main";
import { Modal } from "bootstrap"; import { Modal } from "bootstrap";
import { useForm, useIsFormTouched, useIsFormDirty, useIsFormValid } from "vee-validate"; import { useForm, useIsFormTouched, useIsFormDirty, useIsFormValid } from "vee-validate";
@ -60,14 +60,14 @@ export default {
setup() { setup() {
const modalId = `modal-${crypto.randomUUID()}`; const modalId = `modal-${crypto.randomUUID()}`;
const form = useForm(); const { validate } = useForm();
const isFormTouched = useIsFormTouched(); const isFormTouched = useIsFormTouched();
const isFormDirty = useIsFormDirty(); const isFormDirty = useIsFormDirty();
const isFormValid = useIsFormValid(); const isFormValid = useIsFormValid();
return { return {
modalId, modalId,
form, validate,
isFormTouched, isFormTouched,
isFormDirty, isFormDirty,
isFormValid, isFormValid,
@ -75,9 +75,9 @@ export default {
}, },
methods: { methods: {
async validateAndEmit() { async validateAndEmit() {
const validationResult = await this.form.validate(); const validationResult = await this.validate();
if (validationResult.valid) { if (validationResult.valid) {
this.$emit("footer-button-event"); this.$emit("footer-button-event");
} else { } else {
this.resetButtonStyle(); this.resetButtonStyle();
} }
@ -110,7 +110,7 @@ export default {
}, },
}, },
components: { components: {
buttonMain, modalButtonMain,
}, },
}; };
</script> </script>

View file

@ -24,7 +24,7 @@
:isGlassServiceableMobile="isGlassServiceableMobile" :isGlassServiceableMobile="isGlassServiceableMobile"
:isRecalibrationServiceableMobile="isRecalibrationServiceableMobile" :isRecalibrationServiceableMobile="isRecalibrationServiceableMobile"
ref="serviceTypeQuestion" ref="serviceTypeQuestion"
groupName="ServiceTypeQuestion" groupName="serviceTypeQuestion"
cmsWidgetName="ServiceTypeQuestionWidget" cmsWidgetName="ServiceTypeQuestionWidget"
validationRules="option-required" /> validationRules="option-required" />
<mobileLocationModalQuestions <mobileLocationModalQuestions
@ -96,11 +96,7 @@ export default {
const serviceZipCode = store.getters.order.serviceLocation.zipCode; const serviceZipCode = store.getters.order.serviceLocation.zipCode;
const getZipCodeData = baseMixin.methods.getZipCodeData(serviceZipCode); const getZipCodeData = baseMixin.methods.getZipCodeData(serviceZipCode);
const lineItems = store.getters.order.lineItems; const serviceabilityDetailsPromise = getServiceabilityDetails(serviceZipCode);
const serviceabilityDetailsPromise = getServiceabilityDetails({
serviceZipCode: serviceZipCode,
lineItems: lineItems,
});
const serviceType = store.getters.damage.isRepair ? "Repair" : "Replace"; const serviceType = store.getters.damage.isRepair ? "Repair" : "Replace";
const parentAccountNumber = store.getters.payment.parentAccountNumber; const parentAccountNumber = store.getters.payment.parentAccountNumber;
@ -256,11 +252,8 @@ export default {
}); });
} }
const lineItems = store.getters.order.lineItems; const serviceZipCode = store.getters.order.serviceLocation.zipCode;
const serviceabilityDetails = await getServiceabilityDetails({ const serviceabilityDetails = await getServiceabilityDetails(serviceZipCode);
serviceZipCode: newValue,
lineItems: lineItems,
});
this.setServiceabilityDetails(serviceabilityDetails.data); this.setServiceabilityDetails(serviceabilityDetails.data);
}, },

View file

@ -55,37 +55,20 @@ export default {
return filteredAnswers; return filteredAnswers;
}, },
serviceability() { serviceability() {
const mobileAvailable = this.isGlassServiceableMobile && this.isRecalibrationServiceableMobile;
const inshopAvailable = this.isGlassServiceableInshop && this.isRecalibrationServiceableInshop;
let result; let result;
if ( if (inshopAvailable && mobileAvailable) {
this.isGlassServiceableMobile &&
this.isRecalibrationServiceableMobile &&
this.isGlassServiceableInshop &&
this.isRecalibrationServiceableInshop
) {
result = "All"; result = "All";
} else if ( } else if (inshopAvailable && !mobileAvailable) {
!this.isGlassServiceableInshop &&
!this.isRecalibrationServiceableInshop &&
this.isGlassServiceableMobile &&
this.isRecalibrationServiceableMobile
) {
result = "MobileOnly";
} else if (
this.isGlassServiceableInshop &&
this.isRecalibrationServiceableInshop &&
!this.isGlassServiceableMobile &&
!this.isRecalibrationServiceableMobile
) {
result = "InshopOnly"; result = "InshopOnly";
} else if ( } else if (!inshopAvailable && mobileAvailable) {
!this.isGlassServiceableInshop && result = "MobileOnly";
!this.isRecalibrationServiceableInshop && } else if (!inshopAvailable && !mobileAvailable) {
!this.isGlassServiceableMobile &&
!this.isRecalibrationServiceableMobile
) {
result = "None"; result = "None";
} }
return result; return result;
}, },
selectedValues: { selectedValues: {
@ -99,11 +82,9 @@ export default {
}, },
watch: { watch: {
serviceability: { serviceability: {
handler(newValue, oldValue) { handler(newValue) {
if (newValue == "MobileOnly") { if (newValue == "MobileOnly") {
this.selectedValues = "Mobile"; this.selectedValues = "Mobile";
} else {
this.selectedValues = null;
} }
}, },
}, },

View file

@ -19,6 +19,7 @@
@footer-button-event="setZipCode"> @footer-button-event="setZipCode">
<serviceZipQuestion <serviceZipQuestion
ref="serviceZipQuestion" ref="serviceZipQuestion"
customInputId="serviceZipCode"
v-model="internalModel.zipCode" v-model="internalModel.zipCode"
v-on="{ 'textboxQuestionEvent.inputIdAssigned': onInputIdAssigned }" v-on="{ 'textboxQuestionEvent.inputIdAssigned': onInputIdAssigned }"
:cmsWidgetName="textboxQuestionWidgetName" /> :cmsWidgetName="textboxQuestionWidgetName" />
@ -49,6 +50,7 @@ export default {
internalModel: this.copyModel(this.modelValue), internalModel: this.copyModel(this.modelValue),
serviceZipCodeTextInputId: "", serviceZipCodeTextInputId: "",
displayInvalidZipAlert: false, displayInvalidZipAlert: false,
}; };
}, },
props: { props: {

View file

@ -926,14 +926,13 @@ export const actions = {
}); });
}, },
getServiceabilityDetails(context, { serviceZipCode, lineItems }) { getServiceabilityDetails(context, { serviceZipCode }) {
return globalMethods.callMockHttpClient({ const lineItems = context.getters.order.lineItems;
const lineItemsToSend = [...lineItems.supportingItems];
const encodedLineItems = encodeURIComponent(JSON.stringify(lineItemsToSend))
return globalMethods.callHttpClient({
method: endpoints.GetServiceabilityDetails.method, method: endpoints.GetServiceabilityDetails.method,
//TODO: Remove Mocky Endpoints endpoint: `${endpoints.GetServiceabilityDetails.url}?zip=${serviceZipCode}&lineItems=${encodedLineItems}`,
//endpoint: "https://run.mocky.io/v3/59e1a644-cf16-4f08-8069-1ab2a1e38f79", // NoShopsAvailable
//endpoint: "https://run.mocky.io/v3/4fe1fb89-dd56-4e4a-9af2-96bd1ab77847", // ForcedInshop
//endpoint: "https://run.mocky.io/v3/e2eaa097-6ea5-4906-af53-901edaa94939", // ForcedMobile
endpoint: "https://run.mocky.io/v3/1811a1fe-12a7-48f3-939e-d10a9b77dd25", // All Options
}); });
}, },
@ -1664,3 +1663,12 @@ function getLineItemQueryStringForPricing(lineItems) {
}) })
.join(""); .join("");
} }
function getLineItemQueryStringForServiceability(lineItems) {
return lineItems
.map((lineItem) => {
let queryStringSnippet = `&lineItems=${lineItem.partNumber}`;
return queryStringSnippet;
})
.join("");
}

View file

@ -0,0 +1,187 @@
import { shallowMount } from "@vue/test-utils";
import buttonMain from "./button-main";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { nextTick } from "vue";
describe("buttonMain.vue", () => {
it("Should return btn-primary class", () => {
// Arrange/Act
const wrapper = shallowMount(
buttonMain,
setupMocks({
props: {
isPrimary: true,
},
})
);
const button = wrapper.find("button");
// Assert
expect(button.attributes("class")).toContain("btn-primary");
});
it("Should return aria-disabled state", () => {
// Arrange/Act
const wrapper = shallowMount(
buttonMain,
setupMocks({
props: {
isDisabled: true,
},
})
);
const button = wrapper.find("button");
// Assert
expect(button.attributes()["aria-disabled"]).toEqual("true");
});
it("Should return loader color", async () => {
// Arrange
const wrapper = shallowMount(
buttonMain,
setupMocks({
props: {
loaderColor: "blue",
loaderEnabled: true,
},
})
);
// Act
wrapper.vm.clicked();
await nextTick();
// Assert
const loader = wrapper.find("loader-stub");
expect(loader.attributes("class")).toContain("blue");
});
it("Should return loader position", async () => {
// Arrange
const wrapper = shallowMount(
buttonMain,
setupMocks({
props: {
loaderPosition: "right",
loaderEnabled: true,
},
})
);
// Act
wrapper.vm.clicked();
await nextTick();
// Assert
const loader = wrapper.find("loader-stub");
expect(loader.attributes("class")).toContain("right");
});
it("Should set 'isLoaderDisplayed' to false when calling 'removeLoader'", async () => {
// Arrange
const wrapper = shallowMount(
buttonMain,
setupMocks({
props: {
loaderPosition: "right",
loaderEnabled: true,
},
})
);
wrapper.setData({
isLoaderDisplayed: true,
});
// Act
wrapper.vm.removeLoader();
await nextTick();
// Assert
const loader = wrapper.find("loader-stub");
expect(wrapper.vm.isLoaderDisplayed).toBe(false);
});
it("Should set 'isLoaderDisplayed' to false when calling 'resetButtonStyle'", async () => {
// Arrange
const wrapper = shallowMount(
buttonMain,
setupMocks({
props: {
loaderPosition: "right",
loaderEnabled: true,
},
})
);
wrapper.setData({
isLoaderDisplayed: true,
});
// Act
wrapper.vm.resetButtonStyle();
await nextTick();
// Assert
expect(wrapper.vm.isLoaderDisplayed).toBe(false);
});
it("Should emit 'click-event' event when clicking if the button is enabled", async () => {
// Arrange
const wrapper = shallowMount(
buttonMain,
setupMocks({
props: {
loaderPosition: "right",
loaderEnabled: true,
isDisabled: false,
},
})
);
const buttonElement = wrapper.find("button");
// Act
buttonElement.trigger("click");
await nextTick();
// Assert
expect(wrapper.emitted("click-event")).toBeTruthy();
});
it("Should not emit 'click-event' event when clicking if the button is disabled", async () => {
// Arrange
const wrapper = shallowMount(
buttonMain,
setupMocks({
props: {
loaderPosition: "right",
loaderEnabled: true,
isDisabled: true,
},
})
);
const buttonElement = wrapper.find("button");
// Act
buttonElement.trigger("click");
await nextTick();
// Assert
expect(wrapper.emitted("click-event")).toBeFalsy();
});
});
function setupMocks(mountOptionsMockData = {}) {
const defaultMountOptions = { route: { query: { fmgPage: "page-name" } } };
const baseMountOptions = getMountOptions(
Object.assign(defaultMountOptions, mountOptionsMockData)
);
const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions);
return allMountOptions;
}

View file

@ -0,0 +1,153 @@
<template>
<button
type="button"
:aria-disabled="isDisabled"
class="btn d-flex align-items-center justify-content-center py-3 px-4 delay"
:class="[
isPrimary ? 'btn-primary' : 'btn-secondary',
isFloat ? 'float-end' : '',
isLoaderDisplayed ? 'has-loader' : '',
]"
@click="clicked">
<span class="m-0">{{ this.buttonText }}</span>
<loader
class="ms-2"
v-if="isLoaderDisplayed && !suppressLoader"
v-bind:class="[this.loaderColor, this.loaderPosition]" />
</button>
</template>
<script>
import loader from "@/ux-components/loader/loader";
export default {
name: "modalButtonMain",
props: {
isPrimary: Boolean,
buttonText: String,
isDisabled: Boolean,
loaderColor: String,
loaderPosition: String,
isFloat: Boolean,
suppressLoader: Boolean,
},
data() {
return {
isLoaderDisplayed: false,
};
},
methods: {
removeLoader() {
this.isLoaderDisplayed = false;
},
clicked() {
this.pushEventToGA(
this.$route.query[this.queryStrings.FMG_PAGE],
this.GaActions.CLICKED,
this.buttonText,
true
);
if (!this.isDisabled) {
this.isLoaderDisplayed = true;
this.$emit("click-event");
}
},
resetButtonStyle() {
this.isLoaderDisplayed = false;
},
},
components: {
loader,
},
};
</script>
<style lang="scss">
.btn {
&.btn-primary {
position: relative;
background: linear-gradient(270deg, $blue 0%, $blue-800 100%);
border: none;
border-radius: $border-radius-lg;
color: $white;
justify-content: center;
font-weight: 500;
@media (hover: hover) {
background: linear-gradient(270deg, $blue 0%, $blue-800 100%);
}
&:focus {
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
}
&:focus, // Mouse, touch, stylus focus
&:focus-visible {
// Keyboard focus for accessibility
outline: none;
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
color: $white;
background: linear-gradient(270deg, rgba(6, 87, 124, 1) 0%, rgba(6, 87, 124, 1) 100%);
}
&:disabled {
background: $gray-200 !important;
background: linear-gradient(270deg, $gray-200 0%, $gray-200 100%) !important;
color: $gray-600 !important;
font-weight: 400;
height: 48px;
border: none;
border-radius: $border-radius-lg;
cursor: pointer;
pointer-events: all;
}
&.has-loader {
color: $white;
background: $blue-700;
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
pointer-events: none;
}
&.delay {
// fixes flicker while transitioning between states
transition: background 0s 0s ease-in-out;
}
}
&.btn-secondary {
position: relative;
background: transparent;
border: 1px solid $blue;
border-radius: $border-radius-lg;
color: $blue;
font-weight: 500;
transition: all 150ms linear;
height: 3rem;
&:hover {
color: $white;
@include blue-gradient;
}
&:focus, // Mouse, touch, stylus focus
&:focus-visible {
// Keyboard focus for accessibility
outline: none;
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $blue-700;
color: $white;
@include blue-gradient;
}
&:disabled {
background: transparent;
color: $gray-550 !important;
font-weight: 400;
height: 48px;
border: 1px solid $gray-550;
border-radius: $border-radius-lg;
cursor: pointer;
pointer-events: all;
}
&.has-loader {
color: $white;
@include blue-gradient;
pointer-events: none;
}
&.delay {
// fixes flicker while transitioning between states
transition: background 0s 0s ease-in-out;
}
}
}
</style>