Merged from feature/CSR-1012

This commit is contained in:
Leah Schumann 2023-02-08 13:40:45 -05:00
commit 0517b7072a
11 changed files with 174 additions and 92 deletions

View file

@ -1,5 +1,7 @@
<template> <template>
<div class="dropdown-question" :class="errorMessage || hasError ? 'has-error' : ''"> <div
class="dropdown-question"
:class="(errors && errors.length) || hasError ? 'has-error' : ''">
<label <label
:for="inputId" :for="inputId"
:aria-label="questionText" :aria-label="questionText"
@ -13,7 +15,9 @@
:aria-disabled="isDisabled" :aria-disabled="isDisabled"
:disabled="isDisabled" :disabled="isDisabled"
:aria-required="isRequired" :aria-required="isRequired"
:validationRules="validationRules"> :validationRules="validationRules"
:placeHolderText="placeHolderText">
<option v-if="placeHolderText" value="" selected>{{ placeHolderText }}</option>
<option v-for="(value, name, index) in options" :value="name" :key="index"> <option v-for="(value, name, index) in options" :value="name" :key="index">
{{ value }} {{ value }}
</option> </option>
@ -38,9 +42,11 @@ export default {
}, },
isDisabled: Boolean, isDisabled: Boolean,
isRequired: Boolean, isRequired: Boolean,
disableAutoFill: Boolean,
validationRules: String, validationRules: String,
cmsWidgetName: String, cmsWidgetName: String,
hasError: Boolean, hasError: Boolean,
placeHolderText: String,
}, },
setup(props) { setup(props) {
const propsClone = Object.assign({}, props); const propsClone = Object.assign({}, props);
@ -62,7 +68,7 @@ export default {
initialValue: initialValue, initialValue: initialValue,
}; };
const { errorMessage, handleBlur, handleChange, meta } = useField( const { errorMessage, handleBlur, handleChange, meta, errors } = useField(
props.inputId, props.inputId,
props.validationRules, props.validationRules,
fieldOptions fieldOptions
@ -73,6 +79,7 @@ export default {
handleBlur, handleBlur,
handleChange, handleChange,
meta, meta,
errors,
}; };
}, },
computed: { computed: {
@ -81,7 +88,7 @@ export default {
}, },
selectedOption: { selectedOption: {
get: function () { get: function () {
return this.modelValue; return !this.modelValue ? "" : this.modelValue;
}, },
set: function (newValue) { set: function (newValue) {
this.$emit("update:modelValue", newValue); this.$emit("update:modelValue", newValue);

View file

@ -1,68 +1,31 @@
import { shallowMount } from "@vue/test-utils"; import { shallowMount } from "@vue/test-utils";
import Modal from "./modal"; import Modal from "./modal";
const modalId = "modal-one";
const footerButtonText = "Sample footer text here.";
const headerText = "Sample header text here.";
describe("modal.vue", () => { describe("modal.vue", () => {
it("Should display header text when HeaderText is defined in the CMS", async () => { it("Should display footer button text when footerButtonText is defined", async () => {
// Act
const wrapper = shallowMount(Modal, { const wrapper = shallowMount(Modal, {
mixins: [mockMixin],
props: { props: {
cmsWidgetName: "test", modalId: modalId,
footerButtonText: footerButtonText,
}, },
attachTo: document.body, attachTo: document.body,
}); });
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["HeaderText"])); expect(wrapper.html()).toEqual(expect.stringContaining(footerButtonText));
}); });
it("Should display subheader text when SubheaderText is defined in the CMS", async () => { it("Should display modal header text when headerText is defined", async () => {
// Act
const wrapper = shallowMount(Modal, { const wrapper = shallowMount(Modal, {
mixins: [mockMixin],
props: { props: {
cmsWidgetName: "test", modalId: modalId,
footerButtonText: footerButtonText,
headerText: headerText,
}, },
attachTo: document.body, attachTo: document.body,
}); });
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["SubheaderText"])); expect(wrapper.html()).toEqual(expect.stringContaining(headerText));
});
it("Should insert image url when Image is defined in the CMS", async () => {
// Act
const wrapper = shallowMount(Modal, {
mixins: [mockMixin],
props: {
cmsWidgetName: "test",
},
attachTo: document.body,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["Image"]));
});
it("Should display body text when BodyText is defined in the CMS", async () => {
// Act
const wrapper = shallowMount(Modal, {
mixins: [mockMixin],
props: {
cmsWidgetName: "test",
},
attachTo: document.body,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["BodyText"]));
}); });
}); });
const mockMixin = {
methods: {
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
return mockCmsContent[cmsFieldName];
}),
},
};
const mockCmsContent = {
HeaderText: "Sample header text here.",
SubheaderText: "Sample subheader text here.",
Image: "https://www.sampleImage.sample",
BodyText: "Sample body text here.",
FooterText: "Sample footer text here.",
};

