Merge remote-tracking branch 'origin/develop' into feature/unit-tests-2022.06.09-KO
This commit is contained in:
commit
306499cf95
17 changed files with 282 additions and 116 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
|
||||
|
|
@ -24,7 +24,6 @@ module.exports = {
|
|||
"!src/layouts/address-lookup/customer-questions/customer-questions.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",
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@
|
|||
<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
|
||||
|
|
@ -45,6 +45,9 @@ export default {
|
|||
myModalEl.addEventListener('show.bs.modal', function (event) {
|
||||
self.currentFooterAndHeaderHeight = self.getFooterInfoBoxHeight() + 72;
|
||||
self.isActive = true;
|
||||
document.querySelector('.page-container-grouped-styles').scrollTo({
|
||||
top: 0, behavior: 'smooth'
|
||||
});
|
||||
})
|
||||
},
|
||||
components: {
|
||||
|
|
@ -71,6 +74,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,
|
||||
|
|
@ -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 !== "");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -331,6 +331,11 @@ function setupMocks({
|
|||
// lookupVin: jest.fn(() => Promise.resolve(lookupVinResponse)),
|
||||
|
||||
},
|
||||
computed: {
|
||||
dynamicStrings() {
|
||||
return {ROUTER_LINK: "routerLink:"}
|
||||
}
|
||||
}
|
||||
}
|
||||
// mountOptions.propsData = {
|
||||
// modelValue: modelValueProp,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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