Merge pull request #379 from Safelite/refactor/linting4

Linting of the iss-components
This commit is contained in:
DavidAtSafelite 2023-07-25 09:52:13 -04:00 committed by GitHub
commit c4b06b6a69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 421 additions and 351 deletions

View file

@ -143,13 +143,11 @@ describe('address-questions.vue', () => {
}; };
// Act // Act
autocompleteElement.dispatchEvent( autocompleteElement.dispatchEvent(new CustomEvent('place_changed', { detail: selectedPlace }));
new CustomEvent('place_changed', { detail: selectedPlace })
);
// Assert // Assert
wrapper.vm.$nextTick(function () { wrapper.vm.$nextTick(() => {
const addressModel = wrapper.vm.addressModel; const { addressModel } = wrapper.vm;
expect(addressModel.streetAddress).toEqual('1234 Test Road'); expect(addressModel.streetAddress).toEqual('1234 Test Road');
expect(addressModel.city).toEqual('Columbus'); expect(addressModel.city).toEqual('Columbus');
expect(addressModel.state).toEqual('OH'); expect(addressModel.state).toEqual('OH');
@ -169,9 +167,9 @@ describe('address-questions.vue', () => {
}); });
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
querySelectorFunction: function (query) { querySelectorFunction(query) {
if (query == '.pac-container .pac-item') { if (query == '.pac-container .pac-item') {
let element = document.createElement('div'); const element = document.createElement('div');
element.textContent = '123 Test Street'; element.textContent = '123 Test Street';
return element; return element;
} }
@ -222,8 +220,8 @@ describe('address-questions.vue', () => {
} }
}); });
let noMatchAlert = wrapper.findComponent({ ref: 'alertNoMatchWarning' }); const noMatchAlert = wrapper.findComponent({ ref: 'alertNoMatchWarning' });
let verificationAlert = wrapper.findComponent({ ref: 'alertVerificationWarning' }); const verificationAlert = wrapper.findComponent({ ref: 'alertVerificationWarning' });
expect(noMatchAlert.exists()).toBeFalsy(); expect(noMatchAlert.exists()).toBeFalsy();
expect(verificationAlert.exists()).toBeFalsy(); expect(verificationAlert.exists()).toBeFalsy();
@ -235,7 +233,7 @@ describe('address-questions.vue', () => {
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
// Assert // Assert
const addressModel = wrapper.vm.addressModel; const { addressModel } = wrapper.vm;
expect(addressModel.streetAddress).toEqual('1234 Test Road'); expect(addressModel.streetAddress).toEqual('1234 Test Road');
expect(addressModel.city).toEqual('Columbus'); expect(addressModel.city).toEqual('Columbus');
expect(addressModel.state).toEqual('OH'); expect(addressModel.state).toEqual('OH');
@ -245,8 +243,7 @@ describe('address-questions.vue', () => {
describe('alerts', () => { describe('alerts', () => {
const places = [null, { address_components: null }, undefined, {}]; const places = [null, { address_components: null }, undefined, {}];
test.each(places)( test.each(places)('selected place/place properties is null => display verification alert',
'selected place/place properties is null => display verification alert',
async (place) => { async (place) => {
// Arrange // Arrange
const { wrapper } = setupMocks({}); const { wrapper } = setupMocks({});
@ -259,9 +256,7 @@ describe('address-questions.vue', () => {
const selectedPlace = place; const selectedPlace = place;
// Act // Act
autocompleteElement.dispatchEvent( autocompleteElement.dispatchEvent(new CustomEvent('place_changed', { detail: selectedPlace }));
new CustomEvent('place_changed', { detail: selectedPlace })
);
await wrapper.vm.$nextTick(); await wrapper.vm.$nextTick();
// Assert // Assert
@ -273,8 +268,7 @@ describe('address-questions.vue', () => {
const noMatchAlert = wrapper.findComponent({ ref: 'alertNoMatchWarning' }); const noMatchAlert = wrapper.findComponent({ ref: 'alertNoMatchWarning' });
expect(noMatchAlert.exists()).toBe(false); expect(noMatchAlert.exists()).toBe(false);
} });
);
test('user enters address that yields no autocomplete results => show noMatch alert', async () => { test('user enters address that yields no autocomplete results => show noMatch alert', async () => {
// Arrange // Arrange
@ -318,9 +312,9 @@ describe('address-questions.vue', () => {
}); });
const { wrapper } = setupMocks({ const { wrapper } = setupMocks({
querySelectorFunction: function (query) { querySelectorFunction(query) {
if (query == '.pac-container .pac-item') { if (query == '.pac-container .pac-item') {
let element = document.createElement('div'); const element = document.createElement('div');
element.textContent = '123 Test Street'; element.textContent = '123 Test Street';
return element; return element;
} }
@ -369,7 +363,7 @@ describe('address-questions.vue', () => {
}); });
// Assert // Assert
wrapper.vm.$nextTick(function () { wrapper.vm.$nextTick(() => {
expect(wrapper.vm.displayNoMatchWarning).toBeFalsy(); expect(wrapper.vm.displayNoMatchWarning).toBeFalsy();
noMatchAlert = wrapper.findComponent({ ref: 'alertNoMatchWarning' }); noMatchAlert = wrapper.findComponent({ ref: 'alertNoMatchWarning' });
@ -396,7 +390,7 @@ describe('address-questions.vue', () => {
}); });
// Assert // Assert
wrapper.vm.$nextTick(function () { wrapper.vm.$nextTick(() => {
expect(wrapper.vm.displayNoMatchWarning).toBeFalsy(); expect(wrapper.vm.displayNoMatchWarning).toBeFalsy();
noMatchAlert = wrapper.findComponent({ ref: 'alertNoMatchWarning' }); noMatchAlert = wrapper.findComponent({ ref: 'alertNoMatchWarning' });
@ -423,7 +417,7 @@ describe('address-questions.vue', () => {
}); });
// Assert // Assert
wrapper.vm.$nextTick(function () { wrapper.vm.$nextTick(() => {
expect(wrapper.vm.displayNoMatchWarning).toBeFalsy(); expect(wrapper.vm.displayNoMatchWarning).toBeFalsy();
noMatchAlert = wrapper.findComponent({ ref: 'alertNoMatchWarning' }); noMatchAlert = wrapper.findComponent({ ref: 'alertNoMatchWarning' });
@ -450,7 +444,7 @@ describe('address-questions.vue', () => {
}); });
// Assert // Assert
wrapper.vm.$nextTick(function () { wrapper.vm.$nextTick(() => {
expect(wrapper.vm.displayNoMatchWarning).toBeFalsy(); expect(wrapper.vm.displayNoMatchWarning).toBeFalsy();
noMatchAlert = wrapper.findComponent({ ref: 'alertNoMatchWarning' }); noMatchAlert = wrapper.findComponent({ ref: 'alertNoMatchWarning' });
@ -461,6 +455,15 @@ describe('address-questions.vue', () => {
}); });
}); });
/**
*
* @param root0
* @param root0.mountOptions
* @param root0.props
* @param root0.isShallowMount
* @param root0.querySelectorFunction
* @param root0.geocoderResult
*/
function setupMocks({ function setupMocks({
mountOptions, mountOptions,
props, props,
@ -483,6 +486,10 @@ function setupMocks({
addListener: jest addListener: jest
.fn() .fn()
.mockImplementation((element, eventName, callbackFunction) => { .mockImplementation((element, eventName, callbackFunction) => {
/**
*
* @param e
*/
function interceptedCallbackFunction(e) { function interceptedCallbackFunction(e) {
callbackFunction(e.detail); callbackFunction(e.detail);
} }

View file

@ -1,26 +1,26 @@
<template> <template>
<div role="application"> <div role="application">
<alert <alert
ref="alertVerificationWarning"
v-if="displayVerificationWarning" v-if="displayVerificationWarning"
ref="alertVerificationWarning"
class="mb-4" class="mb-4"
cmsWidgetName="AlertVerificationWarningWidget" cmsWidgetName="AlertVerificationWarningWidget"
alertClass="alert-warning" alertClass="alert-warning"
v-bind:isDismissible="false" /> :isDismissible="false" />
<alert <alert
ref="alertNoMatchWarning"
v-if="displayNoMatchWarning" v-if="displayNoMatchWarning"
ref="alertNoMatchWarning"
class="mb-4 mt-4" class="mb-4 mt-4"
cmsWidgetName="AlertNoMatchWarningWidget" cmsWidgetName="AlertNoMatchWarningWidget"
alertClass="alert-warning" alertClass="alert-warning"
v-bind:isDismissible="false" /> :isDismissible="false" />
<div class="row mt-2 mb-4"> <div class="row mt-2 mb-4">
<div class="col"> <div class="col">
<textboxQuestion <textboxQuestion
id="streetAddressField" id="streetAddressField"
cmsWidgetName="StreetAddressQuestionWidget"
v-model="addressModel.streetAddress"
ref="autocomplete" ref="autocomplete"
v-model="addressModel.streetAddress"
cmsWidgetName="StreetAddressQuestionWidget"
inputId="autocomplete" inputId="autocomplete"
placeholderText="Search" placeholderText="Search"
aria-haspopup="" aria-haspopup=""
@ -30,36 +30,43 @@
@keydown.enter.prevent /> @keydown.enter.prevent />
</div> </div>
</div> </div>
<transition name="fade" mode="out-in"> <transition
name="fade"
mode="out-in">
<div v-if="showAllFields"> <div v-if="showAllFields">
<div class="row mt-2 mb-4" v-if="includeStreetAddress2"> <div
v-if="includeStreetAddress2"
class="row mt-2 mb-4">
<div class="col"> <div class="col">
<textboxQuestion <textboxQuestion
ref="streetAddress2" ref="streetAddress2"
cmsWidgetName="StreetAddress2QuestionWidget"
v-model="addressModel.streetAddress2" v-model="addressModel.streetAddress2"
cmsWidgetName="StreetAddress2QuestionWidget"
aria-haspopup="" aria-haspopup=""
inputId="streetAddress2Field" inputId="streetAddress2Field" />
/>
</div> </div>
</div> </div>
<div class="row mb-4" aria-live="polite"> <div
class="row mb-4"
aria-live="polite">
<div class="col"> <div class="col">
<textboxQuestion <textboxQuestion
cmsWidgetName="CityQuestionWidget"
v-model="addressModel.city"
ref="city" ref="city"
v-model="addressModel.city"
cmsWidgetName="CityQuestionWidget"
inputId="cbf28188fdf2436688fd735915f7ee56" inputId="cbf28188fdf2436688fd735915f7ee56"
disableAutoFill disableAutoFill
validationRules="city-required" /> validationRules="city-required" />
</div> </div>
</div> </div>
<div class="row mb-4" aria-live="polite"> <div
class="row mb-4"
aria-live="polite">
<div class="col"> <div class="col">
<dropdownQuestion <dropdownQuestion
cmsWidgetName="StateQuestionWidget"
v-model="addressModel.state"
ref="state" ref="state"
v-model="addressModel.state"
cmsWidgetName="StateQuestionWidget"
inputId="8fdf9dc2e13e430eb57529499dceb3eb" inputId="8fdf9dc2e13e430eb57529499dceb3eb"
:options="stateOptions" :options="stateOptions"
disableAutoFill disableAutoFill
@ -67,9 +74,9 @@
</div> </div>
<div class="col"> <div class="col">
<textboxQuestion <textboxQuestion
cmsWidgetName="ZipQuestionWidget"
v-model="addressModel.zipCode"
ref="zipCode" ref="zipCode"
v-model="addressModel.zipCode"
cmsWidgetName="ZipQuestionWidget"
inputId="01a9a1c2de0b4c9da8e023c9ae3be498" inputId="01a9a1c2de0b4c9da8e023c9ae3be498"
mask="#####" mask="#####"
disableAutoFill disableAutoFill
@ -101,7 +108,11 @@ defineRule('zip-code-format', regex(/(^\d{5}$)|(^\d{5}-\d{4}$)/, errorMessages.Z
export default { export default {
name: 'address-questions', name: 'address-questions',
emits: ['update:modelValue'], // The component emits an event components: {
textboxQuestion,
dropdownQuestion,
alert
}, // The component emits an event
props: { props: {
modelValue: { modelValue: {
type: Object, type: Object,
@ -116,6 +127,7 @@ export default {
validationRules: String, validationRules: String,
includeStreetAddress2: false includeStreetAddress2: false
}, },
emits: ['update:modelValue'],
data() { data() {
return { return {
displayVerificationWarning: false, displayVerificationWarning: false,
@ -133,29 +145,59 @@ export default {
}, },
computed: { computed: {
stateOptions: { stateOptions: {
get: function () { get() {
return states; return states;
} }
}, },
addressModel: { addressModel: {
get: function () { get() {
return this.modelValue; return this.modelValue;
}, },
set: function (newValue) { set(newValue) {
this.$emit('update:modelValue', newValue); this.$emit('update:modelValue', newValue);
} }
} }
}, },
watch: {
matchFound: {
handler(newValue) {
if (!newValue) {
this.displayNoMatchWarning = true;
this.addressModel.city = '';
this.addressModel.state = '';
this.addressModel.zipCode = '';
this.displayVerificationWarning = false;
this.$nextTick(() => {
// Only deep watch the Address Model after a failed match
this.isAddressWatchActive = true;
});
}
}
},
addressModel: {
handler() {
if (this.isAddressWatchActive) {
this.displayNoMatchWarning = false;
this.isAddressWatchActive = false;
}
},
deep: true
}
},
mounted() {
this.setupAddressLookup();
this.showAllFields = !!this.addressModel.streetAddress;
},
methods: { methods: {
setupAddressLookup() { setupAddressLookup() {
const addressField1 = document.getElementById('autocomplete'); const addressField1 = document.getElementById('autocomplete');
const self = this; const self = this;
const url = endpoints.GooglePlaces.url.replace('{apiKey}', applicationConfig.GOOGLE_PLACES_API_KEY) const url = endpoints.GooglePlaces.url.replace('{apiKey}', applicationConfig.GOOGLE_PLACES_API_KEY);
this.$loadScript( this.$loadScript(url)
url
)
.then(() => { .then(() => {
// Script is loaded, initialize the autocomplete textbox // Script is loaded, initialize the autocomplete textbox
const autocomplete = new window.google.maps.places.Autocomplete(addressField1, { const autocomplete = new window.google.maps.places.Autocomplete(addressField1, {
@ -165,11 +207,9 @@ export default {
}); });
// Standard place_changed event handling // Standard place_changed event handling
const autocompleteListener = window.google.maps.event.addListener( const autocompleteListener = window.google.maps.event.addListener(autocomplete,
autocomplete,
'place_changed', 'place_changed',
fillInAddress fillInAddress);
);
addressField1.addEventListener('focus', () => { addressField1.addEventListener('focus', () => {
// Wrapping the addressField1 element in the Google Address Autocomplete object // Wrapping the addressField1 element in the Google Address Autocomplete object
@ -196,13 +236,12 @@ export default {
} }
addressField1.blur(); addressField1.blur();
} else {
return;
} }
}); });
addressField1.addEventListener('change', () => { addressField1.addEventListener('change', () => {
// NOTE: The "place_changed" event of the autocomplete fires after this and will use either the address the user had chosen // NOTE: The "place_changed" event of the autocomplete fires
// after this and will use either the address the user had chosen
// using either the down / up arrows or the address the user was hovering over when they pressed "Enter." // using either the down / up arrows or the address the user was hovering over when they pressed "Enter."
// If a match has been previously found then do nothing // If a match has been previously found then do nothing
@ -215,9 +254,7 @@ export default {
} }
// Get the address that the user clicked on (if any) // Get the address that the user clicked on (if any)
const clickedAddress = document.querySelector( const clickedAddress = document.querySelector('.pac-container .pac-item:hover');
'.pac-container .pac-item:hover'
);
// If the Street Address field changed without clicking (i.e. by pressing Tab, or clicking outside the field) // If the Street Address field changed without clicking (i.e. by pressing Tab, or clicking outside the field)
if (clickedAddress === null) { if (clickedAddress === null) {
@ -228,16 +265,14 @@ export default {
const firstResult = item.textContent; const firstResult = item.textContent;
const geocoder = new window.google.maps.Geocoder(); const geocoder = new window.google.maps.Geocoder();
geocoder.geocode( geocoder.geocode({
{ address: firstResult
address: firstResult },
}, (results, status) => {
function (results, status) { if (status === window.google.maps.GeocoderStatus.OK) {
if (status === window.google.maps.GeocoderStatus.OK) { fillInAddress(results[0]);
fillInAddress(results[0]);
}
} }
); });
} else { } else {
// No addresses found for the input // No addresses found for the input
self.matchFound = false; self.matchFound = false;
@ -245,6 +280,10 @@ export default {
} }
}); });
/**
*
* @param place
*/
function fillInAddress(place) { function fillInAddress(place) {
if (!place) { if (!place) {
place = autocomplete.getPlace(); place = autocomplete.getPlace();
@ -253,7 +292,7 @@ export default {
if (place && place.address_components) { if (place && place.address_components) {
self.matchFound = true; self.matchFound = true;
self.addressModel.streetAddress = ''; self.addressModel.streetAddress = '';
self.$nextTick(function () { self.$nextTick(() => {
for (const component of place.address_components) { for (const component of place.address_components) {
const componentType = component.types[0]; const componentType = component.types[0];
@ -264,7 +303,7 @@ export default {
} }
case 'route': { case 'route': {
self.addressModel.streetAddress self.addressModel.streetAddress
+= ' ' + component.short_name; += ` ${component.short_name}`;
break; break;
} }
case 'locality': { case 'locality': {
@ -279,6 +318,7 @@ export default {
self.addressModel.zipCode = component.long_name; self.addressModel.zipCode = component.long_name;
break; break;
} }
default:
} }
} }
@ -303,48 +343,11 @@ export default {
console.log('Unable to load Google Places API script'); console.log('Unable to load Google Places API script');
}); });
} }
},
mounted() {
this.setupAddressLookup();
this.showAllFields = !!this.addressModel.streetAddress;
},
watch: {
matchFound: {
handler(newValue) {
if (!newValue) {
this.displayNoMatchWarning = true;
this.addressModel.city = '';
this.addressModel.state = '';
this.addressModel.zipCode = '';
this.displayVerificationWarning = false;
this.$nextTick(function () {
// Only deep watch the Address Model after a failed match
this.isAddressWatchActive = true;
});
}
}
},
addressModel: {
handler() {
if (this.isAddressWatchActive) {
this.displayNoMatchWarning = false;
this.isAddressWatchActive = false;
}
},
deep: true
}
},
components: {
textboxQuestion,
dropdownQuestion,
alert
} }
}; };
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
#streetAddressField { #streetAddressField {
position: relative; position: relative;

View file

@ -1,12 +1,12 @@
<template> <template>
<!-- Modal --> <!-- Modal -->
<div <div
:id="cmsWidgetName"
class="modal fade modal-component" class="modal fade modal-component"
v-on="{ 'hidden.bs.modal': resetButtonStyle }"
:id="this.cmsWidgetName"
tabindex="-1" tabindex="-1"
aria-labelledby="ModalComponentLabel" aria-labelledby="ModalComponentLabel"
aria-hidden="true"> aria-hidden="true"
v-on="{ 'hidden.bs.modal': resetButtonStyle }">
<div class="modal-dialog modal-dialog-centered"> <div class="modal-dialog modal-dialog-centered">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -18,27 +18,27 @@
</div> </div>
<div class="modal-body"> <div class="modal-body">
<label <label
:aria-label="this.ModalQuestionText" :aria-label="ModalQuestionText"
class="form-label text-center w-100 mb-5 fw-bold" class="form-label text-center w-100 mb-5 fw-bold"
v-html="this.ModalQuestionText"></label> v-html="ModalQuestionText"></label>
</div> </div>
<buttonQuestion <buttonQuestion
v-model="selectedValue"
class="radioQuestion" class="radioQuestion"
:questionText="questionText" :questionText="questionText"
:answers="ModalSelectOptionAnswers" :answers="ModalSelectOptionAnswers"
groupName="ChooseVehicleYear" groupName="ChooseVehicleYear"
v-model="selectedValue"
:validationRules="validationRules" :validationRules="validationRules"
isRequired /> isRequired />
<div class="modal-footer px-5 py-4"> <div class="modal-footer px-5 py-4">
<buttonMain <buttonMain
ref="buttonMain"
class="w-100" class="w-100"
isPrimary isPrimary
ref="buttonMain"
suppressLoader suppressLoader
:buttonText="ModalSelectButtonText" :buttonText="ModalSelectButtonText"
@click-event="buttonClick" data-bs-dismiss="modal"
data-bs-dismiss="modal" /> @click-event="buttonClick" />
</div> </div>
</div> </div>
</div> </div>
@ -50,7 +50,11 @@ import buttonMain from '@/ux-components/button-main/button-main';
import buttonQuestion from '@/digital-components/button-question/button-question'; import buttonQuestion from '@/digital-components/button-question/button-question';
export default { export default {
name: 'buttonQuestionModal', name: 'button-question-modal',
components: {
buttonMain,
buttonQuestion
},
props: { props: {
cmsWidgetName: String cmsWidgetName: String
}, },
@ -69,15 +73,11 @@ export default {
resetButtonStyle() { resetButtonStyle() {
this.$refs.buttonMain.resetButtonStyle(); this.$refs.buttonMain.resetButtonStyle();
} }
},
components: {
buttonMain,
buttonQuestion
} }
}; };
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.modal { .modal {
top: auto; top: auto;
bottom: 0; bottom: 0;

View file

@ -4,13 +4,22 @@
:modalId="ModalName" :modalId="ModalName"
:footerButtonText="ModalCloseButtonText" :footerButtonText="ModalCloseButtonText"
@footer-button-event="footerButtonClick"> @footer-button-event="footerButtonClick">
<img :src="ModalImage" class="mw-100 d-flex mx-auto mb-4" alt="" /> <img
<h5 class="mb-4" v-html="ModalHeadline"></h5> :src="ModalImage"
<p class="fw-bold mb-2 subheader-text" v-html="ModalSubheadertext"></p> class="mw-100 d-flex mx-auto mb-4"
<p class="mb-0" v-html="ModalBodyText"></p> alt="" />
<h5
class="mb-4"
v-html="ModalHeadline"></h5>
<p
class="fw-bold mb-2 subheader-text"
v-html="ModalSubheadertext"></p>
<p
class="mb-0"
v-html="ModalBodyText"></p>
<p <p
class="my-4 caption modal-sub-body"
v-if="ModalSubBodyText" v-if="ModalSubBodyText"
class="my-4 caption modal-sub-body"
v-html="ModalSubBodyText"></p> v-html="ModalSubBodyText"></p>
</modal> </modal>
</template> </template>
@ -20,6 +29,9 @@ import modal from '@/digital-components/modal/modal';
export default { export default {
name: 'content-group-modal', name: 'content-group-modal',
components: {
modal
},
props: { props: {
cmsWidgetName: String cmsWidgetName: String
}, },
@ -54,14 +66,11 @@ export default {
footerButtonClick() { footerButtonClick() {
this.$refs[this.ModalName].closeModal(); this.$refs[this.ModalName].closeModal();
} }
},
components: {
modal
} }
}; };
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.modal { .modal {
&.modal-component { &.modal-component {
.modal-dialog { .modal-dialog {

View file

@ -1,10 +1,14 @@
<template> <template>
<div v-if="isModalVisible" class="container-fluid modal-loader"> <div
v-if="isModalVisible"
class="container-fluid modal-loader">
<div class="row g-2 h-100 d-flex align-items-center"> <div class="row g-2 h-100 d-flex align-items-center">
<div class="container-fluid overflow-hidden"> <div class="container-fluid overflow-hidden">
<div class="row h-100"> <div class="row h-100">
<div class="col text-center tagbg mb-4"> <div class="col text-center tagbg mb-4">
<div class="spinner-border text-danger" role="status"> <div
class="spinner-border text-danger"
role="status">
<span class="visually-hidden">Loading...</span> <span class="visually-hidden">Loading...</span>
</div> </div>
</div> </div>
@ -52,7 +56,7 @@
<script> <script>
export default { export default {
name: 'Modal', name: 'modal',
data() { data() {
return { return {
isModalVisible: false isModalVisible: false
@ -63,23 +67,21 @@ export default {
// Display modal // Display modal
this.isModalVisible = true; this.isModalVisible = true;
// Force page reload on back button // Force page reload on back button
window.addEventListener( window.addEventListener('pageshow',
'pageshow', (evt) => {
function (evt) {
if (evt.persisted) { if (evt.persisted) {
setTimeout(function () { setTimeout(() => {
window.location.reload(); window.location.reload();
}, 10); }, 10);
} }
}, },
false false);
);
} }
} }
}; };
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.modal-loader { .modal-loader {
position: fixed; position: fixed;
top: 0; top: 0;

View file

@ -1,23 +1,34 @@
<template> <template>
<input v-if="type == 'button'" type="button" class="nav-button" v-bind:value="text" v-on:click="navTo"/> <input
<p v-else type="" class="nav-text" v-on:click="navTo">{{text}}</p> v-if="type == 'button'"
type="button"
class="nav-button"
:value="text"
@click="navTo" />
<p
v-else
type=""
class="nav-text"
@click="navTo">
{{ text }}
</p>
</template> </template>
<script> <script>
export default ({ export default ({
name: 'navButton', name: 'nav-button',
props: { props: {
text: String, text: String,
scenario: String, scenario: String,
type: String type: String
}, },
methods: { methods: {
navTo(){ navTo() {
this.$router.navigate(this.scenario, this.$route) this.$router.navigate(this.scenario, this.$route);
} }
} }
}) });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -202,15 +202,15 @@ function setupMocks() {
answerResult: '', answerResult: '',
answerText: 'No', answerText: 'No',
nextQuestionSequence: 3 nextQuestionSequence: 3
}, }
] ]
}, }
], ],
glassLocation: 'Windshield', glassLocation: 'Windshield',
glassName: 'Single', glassName: 'Single',
answerKey: 'Windshield-Single', answerKey: 'Windshield-Single',
answerData: null answerData: null
}, }
] ]
}; };
}; };
@ -233,16 +233,16 @@ function setupMocks() {
'Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?', 'Is your vehicle equipped with a heated windshield that melts snow and ice from underneath the windshield wiper blades?',
selectedAnswerText: 'Yes', selectedAnswerText: 'Yes',
questionNum: 2 questionNum: 2
}, }
] ]
}, }
] ]
}; };
}; };
const mountOptions = getMountOptions({ const mountOptions = getMountOptions({
mixins: [baseMixin, vehicleQuestionsMixin] mixins: [baseMixin, vehicleQuestionsMixin]
}); });
mountOptions['attachTo'] = document.body; mountOptions.attachTo = document.body;
const wrapper = shallowMount(questionsPageLayout, mountOptions); const wrapper = shallowMount(questionsPageLayout, mountOptions);
return { wrapper }; return { wrapper };

View file

@ -8,7 +8,10 @@
<div class="row px-3"> <div class="row px-3">
<div class="col"> <div class="col">
<div class="select-car-form rounded"> <div class="select-car-form rounded">
<vehicleBanner class="mt-2 mb-4" cmsWidgetName="VehicleBannerWidget" :displayGenericVehicleImage="false" /> <vehicleBanner
class="mt-2 mb-4"
cmsWidgetName="VehicleBannerWidget"
:displayGenericVehicleImage="false" />
<siteSubHeader cmsWidgetName="SiteSubHeaderWidget" /> <siteSubHeader cmsWidgetName="SiteSubHeaderWidget" />
<alert <alert
ref="alertFewMoreQuestions" ref="alertFewMoreQuestions"
@ -16,25 +19,27 @@
alertClass="alert-warning" alertClass="alert-warning"
:manualHeadline="alertFewMoreQuestionsHeader" :manualHeadline="alertFewMoreQuestionsHeader"
:manualCopy="alertFewMoreQuestionsCopy" :manualCopy="alertFewMoreQuestionsCopy"
v-bind:isDismissible="false" :isDismissible="false"
class="mt-5"/> class="mt-5" />
<div v-for="(questionsDatum, i) in questionsData" :key="questionsDatum.key"> <div
v-for="(questionsDatum, i) in questionsData"
:key="questionsDatum.key">
<questionChain <questionChain
ref="questionChain" v-if="showThisQuestionChain(questionsDatum, i)"
v-model="selectedAnswers[questionsDatum.answerKey]" ref="questionChain"
:questionData="questionsDatum.questions" v-model="selectedAnswers[questionsDatum.answerKey]"
:index="i" :questionData="questionsDatum.questions"
v-if="showThisQuestionChain(questionsDatum, i)" :index="i"
:answerKey="questionsDatum.answerKey" :answerKey="questionsDatum.answerKey"
:validationRules="validationRules" /> :validationRules="validationRules" />
</div> </div>
<siteFooter <siteFooter
class="mt-5" ref="siteFooter"
ref="siteFooter" class="mt-5"
cmsWidgetName="SiteFooterWidget" cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!isMetaValid" :isForwardActionDisabled="!isMetaValid"
@back-clicked="handleBackButtonAction" @back-clicked="handleBackButtonAction"
@ForwardClicked="handleForwardButtonAction" /> @ForwardClicked="handleForwardButtonAction" />
</div> </div>
</div> </div>
</div> </div>
@ -56,6 +61,15 @@ import loadingModal from '@/iss-components/loading-modal/loading-modal.vue';
export default { export default {
name: 'questions-page', name: 'questions-page',
components: {
siteHeader,
vehicleBanner,
alert,
questionChain,
siteSubHeader,
siteFooter,
loadingModal
},
props: { props: {
isMetaValid: Boolean, isMetaValid: Boolean,
alertFewMoreQuestionsHeader: String, alertFewMoreQuestionsHeader: String,
@ -65,6 +79,7 @@ export default {
modelValue: Object, modelValue: Object,
index: Number index: Number
}, },
emits: ['update:modelValue', 'forwardButtonAction', 'back-click'],
computed: { computed: {
selectedAnswers: { selectedAnswers: {
get() { get() {
@ -92,20 +107,11 @@ export default {
showLoadingModal() { showLoadingModal() {
this.$refs.loadingModal.showModal(); this.$refs.loadingModal.showModal();
} }
},
components: {
siteHeader,
vehicleBanner,
alert,
questionChain,
siteSubHeader,
siteFooter,
loadingModal
} }
}; };
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.questions-page { .questions-page {
.question-text { .question-text {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;

View file

@ -1,36 +1,47 @@
<template> <template>
<div> <div>
<footer class="footer container-fluid g-5 px-0" id="infoBox"> <footer
id="infoBox"
class="footer container-fluid g-5 px-0">
<div class="row d-flex flex-row-reverse align-items-center vw-100 mx-0"> <div class="row d-flex flex-row-reverse align-items-center vw-100 mx-0">
<div class="col button-col d-flex px-0" id="stacked" > <div
id="stacked"
class="col button-col d-flex px-0">
<buttonMain <buttonMain
v-if="!isForwardButtonHidden" v-if="!isForwardButtonHidden"
ref="buttonMain" ref="buttonMain"
isPrimary isPrimary
:buttonText="buttonText" :buttonText="buttonText"
loaderColor="white" loaderColor="white"
:class="(disableForwardAction || isForwardActionDisabled) && 'form-test-invalid'" :class="(disableForwardAction || isForwardActionDisabled) && 'form-test-invalid'"
:aria-disabled="isForwardActionDisabled" :aria-disabled="isForwardActionDisabled"
:isDisabled="isForwardActionDisabled" :isDisabled="isForwardActionDisabled"
@click-event="buttonClick" data-bs-target="#footerModal"
data-bs-target="#footerModal" data-bs-dismiss="modal"
data-bs-dismiss="modal" data-test-id="site-footer-main-button"
data-test-id="site-footer-main-button" /> @click-event="buttonClick" />
</div> </div>
<div v-if="!isBackButtonHidden" class="col-auto link-col py-1 px-0 text-break"> <div
v-if="!isBackButtonHidden"
class="col-auto link-col py-1 px-0 text-break">
<textLink <textLink
linkType="navigation" linkType="navigation"
:text="backLink" :text="backLink"
@click-event="linkClick" href="javascript:void(0)"
href="javascript:void(0)" data-bs-target="#footerModal"
data-bs-target="#footerModal" data-bs-dismiss="modal"
data-bs-dismiss="modal" data-test-id="site-footer-back-button"
data-test-id="site-footer-back-button" /> @click-event="linkClick" />
</div> </div>
</div> </div>
</footer> </footer>
<div class="footerImage" id="imgCity" v-if="footerImageURL" > <div
<img id="siteFooterImage" :src="footerImageURL" /> v-if="footerImageURL"
id="imgCity"
class="footerImage">
<img
id="siteFooterImage"
:src="footerImageURL" />
</div> </div>
</div> </div>
</template> </template>
@ -38,21 +49,21 @@
<script> <script>
import textLink from '@/ux-components/text-link/text-link'; import textLink from '@/ux-components/text-link/text-link';
import buttonMain from '@/ux-components/button-main/button-main'; import buttonMain from '@/ux-components/button-main/button-main';
import { issPageValues } from '@/router/router-constants/issPage-values' import { issPageValues } from '@/router/router-constants/issPage-values';
export default { export default {
name: 'siteFooter', name: 'site-footer',
emits: ['backClicked', 'forwardClicked'], components: {
textLink,
buttonMain
},
props: { props: {
isForwardActionDisabled: Boolean, isForwardActionDisabled: Boolean,
isBackButtonHidden: { type: Boolean, default: false }, isBackButtonHidden: { type: Boolean, default: false },
isForwardButtonHidden: { type: Boolean, default: false }, isForwardButtonHidden: { type: Boolean, default: false },
cmsWidgetName: String cmsWidgetName: String
}, },
components: { emits: ['backClicked', 'forwardClicked'],
textLink,
buttonMain
},
data() { data() {
return { return {
paddingHeight: 0, paddingHeight: 0,
@ -60,18 +71,6 @@ export default {
disableForwardAction: false disableForwardAction: false
}; };
}, },
mounted() {
this.paddingHeight = this.includeFooterImage ? this.getFooterInfoBoxHeight() + 16 : this.getFooterInfoBoxHeight() + 24;
this.$nextTick(() => {
window.addEventListener('resize', this.onResize);
});
},
beforeUnmount() {
window.removeEventListener('resize', this.onResize);
},
unmounted() {
document.onkeydown = null;
},
computed: { computed: {
backLink() { backLink() {
return this.getCmsContent(this.cmsWidgetName, 'BackButtonText'); return this.getCmsContent(this.cmsWidgetName, 'BackButtonText');
@ -86,9 +85,21 @@ export default {
}, },
includeFooterImage() { includeFooterImage() {
const currentPageName = this.getPageNameByQueryString(); const currentPageName = this.getPageNameByQueryString();
return (currentPageName.toLowerCase() == issPageValues.WELCOME_PAGE); return (currentPageName.toLowerCase() === issPageValues.WELCOME_PAGE);
} }
}, },
mounted() {
this.paddingHeight = this.includeFooterImage ? this.getFooterInfoBoxHeight() + 16 : this.getFooterInfoBoxHeight() + 24;
this.$nextTick(() => {
window.addEventListener('resize', this.onResize);
});
},
beforeUnmount() {
window.removeEventListener('resize', this.onResize);
},
unmounted() {
document.onkeydown = null;
},
methods: { methods: {
onResize() { onResize() {
this.paddingHeight = this.getFooterInfoBoxHeight(); this.paddingHeight = this.getFooterInfoBoxHeight();
@ -98,15 +109,11 @@ export default {
}, },
removeLoader() { removeLoader() {
this.$refs.buttonMain.removeLoader(); this.$refs.buttonMain.removeLoader();
document.onkeydown = function (e) { document.onkeydown = () => true;
return true;
};
}, },
buttonClick() { buttonClick() {
// prevent keyboard input after button click // prevent keyboard input after button click
document.onkeydown = function (e) { document.onkeydown = () => false;
return false;
};
this.$emit('forwardClicked'); this.$emit('forwardClicked');
}, },
linkClick() { linkClick() {

View file

@ -1,15 +1,31 @@
<template> <template>
<div class="menu-modal-container"> <div class="menu-modal-container">
<button class="menu-button" type="button" :class="[isActive ? 'active' : '']" aria-label="Hamburger Menu (modal window)" @click="toggle()"> <button
class="menu-button"
type="button"
:class="[isActive ? 'active' : '']"
aria-label="Hamburger Menu (modal window)"
@click="toggle()">
<div class="bar1"></div> <div class="bar1"></div>
<div class="bar2"></div> <div class="bar2"></div>
<div class="bar3"></div> <div class="bar3"></div>
</button> </button>
</div> </div>
<!-- Modal --> <!-- Modal -->
<div class="modal menu-modal fade" data-bs-backdrop="false" id="footerModal" tabindex="-1" aria-labelledby="footerModalLabel" aria-hidden="true"> <div
id="footerModal"
class="modal menu-modal fade"
data-bs-backdrop="false"
tabindex="-1"
aria-labelledby="footerModalLabel"
aria-hidden="true">
<div class="menu-modal-container"> <div class="menu-modal-container">
<button class="menu-button" type="button" :class="[isActive ? 'active' : '']" aria-label="Hamburger Menu (modal window)" @click="toggle()"> <button
class="menu-button"
type="button"
:class="[isActive ? 'active' : '']"
aria-label="Hamburger Menu (modal window)"
@click="toggle()">
<div class="bar1"></div> <div class="bar1"></div>
<div class="bar2"></div> <div class="bar2"></div>
<div class="bar3"></div> <div class="bar3"></div>
@ -18,40 +34,44 @@
<div class="modal-dialog modal-fullscreen"> <div class="modal-dialog modal-fullscreen">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header visually-hidden"> <div class="modal-header visually-hidden">
<h5 class="modal-title" id="footerModalLabel">Footer Navigation</h5> <h5
id="footerModalLabel"
class="modal-title">
Footer Navigation
</h5>
</div> </div>
<div class="modal-body d-flex flex-column"> <div class="modal-body d-flex flex-column">
<textLink <textLink
linkType="navigation" linkType="navigation"
text="Terms of service" text="Terms of service"
href="//www.safelite.com/terms-of-use" href="//www.safelite.com/terms-of-use"
target="_blank" /> target="_blank" />
<textLink <textLink
linkType="navigation" linkType="navigation"
text="Your privacy choices" text="Your privacy choices"
href="//www.safelite.com/privacy-center" href="//www.safelite.com/privacy-center"
target="_blank"> target="_blank">
<template v-slot:after-text> <template #after-text>
<img <img
class="ccpa-icon" class="ccpa-icon"
data-id="ccpa-icon" data-id="ccpa-icon"
src="~@/assets/img/icons/ccpa-icon.svg" src="~@/assets/img/icons/ccpa-icon.svg"
alt="Your privacy choices" /> alt="Your privacy choices" />
</template> </template>
</textLink> </textLink>
<textLink <textLink
linkType="navigation" linkType="navigation"
text="Warranty" text="Warranty"
href="//www.safelite.com/national-lifetime-warranty" href="//www.safelite.com/national-lifetime-warranty"
target="_blank" /> target="_blank" />
<textLink <textLink
linkType="navigation" linkType="navigation"
text="Notice at collection" text="Notice at collection"
href="https://www.safelite.com/ccpa-privacy-policy" href="https://www.safelite.com/ccpa-privacy-policy"
target="_blank" /> target="_blank" />
</div> </div>
<div class="modal-footer d-flex justify-content-start"> <div class="modal-footer d-flex justify-content-start">
&copy; {{new Date().getFullYear()}} Safelite Group &copy; {{ new Date().getFullYear() }} Safelite Group
</div> </div>
</div> </div>
</div> </div>
@ -63,7 +83,10 @@ import textLink from '@/ux-components/text-link/text-link';
import { Modal } from 'bootstrap'; import { Modal } from 'bootstrap';
export default { export default {
name: 'menuModal', name: 'menu-modal',
components: {
textLink
},
data() { data() {
return { return {
isActive: false, isActive: false,
@ -88,11 +111,8 @@ export default {
}); });
} }
} }
},
components: {
textLink
} }
} };
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -1,7 +1,6 @@
import siteHeader from '@/iss-components/site-header/site-header'; import siteHeader from '@/iss-components/site-header/site-header';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { getMountOptions } from '@/helpers/unit-test-helper.js'; import { getMountOptions } from '@/helpers/unit-test-helper.js';
import { useMainStore } from '@/store/';
describe('site-header', () => { describe('site-header', () => {
test('renders the logo image', () => { test('renders the logo image', () => {

View file

@ -1,17 +1,23 @@
<template> <template>
<div> <div>
<div class="site-header d-flex justify-content-center align-items-center flex-column py-2" :style="logoBackgroundStyle"> <div
<img id="siteHeaderImage" class="img-fluid" :src="imageSrc" :alt="altText" /> class="site-header d-flex justify-content-center align-items-center flex-column py-2"
<menuModal/> :style="logoBackgroundStyle">
<img
id="siteHeaderImage"
class="img-fluid"
:src="imageSrc"
:alt="altText" />
<menuModal />
</div> </div>
<alert <alert
v-if="displayGlobalAlert" v-if="displayGlobalAlert"
class="position-absolute rounded-0 w-100 border-0 shadow-sm start-0" class="position-absolute rounded-0 w-100 border-0 shadow-sm start-0"
cmsWidgetName="GlobalAlert" cmsWidgetName="GlobalAlert"
:manualHeadline="globalAlertMessage.messageHeadline" :manualHeadline="globalAlertMessage.messageHeadline"
:manualCopy="globalAlertMessage.messageCopy" :manualCopy="globalAlertMessage.messageCopy"
:alertClass="globalAlertMessage.type" :alertClass="globalAlertMessage.type"
v-bind:isDismissible="globalAlertMessage.isDismissible" /> :isDismissible="globalAlertMessage.isDismissible" />
</div> </div>
</template> </template>
@ -22,7 +28,14 @@ import eventBus from '@/helpers/event-bus/event-bus';
import { globalEvents } from '@/constants/events'; import { globalEvents } from '@/constants/events';
export default ({ export default ({
name: 'siteHeader', name: 'site-header',
components: {
menuModal,
alert
},
props: {
cmsWidgetName: String
},
data() { data() {
return { return {
displayGlobalAlert: false, displayGlobalAlert: false,
@ -35,9 +48,6 @@ export default ({
headerAnswers: null headerAnswers: null
}; };
}, },
props: {
cmsWidgetName: String
},
computed: { computed: {
imageSrc() { imageSrc() {
return this.headerAnswers ? this.headerAnswers.AnswerImageUrl : ''; return this.headerAnswers ? this.headerAnswers.AnswerImageUrl : '';
@ -51,23 +61,12 @@ export default ({
}; };
} }
}, },
methods: {
setupHeader() {
const answers = this.getCmsContent(this.cmsWidgetName, 'Answers');
if(Array.isArray(answers)) {
this.headerAnswers = answers.filter(x => x.AccountNumber == this.mainStore.issConfig.accountNumber)[0]
this.headerAnswers = !this.headerAnswers ? answers.filter(x => x.AccountNumber === '0')[0] : this.headerAnswers;
}
}
},
mounted() { mounted() {
this.$nextTick(this.setupHeader); this.$nextTick(this.setupHeader);
// Check if alert event is on the bus // Check if alert event is on the bus
const alertEvent = eventBus.readAndPopEventFromBus( const alertEvent = eventBus.readAndPopEventFromBus(globalEvents.Categories.GLOBAL_ALERT,
globalEvents.Categories.GLOBAL_ALERT, globalEvents.SubCategories.PAGE_NOT_FOUND);
globalEvents.SubCategories.PAGE_NOT_FOUND
);
// If alert event is on the bus, then display the alert // If alert event is on the bus, then display the alert
if (alertEvent !== undefined) { if (alertEvent !== undefined) {
this.displayGlobalAlert = true; this.displayGlobalAlert = true;
@ -77,17 +76,21 @@ export default ({
// load client customization overrides // load client customization overrides
const clientOverrideClass = this.mainStore.issConfig.styleSheet.trim(); const clientOverrideClass = this.mainStore.issConfig.styleSheet.trim();
if (clientOverrideClass if (clientOverrideClass
&& clientOverrideClass != '' && clientOverrideClass !== ''
&& !document.getElementsByTagName('body')[0].className.split(' ').includes(clientOverrideClass)) && !document.getElementsByTagName('body')[0].className.split(' ').includes(clientOverrideClass)) {
{ document.getElementsByTagName('body')[0].className += ` ${clientOverrideClass}`;
document.getElementsByTagName('body')[0].className += ' ' + clientOverrideClass;
} }
}, },
components: { methods: {
menuModal, setupHeader() {
alert const answers = this.getCmsContent(this.cmsWidgetName, 'Answers');
if (Array.isArray(answers)) {
this.headerAnswers = answers.filter((x) => x.AccountNumber == this.mainStore.issConfig.accountNumber)[0];
this.headerAnswers = !this.headerAnswers ? answers.filter((x) => x.AccountNumber === '0')[0] : this.headerAnswers;
}
}
} }
}) });
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -1,23 +1,20 @@
<template> <template>
<button <button
@click="handleClick"
class="back-button-wrapper" class="back-button-wrapper"
:aria-label="backButtonAccessibleText" :aria-label="backButtonAccessibleText"
> @click="handleClick">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
width="34px" width="34px"
height="30px" height="30px"
viewBox="-2.2 -6 30 30" viewBox="-2.2 -6 30 30">
>
<g id="Layer_3"> <g id="Layer_3">
<path <path
class="outer" class="outer"
fill="#FFFFFF" fill="#FFFFFF"
d="M19.934-1.434h-8.486c-1.683,0-2.999,0.528-4.021,1.614L1.685,6.146C0.78,7.079,0.338,8.027,0.338,9.041 d="M19.934-1.434h-8.486c-1.683,0-2.999,0.528-4.021,1.614L1.685,6.146C0.78,7.079,0.338,8.027,0.338,9.041
c0,1.028,0.444,1.979,1.359,2.908l5.753,5.938c1.054,1.093,2.403,1.647,4.007,1.647l8.475,0.01c2.861,0,4.568-1.692,4.568-4.528 c0,1.028,0.444,1.979,1.359,2.908l5.753,5.938c1.054,1.093,2.403,1.647,4.007,1.647l8.475,0.01c2.861,0,4.568-1.692,4.568-4.528
V3.094C24.5,0.259,22.793-1.434,19.934-1.434L19.934-1.434z" V3.094C24.5,0.259,22.793-1.434,19.934-1.434L19.934-1.434z" />
/>
</g> </g>
<g id="Layer_2"> <g id="Layer_2">
<path <path
@ -29,8 +26,7 @@
c0-0.215,0.078-0.42,0.235-0.566l2.861-2.861l-2.861-2.862c-0.156-0.156-0.234-0.352-0.234-0.566c0-0.458,0.362-0.801,0.801-0.801 c0-0.215,0.078-0.42,0.235-0.566l2.861-2.861l-2.861-2.862c-0.156-0.156-0.234-0.352-0.234-0.566c0-0.458,0.362-0.801,0.801-0.801
c0.224,0,0.41,0.069,0.556,0.225l2.881,2.872l2.891-2.881c0.166-0.166,0.353-0.234,0.566-0.234c0.439,0,0.801,0.352,0.801,0.801 c0.224,0,0.41,0.069,0.556,0.225l2.881,2.872l2.891-2.881c0.166-0.166,0.353-0.234,0.566-0.234c0.439,0,0.801,0.352,0.801,0.801
c0,0.225-0.078,0.4-0.234,0.576l-2.871,2.871l2.861,2.852c0.146,0.156,0.234,0.352,0.234,0.576c0,0.449-0.361,0.82-0.811,0.82 c0,0.225-0.078,0.4-0.234,0.576l-2.871,2.871l2.861,2.852c0.146,0.156,0.234,0.352,0.234,0.576c0,0.449-0.361,0.82-0.811,0.82
c-0.225,0-0.43-0.098-0.586-0.244l-2.852-2.871l-2.852,2.871C11.203,13.165,10.999,13.253,10.773,13.253z" c-0.225,0-0.43-0.098-0.586-0.244l-2.852-2.871l-2.852,2.871C11.203,13.165,10.999,13.253,10.773,13.253z" />
/>
</g> </g>
</svg> </svg>
</button> </button>
@ -38,7 +34,7 @@
<script> <script>
export default { export default {
name: 'buttonBack', name: 'button-back',
props: { props: {
backButtonAccessibleText: { backButtonAccessibleText: {
type: String, type: String,
@ -46,6 +42,7 @@ export default {
default: '' default: ''
} }
}, },
emits: ['click-event'],
methods: { methods: {
handleClick() { handleClick() {
this.$emit('click-event'); this.$emit('click-event');
@ -54,7 +51,7 @@ export default {
}; };
</script> </script>
<style lang="scss"> <style lang="scss" scoped>
.back-button-wrapper { .back-button-wrapper {
padding: 0; padding: 0;
position: relative; position: relative;

View file

@ -1,19 +1,24 @@
<template> <template>
<div> <div>
<div class="d-flex align-items-center justify-content-center container-fluid overflow-hidden"> <div class="d-flex align-items-center justify-content-center container-fluid overflow-hidden">
<h5 class="text-center fw-normal mb-0 subheader-primary" :class="headerColor"> <h5
class="text-center fw-normal mb-0 subheader-primary"
:class="headerColor">
<span> <span>
{{ content }} {{ content }}
</span> </span>
<buttonBack <buttonBack
v-if="hasBackButton" v-if="hasBackButton"
:backButtonAccessibleText="backButtonAccessibleText" :backButtonAccessibleText="backButtonAccessibleText"
@click-event="clickEvent" @click-event="clickEvent" />
/>
</h5> </h5>
</div> </div>
<div class="subheader-secondary d-flex align-items-center container-fluid overflow-hidden" :class="justifySubheader"> <div
<p class="fw-normal mb-0" :class="alternateFormatting"> class="subheader-secondary d-flex align-items-center container-fluid overflow-hidden"
:class="justifySubheader">
<p
class="fw-normal mb-0"
:class="alternateFormatting">
<span v-html="subText"> <span v-html="subText">
</span> </span>
</p> </p>
@ -25,7 +30,8 @@
import buttonBack from '@/iss-components/site-sub-header/button-back/button-back'; import buttonBack from '@/iss-components/site-sub-header/button-back/button-back';
export default ({ export default ({
name: 'siteSubHeader', name: 'site-sub-header',
components: { buttonBack },
props: { props: {
cmsWidgetName: String, cmsWidgetName: String,
hasBackButton: Boolean, hasBackButton: Boolean,
@ -33,41 +39,40 @@ export default ({
issContainingPage: String, issContainingPage: String,
darkGraySubText: Boolean darkGraySubText: Boolean
}, },
emits: ['click-event'],
computed: { computed: {
content() { content() {
return this.getCmsContent(this.cmsWidgetName, 'SubHeaderText') return this.getCmsContent(this.cmsWidgetName, 'SubHeaderText');
}, },
subText() { subText() {
const subText = this.getCmsContent(this.cmsWidgetName, 'SecondaryText'); const subText = this.getCmsContent(this.cmsWidgetName, 'SecondaryText');
return subText ?? ''; return subText ?? '';
}, },
backButtonAccessibleText() { backButtonAccessibleText() {
return this.getCmsContent(this.cmsWidgetName, 'BackButtonAccessibleText') return this.getCmsContent(this.cmsWidgetName, 'BackButtonAccessibleText');
}, },
headerColor() { headerColor() {
return this.subText ? 'dark-header' : 'light-header'; return this.subText ? 'dark-header' : 'light-header';
}, },
justifySubheader() { justifySubheader() {
return (this.justification?.toLowerCase() === 'left') ? return (this.justification?.toLowerCase() === 'left')
'justify-content-left' : ? 'justify-content-left'
'justify-content-center'; : 'justify-content-center';
}, },
alternateFormatting() { alternateFormatting() {
// override for service-packages unique style // override for service-packages unique style
if (this.issContainingPage?.toLowerCase() === 'service-packages') { if (this.issContainingPage?.toLowerCase() === 'service-packages') {
return 'service-packages-subtext mt-4 mb-2 px-5'; return 'service-packages-subtext mt-4 mb-2 px-5';
} else {
return this.darkGraySubText ? 'dark-gray' : 'light-gray';
} }
return this.darkGraySubText ? 'dark-gray' : 'light-gray';
} }
}, },
methods: { methods: {
clickEvent() { clickEvent() {
this.$emit('click-event'); this.$emit('click-event');
} }
}, }
components: { buttonBack } });
})
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View file

@ -1,8 +1,8 @@
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import steeringTextModal from './steering-text'; import App from '@/App';
import { createApp } from 'vue';
import { createPinia } from 'pinia'; import { createPinia } from 'pinia';
import App from '@/App.vue'; import { createApp } from 'vue';
import steeringTextModal from './steering-text';
describe('steering-text.vue', () => { describe('steering-text.vue', () => {
test('Should display steering text from CMS for state MA', async () => { test('Should display steering text from CMS for state MA', async () => {

View file

@ -1,11 +1,13 @@
<template> <template>
<div> <div>
<textBlock <textBlock
cmsWidgetName="MASteeringText"
id="SteeringTextContent" id="SteeringTextContent"
/> cmsWidgetName="MASteeringText" />
<div> <div>
<p class="small" v-if="isStateMA" v-html="MASteeringText" ></p> <p
v-if="isStateMA"
class="small"
v-html="MASteeringText"></p>
</div> </div>
</div> </div>
</template> </template>
@ -14,20 +16,21 @@
import { useMainStore } from '@/store'; import { useMainStore } from '@/store';
export default { export default {
name: 'steeringTextModal', name: 'steering-text-modal',
setup() {
const mainStore = useMainStore();
return { mainStore };
},
computed: { computed: {
MASteeringText() { MASteeringText() {
return this.getCmsContent('MASteeringText', 'BodyText'); return this.getCmsContent('MASteeringText', 'BodyText');
}, },
isStateMA() { isStateMA() {
if (this.mainStore.order.customer.address.state=='MA') { if (this.mainStore.order.customer.address.state === 'MA') {
return true return true;
} }
return false;
} }
},
setup() {
const mainStore = useMainStore();
return { mainStore };
} }
} };
</script> </script>

View file

@ -1,11 +1,11 @@
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { vehicleCategories } from '@/constants/vehicle-categories.js'; import { vehicleCategories } from '@/constants/vehicle-categories.js';
import vehicleBanner from './vehicle-banner';
import { useMainStore } from '@/store'; import { useMainStore } from '@/store';
import { createApp } from 'vue'; import { createApp } from 'vue';
import { createPinia } from 'pinia'; import { createPinia, mapStores } from 'pinia';
import { mapStores } from 'pinia';
import App from '@/App.vue'; import App from '@/App.vue';
import vehicleBanner from './vehicle-banner';
const cmsData = { VehicleBannerWidget: const cmsData = { VehicleBannerWidget:
{ {
@ -16,8 +16,7 @@ const cmsData = { VehicleBannerWidget:
VanUnmatchedVehicleIcon: 'van-placeholder.jpg', VanUnmatchedVehicleIcon: 'van-placeholder.jpg',
CommercialVanUnmatchedVehicleIcon: 'commercial-placeholder.jpg', CommercialVanUnmatchedVehicleIcon: 'commercial-placeholder.jpg',
SuvUnmatchedVehicleIcon: 'suv-placeholder.jpg' SuvUnmatchedVehicleIcon: 'suv-placeholder.jpg'
} } };
}
const vueApp = createApp(App); const vueApp = createApp(App);
const pinia = createPinia(); const pinia = createPinia();
@ -37,7 +36,7 @@ const mockMixin = {
return vehicleCategories; return vehicleCategories;
} }
} }
} };
describe('vehicleBanner', () => { describe('vehicleBanner', () => {
test('renders the blurrycar image when displayGenericVehicleImage is true', async () => { test('renders the blurrycar image when displayGenericVehicleImage is true', async () => {
@ -81,7 +80,7 @@ describe('vehicleBanner', () => {
mixins: [mockMixin] mixins: [mockMixin]
}); });
var iconUrl = wrapper.vm.vehicleImageToDisplay; const iconUrl = wrapper.vm.vehicleImageToDisplay;
expect(iconUrl).toEqual('car-placeholder.jpg'); expect(iconUrl).toEqual('car-placeholder.jpg');
wrapper.unmount(); wrapper.unmount();
@ -106,7 +105,7 @@ describe('vehicleBanner', () => {
mixins: [mockMixin] mixins: [mockMixin]
}); });
var vehicleIcon = wrapper.vm.getUnmatchedVehicleIcon(); const vehicleIcon = wrapper.vm.getUnmatchedVehicleIcon();
expect(vehicleIcon).toEqual(expectedIcon); expect(vehicleIcon).toEqual(expectedIcon);
expect(wrapper.find('img').attributes('class')).toContain('vehicle-image'); expect(wrapper.find('img').attributes('class')).toContain('vehicle-image');

View file

@ -3,8 +3,7 @@
<img <img
class="vehicle-image img-fluid" class="vehicle-image img-fluid"
:src="vehicleImageToDisplay" :src="vehicleImageToDisplay"
alt="" alt="" />
/>
</div> </div>
</template> </template>
@ -24,7 +23,7 @@ export default {
return this.genericVehicleImage; return this.genericVehicleImage;
} }
const imageUrl = this.mainStore.order.vehicle.imageUrl; const { imageUrl } = this.mainStore.order.vehicle;
if (!imageUrl || imageUrl === 'NULL') { if (!imageUrl || imageUrl === 'NULL') {
return this.getUnmatchedVehicleIcon(); return this.getUnmatchedVehicleIcon();
} }
@ -52,7 +51,7 @@ export default {
}, },
methods: { methods: {
getUnmatchedVehicleIcon() { getUnmatchedVehicleIcon() {
switch (this.mainStore.order.vehicle.category){ switch (this.mainStore.order.vehicle.category) {
case this.vehicleCategories.CAR: case this.vehicleCategories.CAR:
return this.carUnmatchedVehicleIcon; return this.carUnmatchedVehicleIcon;
case this.vehicleCategories.SUV: case this.vehicleCategories.SUV: