CSR-563 | Refactor Alert and Alert tests
This commit is contained in:
parent
3187b2edc7
commit
2e847436c8
6 changed files with 197 additions and 71 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
const dynamicStrings = {
|
const dynamicStrings = {
|
||||||
GLOBAL_STATE: "globalState",
|
GLOBAL_STATE: "globalState",
|
||||||
CUSTOM: "custom",
|
CUSTOM: "custom",
|
||||||
ROUTER_LINK: "routerLink"
|
ROUTER_LINK: "routerLink:"
|
||||||
};
|
};
|
||||||
|
|
||||||
export { dynamicStrings };
|
export { dynamicStrings };
|
||||||
|
|
@ -121,3 +121,36 @@ function processWidgetItemForReplacement(widgetModel, key) {
|
||||||
// If we have something else like a number, boolean, etc. just return it
|
// If we have something else like a number, boolean, etc. just return it
|
||||||
return widgetModel[key];
|
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 !== "");
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,8 +28,8 @@
|
||||||
/>
|
/>
|
||||||
<div class="alert-provide-vin my-3" v-if="splitAlertProvideVinBodyForLink.length">
|
<div class="alert-provide-vin my-3" v-if="splitAlertProvideVinBodyForLink.length">
|
||||||
<span v-for="copy in splitAlertProvideVinBodyForLink" :key="copy">
|
<span v-for="copy in splitAlertProvideVinBodyForLink" :key="copy">
|
||||||
<span v-if="copy.includes('routerLink:')" class="text-body">
|
<span v-if="doesCopyContainRouterLink(copy)" class="text-body">
|
||||||
<router-link :to="{query: {fmgPage: `${copy.split(':')[1].split(',')[0]}`}, name: 'root'}">{{ copy.split(':')[1].split(',')[1] }}</router-link>
|
<router-link :to="{query: {fmgPage: `${getRouterLinkRouteFromCopy(copy)}`}, name: 'root'}">{{ getRouterLinkDisplayTextFromCopy(copy) }}</router-link>
|
||||||
</span>
|
</span>
|
||||||
<span v-else class="m-0 text-body" v-html="copy"></span>
|
<span v-else class="m-0 text-body" v-html="copy"></span>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -68,6 +68,10 @@ import { required } from "@/helpers/validation-rules";
|
||||||
import { Form, defineRule } from "vee-validate";
|
import { Form, defineRule } from "vee-validate";
|
||||||
import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
import { navigateAfterSaveToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
||||||
import { isGlassAvailableForCarId } from "@/helpers/damage-helper";
|
import { isGlassAvailableForCarId } from "@/helpers/damage-helper";
|
||||||
|
import { doesCopyContainRouterLink,
|
||||||
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
getRouterLinkRouteFromCopy,
|
||||||
|
getRouterLinkDisplayTextFromCopy, } from "@/helpers/cms-content-helper"
|
||||||
|
|
||||||
// DEFINE VALIDATION RULES
|
// DEFINE VALIDATION RULES
|
||||||
defineRule("vehicle-required", required(errorMessages.VEHICLE_REQUIRED));
|
defineRule("vehicle-required", required(errorMessages.VEHICLE_REQUIRED));
|
||||||
|
|
@ -116,7 +120,7 @@ export default {
|
||||||
},
|
},
|
||||||
splitAlertProvideVinBodyForLink() {
|
splitAlertProvideVinBodyForLink() {
|
||||||
// Splits content when brackets are found in text so that text can be looped through and router-link can be injected when needed
|
// 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() {
|
VehiclesForQuestions() {
|
||||||
const vehiclesData = this.VehiclesFromApi;
|
const vehiclesData = this.VehiclesFromApi;
|
||||||
|
|
@ -145,6 +149,10 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
doesCopyContainRouterLink,
|
||||||
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
getRouterLinkRouteFromCopy,
|
||||||
|
getRouterLinkDisplayTextFromCopy,
|
||||||
arePagePrerequisitesValid() {
|
arePagePrerequisitesValid() {
|
||||||
if (
|
if (
|
||||||
store.getters.order.vehicle.carId
|
store.getters.order.vehicle.carId
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { navigationScenarios } from "@/router/router-constants/navigation-scenar
|
||||||
import { vehicleCategories } from "@/constants/vehicle-categories.js";
|
import { vehicleCategories } from "@/constants/vehicle-categories.js";
|
||||||
import { routerParams } from "@/router/router-constants/router-params";
|
import { routerParams } from "@/router/router-constants/router-params";
|
||||||
import { queryStrings } from "@/constants/query-strings";
|
import { queryStrings } from "@/constants/query-strings";
|
||||||
|
import { dynamicStrings } from "@/constants/dynamic-strings";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -65,6 +66,9 @@ export default {
|
||||||
},
|
},
|
||||||
queryStrings(){
|
queryStrings(){
|
||||||
return queryStrings;
|
return queryStrings;
|
||||||
|
},
|
||||||
|
dynamicStrings(){
|
||||||
|
return dynamicStrings;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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";
|
import alert from "./alert";
|
||||||
|
|
||||||
describe("alert.vue", () => {
|
describe("alert.vue", () => {
|
||||||
|
|
||||||
it("Should add class 'alert-dismissible' if isDismissible is true", async () => {
|
it("Should add class 'alert-dismissible' if isDismissible is true", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const wrapper = shallowMount(alert, {
|
const wrapper = shallowMount(alert, setupMocks({
|
||||||
propsData: {
|
propsData: {
|
||||||
isDismissible: true
|
isDismissible: true,
|
||||||
|
manualHeadline: 'testHeader',
|
||||||
|
manualCopy: 'testCopy'
|
||||||
},
|
},
|
||||||
computed: {
|
}));
|
||||||
splitAlertCopyForLink: {
|
|
||||||
get() {
|
|
||||||
return "TEST";
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mixins: [mockMixin]
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrapperDiv = wrapper.find('div');
|
const wrapperDiv = wrapper.find('div');
|
||||||
|
|
||||||
|
|
@ -27,19 +22,13 @@ describe("alert.vue", () => {
|
||||||
|
|
||||||
it("Should add specified alert class", async () => {
|
it("Should add specified alert class", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const wrapper = shallowMount(alert, {
|
const wrapper = shallowMount(alert, setupMocks({
|
||||||
propsData: {
|
propsData: {
|
||||||
alertClass: 'warning'
|
alertClass: 'warning',
|
||||||
|
manualHeadline: 'testHeader',
|
||||||
|
manualCopy: 'testCopy'
|
||||||
},
|
},
|
||||||
computed: {
|
}));
|
||||||
splitAlertCopyForLink: {
|
|
||||||
get() {
|
|
||||||
return "TEST";
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mixins: [mockMixin]
|
|
||||||
});
|
|
||||||
|
|
||||||
const wrapperDiv = wrapper.find('div');
|
const wrapperDiv = wrapper.find('div');
|
||||||
|
|
||||||
|
|
@ -49,34 +38,106 @@ describe("alert.vue", () => {
|
||||||
|
|
||||||
it("Should update alert Headline to manualHeadline datam entered and alert copy to manualCopy datam entered when no cmsWidgetName entered", async () => {
|
it("Should update alert Headline to manualHeadline datam entered and alert copy to manualCopy datam entered when no cmsWidgetName entered", async () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const wrapper = shallowMount(alert, {
|
const wrapper = shallowMount(alert, setupMocks({}));
|
||||||
propsData: {
|
|
||||||
manualHeadline: 'testHeader',
|
|
||||||
manualCopy: 'testCopy'
|
|
||||||
},
|
|
||||||
mixins: [mockMixin]
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(wrapper.vm.alertHeadline).toBe("testHeader");
|
expect(wrapper.vm.alertHeadline).toBe("testHeader");
|
||||||
expect(wrapper.vm.alertCopy).toBe("testCopy");
|
expect(wrapper.vm.alertCopy).toBe("testCopy");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should run scrollContainerToAlert function when the clientBoundingRect is not entirely in the viewport", () => {
|
it("Should call scrollIntoView() when the clientBoundingRect is not entirely in the viewport (out of view top)", () => {
|
||||||
// Arrange
|
// Arrange
|
||||||
const wrapper = shallowMount(alert, {
|
var viewPortHeight = 200;
|
||||||
computed: {
|
setUpViewPort(viewPortHeight);
|
||||||
splitAlertCopyForLink: {
|
|
||||||
get() {
|
Element.prototype.getBoundingClientRect = jest.fn(()=> {
|
||||||
return "TEST";
|
return {top: -100, bottom: 200}
|
||||||
},
|
});
|
||||||
}
|
|
||||||
},
|
var mockScrollIntoView = jest.fn();
|
||||||
mixins: [mockMixin]
|
Element.prototype.scrollIntoView = mockScrollIntoView;
|
||||||
});
|
|
||||||
// Act
|
// Act
|
||||||
|
const wrapper = shallowMount(alert, setupMocks({}));
|
||||||
|
|
||||||
// Assert
|
// 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();
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
@ -84,6 +145,38 @@ describe("alert.vue", () => {
|
||||||
const mockMixin = {
|
const mockMixin = {
|
||||||
methods: {
|
methods: {
|
||||||
getCmsContent: jest.fn(),
|
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">
|
<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-if="!doesCopyContainRouterLink(paragraph)" v-html="paragraph"></p>
|
||||||
<p class="m-0 text-body small" v-else>
|
<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-if="!doesCopyContainRouterLink(copy)" v-html="copy"></span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
<router-link :to="{query: {fmgPage: `${getRouterLinkRouteFromCopy(copy)}`}, name: 'root'}">{{ getRouterLinkDisplayTextFromCopy(copy) }}</router-link>
|
<router-link :to="{query: {fmgPage: `${getRouterLinkRouteFromCopy(copy)}`}, name: 'root'}">{{ getRouterLinkDisplayTextFromCopy(copy) }}</router-link>
|
||||||
|
|
@ -36,6 +36,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { doesCopyContainRouterLink,
|
||||||
|
splitCopyOnCMSPlaceHolder,
|
||||||
|
getRouterLinkRouteFromCopy,
|
||||||
|
getRouterLinkDisplayTextFromCopy,
|
||||||
|
splitCMSCopyOnParagraphTag } from "@/helpers/cms-content-helper"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "alert",
|
name: "alert",
|
||||||
|
|
@ -65,31 +70,14 @@ export default {
|
||||||
return this.cmsWidgetName ? this.getCmsContent(this.cmsWidgetName, 'BodyText') : this.manualCopy;
|
return this.cmsWidgetName ? this.getCmsContent(this.cmsWidgetName, 'BodyText') : this.manualCopy;
|
||||||
},
|
},
|
||||||
splitAlertCopyForParagraphTag(){
|
splitAlertCopyForParagraphTag(){
|
||||||
// splits the alertCopy on <p ... > (with or without attributes) and </p>
|
return splitCMSCopyOnParagraphTag(this.alertCopy);
|
||||||
// filter removes empty strings that are a result of string.split with regex
|
|
||||||
return this.alertCopy.split(/(?:<p(?:.*?)>)|(?:<\/p>)/g).filter(paragraph => paragraph !== "");
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
doesCopyContainRouterLink(copy) {
|
doesCopyContainRouterLink,
|
||||||
return copy.includes('routerLink:');
|
splitCopyOnCMSPlaceHolder,
|
||||||
},
|
getRouterLinkRouteFromCopy,
|
||||||
splitCopyForRouterLink(copy){
|
getRouterLinkDisplayTextFromCopy,
|
||||||
// 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];
|
|
||||||
},
|
|
||||||
ensureAlertIsInViewPort() {
|
ensureAlertIsInViewPort() {
|
||||||
if (this.shouldScrollToOnMount && this.$el.style.display != 'none') {
|
if (this.shouldScrollToOnMount && this.$el.style.display != 'none') {
|
||||||
var footerHeight = this.getFooterInfoBoxHeight();
|
var footerHeight = this.getFooterInfoBoxHeight();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue