merged from release/2022.06.09
This commit is contained in:
commit
be09c6342d
22 changed files with 733 additions and 293 deletions
|
|
@ -133,7 +133,7 @@ stages:
|
|||
|
||||
# Prod Build/Deploy
|
||||
- stage: Prod
|
||||
condition: eq(variables['Build.SourceBranch'], variables['prod-branch'] )
|
||||
condition: succeeded('Qa')
|
||||
variables:
|
||||
- group: FixMyGlassProd
|
||||
jobs:
|
||||
|
|
@ -172,10 +172,7 @@ stages:
|
|||
__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__: $(__VUE_APP_GOOGLE_TAG_MANAGER_SCRIPT_BODY__)
|
||||
__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__: $(__VUE_APP_GOOGLE_TAG_MANAGER_NOSCRIPT_FRAME_SRC__)
|
||||
cfDistributionId: $(cfDistributionId)
|
||||
- stage: AutoTagReleaseBuild
|
||||
displayName: Auto Tag Release Build For Production
|
||||
jobs:
|
||||
- template: templates/digital/auto-tag.yml@AzureDevOps
|
||||
parameters:
|
||||
userName: SafeliteAzureDevops
|
||||
userEmail: githubazuredevops@safelite.com
|
||||
- template: templates/digital/auto-tag.yml@AzureDevOps
|
||||
parameters:
|
||||
userName: SafeliteAzureDevops
|
||||
userEmail: githubazuredevops@safelite.com
|
||||
|
|
@ -25,7 +25,6 @@ module.exports = {
|
|||
"!src/layouts/address-vehicles/address-vehicles-question/address-vehicles-question.vue",
|
||||
"!src/ux-components/alert\alert.vue",
|
||||
"!src/helpers/validation-rules.js",
|
||||
"!src/common-components/menu-modal/menu-modal.vue",
|
||||
// END
|
||||
], // ! means exclude from coverage.
|
||||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<template>
|
||||
<div class="row" :style="`padding-bottom: ${paddingHeight}px`"></div>
|
||||
<footer class="footer container-fluid fixed-bottom g-5 bg-light py-4" id="infoBox">
|
||||
<div class="row d-flex flex-row-reverse align-items-center vw-100">
|
||||
<div class="col button-col d-flex" id="stacked">
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
import alert from "@/ux-components/alert/alert";
|
||||
import eventBus from "@/helpers/event-bus/event-bus";
|
||||
import { globalEvents } from "@/constants/events";
|
||||
import menuModal from "@/common-components/menu-modal/menu-modal";
|
||||
import menuModal from "@/common-components/funnel-header/menu-modal/menu-modal";
|
||||
|
||||
export default {
|
||||
name: "funnel-header",
|
||||
|
|
|
|||
|
|
@ -7,16 +7,16 @@
|
|||
</button>
|
||||
</div>
|
||||
<!-- Modal -->
|
||||
<div class="modal fade" id="footerModal" tabindex="-1" aria-labelledby="footerModalLabel" aria-hidden="true" :style="`height: calc(100% - ${currentFooterAndHeaderHeight}px);`">
|
||||
<div class="modal fade" id="footerModal" tabindex="-1" aria-labelledby="footerModalLabel" aria-hidden="true" v-on="{ 'show.bs.modal' : show, 'hide.bs.modal' : hide }" :style="`height: calc(100% - ${currentFooterAndHeaderHeight}px);`">
|
||||
<div class="modal-dialog modal-fullscreen">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header visually-hidden">
|
||||
<h5 class="modal-title" id="footerModalLabel">Footer Navigation</h5>
|
||||
</div>
|
||||
<div class="modal-body d-flex flex-column">
|
||||
<textLink linkType="navigation" text="Terms of use" href="https://www.safelite.com/terms-of-use" />
|
||||
<textLink linkType="navigation" text="Privacy policy" href="https://www.safelite.com/safelite-group-privacy-policy" />
|
||||
<textLink linkType="navigation" text="Do not sell my information" href="https://privacyportal-cdn.onetrust.com/dsarwebform/d3b95a93-e22e-4d4d-a806-482052406557/9e371601-eae3-4338-9430-b90b9036022b.html" />
|
||||
<textLink linkType="navigation" text="Terms of use" href="https://www.safelite.com/terms-of-use" target="_blank" />
|
||||
<textLink linkType="navigation" text="Privacy policy" href="https://www.safelite.com/safelite-group-privacy-policy" target="_blank" />
|
||||
<textLink linkType="navigation" text="Do not sell my information" href="https://privacyportal-cdn.onetrust.com/dsarwebform/d3b95a93-e22e-4d4d-a806-482052406557/9e371601-eae3-4338-9430-b90b9036022b.html" target="_blank" />
|
||||
</div>
|
||||
<div class="modal-footer d-flex justify-content-start">
|
||||
© {{new Date().getFullYear()}} Safelite Group
|
||||
|
|
@ -36,16 +36,18 @@ export default {
|
|||
currentFooterAndHeaderHeight: 0,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
const myModalEl = document.getElementById('footerModal');
|
||||
var self = this;
|
||||
myModalEl.addEventListener('hide.bs.modal', function (event) {
|
||||
methods: {
|
||||
show() {
|
||||
this.currentFooterAndHeaderHeight = this.getFooterInfoBoxHeight() + 72;
|
||||
this.isActive = true;
|
||||
document.querySelector('.page-container-grouped-styles').scrollTo({
|
||||
top: 0, behavior: 'smooth'
|
||||
});
|
||||
},
|
||||
hide() {
|
||||
var self = this;
|
||||
self.isActive = false;
|
||||
});
|
||||
myModalEl.addEventListener('show.bs.modal', function (event) {
|
||||
self.currentFooterAndHeaderHeight = self.getFooterInfoBoxHeight() + 72;
|
||||
self.isActive = true;
|
||||
})
|
||||
},
|
||||
},
|
||||
components: {
|
||||
textLink,
|
||||
|
|
@ -71,6 +73,7 @@ export default {
|
|||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 0;//Required to prevent 'squish' on iPhone
|
||||
z-index: 1056;
|
||||
.bar1,
|
||||
.bar2,
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
<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-2">
|
||||
<div class="col text-center tagbg mb-4">
|
||||
<div class="spinner-border text-danger" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
|
|
@ -12,17 +12,7 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="text-container slide">
|
||||
<p>Finishing up
|
||||
<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>Generating your quote
|
||||
<p>Assessing your damage
|
||||
<span class="dot-1">.</span>
|
||||
<span class="dot-2">.</span>
|
||||
<span class="dot-3">.</span>
|
||||
|
|
@ -32,7 +22,17 @@
|
|||
<span class="dot-2">.</span>
|
||||
<span class="dot-3">.</span>
|
||||
</p>
|
||||
<p>Assessing your damage
|
||||
<p>Generating your quote
|
||||
<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>
|
||||
|
|
@ -130,39 +130,39 @@ export default {
|
|||
|
||||
p {
|
||||
margin: 0 175px;
|
||||
transform: translateX(-2346px);
|
||||
transform: translateX(180px);
|
||||
}
|
||||
|
||||
@keyframes slide-in {
|
||||
0% {
|
||||
transform: translateX(-2346px)
|
||||
transform: translateX(180px)
|
||||
}
|
||||
11% {
|
||||
transform: translateX(-2010px)
|
||||
transform: translateX(-80px)
|
||||
}
|
||||
22% {
|
||||
transform: translateX(-2010px)
|
||||
transform: translateX(-80px)
|
||||
}
|
||||
33% {
|
||||
transform: translateX(-1490px)
|
||||
transform: translateX(-600px)
|
||||
}
|
||||
44% {
|
||||
transform: translateX(-1490px)
|
||||
transform: translateX(-600px)
|
||||
}
|
||||
55% {
|
||||
transform: translateX(-985px)
|
||||
transform: translateX(-1110px)
|
||||
}
|
||||
66% {
|
||||
transform: translateX(-985px)
|
||||
transform: translateX(-1110px)
|
||||
}
|
||||
77% {
|
||||
transform: translateX(-490px)
|
||||
transform: translateX(-1600px)
|
||||
}
|
||||
88% {
|
||||
transform: translateX(-490px)
|
||||
transform: translateX(-1600px)
|
||||
}
|
||||
100% {
|
||||
transform: translateX(-40px)
|
||||
transform: translateX(-2050px)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
const dynamicStrings = {
|
||||
GLOBAL_STATE: "globalState",
|
||||
CUSTOM: "custom",
|
||||
ROUTER_LINK: "routerLink"
|
||||
ROUTER_LINK: "routerLink:"
|
||||
};
|
||||
|
||||
export { dynamicStrings };
|
||||
|
|
@ -120,4 +120,37 @@ function processWidgetItemForReplacement(widgetModel, key) {
|
|||
|
||||
// If we have something else like a number, boolean, etc. just return it
|
||||
return widgetModel[key];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function doesCopyContainRouterLink(copy) {
|
||||
return copy.includes(this.dynamicStrings.ROUTER_LINK);
|
||||
}
|
||||
|
||||
export function splitCopyOnCMSPlaceHolder(copy){
|
||||
// splits copy on { ... } such as {routerlink: ...}
|
||||
return copy.split(/{(.*?)}/g);
|
||||
}
|
||||
|
||||
export function getRouterLinkRouteFromCopy(copy){
|
||||
// sample input: {routerLink:estimate,provide your VIN}
|
||||
// first split would return 'estimate,provide your VIN'
|
||||
// second split would return 'estimate'
|
||||
return copy.split(':')[1].split(',')[0];
|
||||
}
|
||||
|
||||
export function getRouterLinkDisplayTextFromCopy(copy){
|
||||
// sample input: {routerLink:estimate,provide your VIN}
|
||||
// first split would return 'estimate,provide your VIN'
|
||||
// second split would return 'provide your VIN'
|
||||
return copy.split(':')[1].split(',')[1];
|
||||
}
|
||||
|
||||
// Copy returned from the CMS that has newlines will return blocks wrapped in
|
||||
// <p ... >...</p>
|
||||
// This function returns an array of each paragraph, works with or without html
|
||||
// attributes present
|
||||
export function splitCMSCopyOnParagraphTag(copy) {
|
||||
// filter removes empty strings that are a result of string.split with regex
|
||||
return copy.split(/(?:<p(?:.*?)>)|(?:<\/p>)/g).filter(paragraph => paragraph !== "");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -329,8 +329,8 @@ export default {
|
|||
{
|
||||
licenseLastName: lastName,
|
||||
licenseStreetAddress: streetAddress,
|
||||
licenseZip: zip,
|
||||
licenseState: state
|
||||
licenseZip: zip,
|
||||
licenseState: state,
|
||||
}, false
|
||||
);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import addressVehiclesQuestion from "@/layouts/address-vehicles/address-vehicles-question/address-vehicles-question";
|
||||
|
||||
|
||||
describe("addressVehiclesQuestion.vue", () => {
|
||||
|
||||
it("Should return content for differentVehicleAlertHeader", () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(addressVehiclesQuestion, {
|
||||
mixins: [mockMixin],
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.differentVehicleAlertHeader).toEqual('FoundWindshieldTestReturn');
|
||||
});
|
||||
|
||||
it("Should return content for differentVehicleAlertBody", () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(addressVehiclesQuestion, {
|
||||
mixins: [mockMixin],
|
||||
propsData: {
|
||||
vehicles: ["1", "2"],
|
||||
modelValue: ["1", "2"],
|
||||
}
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.differentVehicleAlertBody).toEqual('FoundWindshieldTestReturn');
|
||||
});
|
||||
|
||||
it("Should emit a modelValue change when setting selectedVehicleVin", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(addressVehiclesQuestion, {
|
||||
mixins: [mockMixin],
|
||||
propsData: {
|
||||
vehicles: ["1", "2"],
|
||||
modelValue: ["1", "2"],
|
||||
}
|
||||
});
|
||||
|
||||
// Act
|
||||
const localThis = { $emit: jest.fn() }
|
||||
addressVehiclesQuestion.computed.selectedVehicleVin.set.call(localThis, 'newValue');
|
||||
|
||||
// Assert
|
||||
expect(localThis.$emit).toBeCalledWith("update:modelValue", "newValue");
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn((contentName) => {
|
||||
if (contentName === "FoundWindshield") {
|
||||
return 'FoundWindshieldTestReturn';
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
vehicles: jest.fn(() => {
|
||||
return [{ vehicle: "test" }];
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import addressVehiclesQuestion from "@/layouts/address-vehicles/address-vehicles-question/address-vehicles-question";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
// import store from "@/store";
|
||||
|
||||
// jest.mock("@/store", () => { return {}; }, { virtual: true });
|
||||
|
||||
describe("addressVehiclesQuestion.vue", () => {
|
||||
|
||||
it("Should include button-question component", () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(addressVehiclesQuestion, {
|
||||
propsData: {
|
||||
// vehicles: [
|
||||
// {
|
||||
// "Name": "testname",
|
||||
// "Text": "testtext",
|
||||
// }
|
||||
// ]
|
||||
}
|
||||
});
|
||||
|
||||
// console.log('wrapper.html: ', wrapper.html());
|
||||
|
||||
// Assert
|
||||
const buttonQuestion = wrapper.find('button-question-stub');
|
||||
expect(buttonQuestion).toBe;
|
||||
});
|
||||
|
||||
it("on initialize should pass in questionText", () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(addressVehiclesQuestion, {
|
||||
propsData: {},
|
||||
});
|
||||
|
||||
//Act
|
||||
addressVehiclesQuestion.methods.initializeComponent.call(wrapper.vm, {QuestionText: 'Testing question text'});
|
||||
|
||||
// console.log('wrapper.html: ', wrapper.html());
|
||||
// console.log('wrapper.vm.questionText: ', wrapper.vm.questionText);
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.questionText).toEqual('Testing question text');
|
||||
});
|
||||
|
||||
// it("Alert should show if prop isCarIdDifferent is true", () => {
|
||||
// // Arrange
|
||||
// const wrapper = shallowMount(addressVehiclesQuestion, setupMountOptions({
|
||||
// propsData: {
|
||||
// isCarIdDifferent: true,
|
||||
// }
|
||||
// }));
|
||||
|
||||
// //Act
|
||||
// const alert = wrapper.find('alert');
|
||||
// console.log('wrapper.html: ', wrapper.html());
|
||||
// console.log('wrapper.vm.questionText: ', wrapper.vm.questionText);
|
||||
|
||||
// // Assert
|
||||
// // expect(wrapper.vm.questionText).toEqual('Testing question text');
|
||||
// });
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
function setupMountOptions(mountOptionsMockData = {}) {
|
||||
// //Mock store
|
||||
// store.dispatch = jest.fn(() => {});
|
||||
// store.getters = {};
|
||||
|
||||
// const mockMixin = {
|
||||
// methods: {
|
||||
// getCmsContent: jest.fn().mockImplementation(() => {
|
||||
// return '';
|
||||
// }),
|
||||
// getDamageString: jest.fn().mockImplementation(() => {
|
||||
// return '';
|
||||
// })
|
||||
// },
|
||||
// store: {
|
||||
// dispatch: store.dispatch,
|
||||
// getters: store.getters,
|
||||
// },
|
||||
// }
|
||||
|
||||
const mockGetCmsContent = jest.fn();
|
||||
mockGetCmsContent((cmsWidget, field) => {
|
||||
return field
|
||||
});
|
||||
const defaultMountOptions = {
|
||||
// route: { query: { fmgPage: 'page-name' } },
|
||||
mixins: {
|
||||
methods: {
|
||||
getCmsContent: mockGetCmsContent,
|
||||
},
|
||||
},
|
||||
global: {
|
||||
mocks: {
|
||||
store: {
|
||||
dispatch: store.dispatch,
|
||||
getters: store.getters,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
const baseMountOptions = getMountOptions(Object.assign(defaultMountOptions, mountOptionsMockData));
|
||||
const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions);
|
||||
|
||||
console.log('what are allMountOptions??? ', allMountOptions);
|
||||
|
||||
return allMountOptions;
|
||||
}
|
||||
354
src/layouts/address-vehicles/address-vehicles.spec.js
Normal file
354
src/layouts/address-vehicles/address-vehicles.spec.js
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
// Components
|
||||
import addressVehicles from "@/layouts/address-vehicles/address-vehicles";
|
||||
|
||||
// Supporting Files
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import store from "@/store";
|
||||
import * as navigateToHeritage from "@/helpers/heritage-integration/navigation-helper";
|
||||
|
||||
|
||||
// Mock our module for promises.
|
||||
jest.mock("@/helpers/damage-helper", () => ({
|
||||
isGlassAvailableForCarId: () => {
|
||||
return false;
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
describe("addressVehicles.vue", () => {
|
||||
|
||||
test("Should return true for valid page requisites if carId / zipCode / emailAddress / pageData exists", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.$router.navigate = jest.fn();
|
||||
|
||||
// Act
|
||||
const result = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(true);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test("Should return false for valid page requisites if carId is missing", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.$router.navigate = jest.fn();
|
||||
|
||||
// Act
|
||||
wrapper.vm.$store.getters.order.vehicle.carId = null;
|
||||
const result = wrapper.vm.arePagePrerequisitesValid();
|
||||
|
||||
//Assert
|
||||
expect(result).toBe(false);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
|
||||
// NOTE: this test is only here to meet code coverage; it does not test any logic in the original function
|
||||
test("Should navigate to CLICKED_BACK if backButtonAction is run", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.$router.navigate = jest.fn();
|
||||
|
||||
// Act
|
||||
wrapper.setData({
|
||||
selectedVehicleVin: ['5NMS3CADXLH233004'],
|
||||
});
|
||||
wrapper.vm.backButtonAction();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.$router.navigate).toBeCalled();
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
// NOTE: this test is only here to meet code coverage; it does not test any logic in the original function
|
||||
test("Should run several related methods if forwardButtonAction is run", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
const lookupVinResponse = {
|
||||
data: {
|
||||
carId: "456"
|
||||
}
|
||||
}
|
||||
|
||||
// the following has to be set BEFORE changing the data which is being watched, and requires updateButtonText to be mocked
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
||||
wrapper.vm.lookupVin = jest.fn(() => Promise.resolve(lookupVinResponse));
|
||||
wrapper.vm.$router.navigateAfterSave = jest.fn();
|
||||
wrapper.vm.updateCustomerInfo = jest.fn().mockImplementation(()=> {});
|
||||
wrapper.vm.navigateForward = jest.fn().mockImplementation(()=> {});
|
||||
|
||||
// Act
|
||||
wrapper.setData({
|
||||
selectedVehicleVin: ['5NMS3CADXLH233004'],
|
||||
});
|
||||
|
||||
await wrapper.vm.forwardButtonAction();
|
||||
wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.updateCustomerInfo).toBeCalled();
|
||||
expect(wrapper.vm.navigateForward).toBeCalled();
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test("Should return out of forwardButtonAction if lookupVin returns with an error", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
const lookupVinResponse = {
|
||||
error: "there is an error"
|
||||
}
|
||||
|
||||
// the following has to be set BEFORE changing the data which is being watched, and requires updateButtonText to be mocked
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
||||
wrapper.vm.lookupVin = jest.fn(() => Promise.reject(lookupVinResponse));
|
||||
wrapper.vm.$router.navigateAfterSave = jest.fn();
|
||||
wrapper.vm.updateCustomerInfo = jest.fn().mockImplementation(()=> {});
|
||||
|
||||
// Act
|
||||
wrapper.setData({
|
||||
selectedVehicleVin: ['5NMS3CADXLH233004'],
|
||||
});
|
||||
await wrapper.vm.forwardButtonAction();
|
||||
wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.forwardButtonAction).toReturn;
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test("Should send dispatch reset if carId is different and selected glass not available for vehicle on updateCustomerInfo", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
const lookupVinResponse = {
|
||||
data: {
|
||||
carId: "456"
|
||||
}
|
||||
}
|
||||
|
||||
// the following has to be set BEFORE changing the data which is being watched, and requires updateButtonText to be mocked
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.lookupVin = jest.fn(() => Promise.resolve(lookupVinResponse));
|
||||
wrapper.vm.$router.navigateAfterSave = jest.fn();
|
||||
|
||||
// Act
|
||||
wrapper.setData({
|
||||
selectedVehicleVin: ['5NMS3CADXLH233004'],
|
||||
isSelectedGlassAvailableForVehicle: false,
|
||||
isCarIdDifferent: true,
|
||||
});
|
||||
await wrapper.vm.updateCustomerInfo(wrapper.vm.selectedVehicle.vin, wrapper.vm.selectedVehicle.vehicle);
|
||||
|
||||
//Assert
|
||||
expect(store.dispatch).toBeCalledWith("resetDamageAndDependencies");
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
// NOTE: this test is only here to meet code coverage; it does not test any logic in the original function
|
||||
test("Should send dispatch store action if lookupVin is called", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
// Act
|
||||
await wrapper.vm.lookupVin('1234567890');
|
||||
|
||||
//Assert
|
||||
expect(store.dispatch).toBeCalledWith("lookupVehicleByVin", {"vin": "1234567890"});
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test("If selectedVehicleVin changes, then should update isCarIdDifferent", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
|
||||
// Act
|
||||
wrapper.setData({
|
||||
selectedVehicleVin: ['5NMS3CADXLH233004'],
|
||||
isCarIdDifferent: false,
|
||||
});
|
||||
await wrapper.vm.resetDependentState();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.isCarIdDifferent).toBe(true);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test("If selectedVehicleVin changes, then text on funnel footer should be updated", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
|
||||
// Act
|
||||
wrapper.setData({
|
||||
selectedVehicleVin: ['5NMS3CADXLH233004'],
|
||||
isCarIdDifferent: false,
|
||||
});
|
||||
await wrapper.vm.resetDependentState();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.$refs.funnelFooter.updateButtonText).toBeCalled();
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test("Should navigate to CLICKED_FORWARD scenario if carId is different and selected glass not available for vehicle on navigateForward", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$router.navigateAfterSave = jest.fn();
|
||||
|
||||
// Act
|
||||
wrapper.setData({
|
||||
selectedVehicleVin: ['5NMS3CADXLH233004'],
|
||||
isSelectedGlassAvailableForVehicle: false,
|
||||
isCarIdDifferent: true,
|
||||
});
|
||||
await wrapper.vm.navigateForward();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.$router.navigateAfterSave).toBeCalledTimes(1);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
test("Should navigate to navigateAfterSaveToHeritageFunnel if carId is not different on navigateForward", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
wrapper.vm.$refs.funnelFooter.updateButtonText = jest.fn();
|
||||
wrapper.vm.$refs.loadingModal.showModal = jest.fn();
|
||||
navigateToHeritage.navigateAfterSaveToHeritageFunnel = jest.fn();
|
||||
|
||||
// Act
|
||||
wrapper.setData({
|
||||
selectedVehicleVin: ['5NMS3CADXLH233004'],
|
||||
isSelectedGlassAvailableForVehicle: true,
|
||||
isCarIdDifferent: false,
|
||||
});
|
||||
await wrapper.vm.navigateForward();
|
||||
|
||||
//Assert
|
||||
expect(navigateToHeritage.navigateAfterSaveToHeritageFunnel).toBeCalledTimes(1);
|
||||
|
||||
wrapper.unmount();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
function setupMocks({
|
||||
// modelValueProp = "1900",
|
||||
cmsQuestionText = "CMS text goes here",
|
||||
// dataFromStoreApi = [],
|
||||
}) {
|
||||
//Mock store
|
||||
store.dispatch = jest.fn(() => {});
|
||||
store.getters = {
|
||||
pageData: jest.fn((pageName) => {
|
||||
// console.log('pageName: ', pageName) // address-vehicles
|
||||
return [
|
||||
{
|
||||
vehicle: {
|
||||
"carId": "CR00069309",
|
||||
"category": "SUV",
|
||||
"year": 2020,
|
||||
"make": "Hyundai",
|
||||
"model": "Santa Fe",
|
||||
"style": "4 door utility",
|
||||
"imageUrl": "https://dbhdyzvm8lm25.cloudfront.net/color_0320_032/MY2020/13769/13769_cc0320_032_WW8.jpg",
|
||||
"imageVifNumber": "13769",
|
||||
"imageVifColor": "white"
|
||||
},
|
||||
vin: "5NMS3CADXLH233004"
|
||||
},
|
||||
];
|
||||
}),
|
||||
order: {
|
||||
vehicle: {
|
||||
carId: "123",
|
||||
},
|
||||
serviceLocation: {
|
||||
zipCode: "12345"
|
||||
},
|
||||
customer: {
|
||||
emailAddress: "qw@er.ty"
|
||||
}
|
||||
},
|
||||
damage: {
|
||||
glassToReplace: "Windshield"
|
||||
},
|
||||
vehicle: {
|
||||
carId: "456",
|
||||
}
|
||||
};
|
||||
|
||||
// baseMixin.methods.dispatchStoreAction = jest.fn();
|
||||
|
||||
|
||||
const mountOptions = getMountOptions({
|
||||
store: {
|
||||
dispatch: store.dispatch,
|
||||
getters: store.getters,
|
||||
},
|
||||
router: {
|
||||
navigate: jest.fn(),
|
||||
},
|
||||
});
|
||||
|
||||
//Mock props
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn((contentName) => {
|
||||
if (contentName === "FoundMultipleVehicles") {
|
||||
return 'FoundMultipleVehiclesTestReturn';
|
||||
}
|
||||
if (contentName === "ProvideVinAlert") {
|
||||
return 'ProvideVinAlertTestReturn';
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
// dispatchStoreAction: jest.fn(() => {
|
||||
// console.log("23424243")
|
||||
// }),
|
||||
// isGlassAvailableForCarId: () => {
|
||||
// console.log("%%%%%%%%%%%%%%%")
|
||||
// return Promise.resolve(true)
|
||||
// }
|
||||
|
||||
// lookupVin: jest.fn(() => Promise.resolve(lookupVinResponse)),
|
||||
|
||||
},
|
||||
computed: {
|
||||
dynamicStrings() {
|
||||
return {ROUTER_LINK: "routerLink:"}
|
||||
}
|
||||
}
|
||||
}
|
||||
// mountOptions.propsData = {
|
||||
// modelValue: modelValueProp,
|
||||
// };
|
||||
|
||||
mountOptions.mixins = [mockMixin];
|
||||
|
||||
const wrapper = shallowMount(addressVehicles, mountOptions);
|
||||
|
||||
//Mock CMS content
|
||||
const cmsContent = {
|
||||
QuestionText: cmsQuestionText,
|
||||
};
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import addressVehicles from "@/layouts/address-vehicles/address-vehicles";
|
||||
|
||||
describe("addressVehicles.vue", () => {
|
||||
|
||||
it("Should include address-vehicles-question component", () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(addressVehicles, {
|
||||
propsData: {
|
||||
vehicles: [
|
||||
{
|
||||
"Name": "testname",
|
||||
"Text": "testtext",
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// console.log('wrapper.html: ', wrapper.html());
|
||||
|
||||
// Assert
|
||||
const addressVehiclesQuestion = wrapper.find('address-vehicles-question-stub');
|
||||
expect(addressVehiclesQuestion).toBe;
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -28,8 +28,8 @@
|
|||
/>
|
||||
<div class="alert-provide-vin my-3" v-if="splitAlertProvideVinBodyForLink.length">
|
||||
<span v-for="copy in splitAlertProvideVinBodyForLink" :key="copy">
|
||||
<span v-if="copy.includes('routerLink:')" class="text-body">
|
||||
<router-link :to="{query: {fmgPage: `${copy.split(':')[1].split(',')[0]}`}, name: 'root'}">{{ copy.split(':')[1].split(',')[1] }}</router-link>
|
||||
<span v-if="doesCopyContainRouterLink(copy)" class="text-body">
|
||||
<router-link :to="{query: {fmgPage: `${getRouterLinkRouteFromCopy(copy)}`}, name: 'root'}">{{ getRouterLinkDisplayTextFromCopy(copy) }}</router-link>
|
||||
</span>
|
||||
<span v-else class="m-0 text-body" v-html="copy"></span>
|
||||
</span>
|
||||
|
|
@ -68,6 +68,10 @@ import { required } from "@/helpers/validation-rules";
|
|||
import { Form, defineRule } from "vee-validate";
|
||||
import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||
import { isGlassAvailableForCarId } from "@/helpers/damage-helper";
|
||||
import { doesCopyContainRouterLink,
|
||||
splitCopyOnCMSPlaceHolder,
|
||||
getRouterLinkRouteFromCopy,
|
||||
getRouterLinkDisplayTextFromCopy, } from "@/helpers/cms-content-helper"
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("vehicle-required", required(errorMessages.VEHICLE_REQUIRED));
|
||||
|
|
@ -116,7 +120,7 @@ export default {
|
|||
},
|
||||
splitAlertProvideVinBodyForLink() {
|
||||
// Splits content when brackets are found in text so that text can be looped through and router-link can be injected when needed
|
||||
return this.AlertProvideVinBody.split(/{(.*?)}/g);
|
||||
return this.splitCopyOnCMSPlaceHolder(this.AlertProvideVinBody);
|
||||
},
|
||||
VehiclesForQuestions() {
|
||||
const vehiclesData = this.VehiclesFromApi;
|
||||
|
|
@ -145,6 +149,10 @@ export default {
|
|||
},
|
||||
},
|
||||
methods: {
|
||||
doesCopyContainRouterLink,
|
||||
splitCopyOnCMSPlaceHolder,
|
||||
getRouterLinkRouteFromCopy,
|
||||
getRouterLinkDisplayTextFromCopy,
|
||||
arePagePrerequisitesValid() {
|
||||
if (
|
||||
store.getters.order.vehicle.carId
|
||||
|
|
@ -162,8 +170,10 @@ export default {
|
|||
async forwardButtonAction() {
|
||||
const vinLookup = await this.lookupVin(this.selectedVehicle.vin).catch(() => {
|
||||
this.$refs.funnelFooter.removeLoader();
|
||||
return;
|
||||
});
|
||||
if (!vinLookup) {
|
||||
return;
|
||||
}
|
||||
this.isSelectedGlassAvailableForVehicle = await isGlassAvailableForCarId(vinLookup.data.carId);
|
||||
this.updateCustomerInfo(this.selectedVehicle.vin, this.selectedVehicle.vehicle);
|
||||
this.navigateForward();
|
||||
|
|
@ -210,7 +220,7 @@ export default {
|
|||
},
|
||||
|
||||
watch: {
|
||||
selectedVehicleVin(vehicleVin) {
|
||||
selectedVehicleVin() {
|
||||
// does this vehicle match the previously selected carId?
|
||||
this.isCarIdDifferent = this.selectedVehicle.vehicle.carId !== store.getters.vehicle.carId;
|
||||
this.$refs.funnelFooter.updateButtonText(`Continue with ${this.selectedVehicle.vehicle.year} ${this.selectedVehicle.vehicle.make} ${this.selectedVehicle.vehicle.model}`);
|
||||
|
|
|
|||
|
|
@ -1089,7 +1089,7 @@
|
|||
import textboxQuestion from "@/common-components/textbox-question/textbox-question";
|
||||
import dropdownQuestion from "@/common-components/dropdown-question/dropdown-question";
|
||||
import vinInformation from "@/layouts/vin-lookup/vin-information/vin-information";
|
||||
import menuModal from "@/common-components/menu-modal/menu-modal";
|
||||
import menuModal from "@/common-components/funnel-header/menu-modal/menu-modal";
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -10,10 +10,6 @@ import baseMixin from "@/mixins/base-mixin";
|
|||
export default {
|
||||
methods: {
|
||||
logPageView(pageEvent) {
|
||||
if (getSessionKeyValue() === 0 || getSessionIdValue() === '00000000-0000-0000-0000-000000000000') {
|
||||
this.initSession();
|
||||
}
|
||||
|
||||
const currentPageName = getPageNameByQueryString();
|
||||
var payload = {
|
||||
userId: getDeviceIdValue(),
|
||||
|
|
@ -29,10 +25,6 @@ export default {
|
|||
},
|
||||
|
||||
logCustomEvent(category, action, label, value) {
|
||||
if (getSessionKeyValue() === 0 || getSessionIdValue() === '00000000-0000-0000-0000-000000000000') {
|
||||
this.initSession();
|
||||
}
|
||||
|
||||
const currentPageName = getPageNameByQueryString();
|
||||
|
||||
var payload = {
|
||||
|
|
@ -50,28 +42,6 @@ export default {
|
|||
baseMixin.methods.dispatchStoreAction(storeActions.LOG_CUSTOM_EVENT, payload, false);
|
||||
},
|
||||
|
||||
async initSession() {
|
||||
const sid = getSessionIdValue();
|
||||
const skey = getSessionKeyValue();
|
||||
var payload = {
|
||||
userId: getDeviceIdValue(),
|
||||
sessionId: sid,
|
||||
userAgent: navigator.userAgent,
|
||||
referrer: document.referrer,
|
||||
};
|
||||
|
||||
const response = await baseMixin.methods.dispatchStoreAction(storeActions.INITIALIZE_SESSION, payload, false);
|
||||
|
||||
if (response.data) {
|
||||
if (response.data.sessionKey && skey === 0) {
|
||||
setCookieProperties({ [cookieNames.SESSION_KEY]: response.data.sessionKey});
|
||||
}
|
||||
if (response.data.sessionId && sid === '00000000-0000-0000-0000-000000000000') {
|
||||
setCookieProperties({ [cookieNames.SESSION_ID]: response.data.sessionId});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
pushEventToGA(category, action, label, pushToLogApp = false) {
|
||||
const currentPageName = getPageNameByQueryString();
|
||||
const eventToBePushed = {
|
||||
|
|
@ -86,7 +56,7 @@ export default {
|
|||
pushToDataLayerIfDefined(eventToBePushed);
|
||||
|
||||
if (pushToLogApp) {
|
||||
this.logCustomEvent(category, action, label, undefined);
|
||||
this.logCustomEvent(category, action, label, undefined);
|
||||
}
|
||||
|
||||
},
|
||||
|
|
@ -136,6 +106,32 @@ export default {
|
|||
return baseMethod.apply(object, arguments);
|
||||
};
|
||||
},
|
||||
|
||||
async initSession() {
|
||||
const sid = getSessionIdValue();
|
||||
const skey = getSessionKeyValue();
|
||||
var payload = {
|
||||
userId: getDeviceIdValue(),
|
||||
sessionId: sid,
|
||||
userAgent: navigator.userAgent,
|
||||
referrer: document.referrer,
|
||||
};
|
||||
|
||||
const response = await baseMixin.methods.dispatchStoreAction(storeActions.INITIALIZE_SESSION, payload, false);
|
||||
|
||||
if (response.data) {
|
||||
if (response.data.sessionKey && skey === 0) {
|
||||
setCookieProperties({ [cookieNames.SESSION_KEY]: response.data.sessionKey});
|
||||
}
|
||||
if (response.data.sessionId && sid === '00000000-0000-0000-0000-000000000000') {
|
||||
setCookieProperties({ [cookieNames.SESSION_ID]: response.data.sessionId});
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
noSession() {
|
||||
return getSessionKeyValue() === 0 || getSessionIdValue() === '00000000-0000-0000-0000-000000000000';
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
analyticsPageEvents() {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { navigationScenarios } from "@/router/router-constants/navigation-scenar
|
|||
import { vehicleCategories } from "@/constants/vehicle-categories.js";
|
||||
import { routerParams } from "@/router/router-constants/router-params";
|
||||
import { queryStrings } from "@/constants/query-strings";
|
||||
import { dynamicStrings } from "@/constants/dynamic-strings";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
|
@ -65,6 +66,9 @@ export default {
|
|||
},
|
||||
queryStrings(){
|
||||
return queryStrings;
|
||||
},
|
||||
dynamicStrings(){
|
||||
return dynamicStrings;
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -40,6 +40,10 @@ const routes = [
|
|||
async beforeEnter(to, from, next) {
|
||||
// If we have no query string, or we don't have the FmgPage query string.
|
||||
try {
|
||||
if (analyticsMixin.methods.noSession()) {
|
||||
await analyticsMixin.methods.initSession();
|
||||
}
|
||||
|
||||
// If the saved session has timed out, clear the session, execute 404 logic.
|
||||
if (getFunnelCookie() !== null && !isSavedSessionStillActive()) {
|
||||
await GoToFunnelStartOn404(next);
|
||||
|
|
|
|||
|
|
@ -53,8 +53,7 @@ body {
|
|||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
max-width: 576px;
|
||||
height: calc(100% - 152px);
|
||||
top: 72px;
|
||||
height: calc(100% - 72px);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,18 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import alert from "./alert";
|
||||
|
||||
describe("alert.vue", () => {
|
||||
|
||||
it("Should add class 'alert-dismissible' if isDismissible is true", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(alert, {
|
||||
const wrapper = shallowMount(alert, setupMocks({
|
||||
propsData: {
|
||||
isDismissible: true
|
||||
isDismissible: true,
|
||||
manualHeadline: 'testHeader',
|
||||
manualCopy: 'testCopy'
|
||||
},
|
||||
computed: {
|
||||
splitAlertCopyForLink: {
|
||||
get() {
|
||||
return "TEST";
|
||||
},
|
||||
}
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
}));
|
||||
|
||||
const wrapperDiv = wrapper.find('div');
|
||||
|
||||
|
|
@ -27,19 +22,13 @@ describe("alert.vue", () => {
|
|||
|
||||
it("Should add specified alert class", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(alert, {
|
||||
const wrapper = shallowMount(alert, setupMocks({
|
||||
propsData: {
|
||||
alertClass: 'warning'
|
||||
alertClass: 'warning',
|
||||
manualHeadline: 'testHeader',
|
||||
manualCopy: 'testCopy'
|
||||
},
|
||||
computed: {
|
||||
splitAlertCopyForLink: {
|
||||
get() {
|
||||
return "TEST";
|
||||
},
|
||||
}
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
}));
|
||||
|
||||
const wrapperDiv = wrapper.find('div');
|
||||
|
||||
|
|
@ -49,24 +38,171 @@ describe("alert.vue", () => {
|
|||
|
||||
it("Should update alert Headline to manualHeadline datam entered and alert copy to manualCopy datam entered when no cmsWidgetName entered", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(alert, {
|
||||
propsData: {
|
||||
manualHeadline: 'testHeader',
|
||||
manualCopy: 'testCopy'
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
});
|
||||
|
||||
const wrapper = shallowMount(alert, setupMocks({}));
|
||||
// Assert
|
||||
expect(wrapper.vm.alertHeadline).toBe("testHeader");
|
||||
expect(wrapper.vm.alertCopy).toBe("testCopy");
|
||||
});
|
||||
|
||||
it("Should container a <router-link> tag if the manualCopy contains a {routerLink: testName, testLink} placeholder", () =>{
|
||||
// Arrange & Act
|
||||
const wrapper = shallowMount(alert, setupMocks({
|
||||
propsData: {
|
||||
manualHeadline: 'testHeader',
|
||||
manualCopy: 'testCopy with a {routerLink: testName, testLink} inside of it'
|
||||
},
|
||||
stubs: ['router-link'],
|
||||
}));
|
||||
// Assert
|
||||
expect(wrapper.find('router-link').exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("Should contain 'n+1' <p> tags if the body copy has 'n' <p> tags", () =>{
|
||||
// Arrange & Act
|
||||
const wrapper = shallowMount(alert, setupMocks({
|
||||
propsData: {
|
||||
manualHeadline: 'testHeader',
|
||||
manualCopy: '<p>testCopy with a {routerLink: testName, testLink} inside of it</p><p>and two paragraphs</p>'
|
||||
},
|
||||
stubs: ['router-link'],
|
||||
}));
|
||||
// Assert
|
||||
expect(wrapper.findAll('p').length === 3).toBe(true);
|
||||
});
|
||||
|
||||
it("Should call scrollIntoView() when the clientBoundingRect is not entirely in the viewport (out of view top)", () => {
|
||||
// Arrange
|
||||
var viewPortHeight = 200;
|
||||
setUpViewPort(viewPortHeight);
|
||||
|
||||
Element.prototype.getBoundingClientRect = jest.fn(()=> {
|
||||
return {top: -100, bottom: 200}
|
||||
});
|
||||
|
||||
var mockScrollIntoView = jest.fn();
|
||||
Element.prototype.scrollIntoView = mockScrollIntoView;
|
||||
|
||||
// Act
|
||||
const wrapper = shallowMount(alert, setupMocks({}));
|
||||
|
||||
// Assert
|
||||
// This is an implementation detail - we just need to test that the final step of snapping
|
||||
// the window to the alert is working. If using a different function to accomplish that
|
||||
// just swap this out with the new function
|
||||
expect(mockScrollIntoView).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Should call scrollIntoView() when the clientBoundingRect is not entirely in the viewport (bottom is hidden behind footer)", () => {
|
||||
// Arrange
|
||||
var viewPortHeight = 240;
|
||||
setUpViewPort(viewPortHeight);
|
||||
|
||||
Element.prototype.getBoundingClientRect = jest.fn(()=> {
|
||||
return {top: 100, bottom: 200}
|
||||
});
|
||||
|
||||
var mockScrollIntoView = jest.fn();
|
||||
Element.prototype.scrollIntoView = mockScrollIntoView;
|
||||
|
||||
// Act
|
||||
const wrapper = shallowMount(alert, setupMocks({}));
|
||||
|
||||
// Assert
|
||||
// This is an implementation detail - we just need to test that the final step of snapping
|
||||
// the window to the alert is working. If using a different function to accomplish that
|
||||
// just swap this out with the new function
|
||||
expect(mockScrollIntoView).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("Should not call scrollIntoView() when the clientBoundingRect is not entirely in the viewport but 'shouldScrollToOnMount' is false", () => {
|
||||
// Arrange
|
||||
var viewPortHeight = 200;
|
||||
setUpViewPort(viewPortHeight);
|
||||
|
||||
Element.prototype.getBoundingClientRect = jest.fn(()=> {
|
||||
return {top: -100, bottom: 200}
|
||||
});
|
||||
|
||||
var mockScrollIntoView = jest.fn();
|
||||
Element.prototype.scrollIntoView = mockScrollIntoView;
|
||||
|
||||
// Act
|
||||
const wrapper = shallowMount(alert, setupMocks({
|
||||
propsData: {
|
||||
shouldScrollToOnMount: false,
|
||||
manualHeadline: 'testHeader',
|
||||
manualCopy: 'testCopy'
|
||||
},
|
||||
}));
|
||||
|
||||
// Assert
|
||||
// This is an implementation detail - we just need to test that the final step of snapping
|
||||
// the window to the alert is working. If using a different function to accomplish that
|
||||
// just swap this out with the new function
|
||||
expect(mockScrollIntoView).not.toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
it("Should not call scrollIntoView() when the clientBoundingRect is entirely in the viewport", () => {
|
||||
// Arrange
|
||||
var viewPortHeight = 500;
|
||||
setUpViewPort(viewPortHeight);
|
||||
|
||||
Element.prototype.getBoundingClientRect = jest.fn(()=> {
|
||||
return {top: 100, bottom: 200}
|
||||
});
|
||||
|
||||
var mockScrollIntoView = jest.fn();
|
||||
Element.prototype.scrollIntoView = mockScrollIntoView;
|
||||
|
||||
// Act
|
||||
const wrapper = shallowMount(alert, setupMocks({}));
|
||||
|
||||
// Assert
|
||||
// This is an implementation detail - we just need to test that the final step of snapping
|
||||
// the window to the alert is working. If using a different function to accomplish that
|
||||
// just swap this out with the new function
|
||||
expect(mockScrollIntoView).not.toHaveBeenCalled();
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent: jest.fn(),
|
||||
getFooterInfoBoxHeight: jest.fn(()=> 80),
|
||||
getFooterInfoBoxHeight: jest.fn(()=> 50),
|
||||
},
|
||||
computed: {
|
||||
dynamicStrings: jest.fn(()=> {
|
||||
return {ROUTER_LINK: 'routerLink:'}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function setUpViewPort(height) {
|
||||
Object.defineProperty(global.window, 'innerHeight', {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
value: height,
|
||||
});
|
||||
|
||||
Object.defineProperty(window.document.documentElement, 'clientHeight', {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
value: height
|
||||
});
|
||||
}
|
||||
|
||||
function setupMocks(mountOptionsMockData = {}) {
|
||||
const defaultMountOptions = {
|
||||
propsData: {
|
||||
manualHeadline: 'testHeader',
|
||||
manualCopy: 'testCopy'
|
||||
},
|
||||
mixins: [mockMixin]
|
||||
};
|
||||
const baseMountOptions = getMountOptions(Object.assign(defaultMountOptions, mountOptionsMockData));
|
||||
const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions);
|
||||
return allMountOptions;
|
||||
}
|
||||
|
|
@ -8,7 +8,7 @@
|
|||
<template v-for="paragraph in splitAlertCopyForParagraphTag" :key="paragraph">
|
||||
<p class="m-0 text-body small" v-if="!doesCopyContainRouterLink(paragraph)" v-html="paragraph"></p>
|
||||
<p class="m-0 text-body small" v-else>
|
||||
<template v-for="copy in splitCopyForRouterLink(paragraph)" :key="copy">
|
||||
<template v-for="copy in splitCopyOnCMSPlaceHolder(paragraph)" :key="copy">
|
||||
<span v-if="!doesCopyContainRouterLink(copy)" v-html="copy"></span>
|
||||
<span v-else>
|
||||
<router-link :to="{query: {fmgPage: `${getRouterLinkRouteFromCopy(copy)}`}, name: 'root'}">{{ getRouterLinkDisplayTextFromCopy(copy) }}</router-link>
|
||||
|
|
@ -36,6 +36,11 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { doesCopyContainRouterLink,
|
||||
splitCopyOnCMSPlaceHolder,
|
||||
getRouterLinkRouteFromCopy,
|
||||
getRouterLinkDisplayTextFromCopy,
|
||||
splitCMSCopyOnParagraphTag } from "@/helpers/cms-content-helper"
|
||||
|
||||
export default {
|
||||
name: "alert",
|
||||
|
|
@ -65,36 +70,19 @@ export default {
|
|||
return this.cmsWidgetName ? this.getCmsContent(this.cmsWidgetName, 'BodyText') : this.manualCopy;
|
||||
},
|
||||
splitAlertCopyForParagraphTag(){
|
||||
// splits the alertCopy on <p ... > (with or without attributes) and </p>
|
||||
// filter removes empty strings that are a result of string.split with regex
|
||||
return this.alertCopy.split(/(?:<p(?:.*?)>)|(?:<\/p>)/g).filter(paragraph => paragraph !== "");
|
||||
return splitCMSCopyOnParagraphTag(this.alertCopy);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
doesCopyContainRouterLink(copy) {
|
||||
return copy.includes('routerLink:');
|
||||
},
|
||||
splitCopyForRouterLink(copy){
|
||||
// splits copy on { ... } such as {routerlink: ...}
|
||||
return copy.split(/{(.*?)}/g);
|
||||
},
|
||||
getRouterLinkRouteFromCopy(copy){
|
||||
// sample input: {routerLink:estimate,provide your VIN}
|
||||
// first split would return 'estimate,provide your VIN'
|
||||
// second split would return 'estimate'
|
||||
return copy.split(':')[1].split(',')[0];
|
||||
},
|
||||
getRouterLinkDisplayTextFromCopy(copy){
|
||||
// sample input: {routerLink:estimate,provide your VIN}
|
||||
// first split would return 'estimate,provide your VIN'
|
||||
// second split would return 'provide your VIN'
|
||||
return copy.split(':')[1].split(',')[1];
|
||||
},
|
||||
doesCopyContainRouterLink,
|
||||
splitCopyOnCMSPlaceHolder,
|
||||
getRouterLinkRouteFromCopy,
|
||||
getRouterLinkDisplayTextFromCopy,
|
||||
ensureAlertIsInViewPort() {
|
||||
if (this.shouldScrollToOnMount && this.$el.style.display != 'none') {
|
||||
var footerHeight = this.getFooterInfoBoxHeight();
|
||||
if (!this.isAlertInViewport(footerHeight)) {
|
||||
this.scrollContainerToAlert(footerHeight);
|
||||
this.$el.scrollIntoView(true); // 'true' attempts to scroll element to top of viewport
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -106,14 +94,6 @@ export default {
|
|||
rect.bottom <= (window.innerHeight - footerHeight || document.documentElement.clientHeight - footerHeight)
|
||||
);
|
||||
},
|
||||
scrollContainerToAlert(footerHeight) {
|
||||
// alert position on page + height of alert + footer height
|
||||
var scrollToHeight = this.$el.scrollHeight + this.$el.offsetHeight + footerHeight;
|
||||
// find the div wrapped by the form element - this is the scrollable container
|
||||
// should be a more future-proof selector in case of CSS class changes
|
||||
var pageContainerScrollable = document.querySelector('form > div');
|
||||
pageContainerScrollable.scrollTo(0, scrollToHeight);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.ensureAlertIsInViewPort();
|
||||
|
|
|
|||
Loading…
Reference in a new issue