Revert "Merge pull request #479 from Safelite/defect/digital/ssr-763"

This reverts commit 549f92af09, reversing
changes made to 2c27d0f2d3.
This commit is contained in:
Jeremy Zimmerman 2023-10-13 09:32:59 -04:00
parent 549f92af09
commit 6979446152
10 changed files with 1185 additions and 1867 deletions

View file

@ -12,8 +12,7 @@
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit --coverage --ci",
"test:unit:lite": "vue-cli-service test:unit --ci",
"test:pc": "vue-cli-service test:unit site-footer.spec.js"
"test:unit:lite": "vue-cli-service test:unit --ci"
},
"dependencies": {
"axios": "^1.4.0",

View file

@ -7,15 +7,6 @@
</router-view>
</template>
<script>
// eslint-disable-next-line no-extend-native
Array.prototype.safeFilter = (propertyName) => {
if (!propertyName) return this;
if (!this?.length) return [];
return this.map((x) => x[propertyName])
.filter((x) => x);
};
</script>
<style lang="scss">
@import "./node_modules/bootstrap/scss/bootstrap";
@import "@/styles/common-styles.scss";

View file

@ -462,16 +462,13 @@ export default {
if (direction === 'future') {
// first 0, then 1
for (let i = 0; i <= monthsAfterToLoadOffset; i++) {
months.push(this.getMonthData(i, options));
months.push(await this.getMonthData(i, options));
}
// See https://eslint.org/docs/latest/rules/no-await-in-loop
Promise.all(months);
} else if (direction === 'past') {
// first 0, then -1
for (let i = 0; i >= 0 - monthsBeforeToLoadOffset; i--) {
months.unshift(this.getMonthData(i, options));
months.unshift(await this.getMonthData(i, options));
}
Promise.all(months);
} else {
// TODO - IF A CALENDAR WITH BOTH PAST AND FUTURE WAS EVER NEEDED
// for (let i = monthsAfterToLoadOffset; i >= monthsBeforeToLoadOffset; i--) {

View file

@ -11,7 +11,7 @@
:text="getRouterLinkDisplayTextFromCopy(copy)"
href="javascript:void(0)"
useLoadingModal
@clickEvent="navigateWithScenario(getRouterLinkRouteFromCopy(copy))" />
@click-event="navigateWithScenario(getRouterLinkRouteFromCopy(copy))" />
</span>
<span v-else-if="doesCopyContainTextLink(copy)">
<textLink
@ -19,7 +19,7 @@
:text="getRouterLinkDisplayTextFromCopy(copy)"
:href="getExternalLink(copy)"
:data-bs-target="'#' + getRouterLinkRouteFromCopy(copy)"
@clickEvent="$emit('textLinkClicked', getRouterLinkRouteFromCopy(copy))" />
@click-event="$emit('textLinkClicked', getRouterLinkRouteFromCopy(copy))" />
</span>
<span
v-else
@ -57,7 +57,6 @@ export default {
cmsWidgetName: String,
marginTopSizeOverride: Number // override mt-2 with a bootstrap size from 0-5 or auto
},
emits: ['textLinkClicked'],
computed: {
pageQueryString() {
return applicationConfig.PAGE_QUERYSTRING;

View file

@ -8,7 +8,7 @@ export function getDamageString() {
const { isRepair } = mainStore.damage;
const damageLocations = mainStore.damage.glassToReplace;
if (isRepair || !damageLocations?.length) {
if (isRepair || !damageLocations || !Array.isArray(damageLocations)) {
return '';
}

View file

@ -1,15 +1,6 @@
import { useMainStore } from '@/store';
import { updateOrCreateISSCookie } from '@/helpers/cookie-helper';
/*
Encapsulates asynchronous Save Session logic inside a promise to allow for Save Session queuing
*/
async function saveSessionHelper(store) {
const savedSessionInfo = await store.saveSession();
store.setSaveSessionInfo(savedSessionInfo.data);
updateOrCreateISSCookie();
}
/*
Will call API to save existing order, or create new one depending where it's called from.
This will also set Referral information in the store after saving, and then
@ -17,8 +8,8 @@ async function saveSessionHelper(store) {
*/
export async function saveSession({ shouldAwaitSaveSessionQueue = false }) {
const store = useMainStore();
const saveSessionPromise = store.applicationUser.saveSessionPromise
? store.applicationUser.saveSessionPromise.then(() => saveSessionHelper(store))
var saveSessionPromise = store.applicationUser.saveSessionPromise
? store.applicationUser.saveSessionPromise.then(() => { return saveSessionHelper(store); })
: saveSessionHelper(store);
store.setSaveSessionPromise(saveSessionPromise);
@ -27,3 +18,12 @@ export async function saveSession({ shouldAwaitSaveSessionQueue = false }) {
await saveSessionPromise;
}
}
/*
Encapsulates asynchronous Save Session logic inside a promise to allow for Save Session queuing
*/
async function saveSessionHelper(store) {
const savedSessionInfo = await store.saveSession();
store.setSaveSessionInfo(savedSessionInfo.data);
updateOrCreateISSCookie();
}

View file

@ -10,24 +10,26 @@ const mockMixin = {
};
describe('site-footer.vue', () => {
it('Should emit ForwardClicked on button click', async () => {
// TODO: fix this so that it works correctly (toHaveBeenCalled() <-- )
it.skip('Should emit ForwardClicked on button click', async () => {
// Act
const wrapper = mount(siteFooter, {
mixins: [mockMixin]
});
wrapper.vm.buttonClick();
// Assert
expect(wrapper.emitted().forwardClicked).toBeTruthy(); // This is how to verify emitted custom events.
expect(wrapper.emitted().forwardClicked[0]).toHaveBeenCalled;
});
it('Should emit BackClicked on link click', async () => {
// TODO: fix this so that it works correctly (toHaveBeenCalled() <--
it.skip('Should emit BackClicked on link click', async () => {
// Act
const wrapper = mount(siteFooter, {
mixins: [mockMixin]
});
wrapper.vm.linkClick();
// Assert
expect(wrapper.emitted().backClicked).toBeTruthy(); // This is how to verify emitted custom events.
expect(wrapper.emitted().backClicked[0]).toHaveBeenCalled;
});
it('Should change button text when update button text is called', async () => {

View file

@ -1,385 +0,0 @@
/* eslint-disable no-plusplus */
/* eslint-disable no-param-reassign */
import getDateDifferenceInDays from '@/helpers/date-helper';
if (!Array.prototype.safeFilter) {
// eslint-disable-next-line no-extend-native
Array.prototype.safeFilter = (propertyName) => {
if (!propertyName) return this;
if (!this?.length) return [];
return this.map((x) => x[propertyName])
.filter((x) => x);
};
}
/**
* @function addPricesToLineItems
* @memberof StoreProcessors
* @author T-Wrecks Team
* @copyright Safelite
* @summary
* Accepts an array of items and an array of corresponding prices, then
* adds the price data to the line items by collating on partNumber.
* The added properties are "**laborAmount**", "**sellingPrice**" and "**kitPrice**".
* @remarks
* This will almost certainly change when Insurance Pricing is live.
* @param {Array} lineItems
* An array filled with order items (windshield, wipers, raindefense, etc.)
* @param {Array} pricingLineItems
* An array filled with prices for repair items.
* @returns {Array}
* The line items array with price data added.
* @static
*/
const addPricesToLineItems = (lineItems, pricingLineItems) => {
if (!lineItems) throw new Error('Invalid line items specified.');
if (!pricingLineItems) throw new Error('Invalid pricing line items specified.');
lineItems.forEach((lineItem) => {
const lineItemIndex = pricingLineItems
.findIndex((pricingLineItem) => pricingLineItem.partNumber === lineItem.partNumber);
if (lineItem.childParts) {
addPricesToLineItems(lineItem.childParts, pricingLineItems);
}
if (lineItemIndex > -1) {
const pricedLineItem = pricingLineItems[lineItemIndex];
lineItem.laborAmount = pricedLineItem.laborAmount;
lineItem.sellingPrice = pricedLineItem.sellingPrice;
lineItem.kitPrice = pricedLineItem.kitPrice;
}
});
return lineItems;
};
/**
* @function buildQueryStringParameterFromArrayOfComplexObjects
* @memberof StoreProcessors
* @author T-Wrecks Team
* @copyright Safelite
* @summary
* Accepts an array of objects and the name of a parameter present on these objects.
* Extracts the value of these properties so as to create a query string snippet containing
* this data in the format "propertyName[index].[key]=[value]&..."
* @param {Array} arrayOfObjects
* Array of items containing data to transform into a query string snippet.
* @param {string} parameterName
* The name of the parameter used to extract data from the "arrayOfObjects".
* @returns {string}
* A string containing the query string snippet.
* @static
*/
const buildQueryStringParameterFromArrayOfComplexObjects = (arrayOfObjects, parameterName) => {
if (!arrayOfObjects) throw new Error('Invalid array of objects specified.');
if (!parameterName) throw new Error('Invalid parameter name specified.');
let queryStringParameter = '';
for (let i = 0; i < arrayOfObjects.length; i++) {
// eslint-disable-next-line no-restricted-syntax
for (const [key, value] of Object.entries(arrayOfObjects[i])) {
queryStringParameter += `${parameterName}[${i}].${key}=${value}&`;
}
}
// Remove trailing &
return queryStringParameter.slice(0, -1);
};
/**
* @function convertGlassPieceNamingFromApi
* @memberof StoreProcessors
* @author T-Wrecks Team
* @copyright Safelite
* @summary
* Accepts an array with glass data properties.
* Array is modified such that "glassPiece", "location" and "name" properties are removed (if present) and
* location/name data is moved to properties "**glassLocation**" and "**glassName**".
* @remarks
* This is one of a few functions that deal with glassLocation data.
* Modified to be a bit more fault tolerant/flexible.
* Essentially the reverse of convertGlassPieceToBackEndCompatibleFormat.
* Could potentially refactor all such to be more DRY.
* @param {Array} glassArray
* An array of glass parts with location and name defined.
* @returns {Array}
* A modified glass array with data defined on **glassLocation** and **glassName** properties.
* @static
*/
const convertGlassPieceNamingFromApi = (glassArray) => {
if (!glassArray) return [];
return glassArray.map((glass) => {
glass.glassLocation = glass.glassPiece?.location ?? glass.location ?? glass.glassLocation;
glass.glassName = glass.glassPiece?.name ?? glass.name ?? glass.glassName;
delete glass.glassPiece;
delete glass.location;
delete glass.name;
return glass;
});
};
/**
* @function convertGlassPieceToBackEndCompatibleFormat
* @memberof StoreProcessors
* @author T-Wrecks Team
* @copyright Safelite
* @summary
* Accepts an array with glass data properties.
* Array is modified such that glassLocation/glassName data is moved to properties "**location**" and "**name**".
* @remarks
* This is one of a few functions that deal with glassLocation data.
* Could potentially be combined with very similar convertResultsForApi
* @param {Array} glassPieces
* An array of glass parts with glassLocation and glassName defined.
* @returns {Array}
* A modified glass array with data defined on **location** and **name** properties.
* @static
*/
// There was no meaningful difference between the below and convertGlassPieceNamingForApi
const convertGlassPieceToBackEndCompatibleFormat = (glassPieces) => {
if (!glassPieces?.length) return [];
// check if array already converted. (likely when a session has been saved previously and then reloaded)
if (glassPieces[0].location !== undefined) {
return glassPieces;
}
return glassPieces.map((glassPiece) => ({
location: glassPiece.glassLocation,
name: glassPiece.glassName
}));
};
/**
* @function convertResultsForApi
* @memberof StoreProcessors
* @author T-Wrecks Team
* @copyright Safelite
* @summary
* Accepts a source array and maps to an API friendly format.
* Array with "**glassLocation**" is mapped to "**location**", "**glassName**" is mapped to
* "**name**", and property "result" is simply copied if present.
* @remarks
* This is one of a few functions that deal with glassLocation data.
* This one also copies result data if present.
* Could potentially be refactored with others to be more DRY
* @param {Array} resultsArray
* Array with glassLocation and glassName properties (often result as well).
* @returns {Array}
* Array that only has **location**, **name** and **result** properties
* @static
*/
const convertResultsForApi = (resultsArray) => {
if (!resultsArray) return [];
// check if array already converted.
// (likely when a session has been saved previously and then reloaded)
if (resultsArray[0].location !== undefined) {
return resultsArray;
}
return resultsArray.map((answer) => ({
location: answer.glassLocation,
name: answer.glassName,
result: answer.result
}));
};
/**
* @function getFlattenedArrayOfLineItemsWithChildParts
* @memberof StoreProcessors
* @author T-Wrecks Team
* @copyright Safelite
* @summary
* Accepts a source array of line item data.
* Flattens array in regard to childPart line items.
* @param {Array} lineItems
* Array with line items and potentially childParts to be flattened.
* @returns {Array}
* Flattened line item array.
* @static
*/
const getFlattenedArrayOfLineItemsWithChildParts = (lineItems) => {
let flattenedArray = [];
lineItems?.forEach((lineItem) => {
flattenedArray.push(lineItem);
if (lineItem.childParts) {
flattenedArray = [
...flattenedArray,
...getFlattenedArrayOfLineItemsWithChildParts(lineItem.childParts)
];
}
});
return flattenedArray;
};
/**
* @function getHasRecalibrationPart
* @memberof StoreProcessors
* @author T-Wrecks Team
* @copyright Safelite
* @summary
* Examines the store.order.lineItems state for a requiresRecalibration portion.
* If present, examines the state -> order -> lineItems -> glassParts state for a
* **recalibrationType** section.
* If present, checks that the first element is defined.
* @param {object} state
* The store state having a valid store -> order -> lineItems section.
* @returns {boolean}
* True if recalibration part is present in state, false otherwise.
* @static
*/
const getHasRecalibrationPart = (state) => {
// eslint-disable-next-line max-len
const hasRequiresRecalibration = state.order.lineItems.glassParts.safeFilter('requiresRecalibration')?.length > 0;
// eslint-disable-next-line max-len
const hasRecalibrationType = state.order.lineItems.glassParts.safeFilter('recalibrationType')?.length > 0;
if (hasRequiresRecalibration) {
if (hasRecalibrationType) {
// Has both 'requiresRecalibration' and 'recalibrationType' and 'recalibrationType'
return (
state.order.lineItems.glassParts.safeFilter('recalibrationType')[0].toLowerCase() !== 'unknown'
);
}
// Has 'requiresRecalibration' but no 'recalibrationType' at all
return true;
}
// Does not have 'requiresRecalibration'
return false;
};
/**
* @function getLineItemQueryStringForPricing
* @memberof StoreProcessors
* @summary
* Accepts an array of line items with "partNumber" properties and converts this
* information to a query string snippet acceptable to the pricing endpoint.
* Currently the endpoint requires the inclusion of an index.
* However it appears that the actual index number is irrelevant.
* @remarks
* Please note that if the index number became relevant this method
* would have a BUG in that it potentially calls itself recursively without
* passing the current index. Thus child parts restart the index.
* However, this isn't a huge concern as Insurance Pricing won't be using this
* and it will go away completely.
* @param {Array} lineItems
* An array of line items to be priced.
* @returns {string}
* A query string text snippet containing part number data to be priced.
* @static
*/
const getLineItemQueryStringForPricing = (lineItems) => lineItems
.map((lineItem, index) => {
let queryStringSnippet = `&LineItems[${index}].partNumber=${lineItem.partNumber}`;
if (lineItem.childParts) {
queryStringSnippet += getLineItemQueryStringForPricing(lineItem.childParts);
}
return queryStringSnippet;
})
.join('');
/**
* @function getTimeSlotsAdditionalEventData
* @memberof StoreProcessors
* @summary
* Converts provided parameters into comma delimited key/value pairs specifying data with state magic number keys.
* @param {Array} provisionalTriggers
* Array that is converted to comma delimited data point.
* @param {string} zipCode
* Service location zip code
* @param {string} firstAvailableAppointmentDateString
* The first available appointment date, used to determine
* the number of days until that date.
* @param {string} shopAppointmentType
* Value included in return data points if present.
* @returns {string}
* A string containing comma delimited data points.
* @static
*/
const getTimeSlotsAdditionalEventData = (
provisionalTriggers,
zipCode,
firstAvailableAppointmentDateString,
shopAppointmentType
) => {
let numberOfDays = null;
if (firstAvailableAppointmentDateString) numberOfDays = getDateDifferenceInDays(new Date(), firstAvailableAppointmentDateString);
return (shopAppointmentType)
// eslint-disable-next-line max-len
? `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ShopAppointmentType:${shopAppointmentType},ProvisionalTriggers:${provisionalTriggers.join(',')}`
: `FirstAvailableAppointment:${numberOfDays},Zip:${zipCode},ProvisionalTriggers:${provisionalTriggers.join(',')}`;
};
/**
* @function providersEqual
* @memberof StoreProcessors
* @summary
* Tests equality of addy/number properties on two provider objects.
* @remarks
* TODO: Change back to deepEqual once zipCodeCtu is added to saveSession.
* @param {Array} providerA
* First Provider object to compare for equality
* @param {string} providerB
* Second Provider object to compare for equality
* @returns {boolean}
* True if provider objects have equal addy/number properties, false otherwise.
* @static
*/
const providersEqual = (providerA, providerB) =>
(
providerA.providerNumber === providerB.providerNumber
&& providerA.address?.city === providerB.address?.city
&& providerA.address?.state === providerB.address?.state
&& providerA.address?.streetAddress === providerB.address?.streetAddress
&& providerA.address?.zipCode === providerB.address?.zipCode
);
/**
* @function provisionalTriggersToString
* @memberof StoreProcessors
* @summary
* Converts provided array to comma delimited string with key prepended.
* @remarks
* @param {Array} provisionalTriggers
* Array of items to include in result.
* @returns {string}
* Returns string with key name and parameter array converted to comma delimited string.
* @static
*/
const provisionalTriggersToString = (provisionalTriggers) => `ProvisionalTriggers:${provisionalTriggers.join(',')}`;
/**
* @module StoreProcessors
* @endpoint
* @property {StoreProcessors.addPricesToLineItems} addPricesToLineItems
* See method for details.
* @property {StoreProcessors.buildQueryStringParameterFromArrayOfComplexObjects} buildQueryStringParameterFromArrayOfComplexObjects
* See method for details.
* @property {StoreProcessors.convertGlassPieceNamingFromApi} convertGlassPieceNamingFromApi
* See method for details.
* @property {StoreProcessors.convertResultsForApi} convertResultsForApi
* See method for details.
* @property {StoreProcessors.getAllPartNumbers} getAllPartNumbers
* See method for details.
* @property {StoreProcessors.getHasRecalibrationPart} getHasRecalibrationPart
* See method for details.
* @property {StoreProcessors.getLineItemQueryStringForPricing} getLineItemQueryStringForPricing
* See method for details.
* @property {StoreProcessors.getTimeSlotsAdditionalEventData} getTimeSlotsAdditionalEventData
* See method for details.
* @static
* @default
*/
const StoreProcessors = {
addPricesToLineItems,
buildQueryStringParameterFromArrayOfComplexObjects,
convertGlassPieceNamingFromApi,
convertGlassPieceToBackEndCompatibleFormat,
convertResultsForApi,
getFlattenedArrayOfLineItemsWithChildParts,
getHasRecalibrationPart,
getLineItemQueryStringForPricing,
getTimeSlotsAdditionalEventData,
providersEqual,
provisionalTriggersToString
};
export default StoreProcessors;

File diff suppressed because it is too large Load diff

View file

@ -898,7 +898,7 @@ describe('Store', () => {
store.damage.glassToReplace = [
{ glassLocation: location1, glassName: name1 },
{ glassLocation: location2, glassName: name2 }
];
],
globalMethods.callHttpClient = jest.fn().mockReturnValue(Promise.resolve({}));
// Act