View file

@ -30,7 +30,7 @@
isPrimary isPrimary
class="w-100" class="w-100"
ref="buttonMain" ref="buttonMain"
suppressLoader loaderColor="white"
:buttonText="footerButtonText" :buttonText="footerButtonText"
@click-event="validateAndEmit" @click-event="validateAndEmit"
:class="isFooterButtonDisabled && 'form-test-invalid'" /> :class="isFooterButtonDisabled && 'form-test-invalid'" />
@ -44,11 +44,11 @@
import buttonMain from "@/ux-components/button-main/button-main"; import buttonMain from "@/ux-components/button-main/button-main";
import { Modal } from "bootstrap"; import { Modal } from "bootstrap";
import { useIsFormTouched, useIsFormDirty, useIsFormValid } from "vee-validate"; import { useIsFormTouched, useIsFormDirty, useIsFormValid } from "vee-validate";
import { checkPropertyChange } from "json-schema";
export default { export default {
name: "modal", name: "modal",
props: { props: {
modalId: String,
headerText: String, headerText: String,
footerButtonText: String, footerButtonText: String,
onModalOpenedCallback: { onModalOpenedCallback: {
@ -56,11 +56,14 @@ export default {
}, },
}, },
setup() { setup() {
const modalId = `modal-${crypto.randomUUID()}`;
const isTouched = useIsFormTouched(); const isTouched = useIsFormTouched();
const isDirty = useIsFormDirty(); const isDirty = useIsFormDirty();
const isValid = useIsFormValid(); const isValid = useIsFormValid();
return { return {
modalId,
isTouched, isTouched,
isDirty, isDirty,
isValid, isValid,

View file

@ -1,3 +1,64 @@
import { mount } from "@vue/test-utils";
import contentGroupModal from "./content-group-modal";
describe("content-group-modal.vue", () => { describe("content-group-modal.vue", () => {
test.todo("test this"); it("Should display header text when HeaderText is defined in the CMS", async () => {
const wrapper = mount(contentGroupModal, {
mixins: [mockMixin],
props: {
cmsWidgetName: "test",
},
attachTo: document.body,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["HeaderText"]));
});
it("Should display subheader text when SubheaderText is defined in the CMS", async () => {
const wrapper = mount(contentGroupModal, {
mixins: [mockMixin],
props: {
cmsWidgetName: "test",
},
attachTo: document.body,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["SubheaderText"]));
});
it("Should insert image url when Image is defined in the CMS", async () => {
const wrapper = mount(contentGroupModal, {
mixins: [mockMixin],
props: {
cmsWidgetName: "test",
},
attachTo: document.body,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["Image"]));
});
it("Should display body text when BodyText is defined in the CMS", async () => {
const wrapper = mount(contentGroupModal, {
mixins: [mockMixin],
props: {
cmsWidgetName: "test",
},
attachTo: document.body,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["BodyText"]));
});
}); });
const mockMixin = {
methods: {
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
return mockCmsContent[cmsFieldName];
}),
},
};
const mockCmsContent = {
HeaderText: "Sample header text here.",
SubheaderText: "Sample subheader text here.",
Image: "https://www.sampleImage.sample",
BodyText: "Sample body text here.",
FooterText: "Sample footer text here.",
};

View file

@ -277,25 +277,23 @@ export default {
} }
}); });
// addressField1.addEventListener("change", () => { addressField1.addEventListener("change", () => {
// // If a match has been previously attempted then do nothing // If a match has been previously attempted then do nothing
// if (self.matchFound !== null) { if (self.matchFound !== null) {
// return; return;
// } }
// // 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) {
// // Fill-in the address using first item in the list. // Fill-in the address using first item in the list.
// fillInAddressUsingFirstItem(); fillInAddressUsingFirstItem();
// } }
});
// console.log("change");
// });
function fillInAddressUsingFirstItem() { function fillInAddressUsingFirstItem() {
// Fill-in the address using first item in the list. // Fill-in the address using first item in the list.
@ -328,10 +326,6 @@ export default {
self.matchFound = true; self.matchFound = true;
self.$nextTick(function () { self.$nextTick(function () {
//self.addressModel.streetAddress = "";
//self.$nextTick();
self.showAddressFields = true; self.showAddressFields = true;
for (const component of place.address_components) { for (const component of place.address_components) {

View file

@ -247,7 +247,7 @@ export default {
line-height: 1.4; line-height: 1.4;
a { a {
text-underline-offset: 0.2em; text-underline-offset: 4px; //Per Devyn. This can't be documented in Figma so there is a comment with the Prototype mocks on the Quote page in Figma
line-height: inherit; line-height: inherit;
} }
} }

View file

@ -55,7 +55,6 @@ import cashOrInsuranceQuestion from "./cash-or-insurance-question/cash-or-insura
import servicePackageQuestion from "./service-package-question/service-package-question"; import servicePackageQuestion from "./service-package-question/service-package-question";
import textBlock from "@/digital-components/text-block/text-block"; import textBlock from "@/digital-components/text-block/text-block";
import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal"; import contentGroupModal from "@/fmg-components/content-group-modal/content-group-modal";
//import modal from "@/digital-components/modal/modal";
import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue"; import loadingModal from "@/fmg-components/loading-modal/loading-modal.vue";
import baseMixin from "@/mixins/base-mixin.js"; import baseMixin from "@/mixins/base-mixin.js";
import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin"; import vehicleQuestionsMixin from "../../mixins/vehicle-questions-mixin";

View file

@ -245,7 +245,6 @@ export default {
a { a {
line-height: 1.714; line-height: 1.714;
padding: 0; padding: 0;
text-underline-offset: 0.1em;
} }
} }
} }

View file

@ -5,6 +5,7 @@ fmg
<funnelHeader cmsWidgetName="FunnelHeaderWidget" /> <funnelHeader cmsWidgetName="FunnelHeaderWidget" />
<funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" /> <funnelSubHeader cmsWidgetName="FunnelSubHeaderWidget" />
<serviceZipModalQuestion <serviceZipModalQuestion
v-model="serviceZipQuestion"
cmsWidgetName="ServiceZipModalWidget" cmsWidgetName="ServiceZipModalWidget"
textboxQuestionWidgetName="ServiceZipQuestionWidget" textboxQuestionWidgetName="ServiceZipQuestionWidget"
alertNonServiceableZipWidgetName="AlertNonServiceableZipWidget" alertNonServiceableZipWidgetName="AlertNonServiceableZipWidget"
@ -41,6 +42,10 @@ export default {
name: "service-location", name: "service-location",
data() { data() {
return { return {
serviceZipQuestion: {
serviceState: "",
serviceZipCode: "",
},
mobileLocationQuestions: { mobileLocationQuestions: {
addressQuestions: { addressQuestions: {
streetAddress: "", streetAddress: "",

View file

@ -1,16 +1,16 @@
<template> <template>
<div class="text-center"> <div class="text-center">
<label <label
:for="displayModalLink" :for="serviceLocationLinkPromptId"
:aria-label="questionText" :aria-label="serviceLocationLinkPromptText"
class="form-label fw-bold w-100 ps-4 pe-4 pt-4" class="form-label fw-bold w-100 ps-4 pe-4 pt-4"
:class="[questionAlignment === 'center' ? 'text-center w-100 mb-5' : '']" :class="[questionAlignment === 'center' ? 'text-center w-100 mb-5' : '']"
v-html="questionText"></label> v-html="serviceLocationLinkPromptText"></label>
<div class="update-zip-text-link"> <div class="update-zip-text-link">
<textLink <textLink
id="displayModalLink" id="serviceLocationLinkPromptId"
linkType="text" linkType="text"
:text="this.serviceZipDisplayText" :text="this.serviceZipLinkText"
href="#!" href="#!"
@click-event="openZipCodeModal" @click-event="openZipCodeModal"
aria-label="Modal window" /> aria-label="Modal window" />
@ -22,13 +22,13 @@
:onModalOpenedCallback="onModalOpened" :onModalOpenedCallback="onModalOpened"
:footerButtonText="footerButtonText" :footerButtonText="footerButtonText"
:headerText="textboxQuestionPrompt" :headerText="textboxQuestionPrompt"
@footer-button-event="setZip" @footer-button-event="setZipCode"
:modalForm="modalForm"> :modalForm="modalForm">
<textboxQuestion <textboxQuestion
ref="zipInputTextQuestion" ref="zipInputTextQuestion"
:cmsWidgetName="this.textboxQuestionWidgetName" :cmsWidgetName="this.textboxQuestionWidgetName"
v-model="serviceZipCodeInput" v-model="serviceZipCode"
inputId="serviceZipCodeInput" inputId="serviceZipCode"
questionAlignment="center" questionAlignment="center"
mask="#####" mask="#####"
:displayQuestionText="false" :displayQuestionText="false"
@ -81,6 +81,15 @@ export default {
}; };
}, },
props: { props: {
modelValue: {
type: Object,
default: () => ({
serviceZipQuestion: {
serviceState: "",
serviceZipCode: "",
},
}),
},
cmsWidgetName: String, cmsWidgetName: String,
textboxQuestionWidgetName: String, textboxQuestionWidgetName: String,
alertNonServiceableZipWidgetName: String, alertNonServiceableZipWidgetName: String,
@ -94,6 +103,10 @@ export default {
}; };
}, },
methods: { methods: {
mobileLocationLinkPromptText() {
return this.getCmsContent(this.cmsWidgetName, "HeaderText");
},
openZipCodeModal() { openZipCodeModal() {
this.$refs[this.modalName].openModal(); this.$refs[this.modalName].openModal();
}, },
@ -128,7 +141,11 @@ export default {
this.focusOnZipInput(); this.focusOnZipInput();
}, },
async setZip() { closeModal() {
this.$refs[this.modalName].closeModal();
},
async setZipCode() {
this.resetAlerts(); this.resetAlerts();
const zipCodeData = await this.getZipCodeData(this.serviceZipCodeInput); const zipCodeData = await this.getZipCodeData(this.serviceZipCodeInput);
@ -141,11 +158,30 @@ export default {
this.serviceZip = this.serviceZipCodeInput; this.serviceZip = this.serviceZipCodeInput;
this.serviceZipState = zipCodeData.state; this.serviceZipState = zipCodeData.state;
this.$refs[this.modalName].closeModal(); this.closeModal();
} }
}, },
}, },
computed: { computed: {
serviceZipLinkPromptText() {
return this.getCmsContent(this.cmsWidgetName, "HeaderText");
},
serviceZipLinkText() {
return this.getCmsContent(this.cmsWidgetName, "BodyText");
},
modalHeaderText() {
return this.getCmsContent(this.textboxQuestionWidgetName, "QuestionText");
},
modalFooterText() {
return this.getCmsContent(this.modalWidgetName, "FooterText");
},
textboxQuestionPrompt() { textboxQuestionPrompt() {
return this.getCmsContent(this.textboxQuestionWidgetName, "QuestionText"); return this.getCmsContent(this.textboxQuestionWidgetName, "QuestionText");
}, },
@ -174,6 +210,21 @@ export default {
).replaceAll("{custom:serviceZipCodeInput}", zipCode); ).replaceAll("{custom:serviceZipCodeInput}", zipCode);
return text; return text;
}, },
serviceZipModel: {
get: function () {
return this.modelValue;
},
set: function (newValue) {
this.$emit("update:modelValue", newValue);
},
},
},
watch: {
serviceZipModel: {
handler() {
this.displayNonServiceableZipAlert = false;
},
},
}, },
mounted() { mounted() {
this.$watch( this.$watch(

View file

@ -45,7 +45,7 @@ export default {
<style lang="scss"> <style lang="scss">
a { a {
color: $blue; color: $blue;
text-underline-offset: 5px; text-underline-offset: 4px; //Per Devyn. This can't be documented in Figma so there is a comment with the Prototype mocks on the Quote page in Figma
line-height: 2; line-height: 2;
padding: 0 0 4px 0; padding: 0 0 4px 0;
font-weight: 500; font-weight: 500;