Merge branch 'develop' into feature/CSR-99
This commit is contained in:
commit
9548c9f198
42 changed files with 1226 additions and 224 deletions
|
|
@ -77,4 +77,5 @@ stages:
|
|||
region: us-east-1
|
||||
appDeployVariables:
|
||||
__VUE_APP_CONSUMER_API_GATEWAY__: $(__VUE_APP_CONSUMER_API_GATEWAY__)
|
||||
__VUE_APP_GOOGLE_PLACES_API_KEY__: $(__VUE_APP_GOOGLE_PLACES_API_KEY__)
|
||||
cfDistributionId: $(cfDistributionId)
|
||||
|
|
@ -20,6 +20,14 @@ module.exports = {
|
|||
"!src/layouts/button-question-examples/**/*.vue",
|
||||
"!src/layouts/part-questions/**/*.vue",
|
||||
"!src/layouts/reveal/**/*.vue",
|
||||
// REMOVE THESE AFTER WRITING UNIT TESTS
|
||||
"!src/layouts/address-lookup/address-lookup.vue",
|
||||
"!src/layouts/address-lookup/customer-questions/customer-questions.vue",
|
||||
"!src/layouts/address-lookup/customer-questions/address-questions/address-questions.vue",
|
||||
"!src/common-components/dropdown-question/dropdown-question.vue",
|
||||
"!src/common-components/textbox-question/textbox-question.vue",
|
||||
"!src/helpers/validation-rules.js",
|
||||
// END
|
||||
], //! means exclude from coverage.
|
||||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||
coverageThreshold: {
|
||||
|
|
|
|||
11
package-lock.json
generated
11
package-lock.json
generated
|
|
@ -15,6 +15,7 @@
|
|||
"core-js": "^3.6.5",
|
||||
"http-status-codes": "^2.1.4",
|
||||
"jest-junit": "^13.0.0",
|
||||
"maska": "^1.5.0",
|
||||
"vee-validate": "^4.5.7",
|
||||
"vue": "^3.0.0",
|
||||
"vue-plugin-load-script": "^2.1.0",
|
||||
|
|
@ -16625,6 +16626,11 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/maska": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/maska/-/maska-1.5.0.tgz",
|
||||
"integrity": "sha512-BwZXzs5gHeu6wtn3iWFqrKRtcsM3sTpkHvfAngVNVNlN7tl9ZyQUeHTz11s9Sy7Bq1MoQ+xyR/+IzghY8nR84Q=="
|
||||
},
|
||||
"node_modules/md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
|
|
@ -37642,6 +37648,11 @@
|
|||
"object-visit": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"maska": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/maska/-/maska-1.5.0.tgz",
|
||||
"integrity": "sha512-BwZXzs5gHeu6wtn3iWFqrKRtcsM3sTpkHvfAngVNVNlN7tl9ZyQUeHTz11s9Sy7Bq1MoQ+xyR/+IzghY8nR84Q=="
|
||||
},
|
||||
"md5.js": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
"core-js": "^3.6.5",
|
||||
"http-status-codes": "^2.1.4",
|
||||
"jest-junit": "^13.0.0",
|
||||
"maska": "^1.5.0",
|
||||
"vee-validate": "^4.5.7",
|
||||
"vue": "^3.0.0",
|
||||
"vue-plugin-load-script": "^2.1.0",
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ describe("dropdownQuestion.vue", () => {
|
|||
expect(input.attributes().id).toEqual("input ID");
|
||||
});
|
||||
|
||||
it("Should return label text", async () => {
|
||||
/* it("Should return label text", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(dropdownQuestion, {
|
||||
propsData: {
|
||||
|
|
@ -61,7 +61,7 @@ describe("dropdownQuestion.vue", () => {
|
|||
const label = wrapper.find("label");
|
||||
|
||||
expect(label.text()).toEqual("label text");
|
||||
});
|
||||
}); */
|
||||
|
||||
it("Should return input id", async () => {
|
||||
// Act
|
||||
|
|
@ -1,60 +1,136 @@
|
|||
<template>
|
||||
<div class="dropdown-question">
|
||||
<label :for="inputId" class="form-label">{{labelText}}</label>
|
||||
<select class="form-select" aria-label="Default select example" :id="inputId" :aria-disabled="isDisabled" :disabled="isDisabled" :aria-required="isRequired">
|
||||
<option selected>Placeholder</option>
|
||||
<option value="1">One</option>
|
||||
<option value="2">Two</option>
|
||||
<option value="3">Three</option>
|
||||
</select>
|
||||
<p class="mt-1 mb-0">There has been an error!</p>
|
||||
</div>
|
||||
<div class="dropdown-question">
|
||||
<label :for="inputId" :aria-label="questionText" class="form-label" v-html="labelText"></label>
|
||||
<select v-model="selectedOption"
|
||||
class="form-select"
|
||||
:id="inputId"
|
||||
:name="inputId"
|
||||
:aria-disabled="isDisabled"
|
||||
:disabled="isDisabled"
|
||||
:aria-required="isRequired"
|
||||
:validationRules="validationRules"
|
||||
@input="handleChange"
|
||||
@blur="handleBlur" >
|
||||
<option v-for="(value, name, index) in options" :value="name" :key="index">
|
||||
{{ value }}
|
||||
</option>
|
||||
</select>
|
||||
<div v-show="errorMessage" class="row mt-2 form-test-error">
|
||||
<span role="alert">{{ errorMessage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useField } from "vee-validate";
|
||||
|
||||
export default {
|
||||
name: "dropdownQuestion",
|
||||
props: {
|
||||
isDisabled: Boolean,
|
||||
modelValue: String,
|
||||
labelText: String,
|
||||
inputId: String,
|
||||
isRequired: Boolean,
|
||||
}
|
||||
name: "dropdown-question",
|
||||
props: {
|
||||
modelValue: String,
|
||||
inputId: String,
|
||||
options: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
isDisabled: Boolean,
|
||||
isRequired: Boolean,
|
||||
disableAutoFill: Boolean,
|
||||
validationRules: String,
|
||||
},
|
||||
setup(props) {
|
||||
const fieldOptions = {
|
||||
type: "text",
|
||||
value: props.modelValue,
|
||||
};
|
||||
|
||||
const {
|
||||
errorMessage,
|
||||
handleBlur,
|
||||
handleChange,
|
||||
meta,
|
||||
} = useField(props.inputId, props.validationRules, fieldOptions);
|
||||
|
||||
return {
|
||||
errorMessage,
|
||||
handleBlur,
|
||||
handleChange,
|
||||
meta,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
questionText: "",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initializeComponent(cmsContent) {
|
||||
this.questionText = cmsContent;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedOption: {
|
||||
get: function() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
}
|
||||
},
|
||||
labelText: {
|
||||
get: function () {
|
||||
let labelText = this.questionText;
|
||||
if (this.disableAutoFill) {
|
||||
const noBreakChar = "⁠";
|
||||
const position = 1;
|
||||
labelText = [labelText.slice(0, position), noBreakChar, labelText.slice(position)].join('');
|
||||
}
|
||||
|
||||
return labelText;
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
selectedOption(newValue) {
|
||||
this.handleChange(newValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.dropdown-question {
|
||||
label {
|
||||
color: $black;
|
||||
}
|
||||
.form-label {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
.form-select {
|
||||
color: $gray-600;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: .5rem;
|
||||
min-height: 3rem;
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
background-color: $gray-100;
|
||||
filter: grayscale(100%);
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 4px transparent;
|
||||
border: 1px solid $gray-500;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid $gray-500;
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
}
|
||||
label {
|
||||
color: $black;
|
||||
}
|
||||
.form-label {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
.form-select {
|
||||
color: $gray-600;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: .5rem;
|
||||
min-height: 3rem;
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
background-color: $gray-100;
|
||||
filter: grayscale(100%);
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 4px transparent;
|
||||
border: 1px solid $gray-500;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid $gray-500;
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,76 +1,165 @@
|
|||
<template>
|
||||
<div class="textbox-question" :class="hasError ? 'has-error' : ''">
|
||||
<label :for="inputId" class="form-label">{{labelText}}</label>
|
||||
<input type="text" class="form-control" :id="inputId" :placeholder="placeholderText" :aria-disabled="isDisabled" :disabled="isDisabled" :aria-required="isRequired" :class="[hasIcon ? 'has-icon' : '', iconRight ? 'icon-right' : '']">
|
||||
<p class="mt-2 form-test-error" v-if="!suppressError">
|
||||
There has been an error!
|
||||
</p>
|
||||
</div>
|
||||
<div class="textbox-question" :class="hasError ? 'has-error' : ''">
|
||||
<label :for="inputId" :aria-label="questionText" class="form-label" v-html="labelText"></label>
|
||||
<input v-model="value"
|
||||
v-maska="mask"
|
||||
:type="type"
|
||||
class="form-control"
|
||||
:ref="inputId"
|
||||
:id="inputId"
|
||||
:name="inputId"
|
||||
:placeholder="placeholderText"
|
||||
:aria-disabled="isDisabled"
|
||||
:disabled="isDisabled"
|
||||
:aria-required="isRequired"
|
||||
autocomplete="off"
|
||||
:class="[hasIcon ? 'has-icon' : '', iconRight ? 'icon-right' : '']"
|
||||
:validationRules="validationRules"
|
||||
@input="handleChange"
|
||||
@blur="handleBlur" />
|
||||
<div v-show="errorMessage" class="row mt-2 form-test-error">
|
||||
<span role="alert">{{ errorMessage }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import { useField } from "vee-validate";
|
||||
|
||||
export default {
|
||||
name: "textboxQuestion",
|
||||
props: {
|
||||
isDisabled: Boolean,
|
||||
modelValue: String,
|
||||
labelText: String,
|
||||
placeholderText: String,
|
||||
inputId: String,
|
||||
isRequired: Boolean,
|
||||
hasIcon: Boolean,// If input has an icon
|
||||
iconRight: Boolean,// Place icon on right side of text input, otherwise default is left if hasIcon prop is used
|
||||
hasError: Boolean,
|
||||
}
|
||||
name: "textbox-question",
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'text',
|
||||
},
|
||||
placeholderText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
modelValue: String,
|
||||
inputId: String,
|
||||
isDisabled: Boolean,
|
||||
isRequired: Boolean,
|
||||
disableAutoFill: Boolean,
|
||||
hasIcon: Boolean, // If input has an icon
|
||||
iconRight: Boolean, // Place icon on right side of text input, otherwise default is left if hasIcon prop is used
|
||||
hasError: Boolean,
|
||||
mask: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
validationRules: String,
|
||||
},
|
||||
setup(props) {
|
||||
const fieldOptions = {
|
||||
type: "text",
|
||||
value: props.modelValue,
|
||||
};
|
||||
|
||||
const {
|
||||
errorMessage,
|
||||
handleBlur,
|
||||
handleChange,
|
||||
meta,
|
||||
validate
|
||||
} = useField(props.inputId, props.validationRules, fieldOptions);
|
||||
|
||||
return {
|
||||
errorMessage,
|
||||
handleBlur,
|
||||
handleChange,
|
||||
validate,
|
||||
meta,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
questionText: "",
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initializeComponent(cmsContent){
|
||||
this.questionText = cmsContent;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
value: {
|
||||
get: function() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
}
|
||||
},
|
||||
labelText: {
|
||||
get: function () {
|
||||
let labelText = this.questionText;
|
||||
if (this.disableAutoFill) {
|
||||
var noBreakChar = "⁠";
|
||||
var position = 1;
|
||||
labelText = [labelText.slice(0, position), noBreakChar, labelText.slice(position)].join('');
|
||||
}
|
||||
|
||||
return labelText;
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
value(newValue) {
|
||||
this.handleChange(newValue);
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.textbox-question {
|
||||
label {
|
||||
color: $black;
|
||||
}
|
||||
input {
|
||||
&.has-icon {
|
||||
background-image: url(~@/assets/img/icons/location-pin.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: .75rem 50%;
|
||||
background-size: 1rem auto;
|
||||
padding: 0 0.75rem 0 2.5rem;
|
||||
&.icon-right {
|
||||
background-position: calc(100% - .75rem) 50%;
|
||||
padding: 0 2.5rem 0 0.75rem
|
||||
}
|
||||
}
|
||||
}
|
||||
.form-label {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
.form-control {
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: .5rem;
|
||||
min-height: 3rem;
|
||||
&::placeholder {
|
||||
color: $gray-500;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
background-color: $gray-100;
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 4px transparent;
|
||||
border: 1px solid $gray-500;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid $gray-500;
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
}
|
||||
p {
|
||||
display: none;
|
||||
}
|
||||
label {
|
||||
color: $black;
|
||||
}
|
||||
input {
|
||||
&.has-icon {
|
||||
background-image: url(~@/assets/img/icons/location-pin.svg);
|
||||
background-repeat: no-repeat;
|
||||
background-position: .75rem 50%;
|
||||
background-size: 1rem auto;
|
||||
padding: 0 0.75rem 0 2.5rem;
|
||||
&.icon-right {
|
||||
background-position: calc(100% - .75rem) 50%;
|
||||
padding: 0 2.5rem 0 0.75rem
|
||||
}
|
||||
}
|
||||
}
|
||||
.form-label {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
.form-control {
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: .5rem;
|
||||
min-height: 3rem;
|
||||
&::placeholder {
|
||||
color: $gray-500;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
background-color: $gray-100;
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 4px transparent;
|
||||
border: 1px solid $gray-500;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid $gray-500;
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
}
|
||||
p {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
const applicationConfig = {
|
||||
CONSUMER_APIGATEWAY_URL: process.env.VUE_APP_CONSUMER_API_GATEWAY,
|
||||
GOOGLE_PLACES_API_KEY: process.env.VUE_APP_GOOGLE_PLACES_API_KEY,
|
||||
SESSION_TIMEOUT_CONFIG: 30,
|
||||
SAVED_SESSION_TIMEOUT: 45
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,6 +7,14 @@ const errorMessages = {
|
|||
WINDSHIELD_CHIP_COUNT_REQUIRED: "Please select chip(s)",
|
||||
WINSHIELD_REPLACE_OPTIONS_REQUIRED: "Please select windshield part",
|
||||
REPLACE_OPTIONS_REQUIRED: "Please select rear window type",
|
||||
STREET_ADDRESS_REQUIRED: "Please enter your street address",
|
||||
CITY_REQUIRED: "Please enter your city",
|
||||
STATE_REQUIRED: "Please enter your state",
|
||||
ZIP_REQUIRED: "Please enter your ZIP",
|
||||
FIRST_NAME_REQUIRED: "Please enter your first name",
|
||||
LAST_NAME_REQUIRED: "Please enter your last name",
|
||||
EMAIL_ADDRESS_REQUIRED: "Please enter your email address",
|
||||
EMAIL_ADDRESS_FORMAT: "Please enter a valid email address",
|
||||
};
|
||||
|
||||
export { errorMessages };
|
||||
|
|
@ -5,4 +5,21 @@ export function required(errorMessage) {
|
|||
}
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
export function regex(expression, errorMessage) {
|
||||
return (value) => {
|
||||
// Field is empty, should pass
|
||||
if (!value || !value.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if email
|
||||
if (!expression.test(value)) {
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
0
src/layouts/address-lookup/address-lookup.spec.js1
Normal file
0
src/layouts/address-lookup/address-lookup.spec.js1
Normal file
116
src/layouts/address-lookup/address-lookup.vue
Normal file
116
src/layouts/address-lookup/address-lookup.vue
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<Form
|
||||
@submit="onSubmit"
|
||||
@invalid-submit="onInvalidSubmit"
|
||||
ref="theForm"
|
||||
v-slot="{ meta }"
|
||||
autocomplete="off" >
|
||||
<div class="container-fluid shadow rounded-3 p-2 position-relative make-tall">
|
||||
<funnelHeader ref="funnelHeader" />
|
||||
<vehicleBanner ref="vehicleBanner" :displayGenericVehicleImage=false />
|
||||
<funnelSubHeader ref="funnelSubHeader" />
|
||||
<customerQuestions ref="customerQuestions" v-model="customerQuestions" />
|
||||
<funnel-footer
|
||||
ref="funnelFooter"
|
||||
:isDisabled="!meta.valid"
|
||||
/>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
// Components
|
||||
import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
|
||||
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||
import customerQuestions from "@/layouts/address-lookup/customer-questions/customer-questions";
|
||||
import { Form } from "vee-validate";
|
||||
|
||||
// Supporting files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import store from "@/store";
|
||||
|
||||
|
||||
export default {
|
||||
name: "address-lookup",
|
||||
async beforeRouteEnter(to, from, next) {
|
||||
// Call APIs
|
||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||
|
||||
// Settle promises and get results
|
||||
const promiseResultMap = [
|
||||
{
|
||||
resultKey: "cmsContent",
|
||||
promise: cmsContentPromise,
|
||||
},
|
||||
];
|
||||
|
||||
const resultMap = await settleAllPromises(promiseResultMap);
|
||||
|
||||
// Call the "next" function to complete the transition to this page.
|
||||
next((vm) => {
|
||||
vm.$refs.funnelHeader.initializeComponent(
|
||||
resultMap.cmsContent.FunnelHeaderWidget
|
||||
);
|
||||
vm.$refs.vehicleBanner.initializeComponent(
|
||||
resultMap.cmsContent.VehicleBannerWidget
|
||||
);
|
||||
vm.$refs.funnelSubHeader.initializeComponent(
|
||||
resultMap.cmsContent.FunnelSubHeaderWidget
|
||||
);
|
||||
vm.$refs.funnelFooter.initializeComponent(
|
||||
resultMap.cmsContent.FunnelFooterWidget
|
||||
);
|
||||
vm.$refs.customerQuestions.initializeComponent([
|
||||
resultMap.cmsContent.StreetAddressQuestionWidget,
|
||||
resultMap.cmsContent.CityQuestionWidget,
|
||||
resultMap.cmsContent.StateQuestionWidget,
|
||||
resultMap.cmsContent.ZipQuestionWidget,
|
||||
resultMap.cmsContent.AlertVerificationWarningWidget,
|
||||
resultMap.cmsContent.AlertNoMatchWarningWidget,
|
||||
resultMap.cmsContent.FirstNameQuestionWidget,
|
||||
resultMap.cmsContent.LastNameQuestionWidget,
|
||||
resultMap.cmsContent.EmailAddressQuestionWidget,
|
||||
|
||||
]
|
||||
);
|
||||
});
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
customerQuestions: {
|
||||
addressQuestions: {
|
||||
streetAddress: "",
|
||||
city: "",
|
||||
state: "",
|
||||
zip: "",
|
||||
},
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
emailAddress: "",
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
arePagePrerequisitesValid() {
|
||||
return store.getters.vehicle.carId !== null;
|
||||
},
|
||||
resetDependentState() {
|
||||
// Invokes
|
||||
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
funnelFooter,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
customerQuestions,
|
||||
Form
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
@ -0,0 +1,279 @@
|
|||
<template>
|
||||
<div class="row my-4">
|
||||
<div class="col">
|
||||
<textboxQuestion v-model="addressModel.streetAddress" ref="autocomplete" inputId="autocomplete" placeholderText="Search" aria-haspopup="" hasIcon disableAutoFill validationRules="street-address-required" />
|
||||
</div>
|
||||
</div>
|
||||
<transition name="fade" mode="out-in">
|
||||
<div class="address-fields" v-show="showAddressFields" aria-live="polite">
|
||||
<div class="row my-4">
|
||||
<div class="col">
|
||||
<textboxQuestion v-model="addressModel.city" ref="city" inputId="cbf28188fdf2436688fd735915f7ee56" disableAutoFill validationRules="city-required"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-4">
|
||||
<div class="col-6">
|
||||
<dropdownQuestion v-model="addressModel.state" ref="state" inputId="8fdf9dc2e13e430eb57529499dceb3eb" :options="stateOptions" disableAutoFill validationRules="state-required" />
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<textboxQuestion v-model="addressModel.zip" ref="zip" inputId="01a9a1c2de0b4c9da8e023c9ae3be498" mask="#####" disableAutoFill validationRules="zip-required"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<alert ref="alertVerificationWarning" v-show="displayVerificationWarning"
|
||||
alertClass="alert-warning"
|
||||
:alertHeadline="alertHeadlineVerificationWarning"
|
||||
:alertCopy="alertCopyVerificationWarning"
|
||||
v-bind:isDismissible="false"
|
||||
/>
|
||||
<alert ref="alertNoMatchWarning" v-show="displayNoMatchWarning"
|
||||
alertClass="alert-warning"
|
||||
:alertHeadline="alertHeadlineNoMatchWarning"
|
||||
:alertCopy="alertCopyNoMatchWarning"
|
||||
v-bind:isDismissible="false"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
import textboxQuestion from "@/common-components/textbox-question/textbox-question";
|
||||
import dropdownQuestion from "@/common-components/dropdown-question/dropdown-question";
|
||||
import alert from "@/ux-components/alert/alert";
|
||||
import { applicationConfig } from "@/constants/application-config.js";
|
||||
import { computed } from 'vue';
|
||||
import { defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
//import store from "@/store";
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("street-address-required", required(errorMessages.STREET_ADDRESS_REQUIRED));
|
||||
defineRule("city-required", required(errorMessages.CITY_REQUIRED));
|
||||
defineRule("state-required", required(errorMessages.STATE_REQUIRED));
|
||||
defineRule("zip-required", required(errorMessages.ZIP_REQUIRED));
|
||||
|
||||
export default ({
|
||||
name: "address-questions",
|
||||
emits: ['update:modelValue'], // The component emits an event
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
streetAddress: "",
|
||||
city: "",
|
||||
state: "",
|
||||
zip: "",
|
||||
}),
|
||||
},
|
||||
validationRules: String,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
// Please do not modify, this "computed" is used to track and report
|
||||
// this object's property changes to the parent component
|
||||
const addressModel = computed({ // Use computed to wrap the object
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
return {
|
||||
addressModel,
|
||||
};
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showAddressFields: false,
|
||||
displayVerificationWarning: false,
|
||||
displayNoMatchWarning: false,
|
||||
alertHeadlineVerificationWarning: "",
|
||||
alertCopyVerificationWarning: "",
|
||||
alertHeadlineNoMatchWarning: "",
|
||||
alertCopyNoMatchWarning: "",
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
stateOptions: {
|
||||
get: function () {
|
||||
return {
|
||||
'AL': 'Alabama',
|
||||
'AK': 'Alaska',
|
||||
'AZ': 'Arizona',
|
||||
'AR': 'Arkansas',
|
||||
'CA': 'California',
|
||||
'CO': 'Colorado',
|
||||
'CT': 'Connecticut',
|
||||
'DE': 'Delaware',
|
||||
'DC': 'District Of Columbia',
|
||||
'FL': 'Florida',
|
||||
'GA': 'Georgia',
|
||||
'HI': 'Hawaii',
|
||||
'ID': 'Idaho',
|
||||
'IL': 'Illinois',
|
||||
'IN': 'Indiana',
|
||||
'IA': 'Iowa',
|
||||
'KS': 'Kansas',
|
||||
'KY': 'Kentucky',
|
||||
'LA': 'Louisiana',
|
||||
'ME': 'Maine',
|
||||
'MD': 'Maryland',
|
||||
'MA': 'Massachusetts',
|
||||
'MI': 'Michigan',
|
||||
'MN': 'Minnesota',
|
||||
'MS': 'Mississippi',
|
||||
'MO': 'Missouri',
|
||||
'MT': 'Montana',
|
||||
'NE': 'Nebraska',
|
||||
'NV': 'Nevada',
|
||||
'NH': 'New Hampshire',
|
||||
'NJ': 'New Jersey',
|
||||
'NM': 'New Mexico',
|
||||
'NY': 'New York',
|
||||
'NC': 'North Carolina',
|
||||
'ND': 'North Dakota',
|
||||
'OH': 'Ohio',
|
||||
'OK': 'Oklahoma',
|
||||
'OR': 'Oregon',
|
||||
'PA': 'Pennsylvania',
|
||||
'RI': 'Rhode Island',
|
||||
'SC': 'South Carolina',
|
||||
'SD': 'South Dakota',
|
||||
'TN': 'Tennessee',
|
||||
'TX': 'Texas',
|
||||
'UT': 'Utah',
|
||||
'VT': 'Vermont',
|
||||
'VA': 'Virginia',
|
||||
'WA': 'Washington',
|
||||
'WV': 'West Virginia',
|
||||
'WI': 'Wisconsin',
|
||||
'WY': 'Wyoming',
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
initializeComponent(cmsContent) {
|
||||
this.$refs.autocomplete.initializeComponent(cmsContent[0].QuestionText);
|
||||
this.$refs.city.initializeComponent(cmsContent[1].QuestionText);
|
||||
this.$refs.state.initializeComponent(cmsContent[2].QuestionText);
|
||||
this.$refs.zip.initializeComponent(cmsContent[3].QuestionText);
|
||||
|
||||
// assign alert texts to this component
|
||||
this.alertHeadlineVerificationWarning = cmsContent[4].HeadlineText;
|
||||
this.alertCopyVerificationWarning = cmsContent[4].BodyText;
|
||||
|
||||
this.alertHeadlineNoMatchWarning = cmsContent[5].HeadlineText;
|
||||
this.alertCopyNoMatchWarning = cmsContent[5].BodyText;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
|
||||
const addressField1 = document.getElementById("autocomplete");
|
||||
const self = this;
|
||||
|
||||
const apiKey = applicationConfig.GOOGLE_PLACES_API_KEY;
|
||||
|
||||
this.$loadScript(`https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places`)
|
||||
.then(() => {
|
||||
// Script is loaded, initialize the autocomplete textbox
|
||||
const autocomplete = new window.google.maps.places.Autocomplete(
|
||||
addressField1,
|
||||
{
|
||||
componentRestrictions: { country: ["us"] },
|
||||
fields: ["address_components"],
|
||||
types: ["address"],
|
||||
}
|
||||
);
|
||||
|
||||
// Standard place_changed event handling
|
||||
autocomplete.addListener('place_changed', fillInAddress);
|
||||
|
||||
addressField1.onblur = function() {
|
||||
const hover = document.querySelector(".pac-container .pac-item:hover");
|
||||
|
||||
// if an item has been clicked, do nothing, otherwise get first solution and use Geocoder to get the place
|
||||
if (hover === null) {
|
||||
const item = document.querySelector(".pac-container .pac-item");
|
||||
if (item != null) {
|
||||
const firstResult = item.textContent;
|
||||
const geocoder = new window.google.maps.Geocoder();
|
||||
geocoder.geocode({
|
||||
address: firstResult
|
||||
}, function (results, status) {
|
||||
if (status === window.google.maps.GeocoderStatus.OK) {
|
||||
fillInAddress(results[0]);
|
||||
self.displayVerificationWarning = true;
|
||||
self.displayNoMatchWarning = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
self.addressModel.city = "";
|
||||
self.addressModel.state = "";
|
||||
self.addressModel.zip = "";
|
||||
self.showAddressFields = true;
|
||||
self.displayVerificationWarning = false;
|
||||
self.displayNoMatchWarning = true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
function fillInAddress(place) {
|
||||
if (!place) {
|
||||
place = autocomplete.getPlace();
|
||||
}
|
||||
|
||||
if (place && place.address_components) {
|
||||
self.addressModel.streetAddress= "";
|
||||
self.showAddressFields = true;
|
||||
|
||||
for (const component of place.address_components) {
|
||||
const componentType = component.types[0];
|
||||
|
||||
switch (componentType) {
|
||||
case "street_number": {
|
||||
self.addressModel.streetAddress = component.long_name;
|
||||
break;
|
||||
}
|
||||
case "route": {
|
||||
self.addressModel.streetAddress += ' ' + component.short_name;
|
||||
break;
|
||||
}
|
||||
case "locality": {
|
||||
self.addressModel.city = component.long_name;
|
||||
break;
|
||||
}
|
||||
case "administrative_area_level_1": {
|
||||
self.addressModel.state = component.short_name;
|
||||
break;
|
||||
}
|
||||
case "postal_code": {
|
||||
self.addressModel.zip = component.long_name;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
self.displayVerificationWarning = false;
|
||||
self.displayNoMatchWarning = false;
|
||||
}
|
||||
else {
|
||||
self.displayVerificationWarning = true;
|
||||
self.displayNoMatchWarning = false;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
// Failed to fetch script
|
||||
console.log("Unable to load Google Places API script");
|
||||
});
|
||||
},
|
||||
components: {
|
||||
textboxQuestion,
|
||||
dropdownQuestion,
|
||||
alert,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<addressQuestions ref="addressQuestions" v-model="customerModel.addressQuestions" />
|
||||
<div class="row my-4">
|
||||
<div class="col">
|
||||
<textboxQuestion v-model="customerModel.firstName" ref="firstName" inputId="08497a2efd9a4a73a70360ab47b4838d" disableAutoFill validationRules="first-name-required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-4">
|
||||
<div class="col">
|
||||
<textboxQuestion v-model="customerModel.lastName" ref="lastName" inputId="0030e56a57e74a4ab92de7fb8e97fec5" disableAutoFill validationRules="last-name-required" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-4">
|
||||
<div class="col">
|
||||
<textboxQuestion v-model="customerModel.emailAddress" ref="emailAddress" inputId="00450a91b8964a768ce3992e6feb890f" disableAutoFill validationRules="email-address-required|email-address-format"/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import addressQuestions from "@/layouts/address-lookup/customer-questions/address-questions/address-questions";
|
||||
import textboxQuestion from "@/common-components/textbox-question/textbox-question";
|
||||
import { computed } from 'vue';
|
||||
import { defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
import { regex } from "@/helpers/validation-rules";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
//import store from "@/store";
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("first-name-required", required(errorMessages.FIRST_NAME_REQUIRED));
|
||||
defineRule("last-name-required", required(errorMessages.LAST_NAME_REQUIRED));
|
||||
defineRule("email-address-required", required(errorMessages.EMAIL_ADDRESS_REQUIRED));
|
||||
defineRule("email-address-format", regex(/^([a-zA-Z0-9_\-.+]+)@([a-zA-Z0-9_\-.]+).([a-zA-Z]{2,})$/, errorMessages.EMAIL_ADDRESS_FORMAT));
|
||||
|
||||
//EMAIL_ADDRESS_FORMAT
|
||||
|
||||
export default ({
|
||||
name: "customer-questions",
|
||||
emits: ['update:modelValue'], // The component emits an event
|
||||
props: {
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
customerQuestions: {
|
||||
addressQuestions: {
|
||||
streetAddress: "",
|
||||
city: "",
|
||||
state: "",
|
||||
zip: "",
|
||||
},
|
||||
firstName: "",
|
||||
lastName: "",
|
||||
emailAddress: "",
|
||||
}
|
||||
}),
|
||||
},
|
||||
validationRules: String,
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
// Please do not modify, this "computed" is used to track and report
|
||||
// this object's property changes to the parent component
|
||||
const customerModel = computed({ // Use computed to wrap the object
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value),
|
||||
});
|
||||
|
||||
return { customerModel };
|
||||
},
|
||||
methods: {
|
||||
initializeComponent(cmsContent){
|
||||
// pass alert texts to addressQuestions component
|
||||
this.$refs.addressQuestions.initializeComponent(cmsContent);
|
||||
|
||||
this.$refs.firstName.initializeComponent(cmsContent[6].QuestionText);
|
||||
this.$refs.lastName.initializeComponent(cmsContent[7].QuestionText);
|
||||
this.$refs.emailAddress.initializeComponent(cmsContent[8].QuestionText);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
value: {
|
||||
get: function() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
addressQuestions,
|
||||
textboxQuestion,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -1129,6 +1129,16 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-2">
|
||||
<div class="col">
|
||||
<alert
|
||||
alertClass="alert-success"
|
||||
alertHeadline="Multi-Paragraph Alert"
|
||||
:alertCopy="['This is an example of a MULTI-PARAGRAPH alert, which takes an array of strings instead of a single string value for alertCopy.', 'This is the second item in the array of strings.']"
|
||||
v-bind:isDismissible="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -1144,7 +1154,7 @@
|
|||
import textLink from "@/ux-components/text-link/text-link";
|
||||
import textboxQuestion from "@/common-components/textbox-question/textbox-question";
|
||||
import dropdownQuestion from "@/common-components/dropdown-question/dropdown-question";
|
||||
import vinInformation from "@/common-components/vin-information/vin-information";
|
||||
import vinInformation from "@/layouts/vin-lookup/vin-information/vin-information";
|
||||
export default {
|
||||
name: "App",
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -782,41 +782,6 @@ describe("vehicle-damage.vue", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("vehicle-damage.vue", () => {
|
||||
test("when onInvalidSubmit is triggered with errors focus will be put on the first element with an error", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
const mockedValidationPayload = {
|
||||
values: {},
|
||||
errors: {
|
||||
driverSideOptions: 'Please select window',
|
||||
passengerSideOptions: 'Please select window'
|
||||
},
|
||||
results: {},
|
||||
}
|
||||
const newObj = document.createElement('input');
|
||||
newObj.setAttribute("id", "testInput");
|
||||
newObj.setAttribute("data-focus-target", "driverSideOptions");
|
||||
document.body.appendChild(newObj);
|
||||
const testInputElement = document.getElementById("testInput");
|
||||
|
||||
//Act
|
||||
vehicleDamage.beforeRouteEnter.call(
|
||||
wrapper.vm,
|
||||
{ query: { fmgPage: "vehicle-damage" } },
|
||||
undefined,
|
||||
(c) => c(wrapper.vm)
|
||||
);
|
||||
wrapper.vm.onInvalidSubmit(mockedValidationPayload);
|
||||
await nextTick();
|
||||
const focusedEl = document.activeElement;
|
||||
|
||||
//Assert
|
||||
expect(testInputElement).toBe(focusedEl);
|
||||
});
|
||||
});
|
||||
|
||||
// THE FOLLOWING TEST IS NOT NECESSARILY REQUIRED FOR COVERAGE
|
||||
// BUT KEEP FOR AN EXAMPLE OF A VALIDATION TEST
|
||||
//
|
||||
|
|
|
|||
|
|
@ -1,15 +1,14 @@
|
|||
<template>
|
||||
<div class="container-fluid shadow rounded-3 p-2 position-relative make-tall">
|
||||
<Form
|
||||
@submit="onSubmit"
|
||||
@invalid-submit="onInvalidSubmit"
|
||||
ref="theForm"
|
||||
v-slot="{ meta }"
|
||||
>
|
||||
<div class="container-fluid shadow rounded-3 p-2 position-relative make-tall">
|
||||
<funnelHeader ref="funnelHeader" />
|
||||
<vehicleBanner ref="vehicleBanner" :displayGenericVehicleImage=false />
|
||||
<funnelSubHeader ref="funnelSubHeader" />
|
||||
<Form
|
||||
@submit="onSubmit"
|
||||
@invalid-submit="onInvalidSubmit"
|
||||
ref="theForm"
|
||||
v-slot="{ meta }"
|
||||
class="vehicle-damage-form"
|
||||
>
|
||||
<damageLocationQuestion
|
||||
ref="damageLocation"
|
||||
v-model="selectedDamageLocations"
|
||||
|
|
@ -27,7 +26,7 @@
|
|||
v-show="hasRepairReplaceConflict"
|
||||
alertClass="alert-danger"
|
||||
alertHeadline="You'll need to schedule separate appointments"
|
||||
alertCopy="Vehicle service requiring both glass repair and replacement must be scheduled separately, as they're performed by different technicians. Continue scheduling your first service now, and then come back to schedule the second service."
|
||||
:alertCopy="['Vehicle service requiring both glass repair and replacement must be scheduled separately, as they\'re performed by different technicians.', 'Continue scheduling your first service now, and then come back to schedule the second service.']"
|
||||
:isDismissible="false"
|
||||
/>
|
||||
<sideDoorOptions
|
||||
|
|
@ -50,8 +49,8 @@
|
|||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -166,19 +165,6 @@ export default {
|
|||
this.$route
|
||||
);
|
||||
},
|
||||
onSubmit() {}, // DO NOT REMOVE; needed to prevent default form submit behavior
|
||||
onInvalidSubmit({ values, errors, results }) {
|
||||
// identify the first error field and put focus on it
|
||||
// get error names array
|
||||
const errorNames = errors ? Object.keys(errors) : [];
|
||||
const firstErrorEl = errorNames[0];
|
||||
if (firstErrorEl) {
|
||||
const qsString = "[data-focus-target='" + firstErrorEl + "']";
|
||||
const el = document.querySelector(qsString);
|
||||
el && el.focus();
|
||||
}
|
||||
},
|
||||
|
||||
getDamageLocationsFromStore() {
|
||||
var glassSelections = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
v-show="showNoReplacementAvailableError"
|
||||
alertClass="alert-danger"
|
||||
alertHeadline="Service not available"
|
||||
alertCopy="We're sorry, but we currently offer only repair service for your vehicle type. Need help with next steps? Call us at 800-394-0288."
|
||||
:alertCopy="['We\'re sorry, but we currently offer only repair service for your vehicle type.', 'Need help with next steps? Call us at 800-394-0288.']"
|
||||
:isDismissible="false"
|
||||
/>
|
||||
<windshieldChipCountQuestion ref="windshieldChipCountQuestion"
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
isMultiSelect
|
||||
groupName="WindshieldReplaceOptions"
|
||||
v-model="selectedWindshieldReplaceOptionsValues"
|
||||
validationRules="windshield-replace-options-required|preventSplitAndSingleTogether"
|
||||
validationRules="windshield-replace-options-required|prevent-split-and-single-together"
|
||||
:suppressError="hasSplitSingleConflict"
|
||||
/>
|
||||
<alert
|
||||
|
|
@ -56,7 +56,7 @@ defineRule("windshield-damage-type-required", required(errorMessages.WINDSHIELD_
|
|||
defineRule("windshield-chip-count-required", required(errorMessages.WINDSHIELD_CHIP_COUNT_REQUIRED));
|
||||
defineRule("windshield-replace-options-required", required(errorMessages.WINSHIELD_REPLACE_OPTIONS_REQUIRED));
|
||||
|
||||
defineRule("checkForRepairAndReplace", (value, [otherFieldValue]) => {
|
||||
defineRule("check-for-repair-and-replace", (value, [otherFieldValue]) => {
|
||||
if (value.toString().toUpperCase().includes(damageLocationsSelected.REPAIR.toUpperCase()) &&
|
||||
otherFieldValue.toString().toUpperCase().includes(damageLocationsSelected.WINDSHIELD.toUpperCase()) &&
|
||||
Array.isArray(otherFieldValue) &&
|
||||
|
|
@ -66,13 +66,13 @@ defineRule("checkForRepairAndReplace", (value, [otherFieldValue]) => {
|
|||
}
|
||||
return true;
|
||||
});
|
||||
defineRule("repairOnly", (value) => {
|
||||
defineRule("repair-only", (value) => {
|
||||
if (value.toString().toUpperCase() === damageLocationsSelected.REPAIR.toUpperCase()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
defineRule("preventSplitAndSingleTogether", (value) => {
|
||||
defineRule("prevent-split-and-single-together", (value) => {
|
||||
if (value.toString().toUpperCase().includes(damageLocationsSelected.SINGLE.toUpperCase()) &&
|
||||
(value.toString().toUpperCase().includes(damageLocationsSelected.DRIVER.toUpperCase()) ||
|
||||
value.toString().toUpperCase().includes(damageLocationsSelected.PASSENGER.toUpperCase())))
|
||||
|
|
@ -184,10 +184,10 @@ export default ({
|
|||
},
|
||||
windshieldDamageTypeQuestionValidationRules() {
|
||||
// Note: the validation rules string is not dynamic (it cannot be changed once component has been created)
|
||||
let validationRules = "windshield-damage-type-required|checkForRepairAndReplace:@DamageLocationQuestion";
|
||||
let validationRules = "windshield-damage-type-required|check-for-repair-and-replace:@DamageLocationQuestion";
|
||||
// if vehicle has no windshield replacement option
|
||||
if (!this.isWindshieldReplaceAvailable) {
|
||||
validationRules = validationRules.concat('|repairOnly');
|
||||
validationRules = validationRules.concat('|repair-only');
|
||||
}
|
||||
return validationRules;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import vinInformation from "@/common-components/vin-information/vin-information";
|
||||
import vinInformation from "@/layouts/vin-lookup/vin-information/vin-information";
|
||||
|
||||
describe("vinInformation.vue", () => {
|
||||
it("Should return input class active if isActive is true", async () => {
|
||||
|
|
@ -1 +1,167 @@
|
|||
test.todo("some test to be written in the future");
|
||||
// Components
|
||||
import vinLookup from "@/layouts/vin-lookup/vin-lookup.vue";
|
||||
import vehicleDamage from "@/layouts/vehicle-damage/vehicle-damage.vue";
|
||||
import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
||||
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
|
||||
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||
import sideDoorOptions from "@/layouts/vehicle-damage/side-door-options/side-door-options";
|
||||
import damageLocationQuestion from "@/layouts/vehicle-damage/damage-location-question/damage-location-question";
|
||||
import windshieldOptions from "@/layouts/vehicle-damage/windshield-options/windshield-options";
|
||||
import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-question/replace-options-question";
|
||||
|
||||
// Supporting Files
|
||||
import { settleAllPromises } from "@/helpers/layout-helper.js";
|
||||
import baseMixin from "@/mixins/base-mixin";
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { mount, flushPromises } from "@vue/test-utils";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import { nextTick } from "vue";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import { storeMutations } from "@/constants/store-mutations";
|
||||
import store from "@/store";
|
||||
import { validate } from "vee-validate";
|
||||
import { damageLocationsSelected } from "@/constants/damage-locations-selected.js";
|
||||
|
||||
// Mock our module for promises.
|
||||
jest.mock("@/helpers/layout-helper.js", () => ({
|
||||
settleAllPromises: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock fetchCmsContentForPage
|
||||
jest.mock("@/helpers/cms-content-helper", () => ({
|
||||
fetchCmsContentForPage: jest.fn(),
|
||||
}));
|
||||
|
||||
// Mock Store
|
||||
jest.mock("@/store", () => ({
|
||||
commit: jest.fn(),
|
||||
dispatch: jest.fn(),
|
||||
getters: {
|
||||
vehicle: {
|
||||
carId: "C00000000",
|
||||
image: "test.jpg",
|
||||
},
|
||||
eventBusItem: jest.fn(),
|
||||
damage: {
|
||||
glassToReplace: []
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
describe("vin-lookup.vue", () => {
|
||||
|
||||
test("Call resetDependentState", async() => {
|
||||
const {wrapper} = setupMocks({});
|
||||
wrapper.vm.resetDependentState();
|
||||
expect(store.dispatch).toBeCalledWith(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// TEMP
|
||||
function setupMocks({
|
||||
pageHeaderWidgetHeaderText = {},
|
||||
mountOptionsMockData = {
|
||||
router: {
|
||||
navigate: jest.fn(),
|
||||
},
|
||||
store: {
|
||||
getters: {
|
||||
vehicle: {},
|
||||
},
|
||||
},
|
||||
},
|
||||
}) {
|
||||
//Mock api responses
|
||||
baseMixin.methods.dispatchNonBlockingStoreAction = jest.fn();
|
||||
const apiResponses = {
|
||||
cmsContent: {
|
||||
FunnelSubHeaderWidget: pageHeaderWidgetHeaderText,
|
||||
VehicleBannerWidget: {
|
||||
GenericVehicleImage:
|
||||
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
|
||||
},
|
||||
FunnelHeaderWidget: {
|
||||
LogoImage:
|
||||
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
|
||||
},
|
||||
},
|
||||
damageOptions: {
|
||||
driverSideOptions: {
|
||||
availableReplacementOptions: ["Front", "Back", "Side"],
|
||||
},
|
||||
passengerSideOptions: {
|
||||
availableReplacementOptions: ["Front", "Back", "Side"],
|
||||
},
|
||||
windshieldOptions: {
|
||||
availableReplacementOptions: ["Single", "Driver", "Passenger"],
|
||||
},
|
||||
backGlassOptions: {
|
||||
availableReplacementOptions: ["Front", "Back", "Side"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const apiPromise = Promise.resolve(apiResponses);
|
||||
|
||||
settleAllPromises.mockImplementation(() => apiPromise);
|
||||
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
|
||||
|
||||
//Mock damage initialize methods
|
||||
funnelHeader.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
vehicleBanner.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
funnelSubHeader.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
damageLocationQuestion.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
sideDoorOptions.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
windshieldOptions.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
replaceOptionsQuestion.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
updateSelectedValues: jest.fn(),
|
||||
};
|
||||
|
||||
funnelFooter.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
}
|
||||
|
||||
const mountOptions = getMountOptions(mountOptionsMockData);
|
||||
mountOptions['attachTo'] = document.body; // append wrapper to document.body to test DOM methods
|
||||
|
||||
const wrapper = mount(vinLookup, mountOptions);
|
||||
|
||||
const funnelHeaderWrapper = wrapper.findComponent({ name: "funnelHeader" });
|
||||
funnelHeaderWrapper.vm.initializeComponent =
|
||||
funnelHeader.methods.initializeComponent;
|
||||
|
||||
const vehicleBannerWrapper = wrapper.findComponent({ name: "vehicleBanner" });
|
||||
vehicleBannerWrapper.vm.initializeComponent =
|
||||
vehicleBanner.methods.initializeComponent;
|
||||
|
||||
const funnelSubHeaderWrapper = wrapper.findComponent({ name: "funnelSubHeader" });
|
||||
funnelSubHeaderWrapper.vm.initializeComponent =
|
||||
funnelSubHeader.methods.initializeComponent;
|
||||
|
||||
const funnelFooterWrapper = wrapper.findComponent({ name: "funnelFooter" });
|
||||
funnelFooterWrapper.vm.initializeComponent =
|
||||
funnelFooter.methods.initializeComponent;
|
||||
|
||||
return { wrapper, apiPromise };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,17 @@
|
|||
<funnelSubHeader ref="funnelSubHeader" />
|
||||
<h1>VIN Lookup Placeholder Page</h1>
|
||||
<Form
|
||||
@submit="onSubmit"
|
||||
@invalid-submit="onInvalidSubmit"
|
||||
ref="theForm"
|
||||
v-slot="{ meta }"
|
||||
class="d-flex flex-column h-100"
|
||||
@submit="onSubmit"
|
||||
@invalid-submit="onInvalidSubmit"
|
||||
ref="theForm"
|
||||
v-slot="{ meta }"
|
||||
class="d-flex flex-column h-100"
|
||||
>
|
||||
<funnel-footer
|
||||
ref="funnelFooter"
|
||||
:isDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
ref="funnelFooter"
|
||||
:isDisabled="!meta.valid"
|
||||
@back-clicked="backButtonAction"
|
||||
@ForwardClicked="forwardButtonAction"
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
|
|
@ -33,6 +33,7 @@ import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
|||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import store from "@/store";
|
||||
import { storeMutations } from "@/constants/store-mutations";
|
||||
import baseMixin from "@/mixins/base-mixin";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
import { required } from "@/helpers/validation-rules";
|
||||
|
|
@ -86,6 +87,16 @@ export default {
|
|||
arePagePrerequisitesValid() {
|
||||
return true;
|
||||
},
|
||||
resetDependentState() {
|
||||
store.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
||||
},
|
||||
backButtonAction() {
|
||||
// route to move backwards
|
||||
this.$router.navigate(
|
||||
this.navigationScenarios.CLICKED_BACK,
|
||||
this.$route
|
||||
);
|
||||
},
|
||||
},
|
||||
components: {
|
||||
funnelHeader,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { createApp } from "vue";
|
||||
import LoadScript from "vue-plugin-load-script";
|
||||
import Maska from "maska";
|
||||
import App from "./App.vue";
|
||||
import router from "./router";
|
||||
import store from "@/store";
|
||||
|
|
@ -12,6 +13,7 @@ const vueApp = createApp(App);
|
|||
vueApp.use(router);
|
||||
vueApp.use(store);
|
||||
vueApp.use(LoadScript);
|
||||
vueApp.use(Maska);
|
||||
vueApp.mixin(baseMixin);
|
||||
|
||||
vueApp.mount("#app");
|
||||
|
|
|
|||
|
|
@ -20,6 +20,18 @@ export default {
|
|||
savePageDataToStore(page, data){
|
||||
store.commit(storeMutations.UPDATE_PAGE_DATA, { page: page, data: data });
|
||||
},
|
||||
onSubmit() {}, // DO NOT REMOVE; needed to prevent default form submit behavior
|
||||
onInvalidSubmit({ values, errors, results }) {
|
||||
// identify the first error field and put focus on it
|
||||
// get error names array
|
||||
const errorNames = errors ? Object.keys(errors) : [];
|
||||
const firstErrorEl = errorNames[0];
|
||||
if (firstErrorEl) {
|
||||
const qsString = "[data-focus-target='" + firstErrorEl + "']";
|
||||
const el = document.querySelector(qsString);
|
||||
el && el.focus();
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
storeActions() {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import baseMixin from "@/mixins/base-mixin";
|
|||
import { storeActions } from "@/constants/store-actions.js";
|
||||
import { storeMutations } from "@/constants/store-mutations.js";
|
||||
import { navigationScenarios } from "@/router/router-constants/navigation-scenarios";
|
||||
import { vehicleCategories } from "@/constants/vehicle-categories.js";
|
||||
import store from "@/store";
|
||||
|
||||
describe("baseMixin.js", () => {
|
||||
|
|
@ -65,6 +66,35 @@ describe("baseMixin.js", () => {
|
|||
// Assert
|
||||
expect(navigationScenariosForTest).toEqual(navigationScenarios);
|
||||
});
|
||||
test("computed: vehicleCategories should be equal to import object", () => {
|
||||
// Arrange
|
||||
const mixIn = getMixInInstance({});
|
||||
|
||||
// Act
|
||||
let vehicleCategoriesForTest = mixIn.computed.vehicleCategories();
|
||||
|
||||
// Assert
|
||||
expect(vehicleCategoriesForTest).toEqual(vehicleCategories);
|
||||
});
|
||||
|
||||
test("onInvalidSubmit: puts focus on first error", () => {
|
||||
// Arrange
|
||||
const mixIn = getMixInInstance({});
|
||||
const validationData = {
|
||||
errors: {
|
||||
fieldOne: 'error message 1',
|
||||
fieldTwo: 'error message 2',
|
||||
}
|
||||
};
|
||||
|
||||
global.document.querySelector = jest.fn();
|
||||
|
||||
// Act
|
||||
mixIn.methods.onInvalidSubmit(validationData);
|
||||
|
||||
// Assert
|
||||
expect(global.document.querySelector).toBeCalledWith("[data-focus-target='fieldOne']");
|
||||
});
|
||||
});
|
||||
|
||||
function getMixInInstance({ isDispatchSuccess = true }) {
|
||||
|
|
@ -88,6 +118,7 @@ function getMixInInstance({ isDispatchSuccess = true }) {
|
|||
const baseMixIn = baseMixin;
|
||||
baseMixIn.methods.$route = route;
|
||||
baseMixIn.methods.storeActions = storeActions;
|
||||
baseMixIn.methods.vehicleCategories = vehicleCategories;
|
||||
|
||||
store.dispatch = storeDispatch;
|
||||
store.commit = jest.fn();
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ const fmgPageValues = {
|
|||
VEHICLE_MODEL: "vehicle-model",
|
||||
VEHICLE_STYLE: "vehicle-style",
|
||||
VEHICLE_DAMAGE: "vehicle-damage",
|
||||
ADDRESS_LOOKUP: "address-lookup",
|
||||
VIN_LOOKUP: "vin-lookup",
|
||||
VEHICLE_PARTS: "vehicle-parts",
|
||||
PART_QUESTIONS: "part-questions",
|
||||
|
|
|
|||
|
|
@ -101,6 +101,19 @@ const routingTable = [
|
|||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
fmgPageValue: fmgPageValues.VIN_LOOKUP,
|
||||
maps: [
|
||||
{
|
||||
scenario: navigationScenarios.CLICKED_BACK,
|
||||
destinationFmgPageValue: fmgPageValues.VEHICLE_DAMAGE,
|
||||
},
|
||||
{
|
||||
scenario: navigationScenarios.VIN_LOOKUP,
|
||||
destinationFmgPageValue: fmgPageValues.PART_QUESTIONS,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export { routingTable };
|
||||
|
|
|
|||
|
|
@ -154,7 +154,13 @@ export const mutations = {
|
|||
state.order.damage.glassToReplace = null;
|
||||
},
|
||||
resetRegistrationState(state) {
|
||||
|
||||
state.order.vehicle.registration.licensePlate = null;
|
||||
state.order.vehicle.registration.address = null;
|
||||
state.order.vehicle.registration.city = null;
|
||||
state.order.vehicle.registration.state = null;
|
||||
state.order.vehicle.registration.zipCode = null;
|
||||
state.order.vehicle.registration.firstName = null;
|
||||
state.order.vehicle.registration.lastName = null;
|
||||
},
|
||||
resetPartsState(state) {
|
||||
state.order.lineItems.glassParts = null;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,18 @@
|
|||
.has-error {
|
||||
&.list-button,
|
||||
&.list-card {
|
||||
border: 1px solid $gray-500;
|
||||
border: 1px solid $red;
|
||||
color: $red;
|
||||
label {
|
||||
box-shadow: 0 0 1px $red;
|
||||
box-shadow: 0 0 1px $red !important;
|
||||
border-radius: .5rem;
|
||||
}
|
||||
input[type=checkbox]:focus + label,
|
||||
input[type=radio]:focus + label {
|
||||
box-shadow: 0 0 0 2.5px $red;
|
||||
}
|
||||
}
|
||||
input[type=checkbox]:focus + label,
|
||||
input[type=radio]:focus + label,
|
||||
|
||||
input[type=checkbox]:checked + label,
|
||||
input[type=radio]:checked + label {
|
||||
box-shadow: 0 0 0 2.5px transparent !important;
|
||||
|
|
@ -19,6 +22,10 @@
|
|||
label {
|
||||
border: 1px solid $red;
|
||||
}
|
||||
input[type=checkbox]:focus + label,
|
||||
input[type=radio]:focus + label {
|
||||
box-shadow: 0 0 1px $red !important;
|
||||
}
|
||||
}
|
||||
&.ui-radio,
|
||||
&.ui-checkbox {
|
||||
|
|
@ -69,9 +76,9 @@
|
|||
&.btn.btn-primary:hover,
|
||||
&.btn.btn-primary:focus,
|
||||
&.btn.btn-primary:focus-visible {
|
||||
color: $gray;
|
||||
background: $gray-200;
|
||||
box-shadow: none;
|
||||
color: $gray !important;
|
||||
background: $gray-200 !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1 +1,30 @@
|
|||
test.todo("some test to be written in the future");
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import alert from "./alert";
|
||||
|
||||
describe("alert.vue", () => {
|
||||
|
||||
it("Should set isMultiParagraph to true if alertCopy is an array of strings", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(alert, {
|
||||
propsData: {
|
||||
alertCopy: ["one", "two"]
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.isMultiParagraph).toBe(true);
|
||||
});
|
||||
|
||||
it("Should set isMultiParagraph to false if alertCopy is a single string", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(alert, {
|
||||
propsData: {
|
||||
alertCopy: "three"
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.isMultiParagraph).toBe(false);
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,11 +1,16 @@
|
|||
<template>
|
||||
<div
|
||||
class="alert fade show text-center mb-0 py-2 px-3"
|
||||
class="alert fade show text-center mb-0 py-2 px-4"
|
||||
role="alert"
|
||||
:class="[isDismissible ? 'alert-dismissible' : '', this.alertClass]"
|
||||
>
|
||||
<p class="m-0 fw-bold small alert-heading">{{ alertHeadline }}</p>
|
||||
<p class="m-0 text-body small">{{ alertCopy }}</p>
|
||||
<span v-if="isMultiParagraph">
|
||||
<p class="m-1 text-body small" v-for="(para, index) in alertCopy" :key="index">
|
||||
{{ para }}
|
||||
</p>
|
||||
</span>
|
||||
<p class="m-0 text-body small" v-else>{{ alertCopy }}</p>
|
||||
<button
|
||||
type="button"
|
||||
class="btn-close p-2"
|
||||
|
|
@ -30,7 +35,7 @@ export default {
|
|||
name: "alert",
|
||||
props: {
|
||||
alertHeadline: String,
|
||||
alertCopy: String,
|
||||
alertCopy: [Array, String],
|
||||
isDismissible: Boolean,
|
||||
/*
|
||||
alertClass class names:
|
||||
|
|
@ -41,6 +46,14 @@ export default {
|
|||
*/
|
||||
alertClass: String,
|
||||
},
|
||||
computed: {
|
||||
isMultiParagraph() {
|
||||
if (typeof this.alertCopy == "string") {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -55,12 +55,8 @@ export default {
|
|||
color: $white;
|
||||
justify-content: center;
|
||||
font-weight: 500;
|
||||
&:hover {
|
||||
background: linear-gradient(
|
||||
270deg,
|
||||
rgba(6, 87, 124, 1) 0%,
|
||||
rgba(6, 87, 124, 1) 100%
|
||||
);
|
||||
@media (hover: hover) {
|
||||
background: linear-gradient(270deg, $blue-500 0%, $blue-700 100%);
|
||||
}
|
||||
&:focus, // Mouse, touch, stylus focus
|
||||
&:focus-visible {
|
||||
|
|
|
|||
|
|
@ -109,11 +109,15 @@ export default {
|
|||
const fieldOptions = {
|
||||
type: inputType,
|
||||
checkedValue: props.value,
|
||||
potentialInitialValue: props.selectedValues,
|
||||
};
|
||||
|
||||
if (Array.isArray(props.selectedValues) && props.selectedValues.length == 1) {
|
||||
fieldOptions['initialValue'] = fieldOptions.checkedValue;
|
||||
// Set initialValue for validation setup if pre-selected
|
||||
// NOTE: props.selectedValues could be an array of strings, or an array of integers...
|
||||
if (props.selectedValues && (props.selectedValues.includes(props.value) || props.selectedValues.includes(parseInt(props.value)))) {
|
||||
fieldOptions['initialValue'] = fieldOptions.potentialInitialValue;
|
||||
}
|
||||
|
||||
const {
|
||||
checked,
|
||||
handleChange,
|
||||
|
|
@ -124,6 +128,7 @@ export default {
|
|||
checked,
|
||||
handleChange,
|
||||
errors,
|
||||
fieldOptions, // only need to expose this for unit test purposes
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -166,11 +166,15 @@ describe("list-button.vue", () => {
|
|||
isRequired: true,
|
||||
isWide: false,
|
||||
modelValue: ["List Card Checkbox"],
|
||||
buttonID: 'list-card-id'
|
||||
},
|
||||
});
|
||||
|
||||
wrapper.vm.handleCheckChange();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: Boolean}]);
|
||||
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: Boolean, buttonId: 'list-card-id'}]);
|
||||
|
||||
});
|
||||
|
||||
it("Should set checkValue data if selectedButtonIDs has value(s)", async () => {
|
||||
|
|
@ -192,4 +196,5 @@ describe("list-button.vue", () => {
|
|||
// Assert
|
||||
expect(wrapper.componentVM.checkValue).toEqual("Car-Front");
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -100,7 +100,13 @@ export default {
|
|||
handleCheckChange(newValue, oldValue){
|
||||
const isInitialization = typeof(oldValue) === 'function';
|
||||
if (!isInitialization) {
|
||||
this.$emit('isCheckedChanged', { checkValue: this.checkValue, value: this.value.toString() });
|
||||
const emitEvent = {
|
||||
checkValue: this.checkValue,
|
||||
value: this.value.toString(),
|
||||
buttonId: this.buttonID.toString(),
|
||||
};
|
||||
this.$emit('isCheckedChanged', emitEvent);
|
||||
this.$emit("update:modelValue", emitEvent);
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -109,14 +115,19 @@ export default {
|
|||
},
|
||||
setup(props) {
|
||||
const inputType = props.isMultiSelect ? "checkbox" : "radio";
|
||||
|
||||
const fieldOptions = {
|
||||
type: inputType,
|
||||
checkedValue: props.value,
|
||||
potentialInitialValue: props.selectedValues,
|
||||
};
|
||||
|
||||
if (Array.isArray(props.selectedValues) && props.selectedValues.length == 1) {
|
||||
fieldOptions['initialValue'] = fieldOptions.checkedValue;
|
||||
// Set initialValue for validation setup if pre-selected
|
||||
// NOTE: props.selectedValues could be an array of strings, or an array of integers...
|
||||
if (props.selectedValues && (props.selectedValues.includes(props.value) || props.selectedValues.includes(parseInt(props.value)))) {
|
||||
fieldOptions['initialValue'] = fieldOptions.potentialInitialValue;
|
||||
}
|
||||
|
||||
const {
|
||||
checked,
|
||||
handleChange,
|
||||
|
|
@ -127,6 +138,7 @@ export default {
|
|||
checked,
|
||||
handleChange,
|
||||
errors,
|
||||
fieldOptions, // only need to expose this for unit test purposes
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -232,5 +232,20 @@ describe("list-card.vue", () => {
|
|||
expect(wrapper.componentVM.checkValue).toEqual("Car-Front");
|
||||
});
|
||||
|
||||
it("Should set an initial value for validation if selectedValues include the value", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(listCard, {
|
||||
propsData: {
|
||||
value: "Windshield",
|
||||
groupName: "radio 1",
|
||||
modelValue: ["Windshield"],
|
||||
selectedValues: ["Windshield"],
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.fieldOptions.initialValue).toEqual([ 'Windshield' ]);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
|
||||
<script>
|
||||
import { useField } from "vee-validate";
|
||||
|
||||
export default {
|
||||
name: "listCard",
|
||||
props: {
|
||||
|
|
@ -135,11 +136,14 @@ export default {
|
|||
|
||||
const fieldOptions = {
|
||||
type: inputType,
|
||||
checkedValue: props.value,
|
||||
checkedValue: props.value, // EX: "Single" or "Passenger"
|
||||
potentialInitialValue: props.selectedValues,
|
||||
};
|
||||
|
||||
if (Array.isArray(props.selectedValues) && props.selectedValues.length == 1) {
|
||||
fieldOptions['initialValue'] = fieldOptions.checkedValue;
|
||||
// Set initialValue for validation setup if pre-selected
|
||||
// NOTE: props.selectedValues could be an array of strings, or an array of integers...
|
||||
if (props.selectedValues && (props.selectedValues.includes(props.value) || props.selectedValues.includes(parseInt(props.value)))) {
|
||||
fieldOptions['initialValue'] = fieldOptions.potentialInitialValue;
|
||||
}
|
||||
|
||||
const {
|
||||
|
|
@ -150,6 +154,7 @@ export default {
|
|||
return {
|
||||
handleChange,
|
||||
errors,
|
||||
fieldOptions, // only need to expose this for unit test purposes
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
@ -204,17 +209,23 @@ export default {
|
|||
}
|
||||
}
|
||||
|
||||
&:focus + label {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&:checked + label {
|
||||
background: $blue-100;
|
||||
box-shadow: 0 0 0 1px $blue;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&:focus + label {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
&:checked {
|
||||
&:focus + label {
|
||||
box-shadow: 0 0 0 2.5px $blue;
|
||||
}
|
||||
}
|
||||
|
||||
&:checked + label {
|
||||
p {
|
||||
color: $black;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,9 @@ process.env.VUE_APP_CONSUMER_API_GATEWAY =
|
|||
process.env.VUE_APP_HERITAGE_FUNNEL =
|
||||
"http://localhost:38000/default.aspx";
|
||||
|
||||
process.env.VUE_APP_GOOGLE_PLACES_API_KEY =
|
||||
"AIzaSyDptGCkOPgN2uWJOy4ou4M33phRD4MAoJo"
|
||||
|
||||
module.exports = {
|
||||
outputDir: "dist/fmg",
|
||||
publicPath: "/fmg",
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
process.env.VUE_APP_CONSUMER_API_GATEWAY = "__VUE_APP_CONSUMER_API_GATEWAY__";
|
||||
process.env.VUE_APP_GOOGLE_PLACES_API_KEY = "__VUE_APP_GOOGLE_PLACES_API_KEY__";
|
||||
process.env.VUE_APP_HERITAGE_FUNNEL = "__VUE_APP_HERITAGE_FUNNEL__";
|
||||
|
||||
module.exports = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue