commit
61ecd7cf9f
55 changed files with 5427 additions and 2783 deletions
5
.prettierrc
Normal file
5
.prettierrc
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"tabWidth": 4,
|
||||||
|
"bracketSameLine": true,
|
||||||
|
"printWidth": 100
|
||||||
|
}
|
||||||
35
src/App.vue
35
src/App.vue
|
|
@ -1,16 +1,29 @@
|
||||||
<template>
|
<template>
|
||||||
<router-view v-slot="{ Component }">
|
<router-view v-slot="{ Component }">
|
||||||
<transition :duration="{ enter: 200, leave: 200 }" name="route-fade" mode="out-in">
|
<transition
|
||||||
<!-- The above durations should be kept in sync with the global css class "fade-on-route-transition" -->
|
:duration="{ enter: 200, leave: 200 }"
|
||||||
<component :is="Component" />
|
name="route-fade"
|
||||||
</transition>
|
mode="out-in">
|
||||||
</router-view>
|
<!-- The above durations should be kept in sync with the global css class "fade-on-route-transition" -->
|
||||||
|
<component :is="Component" @focusin="handleAnyComponentFocus" />
|
||||||
|
</transition>
|
||||||
|
</router-view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { handleAnyComponentFocus } from "@/helpers/button-question-focus-helper"
|
||||||
|
export default {
|
||||||
|
name: "app",
|
||||||
|
methods: {
|
||||||
|
handleAnyComponentFocus: handleAnyComponentFocus
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import "./node_modules/bootstrap/scss/bootstrap";
|
@import "./node_modules/bootstrap/scss/bootstrap";
|
||||||
@import "@/styles/common-styles.scss";
|
@import "@/styles/common-styles.scss";
|
||||||
@import "@/styles/common-typography-styles.scss";
|
@import "@/styles/common-typography-styles.scss";
|
||||||
@import "@/styles/common-error-styles.scss";
|
@import "@/styles/common-error-styles.scss";
|
||||||
@import "@/styles/common-animations.scss";
|
@import "@/styles/common-animations.scss";
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
1263
src/common-components/base-input-button/base-input-button.spec.js
Normal file
1263
src/common-components/base-input-button/base-input-button.spec.js
Normal file
File diff suppressed because it is too large
Load diff
201
src/common-components/base-input-button/base-input-button.vue
Normal file
201
src/common-components/base-input-button/base-input-button.vue
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
<template>
|
||||||
|
<label
|
||||||
|
:class="[buttonWrapperClasses, { 'has-error': errors.length > 0 }]"
|
||||||
|
:for="buttonId"
|
||||||
|
@focusin="handleFocus"
|
||||||
|
@focusout="handleBlur"
|
||||||
|
@mousedown.left="handleEventAction(eventTypes.CLICK, $event)">
|
||||||
|
<input
|
||||||
|
:type="inputType"
|
||||||
|
:id="buttonId"
|
||||||
|
:key="buttonId"
|
||||||
|
:name="groupName"
|
||||||
|
:class="inputClasses"
|
||||||
|
:aria-required="isRequired"
|
||||||
|
:value="value"
|
||||||
|
:checked="isChecked"
|
||||||
|
@keypress.space="handleEventAction(eventTypes.SPACE, $event)"
|
||||||
|
@keypress.enter="handleEventAction(eventTypes.ENTER, $event)"
|
||||||
|
@change="handleEventAction(eventTypes.CHANGE, $event)" />
|
||||||
|
|
||||||
|
<slot></slot>
|
||||||
|
</label>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { useField } from "vee-validate";
|
||||||
|
import { toRef } from "vue";
|
||||||
|
import { queryStrings } from "@/constants/query-strings";
|
||||||
|
import { handleButtonComponentFocus, handleInputComponentBlur } from "@/helpers/button-question-focus-helper";
|
||||||
|
import { inputButtonProps } from "@/common-components/base-input-button/button-functionality-props";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "base-input-button",
|
||||||
|
props: {
|
||||||
|
...inputButtonProps,
|
||||||
|
buttonWrapperClasses: [String, Array, Object],
|
||||||
|
inputClasses: [String, Array, Object],
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
valueToEmit: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.isChecked) {
|
||||||
|
this.handleChange(this.modelValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleEventAction(eventType, e) {
|
||||||
|
if (this.isMultiSelect) {
|
||||||
|
switch (eventType) {
|
||||||
|
case this.eventTypes.ENTER:
|
||||||
|
case this.eventTypes.CHANGE:
|
||||||
|
this.handleClick(e);
|
||||||
|
this.handlePushClickEventToGACheck(
|
||||||
|
this.eventTypes.CLICK
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
switch (eventType) {
|
||||||
|
case this.eventTypes.CLICK:
|
||||||
|
case this.eventTypes.ENTER:
|
||||||
|
case this.eventTypes.SPACE:
|
||||||
|
this.handleClick(e);
|
||||||
|
this.handlePushClickEventToGACheck(
|
||||||
|
this.eventTypes.CLICK
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case this.eventTypes.CHANGE:
|
||||||
|
this.selectingInitiatesLoad
|
||||||
|
? this.handleSelectionChange(e)
|
||||||
|
: this.handleClick(e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleSelectionChange(e) {
|
||||||
|
if (
|
||||||
|
this.isMultiSelect &&
|
||||||
|
(this.modelValue instanceof Array || this.modelValue == null)
|
||||||
|
) {
|
||||||
|
let newValue = this.modelValue ? [...this.modelValue] : [];
|
||||||
|
if (!newValue.includes(this.value)) {
|
||||||
|
newValue.push(this.value);
|
||||||
|
} else {
|
||||||
|
newValue.splice(newValue.indexOf(this.value), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.valueToEmit = newValue;
|
||||||
|
} else if (!this.isMultiSelect) {
|
||||||
|
this.valueToEmit = this.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleChange(this.valueToEmit);
|
||||||
|
},
|
||||||
|
handleClick(e) {
|
||||||
|
this.handleSelectionChange(e);
|
||||||
|
this.$emit("update:modelValue", this.valueToEmit);
|
||||||
|
},
|
||||||
|
handleFocus() {
|
||||||
|
handleButtonComponentFocus({
|
||||||
|
groupName: this.groupName,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handleBlur() {
|
||||||
|
handleInputComponentBlur({
|
||||||
|
groupName: this.groupName,
|
||||||
|
onButtonQuestionLostFocusCallback: this.handlePushClickEventToGACheck,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
handlePushClickEventToGACheck(source) {
|
||||||
|
// if from a click or click-like event
|
||||||
|
if (source === this.eventTypes.CLICK) {
|
||||||
|
this.pushClickEventToGA();
|
||||||
|
} else { // if from tabbing around
|
||||||
|
if (
|
||||||
|
this.valueToEmit !== null &&
|
||||||
|
!this.isValueSelectedOnClick &&
|
||||||
|
this.isChecked &&
|
||||||
|
this.lastValuePushedToGa != this.value
|
||||||
|
) {
|
||||||
|
this.pushClickEventToGA();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pushClickEventToGA(value) {
|
||||||
|
this.pushEventToGA(
|
||||||
|
this.$route.query[queryStrings.FMG_PAGE],
|
||||||
|
this.GaActions.CLICKED,
|
||||||
|
value?.toString() ?? this.value?.toString(),
|
||||||
|
true,
|
||||||
|
this.valueToLogType
|
||||||
|
);
|
||||||
|
|
||||||
|
this.setLastValuePushedToGa(value ?? this.value);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isChecked() {
|
||||||
|
if (this.isMultiSelect && this.modelValue instanceof Array) {
|
||||||
|
return this.modelValue.includes(this.value);
|
||||||
|
} else if (!this.isMultiSelect) {
|
||||||
|
return this.modelValue == this.value;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
inputType() {
|
||||||
|
return this.isMultiSelect ? "checkbox" : "radio";
|
||||||
|
},
|
||||||
|
buttonId() {
|
||||||
|
return `${this.groupName?.replace(" ", "-")}-${this.value
|
||||||
|
?.toString()
|
||||||
|
?.replace(" ", "-")}`;
|
||||||
|
},
|
||||||
|
isValueSelectedOnClick() {
|
||||||
|
return this.isMultiSelect || this.selectingInitiatesLoad;
|
||||||
|
},
|
||||||
|
eventTypes() {
|
||||||
|
return {
|
||||||
|
CHANGE: "change",
|
||||||
|
ENTER: "enter",
|
||||||
|
SPACE: "space",
|
||||||
|
CLICK: "click",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props) {
|
||||||
|
const inputType = props.isMultiSelect ? "checkbox" : "radio";
|
||||||
|
|
||||||
|
const fieldOptions = {
|
||||||
|
type: inputType,
|
||||||
|
validateOnValueUpdate: false,
|
||||||
|
validateOnMount: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
const { handleChange, meta, errors } = useField(
|
||||||
|
toRef(props, "groupName"),
|
||||||
|
toRef(props, "validationRules"),
|
||||||
|
fieldOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
handleChange,
|
||||||
|
errors,
|
||||||
|
meta,
|
||||||
|
fieldOptions, // only need to expose this for unit test purposes
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
input {
|
||||||
|
opacity: 0;
|
||||||
|
height: 0.1px; // NOTE: cannot be zero or Safari can't put focus on it
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
export const inputButtonProps = {
|
||||||
|
value: {
|
||||||
|
type: [String, Number],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: [Array, String, Number],
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
isMultiSelect: Boolean,
|
||||||
|
groupName: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
validationRules: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
valueToLogType: String,
|
||||||
|
isRequired: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
},
|
||||||
|
lastValuePushedToGa: [String, Number],
|
||||||
|
setLastValuePushedToGa: Function,
|
||||||
|
selectingInitiatesLoad: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,253 +1,249 @@
|
||||||
<!-- Documented in confluence https://safelite.atlassian.net/wiki/spaces/DC/pages/76644418/Button+Question+Component -->
|
<!-- Documented in confluence https://safelite.atlassian.net/wiki/spaces/DC/pages/76644418/Button+Question+Component -->
|
||||||
<template>
|
<template>
|
||||||
<div :class="isOverflowScrollable ? 'button-question button-question-overflow' : 'button-question'">
|
<div
|
||||||
<div v-if="questionText && answers && answers.length > 0" class="question-text d-flex">
|
:class="
|
||||||
<span class="fw-bold w-100">{{ questionText }}</span>
|
isOverflowScrollable ? 'button-question button-question-overflow' : 'button-question'
|
||||||
</div>
|
">
|
||||||
<div class="w-100 d-flex justify-content-center">
|
<div v-if="questionText && answers && answers.length > 0" class="question-text d-flex">
|
||||||
<fieldset class="w-100" :aria-required=isRequired :class="getFieldSetClasses" :role="isMultiSelect ? 'group' : 'radiogroup'" :aria-labelledby="formatString(groupName)">
|
<span class="fw-bold w-100">{{ questionText }}</span>
|
||||||
<legend class="sr-only" :data-focus-target="formatString(groupName)" :id="formatString(groupName)" tabindex="-1">
|
</div>
|
||||||
{{ questionText }}
|
|
||||||
{{(isMultiSelect && answers && answers.length > 1) ? 'Select one or more options below.' : 'Select an option below.' }}
|
<div class="w-100 d-flex justify-content-center">
|
||||||
</legend>
|
<fieldset
|
||||||
<div :class="getComponentLoopWrapperClasses">
|
class="w-100"
|
||||||
<div :class="getComponentWrapperClasses" v-for="answer in answers" :key="answer.Name ? answer.Name : answer">
|
:aria-required="isRequired"
|
||||||
<component
|
:class="getFieldSetClasses"
|
||||||
:is="buttonType"
|
:role="isMultiSelect ? 'group' : 'radiogroup'"
|
||||||
@isCheckedChanged="handleCheckedChanged"
|
:aria-labelledby="formatString(groupName)">
|
||||||
:buttonID="answer.Name ? formatString(groupName) + '-' + answer.Name : formatString(groupName) + '-' + getAnswerString(answer, 'Text')"
|
<legend
|
||||||
:value="getValue(answer)"
|
class="sr-only"
|
||||||
:buttonLabel="answer.Text ? answer.Text : getAnswerString(answer, 'Name')"
|
:data-focus-target="formatString(groupName)"
|
||||||
:buttonLabelSubCopy="answer.SubText"
|
tabindex="-1"
|
||||||
:textPosition="textPosition"
|
:id="formatString(groupName)">
|
||||||
:isMultiSelect="isMultiSelect"
|
{{ questionText }}
|
||||||
:groupName="formatString(groupName)"
|
{{
|
||||||
:selectingInitiatesLoad="selectingInitiatesLoad"
|
isMultiSelect && answers && answers.length > 1
|
||||||
:loaderColor="loaderColor"
|
? "Select one or more options below."
|
||||||
:loaderPosition="loaderPosition"
|
: "Select an option below."
|
||||||
:isWide=isWide
|
}}
|
||||||
:isCashOrInsurance=isCashOrInsurance
|
</legend>
|
||||||
:isRequired=isRequired
|
<div :class="getComponentLoopWrapperClasses">
|
||||||
:buttonImage="answer.AnswerImageUrl"
|
<div
|
||||||
:buttonImageId="answer.ImageId"
|
:class="getComponentWrapperClasses"
|
||||||
:altText="answer.Name ? answer.Name : answer"
|
v-for="answer in buttonsInfo"
|
||||||
screenReaderOnlyText="(opens new window)"
|
:key="answer.value ? answer.value : answer">
|
||||||
:colLength="getColLength"
|
<component
|
||||||
:selectedValues="selectedValues"
|
:is="buttonType"
|
||||||
data-test="button"
|
:buttonLabel="answer.buttonLabel"
|
||||||
:validationRules="validationRules"
|
:buttonLabelSubCopy="answer.buttonLabelSubCopy"
|
||||||
:class="[suppressError ? 'alertError' : '' , isCashOrInsurance ? 'radio-fancy' : '']"
|
:buttonImage="answer.buttonImage"
|
||||||
:valueToLogType="valueToLogType"
|
:buttonImageId="answer.buttonImageId"
|
||||||
/>
|
:groupName="answer.groupName"
|
||||||
<!-- For nested questions -->
|
:isMultiSelect="isMultiSelect"
|
||||||
<transition name="fade" mode="out-in">
|
:value="answer.value"
|
||||||
<div v-if="typeof selectedValues == 'string' && selectedValues == answer.Name">
|
:selectingInitiatesLoad="selectingInitiatesLoad"
|
||||||
<slot></slot>
|
:isWide="isWide"
|
||||||
</div>
|
:validationRules="validationRules"
|
||||||
</transition>
|
:textPosition="textPosition"
|
||||||
</div>
|
:lastValuePushedToGa="lastValuePushedToGa"
|
||||||
|
:setLastValuePushedToGa="setLastValuePushedToGa"
|
||||||
|
v-model="selectedValues" />
|
||||||
|
<!-- For nested questions -->
|
||||||
|
<transition name="fade" mode="out-in">
|
||||||
|
<div
|
||||||
|
v-if="
|
||||||
|
typeof selectedValues == 'string' &&
|
||||||
|
selectedValues == answer.value
|
||||||
|
">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
<div class="row form-test-error mt-1">
|
||||||
|
<error-message :name="formatString(groupName)" v-if="!suppressError"></error-message>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row form-test-error mt-1">
|
|
||||||
<error-message :name="formatString(groupName)" v-if="!suppressError"></error-message>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import listButton from "@/ux-components/list-button/list-button";
|
import listButton from "@/ux-components/list-button/list-button";
|
||||||
import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal";
|
import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal";
|
||||||
import listCard from "@/ux-components/list-card/list-card";
|
import listCard from "@/ux-components/list-card/list-card";
|
||||||
import { ErrorMessage } from 'vee-validate';
|
|
||||||
import radio from "@/ux-components/radio/radio";
|
import radio from "@/ux-components/radio/radio";
|
||||||
|
import { ErrorMessage } from "vee-validate";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "buttonQuestion",
|
name: "buttonQuestion",
|
||||||
props: {
|
props: {
|
||||||
buttonType: {
|
buttonType: {
|
||||||
type: String,
|
type: String,
|
||||||
default: "listButton",
|
default: "listButton",
|
||||||
|
},
|
||||||
|
isMultiSelect: Boolean,
|
||||||
|
groupName: String,
|
||||||
|
questionText: String,
|
||||||
|
answers: Array,
|
||||||
|
textPosition: {
|
||||||
|
type: String,
|
||||||
|
default: "text-center",
|
||||||
|
},
|
||||||
|
selectingInitiatesLoad: Boolean,
|
||||||
|
loaderColor: {
|
||||||
|
type: String,
|
||||||
|
default: "blue",
|
||||||
|
},
|
||||||
|
loaderPosition: {
|
||||||
|
type: String,
|
||||||
|
default: "right",
|
||||||
|
},
|
||||||
|
isRequired: Boolean,
|
||||||
|
isOverflowScrollable: Boolean,
|
||||||
|
isWide: Boolean,
|
||||||
|
isCashOrInsurance: Boolean,
|
||||||
|
modelValue: [Array, Number, String],
|
||||||
|
value: [Number, String],
|
||||||
|
validationRules: String,
|
||||||
|
suppressError: Boolean,
|
||||||
|
useTextForValue: Boolean,
|
||||||
|
valueToLogType: String,
|
||||||
},
|
},
|
||||||
isMultiSelect: Boolean,
|
data() {
|
||||||
groupName: String,
|
return {
|
||||||
questionText: String,
|
lastValuePushedToGa: null,
|
||||||
answers: Array,
|
};
|
||||||
textPosition: {
|
|
||||||
type: String,
|
|
||||||
default: "text-center",
|
|
||||||
},
|
},
|
||||||
selectingInitiatesLoad: Boolean,
|
computed: {
|
||||||
loaderColor: {
|
getFieldSetClasses() {
|
||||||
type: String,
|
if (this.isOverflowScrollable) {
|
||||||
default: "blue",
|
return "container-fluid overflow-scroll position-absolute px-5 pt-1 py-0";
|
||||||
},
|
} else if (this.buttonType == "listCard") {
|
||||||
loaderPosition: {
|
return "w-100";
|
||||||
type: String,
|
} else {
|
||||||
default: "right",
|
return "";
|
||||||
},
|
}
|
||||||
isRequired: Boolean,
|
},
|
||||||
isOverflowScrollable: Boolean,
|
getComponentLoopWrapperClasses() {
|
||||||
isWide: Boolean,
|
let classes;
|
||||||
isCashOrInsurance: Boolean,
|
switch (this.buttonType) {
|
||||||
modelValue: [Array, String],
|
case "listButton":
|
||||||
validationRules: String,
|
classes = "w-100";
|
||||||
suppressError: Boolean,
|
break;
|
||||||
useTextForValue: Boolean,
|
case "listButtonHorizontal":
|
||||||
valueToLogType: String,
|
classes = "d-flex flex-row p-0";
|
||||||
},
|
break;
|
||||||
computed: {
|
case "listCard":
|
||||||
getFieldSetClasses() {
|
classes = "row g-2 justify-content-center";
|
||||||
if (this.isOverflowScrollable) {
|
if (this.isWide) {
|
||||||
return "container-fluid overflow-scroll position-absolute px-5 pt-1 py-0";
|
classes += " flex-column";
|
||||||
}
|
}
|
||||||
else if (this.buttonType == "listCard") {
|
break;
|
||||||
return "w-100";
|
case "radio":
|
||||||
}
|
classes = "ui-radio d-flex";
|
||||||
else {
|
break;
|
||||||
return "";
|
}
|
||||||
}
|
return classes;
|
||||||
},
|
},
|
||||||
getComponentLoopWrapperClasses() {
|
getComponentWrapperClasses() {
|
||||||
let classes;
|
let classes = "";
|
||||||
switch (this.buttonType) {
|
|
||||||
case "listButton":
|
|
||||||
classes = "w-100";
|
|
||||||
break;
|
|
||||||
case "listButtonHorizontal":
|
|
||||||
classes = "d-flex flex-row p-0";
|
|
||||||
break;
|
|
||||||
case 'listCard':
|
|
||||||
classes = "row g-2 justify-content-center";
|
|
||||||
break;
|
|
||||||
case 'radio':
|
|
||||||
classes = 'ui-radio d-flex'
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return classes;
|
|
||||||
},
|
|
||||||
getComponentWrapperClasses() {
|
|
||||||
let classes = "";
|
|
||||||
|
|
||||||
classes += this.isWide ? "col-12" : "col";
|
classes += this.isWide ? "col-12" : "col";
|
||||||
|
|
||||||
if (this.buttonType == "radio") {
|
if (this.buttonType == "radio") {
|
||||||
classes += " radio-button-container";
|
classes += " radio-button-container";
|
||||||
}
|
}
|
||||||
|
|
||||||
return classes;
|
return classes;
|
||||||
|
},
|
||||||
|
buttonsInfo() {
|
||||||
|
return (Array.isArray(this.answers) ? this.answers : [])?.map((answer) => ({
|
||||||
|
buttonLabel: answer.buttonLabel ?? answer.Text ?? answer,
|
||||||
|
altText: answer.altText ?? (answer.Name ? answer.Name : answer),
|
||||||
|
buttonLabelSubCopy: answer.buttonLabelSubCopy ?? answer.SubText,
|
||||||
|
buttonImage: answer.buttonImage ?? answer.AnswerImageUrl,
|
||||||
|
buttonImageId: answer.buttonImageId ?? answer.ImageId,
|
||||||
|
groupName: this.formatString(this.groupName),
|
||||||
|
value:
|
||||||
|
answer.value ??
|
||||||
|
(this.useTextForValue && answer.Text ? answer.Text : answer.Name) ??
|
||||||
|
(typeof answer !== "object" ? answer : null),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
selectedValues: {
|
||||||
|
get() {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
set(selectedAnswers) {
|
||||||
|
this.$emit("update:modelValue", selectedAnswers);
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
getColLength(){
|
methods: {
|
||||||
if(this.isWide) {
|
formatString(str) {
|
||||||
return "12"
|
return str?.replaceAll(" ", "-");
|
||||||
} else {
|
},
|
||||||
return "";
|
setLastValuePushedToGa(lastValuePushedToGa) {
|
||||||
}
|
this.lastValuePushedToGa = lastValuePushedToGa;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
selectedValues: {
|
components: {
|
||||||
get: function() {
|
listButton,
|
||||||
return this.modelValue;
|
listButtonHorizontal,
|
||||||
},
|
listCard,
|
||||||
set: function(newValue) {
|
ErrorMessage,
|
||||||
this.$emit("update:modelValue", newValue);
|
radio,
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
formatString(str) {
|
|
||||||
return str.replace(" ", "-");
|
|
||||||
},
|
|
||||||
getValue(answer){
|
|
||||||
if (this.useTextForValue) { return answer.Text }
|
|
||||||
return answer.Name ? answer.Name : answer;
|
|
||||||
},
|
|
||||||
getAnswerString(answer, prop = "Name") {
|
|
||||||
switch (typeof answer) {
|
|
||||||
case "string":
|
|
||||||
case "number":
|
|
||||||
case "boolean":
|
|
||||||
return this.formatString(answer.toString());
|
|
||||||
default:
|
|
||||||
return answer[prop] ? this.formatString(answer[prop]) : this.formatString(answer.toString());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleCheckedChanged(val) {
|
|
||||||
if(this.selectingInitiatesLoad) {
|
|
||||||
this.selectedValues = val.value;
|
|
||||||
} else {
|
|
||||||
if(this.isMultiSelect) {
|
|
||||||
const newSelectedValues = this.selectedValues;
|
|
||||||
val.checkValue ? newSelectedValues.push(val.value) : newSelectedValues.splice(newSelectedValues.indexOf(val.value), 1);
|
|
||||||
this.selectedValues = newSelectedValues;
|
|
||||||
}
|
|
||||||
else if (Array.isArray(this.selectedValues)) {
|
|
||||||
this.selectedValues[0] = val.value;
|
|
||||||
const temp = this.selectedValues;
|
|
||||||
this.selectedValues = temp;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.selectedValues = val.value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.$emit("isCheckedChanged", val);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
listButton,
|
|
||||||
listButtonHorizontal,
|
|
||||||
listCard,
|
|
||||||
ErrorMessage,
|
|
||||||
radio,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.button-question-overflow {
|
.button-question-overflow {
|
||||||
height: calc(100vh - 274px);
|
height: calc(100vh - 274px);
|
||||||
|
|
||||||
.overflow-scroll {
|
.overflow-scroll {
|
||||||
// Height will be determined by overall height of content above list
|
// Height will be determined by overall height of content above list
|
||||||
height: calc(100% - 314px);
|
height: calc(100% - 314px);
|
||||||
overflow-x: hidden !important;
|
overflow-x: hidden !important;
|
||||||
-webkit-overflow-scrolling: touch;
|
-webkit-overflow-scrolling: touch;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.button-question {
|
.button-question {
|
||||||
color: $black;
|
color: $black;
|
||||||
|
|
||||||
.radio-button-container {
|
.radio-button-container {
|
||||||
&:not(:last-child) {
|
&:not(:last-child) {
|
||||||
padding-bottom: map-get($spacers, 2);
|
padding-bottom: map-get($spacers, 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.question-text {
|
.question-text {
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
line-height: 1.625rem;
|
line-height: 1.625rem;
|
||||||
|
|
||||||
& > span {
|
& > span {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.vehicle-parts {
|
.vehicle-parts {
|
||||||
.question-text {
|
.question-text {
|
||||||
span {
|
span {
|
||||||
font-size: .875rem;
|
font-size: 0.875rem;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
margin: 0 0 .5rem 0;
|
margin: 0 0 0.5rem 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.question-text {
|
||||||
.question-text {
|
margin: 0;
|
||||||
margin: 0;
|
}
|
||||||
}
|
fieldset {
|
||||||
fieldset {
|
.ui-radio {
|
||||||
.ui-radio {
|
margin: 0;
|
||||||
margin: 0;
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@
|
||||||
:questionText="q.questionText"
|
:questionText="q.questionText"
|
||||||
:answers="q.answers"
|
:answers="q.answers"
|
||||||
:groupName="`question-${glassIndex}-${q.questionSequence}`"
|
:groupName="`question-${glassIndex}-${q.questionSequence}`"
|
||||||
v-model="q.answerSelected"
|
:modelValue="q.answerSelected"
|
||||||
@isCheckedChanged="handleAnswer"
|
@update:modelValue="handleAnswer(q, $event)"
|
||||||
isRequired
|
isRequired
|
||||||
:validationRules="validationRules"
|
:validationRules="validationRules"
|
||||||
/>
|
/>
|
||||||
|
|
@ -45,13 +45,13 @@ export default {
|
||||||
questionSequence: q.questionSequence,
|
questionSequence: q.questionSequence,
|
||||||
answers: q.answers.map((a) => {
|
answers: q.answers.map((a) => {
|
||||||
return {
|
return {
|
||||||
Text: a.answerText,
|
buttonLabel: a.answerText,
|
||||||
// Name will either be nextQuestionSequence or answerResult
|
// Name will either be nextQuestionSequence or answerResult
|
||||||
// Name will be used by list-button as the input value.
|
// Name will be used by list-button as the input value.
|
||||||
// It must be a single string or number, so concatenating together a string with
|
// It must be a single string or number, so concatenating together a string with
|
||||||
// 4 pieces of data separated by pipe characters:
|
// 4 pieces of data separated by pipe characters:
|
||||||
// question number|type of answer|answer value|answer text
|
// question number|type of answer|answer value|answer text
|
||||||
Name: a.nextQuestionSequence ?
|
value: a.nextQuestionSequence ?
|
||||||
q.questionSequence + "|nextQuestion|" + a.nextQuestionSequence + "|" + a.answerText :
|
q.questionSequence + "|nextQuestion|" + a.nextQuestionSequence + "|" + a.answerText :
|
||||||
q.questionSequence + "|answer|" + a.answerResult + "|" + a.answerText,
|
q.questionSequence + "|answer|" + a.answerResult + "|" + a.answerText,
|
||||||
nextQuestionSequence: a.nextQuestionSequence,
|
nextQuestionSequence: a.nextQuestionSequence,
|
||||||
|
|
@ -77,7 +77,8 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleAnswer(returnedAnswer) {
|
handleAnswer(question, returnedAnswer) {
|
||||||
|
question.answerSelected = returnedAnswer;
|
||||||
/*
|
/*
|
||||||
returnedAnswer example format:
|
returnedAnswer example format:
|
||||||
{
|
{
|
||||||
|
|
@ -86,7 +87,7 @@ export default {
|
||||||
"buttonId": "Driver-Front-1-1|answer|DD11132|Yes"
|
"buttonId": "Driver-Front-1-1|answer|DD11132|Yes"
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
const isQuestionChainComplete = this.getQuestionChainAnswerIfComplete(returnedAnswer.value);
|
const isQuestionChainComplete = this.getQuestionChainAnswerIfComplete(returnedAnswer);
|
||||||
|
|
||||||
if (isQuestionChainComplete) {
|
if (isQuestionChainComplete) {
|
||||||
this.$emit("update:modelValue", isQuestionChainComplete);
|
this.$emit("update:modelValue", isQuestionChainComplete);
|
||||||
|
|
|
||||||
|
|
@ -18,11 +18,11 @@ const storeActions = {
|
||||||
LOOKUP_VEHICLE_BY_VIN: "lookupVehicleByVin",
|
LOOKUP_VEHICLE_BY_VIN: "lookupVehicleByVin",
|
||||||
LOOKUP_VIN_BY_PLATE: "lookupVinByPlate",
|
LOOKUP_VIN_BY_PLATE: "lookupVinByPlate",
|
||||||
LOOKUP_VIN_BY_ADDRESS: "lookupVinByAddress",
|
LOOKUP_VIN_BY_ADDRESS: "lookupVinByAddress",
|
||||||
|
|
||||||
GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions",
|
GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions",
|
||||||
GET_PARTS: "getParts",
|
GET_PARTS: "getParts",
|
||||||
GET_CAPABILITY_QUESTIONS: "getCapabilityQuestions",
|
GET_CAPABILITY_QUESTIONS: "getCapabilityQuestions",
|
||||||
GET_PART_FROM_CAPABILITY_QUESTION_ANSWER: "getPartFromCapabilityQuestionAnswer",
|
GET_PART_FROM_CAPABILITY_QUESTION_ANSWER:
|
||||||
|
"getPartFromCapabilityQuestionAnswer",
|
||||||
GET_MOLDING_QUESTIONS: "getMoldingQuestions",
|
GET_MOLDING_QUESTIONS: "getMoldingQuestions",
|
||||||
SAVE_SESSION: "saveSession",
|
SAVE_SESSION: "saveSession",
|
||||||
LOAD_SESSION: "loadSession",
|
LOAD_SESSION: "loadSession",
|
||||||
|
|
@ -45,22 +45,23 @@ const storeActions = {
|
||||||
RESET_STATE: "resetState",
|
RESET_STATE: "resetState",
|
||||||
|
|
||||||
// SAVE COMPONENT STATE
|
// SAVE COMPONENT STATE
|
||||||
SAVE_VEHICLE_YEAR: "saveVehicleYear",
|
SAVE_VEHICLE_YEAR: "saveVehicleYear",
|
||||||
SAVE_VEHICLE_MAKE:"saveVehicleMake",
|
SAVE_VEHICLE_MAKE: "saveVehicleMake",
|
||||||
SAVE_VEHICLE_MODEL:"saveVehicleModel",
|
SAVE_VEHICLE_MODEL: "saveVehicleModel",
|
||||||
SAVE_VEHICLE_STYLE: "saveVehicleStyle",
|
SAVE_VEHICLE_STYLE: "saveVehicleStyle",
|
||||||
SAVE_VEHICLE_DAMAGE: "saveVehicleDamage",
|
SAVE_VEHICLE_DAMAGE: "saveVehicleDamage",
|
||||||
SAVE_VIN_LOOKUP: "saveVinLookup",
|
SAVE_VIN_LOOKUP: "saveVinLookup",
|
||||||
SAVE_SERVICE_LOCATION: "saveServiceLocation",
|
SAVE_SERVICE_LOCATION: "saveServiceLocation",
|
||||||
SAVE_EMAIL: "saveEmail",
|
SAVE_EMAIL: "saveEmail",
|
||||||
SAVE_REGISTRATION_LICENSE_PLATE_LOOKUP: "saveRegistrationLicensePlateLookup",
|
SAVE_REGISTRATION_LICENSE_PLATE_LOOKUP: "saveRegistrationLicensePlateLookup",
|
||||||
SAVE_VIN: "saveVin",
|
SAVE_VIN: "saveVin",
|
||||||
SAVE_REGISTRATION_ADDRESS_LOOKUP: "saveRegistrationAddressLookup",
|
SAVE_REGISTRATION_ADDRESS_LOOKUP: "saveRegistrationAddressLookup",
|
||||||
SAVE_GLASS_PARTS: "saveGlassParts",
|
SAVE_GLASS_PARTS: "saveGlassParts",
|
||||||
SAVE_PART_QUESTION_ANSWERS: "savePartQuestionAnswers",
|
SAVE_PART_QUESTION_ANSWERS: "savePartQuestionAnswers",
|
||||||
RESET_MOLDING_AND_CAPABILITY_QUESTIONS_IF_NEEDED: "resetMoldingAndCapabilityQuestionAnswersIfNeeded",
|
RESET_MOLDING_AND_CAPABILITY_QUESTIONS_IF_NEEDED:
|
||||||
|
"resetMoldingAndCapabilityQuestionAnswersIfNeeded",
|
||||||
SAVE_MOLDING_QUESTION_ANSWERS: "saveMoldingQuestionAnswers",
|
SAVE_MOLDING_QUESTION_ANSWERS: "saveMoldingQuestionAnswers",
|
||||||
SAVE_CAPABILITY_QUESTION_ANSWERS: "saveCapabilityQuestionAnswers"
|
SAVE_CAPABILITY_QUESTION_ANSWERS: "saveCapabilityQuestionAnswers",
|
||||||
};
|
};
|
||||||
|
|
||||||
export { storeActions };
|
export { storeActions };
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
const storeMutations = {
|
const storeMutations = {
|
||||||
|
|
||||||
// VEHICLE MUTATIONS
|
// VEHICLE MUTATIONS
|
||||||
UPDATE_YEAR: "updateYear",
|
UPDATE_YEAR: "updateYear",
|
||||||
UPDATE_MAKE: "updateMake",
|
UPDATE_MAKE: "updateMake",
|
||||||
|
|
@ -22,7 +21,7 @@ const storeMutations = {
|
||||||
UPDATE_GLASS_PARTS: "updateGlassParts",
|
UPDATE_GLASS_PARTS: "updateGlassParts",
|
||||||
UPDATE_OTHER_PARTS: "updateOtherParts",
|
UPDATE_OTHER_PARTS: "updateOtherParts",
|
||||||
|
|
||||||
UPDATE_REGISTRATION_LICENSE_PLATE : "updateRegistrationLicensePlate",
|
UPDATE_REGISTRATION_LICENSE_PLATE: "updateRegistrationLicensePlate",
|
||||||
UPDATE_REGISTRATION_ADDRESS: "updateRegistrationAddress",
|
UPDATE_REGISTRATION_ADDRESS: "updateRegistrationAddress",
|
||||||
UPDATE_REGISTRATION_CITY: "updateRegistrationCity",
|
UPDATE_REGISTRATION_CITY: "updateRegistrationCity",
|
||||||
UPDATE_REGISTRATION_STATE: "updateRegistrationState",
|
UPDATE_REGISTRATION_STATE: "updateRegistrationState",
|
||||||
|
|
@ -69,4 +68,4 @@ const storeMutations = {
|
||||||
UPDATE_TRIGGERED_SITE_ENTRY: "updateTriggeredSiteEntry",
|
UPDATE_TRIGGERED_SITE_ENTRY: "updateTriggeredSiteEntry",
|
||||||
};
|
};
|
||||||
|
|
||||||
export { storeMutations };
|
export { storeMutations };
|
||||||
41
src/helpers/button-question-focus-helper.js
Normal file
41
src/helpers/button-question-focus-helper.js
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
/**
|
||||||
|
* Helper for GA click event. When the user mouse clicks on a `base-input-button`, we
|
||||||
|
* push the click event. When the user tabs through a list of radio buttons via a
|
||||||
|
* keyboard, we only want to push the GA click event if the selection was deliberate
|
||||||
|
* (space/enter key) or if there is a selection and the user tabs off of the radio group.
|
||||||
|
*/
|
||||||
|
|
||||||
|
let lastFocusedInputGroupName = "";
|
||||||
|
let onButtonQuestionLostFocusCallback = null;
|
||||||
|
|
||||||
|
const handleAnyComponentFocus = (e) => {
|
||||||
|
const targetType = e.target.type;
|
||||||
|
if (targetType !== "radio" && targetType !== "checkbox") {
|
||||||
|
invokeButtonQuestionLostFocusCallback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleButtonComponentFocus = (e) => {
|
||||||
|
if (e && lastFocusedInputGroupName !== e.groupName) {
|
||||||
|
invokeButtonQuestionLostFocusCallback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputComponentBlur = (e) => {
|
||||||
|
if (e) {
|
||||||
|
lastFocusedInputGroupName = e.groupName;
|
||||||
|
onButtonQuestionLostFocusCallback = e.onButtonQuestionLostFocusCallback;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const invokeButtonQuestionLostFocusCallback = () => {
|
||||||
|
if (onButtonQuestionLostFocusCallback) {
|
||||||
|
onButtonQuestionLostFocusCallback();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
handleAnyComponentFocus,
|
||||||
|
handleButtonComponentFocus,
|
||||||
|
handleInputComponentBlur,
|
||||||
|
};
|
||||||
251
src/helpers/button-question-focus-helper.spec.js
Normal file
251
src/helpers/button-question-focus-helper.spec.js
Normal file
|
|
@ -0,0 +1,251 @@
|
||||||
|
import {
|
||||||
|
handleAnyComponentFocus,
|
||||||
|
handleButtonComponentFocus,
|
||||||
|
handleInputComponentBlur,
|
||||||
|
} from "@/helpers/button-question-focus-helper";
|
||||||
|
|
||||||
|
describe("buttonQuestionFocusHelper", () => {
|
||||||
|
let onButtonQuestionLostFocusCallbackOne = jest.fn();
|
||||||
|
let onButtonQuestionLostFocusCallbackTwo = jest.fn();
|
||||||
|
|
||||||
|
let focusOnInputInGroupOne;
|
||||||
|
let blurFromInputInGroupOne;
|
||||||
|
let focusOnInputInGroupTwo;
|
||||||
|
let blurFromInputInGroupTwo;
|
||||||
|
|
||||||
|
let focusOnNonRadioCheckboxElement;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
onButtonQuestionLostFocusCallbackOne = jest.fn();
|
||||||
|
onButtonQuestionLostFocusCallbackTwo = jest.fn();
|
||||||
|
|
||||||
|
// Sanity check
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled();
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
focusOnInputInGroupOne = () => {
|
||||||
|
handleButtonComponentFocus({
|
||||||
|
groupName: "group1",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
blurFromInputInGroupOne = () => {
|
||||||
|
handleInputComponentBlur({
|
||||||
|
groupName: "group1",
|
||||||
|
onButtonQuestionLostFocusCallback: onButtonQuestionLostFocusCallbackOne,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
focusOnInputInGroupTwo = () => {
|
||||||
|
handleButtonComponentFocus({
|
||||||
|
groupName: "group2",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
blurFromInputInGroupTwo = () => {
|
||||||
|
handleInputComponentBlur({
|
||||||
|
groupName: "group2",
|
||||||
|
onButtonQuestionLostFocusCallback: onButtonQuestionLostFocusCallbackTwo,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
focusOnNonRadioCheckboxElement = () => {
|
||||||
|
handleAnyComponentFocus({
|
||||||
|
target: {
|
||||||
|
type: "nonRadioCheckbox",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
test("focus on input => no callbacks were called", () => {
|
||||||
|
// focus on input in group 1
|
||||||
|
focusOnInputInGroupOne();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled();
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("focus on input, then focus on input in same group => no callbacks are called", () => {
|
||||||
|
// focus on input in group 1
|
||||||
|
focusOnInputInGroupOne();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled();
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// focus on input in group 1
|
||||||
|
blurFromInputInGroupOne();
|
||||||
|
focusOnInputInGroupOne();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled();
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("focus on non-radio/checkbox, focus on input => no callbacks are called", () => {
|
||||||
|
// focus on non-radio/checkbox
|
||||||
|
focusOnNonRadioCheckboxElement();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled();
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// focus on input
|
||||||
|
// focus on input in group 1
|
||||||
|
focusOnInputInGroupOne();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled();
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("focus on input in group 1, then focus on input in different group => callback for group 1 is called", () => {
|
||||||
|
// focus on input in group 1
|
||||||
|
focusOnInputInGroupOne();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled();
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// focus on input in different group
|
||||||
|
blurFromInputInGroupOne();
|
||||||
|
focusOnInputInGroupTwo();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("focus on input in group 1, focus on input in same group, then focus on input in different group => callback for group 1 is called", () => {
|
||||||
|
// focus on input in group 1
|
||||||
|
focusOnInputInGroupOne();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled();
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// focus on input in same group
|
||||||
|
blurFromInputInGroupOne();
|
||||||
|
focusOnInputInGroupOne();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled();
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// focus on input in different group
|
||||||
|
blurFromInputInGroupOne();
|
||||||
|
focusOnInputInGroupTwo();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("focus on input in group 1, focus on non-radio/checkbox element => callback from group 1 is called", () => {
|
||||||
|
// focus on input in group 1
|
||||||
|
focusOnInputInGroupOne();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled();
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// focus on input in group 2
|
||||||
|
blurFromInputInGroupOne();
|
||||||
|
focusOnNonRadioCheckboxElement();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("focus on input in group 1, focus on input in group 2, focus on non-radio/checkbox element => both callbacks are called", () => {
|
||||||
|
// focus on input in group 1
|
||||||
|
focusOnInputInGroupOne();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled();
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// focus on input in group 2
|
||||||
|
blurFromInputInGroupOne();
|
||||||
|
focusOnInputInGroupTwo();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
|
// focus on non-radio/checkbox
|
||||||
|
blurFromInputInGroupTwo();
|
||||||
|
focusOnNonRadioCheckboxElement();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("focus on input in group 1, focus on input in group 2, focus on input in group 1 => both callbacks are called", () => {
|
||||||
|
// focus on input in group 1
|
||||||
|
focusOnInputInGroupOne();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled();
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// focus on input in group 2
|
||||||
|
blurFromInputInGroupOne();
|
||||||
|
focusOnInputInGroupTwo();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).toHaveBeenCalledTimes(0);
|
||||||
|
|
||||||
|
// focus on input in group 1
|
||||||
|
blurFromInputInGroupTwo();
|
||||||
|
focusOnInputInGroupOne();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("go back and forth a lot => correct callbacks are called at the right time", () => {
|
||||||
|
// Arrange/Act
|
||||||
|
// focus on input in group 1
|
||||||
|
focusOnInputInGroupOne();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).not.toHaveBeenCalled();
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// focus on input in group 2
|
||||||
|
blurFromInputInGroupOne();
|
||||||
|
focusOnInputInGroupTwo();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).not.toHaveBeenCalled();
|
||||||
|
|
||||||
|
// focus on input in group 1
|
||||||
|
blurFromInputInGroupTwo();
|
||||||
|
focusOnInputInGroupOne();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(1);
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// focus on non-radio/checkbox element
|
||||||
|
blurFromInputInGroupOne();
|
||||||
|
focusOnNonRadioCheckboxElement();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(2);
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
// focus on input in group 1
|
||||||
|
focusOnInputInGroupOne();
|
||||||
|
expect(onButtonQuestionLostFocusCallbackOne).toHaveBeenCalledTimes(2);
|
||||||
|
expect(onButtonQuestionLostFocusCallbackTwo).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -25,7 +25,6 @@ export async function loadSessionIfPresent() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Load referral if there is a cookie, and it doesn't indicate it needs a state reset.
|
// Load referral if there is a cookie, and it doesn't indicate it needs a state reset.
|
||||||
return (await loadSession(funnelCookie.ReferralNumber, funnelCookie.ReferralDate, funnelCookie.ReferralCorrelationId, funnelCookie.ReferralParentAccountNumber)).data;
|
return (await loadSession(funnelCookie.ReferralNumber, funnelCookie.ReferralDate, funnelCookie.ReferralCorrelationId, funnelCookie.ReferralParentAccountNumber)).data;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,14 +34,14 @@ describe("addressVehiclesQuestion.vue", () => {
|
||||||
const wrapper = shallowMount(addressVehiclesQuestion, {
|
const wrapper = shallowMount(addressVehiclesQuestion, {
|
||||||
mixins: [mockMixin],
|
mixins: [mockMixin],
|
||||||
propsData: {
|
propsData: {
|
||||||
vehicles: ["1", "2"],
|
vehicles: ["1", "2", "newValue"],
|
||||||
modelValue: ["1", "2"],
|
modelValue: "2",
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
const localThis = { $emit: jest.fn() }
|
const localThis = { $emit: jest.fn() }
|
||||||
addressVehiclesQuestion.computed.selectedVehicleVinAsArray.set.call(localThis, ['newValue']);
|
addressVehiclesQuestion.computed.selectedVehicleVin.set.call(localThis, 'newValue');
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
expect(localThis.$emit).toBeCalledWith("update:modelValue", "newValue");
|
expect(localThis.$emit).toBeCalledWith("update:modelValue", "newValue");
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
groupName="ChooseAddressVehicle"
|
groupName="ChooseAddressVehicle"
|
||||||
:questionText="questionText"
|
:questionText="questionText"
|
||||||
:answers="vehicles"
|
:answers="vehicles"
|
||||||
v-model="selectedVehicleVinAsArray"
|
v-model="selectedVehicleVin"
|
||||||
isRequired
|
isRequired
|
||||||
:validation-rules="validationRules"
|
:validation-rules="validationRules"
|
||||||
:valueToLogType="ValueToLogTypes.LAST_5"
|
:valueToLogType="ValueToLogTypes.LAST_5"
|
||||||
|
|
@ -63,18 +63,16 @@ export default {
|
||||||
questionText() {
|
questionText() {
|
||||||
return this.getCmsContent("VehicleConfirmationQuestion", "QuestionText");
|
return this.getCmsContent("VehicleConfirmationQuestion", "QuestionText");
|
||||||
},
|
},
|
||||||
selectedVehicleVinAsArray: {
|
selectedVehicleVin: {
|
||||||
get: function() {
|
get: function() {
|
||||||
const modelValueAsArray = this.modelValue ? [this.modelValue] : [];
|
return this.modelValue;
|
||||||
return modelValueAsArray;
|
|
||||||
},
|
},
|
||||||
set: function(newValue) {
|
set: function(newValue) {
|
||||||
const newValueAsScalar = newValue && newValue.length > 0 ? newValue[newValue.length-1] : null;
|
this.$emit("update:modelValue", newValue);
|
||||||
this.$emit("update:modelValue", newValueAsScalar);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectedVehicle() { // this computed is only needed for the computed differentVehicleAlertBody text above
|
selectedVehicle() { // this computed is only needed for the computed differentVehicleAlertBody text above
|
||||||
return this.vehicles.find( ({ vin }) => vin === this.selectedVehicleVinAsArray[this.selectedVehicleVinAsArray.length-1] );
|
return this.vehicles.find( ({ vin }) => vin === this.selectedVehicleVin[this.selectedVehicleVin.length-1] );
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,7 @@ export default {
|
||||||
selectedVehicleVin: {
|
selectedVehicleVin: {
|
||||||
handler() {
|
handler() {
|
||||||
// does this vehicle match the previously selected carId?
|
// does this vehicle match the previously selected carId?
|
||||||
this.isCarIdDifferent = this.selectedVehicle.vehicle.carId !== store.getters.vehicle.carId;
|
this.isCarIdDifferent = this.selectedVehicle?.vehicle.carId !== store.getters.vehicle.carId;
|
||||||
if (this.isCarIdDifferent) {
|
if (this.isCarIdDifferent) {
|
||||||
this.$refs.funnelFooter.updateButtonText(`Continue with ${this.selectedVehicle.vehicle.year} ${this.selectedVehicle.vehicle.make} ${this.selectedVehicle.vehicle.model}`);
|
this.$refs.funnelFooter.updateButtonText(`Continue with ${this.selectedVehicle.vehicle.year} ${this.selectedVehicle.vehicle.make} ${this.selectedVehicle.vehicle.model}`);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -168,7 +168,6 @@ describe("estimate.vue", () => {
|
||||||
store.commit(storeMutations.UPDATE_IS_REPAIR, null);
|
store.commit(storeMutations.UPDATE_IS_REPAIR, null);
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
console.log(store.getters.damage)
|
|
||||||
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@
|
||||||
validationRules="option-required"
|
validationRules="option-required"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="isRepair">
|
<div v-else>
|
||||||
<alert
|
<alert
|
||||||
class="my-4"
|
class="my-4"
|
||||||
cmsWidgetName="AlertQuoteReady"
|
cmsWidgetName="AlertQuoteReady"
|
||||||
|
|
@ -87,7 +87,6 @@
|
||||||
cmsWidgetName="FunnelFooterWidget"
|
cmsWidgetName="FunnelFooterWidget"
|
||||||
ref="funnelFooter"
|
ref="funnelFooter"
|
||||||
:isForwardActionDisabled="!meta.valid"
|
:isForwardActionDisabled="!meta.valid"
|
||||||
@isDisabled="!meta.valid"
|
|
||||||
@back-clicked="backButtonAction"
|
@back-clicked="backButtonAction"
|
||||||
@ForwardClicked="forwardButtonAction"
|
@ForwardClicked="forwardButtonAction"
|
||||||
/>
|
/>
|
||||||
|
|
@ -131,7 +130,7 @@ export default {
|
||||||
name: "estimate",
|
name: "estimate",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
selectedVinLookupMethod: "",
|
selectedVinLookupMethod: null,
|
||||||
serviceZipCode: this.getZipFromStore(),
|
serviceZipCode: this.getZipFromStore(),
|
||||||
emailAddress: this.getEmailFromStore(),
|
emailAddress: this.getEmailFromStore(),
|
||||||
displayInvalidZipAlert: false,
|
displayInvalidZipAlert: false,
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ describe("replace-options-question.vue", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("replace-options-question.vue", () => {
|
describe("replace-options-question.vue", () => {
|
||||||
test("when updateSelectedValues method is called with a single answerToDisplay it will call to update this.selectedValues", async () => {
|
test("when updateSelectedValues method is called with a single answerToDisplay it will call to update this.selectedReplaceOptions", async () => {
|
||||||
|
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper, cmsContent, replaceOptions
|
const { wrapper, cmsContent, replaceOptions
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
v-model="selectedValues"
|
v-model="selectedValues"
|
||||||
:validationRules="validationRules"
|
:validationRules="validationRules"
|
||||||
:suppressError="suppressError"
|
:suppressError="suppressError"
|
||||||
:isRequired=isRequired
|
:isRequired="isRequired"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</transition>
|
</transition>
|
||||||
|
|
@ -25,14 +25,14 @@ export default ({
|
||||||
name: "replaceOptionsQuestion",
|
name: "replaceOptionsQuestion",
|
||||||
data(){
|
data(){
|
||||||
return {
|
return {
|
||||||
replaceOptions: [],
|
replaceOptions: this.isMultiSelect ? [] : "",
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
isAvailable: Boolean,
|
isAvailable: Boolean,
|
||||||
filterByVehicleCategory: Boolean,
|
filterByVehicleCategory: Boolean,
|
||||||
groupName: String,
|
groupName: String,
|
||||||
modelValue: Array,
|
modelValue: [Array, String, Number],
|
||||||
isMultiSelect: Boolean,
|
isMultiSelect: Boolean,
|
||||||
validationRules: String,
|
validationRules: String,
|
||||||
suppressError: Boolean,
|
suppressError: Boolean,
|
||||||
|
|
@ -46,7 +46,7 @@ export default ({
|
||||||
updateSelectedValues() {
|
updateSelectedValues() {
|
||||||
// UPDATE SELECTEDVALUES IF ONLY ONE ANSWER
|
// UPDATE SELECTEDVALUES IF ONLY ONE ANSWER
|
||||||
if(Array.isArray(this.answersToDisplay) && this.answersToDisplay.length === 1) {
|
if(Array.isArray(this.answersToDisplay) && this.answersToDisplay.length === 1) {
|
||||||
this.selectedValues = [this.answersToDisplay[0].Name];
|
this.selectedValues = this.isMultiSelect ? [this.answersToDisplay[0].Name] : this.answersToDisplay[0].Name;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -91,7 +91,7 @@ export default ({
|
||||||
},
|
},
|
||||||
shouldDisplayReplaceOptionsQuestion(shouldDisplayReplaceOptionsQuestion) {
|
shouldDisplayReplaceOptionsQuestion(shouldDisplayReplaceOptionsQuestion) {
|
||||||
if (!shouldDisplayReplaceOptionsQuestion) {
|
if (!shouldDisplayReplaceOptionsQuestion) {
|
||||||
this.selectedValues = [];
|
this.selectedValues = this.isMultiSelect ? [] : "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -57,7 +57,7 @@ export default ({
|
||||||
name: "sideDoorOptions",
|
name: "sideDoorOptions",
|
||||||
props: {
|
props: {
|
||||||
groupName: String,
|
groupName: String,
|
||||||
modelValue: Array,
|
modelValue: Object,
|
||||||
selectedDamageLocations: Array,
|
selectedDamageLocations: Array,
|
||||||
cmsWidgetName: String,
|
cmsWidgetName: String,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -129,20 +129,20 @@ describe("vehicle-damage.vue", () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
wrapper.vm.selectedDamageLocations = ["Windshield", "SideDoor", "RearWindow"];
|
wrapper.setData({
|
||||||
wrapper.vm.selectedWindshieldOptions = {
|
selectedDamageLocations: ["Windshield", "SideDoor", "RearWindow"],
|
||||||
selectedWindshieldChipCount: null,
|
selectedWindshieldOptions: {
|
||||||
selectedWindshieldReplaceOptions: ["Single"],
|
selectedWindshieldChipCount: null,
|
||||||
selectedWindshieldDamageType: "Replace"
|
selectedWindshieldReplaceOptions: ["Single"],
|
||||||
};
|
selectedWindshieldDamageType: "Replace"
|
||||||
|
},
|
||||||
wrapper.vm.sideDoorOptionsData = {
|
sideDoorOptionsData: {
|
||||||
selectedDoorSides: ["DriverSide", "PassengerSide"],
|
selectedDoorSides: ["DriverSide", "PassengerSide"],
|
||||||
selectedDriverSideReplaceOptions: ["Back"],
|
selectedDriverSideReplaceOptions: ["Back"],
|
||||||
selectedPassengerSideReplaceOptions: ["Quarter"]
|
selectedPassengerSideReplaceOptions: ["Quarter"]
|
||||||
};
|
},
|
||||||
|
selectedRearReplaceOptions: "Stationary",
|
||||||
wrapper.vm.selectedRearReplaceOptions = ["Stationary"];
|
})
|
||||||
|
|
||||||
const expectedGlassToReplace = [{ location: "Windshield", name: "Single" }, { location: "Driver", name: "Back" },
|
const expectedGlassToReplace = [{ location: "Windshield", name: "Single" }, { location: "Driver", name: "Back" },
|
||||||
{ location: "Passenger", name: "Quarter" }, { location: "Rear", name: "Stationary" }];
|
{ location: "Passenger", name: "Quarter" }, { location: "Rear", name: "Stationary" }];
|
||||||
|
|
@ -519,19 +519,19 @@ describe("vehicle-damage.vue", () => {
|
||||||
|
|
||||||
const storeWindshieldOptions = [[1, false, "Windshield", "Single", {
|
const storeWindshieldOptions = [[1, false, "Windshield", "Single", {
|
||||||
selectedWindshieldDamageType: damageLocationsSelected.REPLACE,
|
selectedWindshieldDamageType: damageLocationsSelected.REPLACE,
|
||||||
selectedWindshieldChipCount: [], selectedWindshieldReplaceOptions: [damageLocationsSelected.SINGLE]
|
selectedWindshieldChipCount: null, selectedWindshieldReplaceOptions: [damageLocationsSelected.SINGLE]
|
||||||
}],
|
}],
|
||||||
[2, false, "Windshield", "Driver", {
|
[2, false, "Windshield", "Driver", {
|
||||||
selectedWindshieldDamageType: damageLocationsSelected.REPLACE,
|
selectedWindshieldDamageType: damageLocationsSelected.REPLACE,
|
||||||
selectedWindshieldChipCount: [], selectedWindshieldReplaceOptions: [damageLocationsSelected.DRIVER]
|
selectedWindshieldChipCount: null, selectedWindshieldReplaceOptions: [damageLocationsSelected.DRIVER]
|
||||||
}],
|
}],
|
||||||
[3, false, "Windshield", "Passenger", {
|
[3, false, "Windshield", "Passenger", {
|
||||||
selectedWindshieldDamageType: damageLocationsSelected.REPLACE,
|
selectedWindshieldDamageType: damageLocationsSelected.REPLACE,
|
||||||
selectedWindshieldChipCount: [], selectedWindshieldReplaceOptions: [damageLocationsSelected.PASSENGER]
|
selectedWindshieldChipCount: null, selectedWindshieldReplaceOptions: [damageLocationsSelected.PASSENGER]
|
||||||
}],
|
}],
|
||||||
[4, true, "", "", {
|
[4, true, "", "", {
|
||||||
selectedWindshieldDamageType: damageLocationsSelected.REPAIR,
|
selectedWindshieldDamageType: damageLocationsSelected.REPAIR,
|
||||||
selectedWindshieldChipCount: [2], selectedWindshieldReplaceOptions: []
|
selectedWindshieldChipCount: 2, selectedWindshieldReplaceOptions: []
|
||||||
}]
|
}]
|
||||||
];
|
];
|
||||||
test.each(storeWindshieldOptions)("getWindshieldOptionsFromStore test #%s", async (testNum, isRepair, damageLocation, damageName, expectedWindshieldOptions) => {
|
test.each(storeWindshieldOptions)("getWindshieldOptionsFromStore test #%s", async (testNum, isRepair, damageLocation, damageName, expectedWindshieldOptions) => {
|
||||||
|
|
@ -618,8 +618,8 @@ describe("vehicle-damage.vue", () => {
|
||||||
expect(glassSelections).toEqual(expectedGlass);
|
expect(glassSelections).toEqual(expectedGlass);
|
||||||
});
|
});
|
||||||
|
|
||||||
const rearReplaceOptions = [["Rear", "Stationary", [damageLocationsSelected.STATIONARY]],
|
const rearReplaceOptions = [["Rear", "Stationary", damageLocationsSelected.STATIONARY],
|
||||||
["Rear", "Slider", [damageLocationsSelected.SLIDER]]
|
["Rear", "Slider", damageLocationsSelected.SLIDER]
|
||||||
];
|
];
|
||||||
test.each(rearReplaceOptions)("getRearReplaceOptionsFromStore for %s-%s returns expected %s", async (damageLocation, damageName, expectedGlass) => {
|
test.each(rearReplaceOptions)("getRearReplaceOptionsFromStore for %s-%s returns expected %s", async (damageLocation, damageName, expectedGlass) => {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -196,11 +196,15 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
getWindshieldOptionsFromStore() {
|
getWindshieldOptionsFromStore() {
|
||||||
var windShieldOptions = { selectedWindshieldDamageType: "", selectedWindshieldChipCount: [], selectedWindshieldReplaceOptions: []};
|
var windShieldOptions = { selectedWindshieldDamageType: "", selectedWindshieldChipCount: null, selectedWindshieldReplaceOptions: []};
|
||||||
|
|
||||||
if (store.getters.damage.isRepair === undefined) return windshieldOptions;
|
if (store.getters.damage.isRepair === undefined) return windshieldOptions;
|
||||||
|
|
||||||
if (!store.getters.damage.isRepair) {
|
if (store.getters.damage.isRepair) {
|
||||||
|
windShieldOptions.selectedWindshieldDamageType = damageLocationsSelected.REPAIR;
|
||||||
|
windShieldOptions.selectedWindshieldChipCount = store.getters.damage.numberOfChips;
|
||||||
|
}
|
||||||
|
else {
|
||||||
if (store.getters.damage.glassToReplace?.some(glass => { return glass.location === damageLocationsSelected.WINDSHIELD &&
|
if (store.getters.damage.glassToReplace?.some(glass => { return glass.location === damageLocationsSelected.WINDSHIELD &&
|
||||||
glass.name === damageLocationsSelected.SINGLE })) {
|
glass.name === damageLocationsSelected.SINGLE })) {
|
||||||
windShieldOptions.selectedWindshieldDamageType = damageLocationsSelected.REPLACE;
|
windShieldOptions.selectedWindshieldDamageType = damageLocationsSelected.REPLACE;
|
||||||
|
|
@ -220,13 +224,7 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (store.getters.damage.isRepair) {
|
|
||||||
windShieldOptions.selectedWindshieldDamageType = damageLocationsSelected.REPAIR;
|
|
||||||
windShieldOptions.selectedWindshieldChipCount.push(store.getters.damage.numberOfChips);
|
|
||||||
}
|
|
||||||
|
|
||||||
return windShieldOptions;
|
return windShieldOptions;
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
getDoorSidesFromStore() {
|
getDoorSidesFromStore() {
|
||||||
|
|
@ -265,15 +263,9 @@ export default {
|
||||||
|
|
||||||
return passengerSideReplaceOptions;
|
return passengerSideReplaceOptions;
|
||||||
},
|
},
|
||||||
|
|
||||||
getRearReplaceOptionsFromStore(){
|
getRearReplaceOptionsFromStore() {
|
||||||
var rearReplaceOptions = [];
|
var rearReplaceOptions = store.getters.damage.glassToReplace?.filter(glass => glass.location === damageLocationsSelected.REAR)[0]?.name;
|
||||||
|
|
||||||
store.getters.damage.glassToReplace?.forEach(glass => {
|
|
||||||
if (glass.location === damageLocationsSelected.REAR){
|
|
||||||
rearReplaceOptions.push(glass.name);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return rearReplaceOptions;
|
return rearReplaceOptions;
|
||||||
},
|
},
|
||||||
|
|
@ -290,7 +282,6 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
navigateForward(){
|
navigateForward(){
|
||||||
|
|
||||||
// If vin already exists, navigate directly to vin-lookup
|
// If vin already exists, navigate directly to vin-lookup
|
||||||
if(store.getters.vehicle.vin) {
|
if(store.getters.vehicle.vin) {
|
||||||
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD_WITH_VIN, this.$route);
|
this.$router.navigateWithSaving(this.navigationScenarios.CLICKED_FORWARD_WITH_VIN, this.$route);
|
||||||
|
|
@ -321,9 +312,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isRearWindowDamageLocation) {
|
if (this.isRearWindowDamageLocation) {
|
||||||
this.selectedRearReplaceOptions.forEach(rearItem => {
|
selectedGlassToReplace.push({ location: damageLocationsSelected.REAR, name: this.selectedRearReplaceOptions});
|
||||||
selectedGlassToReplace.push({ location: damageLocationsSelected.REAR, name: rearItem});
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectedGlassToReplace;
|
return selectedGlassToReplace;
|
||||||
|
|
@ -373,15 +362,15 @@ export default {
|
||||||
hasSplitSingleConflict() {
|
hasSplitSingleConflict() {
|
||||||
if (!this.selectedDamageLocations?.includes("Windshield") || this.selectedWindshieldOptions.selectedWindshieldDamageType === damageLocationsSelected.REPAIR || !this.selectedWindshieldOptions.selectedWindshieldReplaceOptions) return false;
|
if (!this.selectedDamageLocations?.includes("Windshield") || this.selectedWindshieldOptions.selectedWindshieldDamageType === damageLocationsSelected.REPAIR || !this.selectedWindshieldOptions.selectedWindshieldReplaceOptions) return false;
|
||||||
|
|
||||||
return this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.some(selectedSingleWindshield =>
|
return this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some(selectedSingleWindshield =>
|
||||||
{
|
{
|
||||||
return selectedSingleWindshield.toUpperCase() === damageLocationsSelected.SINGLE.toUpperCase();
|
return selectedSingleWindshield.toUpperCase() === damageLocationsSelected.SINGLE.toUpperCase();
|
||||||
}) &&
|
}) &&
|
||||||
(this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.some(selectedDriverWindshield =>
|
(this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some(selectedDriverWindshield =>
|
||||||
{
|
{
|
||||||
return selectedDriverWindshield.toUpperCase() === damageLocationsSelected.DRIVER.toUpperCase();
|
return selectedDriverWindshield.toUpperCase() === damageLocationsSelected.DRIVER.toUpperCase();
|
||||||
}) ||
|
}) ||
|
||||||
this.selectedWindshieldOptions.selectedWindshieldReplaceOptions.some(selectedPassengerWindshield =>
|
this.selectedWindshieldOptions.selectedWindshieldReplaceOptions?.some(selectedPassengerWindshield =>
|
||||||
{
|
{
|
||||||
return selectedPassengerWindshield.toUpperCase() === damageLocationsSelected.PASSENGER.toUpperCase();
|
return selectedPassengerWindshield.toUpperCase() === damageLocationsSelected.PASSENGER.toUpperCase();
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -1,78 +1,81 @@
|
||||||
import { shallowMount } from "@vue/test-utils";
|
import { shallowMount } from "@vue/test-utils";
|
||||||
import windshieldChipCountQuestion from "@/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question";
|
import windshieldChipCountQuestion from "@/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question";
|
||||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||||
import { nextTick } from "vue";
|
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
|
|
||||||
jest.mock("@/store", () => { return {}; }, {virtual: true});
|
jest.mock(
|
||||||
|
"@/store",
|
||||||
|
() => {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
{ virtual: true }
|
||||||
|
);
|
||||||
|
|
||||||
describe("windshield-chip-count-question.vue", () => {
|
describe("windshield-chip-count-question.vue", () => {
|
||||||
test("Selected chip count is emitted upon selection.", async () => {
|
test("Selected chip count is emitted upon selection.", async () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({modelValueProp: ["One"]});
|
const { wrapper } = setupMocks({ modelValueProp: 1 });
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
wrapper.setValue({ modelValue: ["Two"] });
|
wrapper.vm.selectedValue = "2";
|
||||||
await wrapper.vm.$nextTick();
|
|
||||||
|
//Assert
|
||||||
//Assert
|
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([2]);
|
||||||
expect(wrapper.vm.selectedChipCountValues).toEqual(["One"]);
|
|
||||||
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ modelValue: ["Two"] }]);
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
describe("Windshield-chip-count-question.vue", () => {
|
test("selectedValue matches modelValue", () => {
|
||||||
test("Should display question and answers from api.", async () => {
|
// Arrange/Act
|
||||||
//Arrange
|
const { wrapper } = setupMocks({ modelValueProp: 2 });
|
||||||
const { wrapper } = setupMocks({modelValueProp: ["One"]});
|
|
||||||
|
|
||||||
//Act
|
// Assert
|
||||||
wrapper.setProps({isAvailable: true});
|
expect(wrapper.vm.selectedValue).toBe(2);
|
||||||
wrapper.vm.updateSelectedValues = jest.fn();
|
});
|
||||||
await wrapper.vm.$nextTick();
|
});
|
||||||
|
|
||||||
//Assert
|
function setupMocks({
|
||||||
expect(wrapper.vm.updateSelectedValues).toBeCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function setupMocks({
|
|
||||||
modelValueProp = ["Two"],
|
modelValueProp = ["Two"],
|
||||||
groupName = "WindshieldChipCountQuestion",
|
groupName = "WindshieldChipCountQuestion",
|
||||||
cmsQuestionText = "How many chips are we repairing?",
|
cmsQuestionText = "How many chips are we repairing?",
|
||||||
cmsAnswers = [{Name: "One"}, {Name: "Two"}, {Name: "Three"}],
|
cmsAnswers = [{ Name: "One" }, { Name: "Two" }, { Name: "Three" }],
|
||||||
dataFromStoreApi = [],
|
dataFromStoreApi = [],
|
||||||
}) {
|
}) {
|
||||||
|
|
||||||
//Mock store
|
//Mock store
|
||||||
store.dispatch = jest.fn(() => dataFromStoreApi);
|
store.dispatch = jest.fn(() => dataFromStoreApi);
|
||||||
store.getters = { vehicle: {year: 2019, make: 'honda', model: 'civc', style: '2 Door', category: 'CAR'} };
|
store.getters = {
|
||||||
|
vehicle: {
|
||||||
|
year: 2019,
|
||||||
|
make: "honda",
|
||||||
|
model: "civc",
|
||||||
|
style: "2 Door",
|
||||||
|
category: "CAR",
|
||||||
|
},
|
||||||
|
};
|
||||||
const mountOptions = getMountOptions({
|
const mountOptions = getMountOptions({
|
||||||
store: {
|
store: {
|
||||||
dispatch: store.dispatch,
|
dispatch: store.dispatch,
|
||||||
getters: store.getters,
|
getters: store.getters,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//Mock props
|
//Mock props
|
||||||
const mockMixin = {
|
const mockMixin = {
|
||||||
methods: {
|
methods: {
|
||||||
getCmsContent: jest.fn()
|
getCmsContent: jest.fn(),
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
mountOptions.propsData = {
|
mountOptions.propsData = {
|
||||||
modelValue: modelValueProp
|
modelValue: modelValueProp,
|
||||||
};
|
};
|
||||||
mountOptions.mixins = [mockMixin];
|
mountOptions.mixins = [mockMixin];
|
||||||
|
|
||||||
const wrapper = shallowMount(windshieldChipCountQuestion, mountOptions);
|
const wrapper = shallowMount(windshieldChipCountQuestion, mountOptions);
|
||||||
|
|
||||||
//Mock CMS content
|
//Mock CMS content
|
||||||
const cmsContent = {
|
const cmsContent = {
|
||||||
groupName: groupName,
|
groupName: groupName,
|
||||||
QuestionText: cmsQuestionText,
|
QuestionText: cmsQuestionText,
|
||||||
Answers: cmsAnswers,
|
Answers: cmsAnswers,
|
||||||
};
|
};
|
||||||
const damageOptions = dataFromStoreApi;
|
const damageOptions = dataFromStoreApi;
|
||||||
return { wrapper, cmsContent, damageOptions };
|
return { wrapper, cmsContent, damageOptions };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
:groupName="groupName"
|
:groupName="groupName"
|
||||||
buttonType="listButtonHorizontal"
|
buttonType="listButtonHorizontal"
|
||||||
useTextForValue
|
useTextForValue
|
||||||
v-model="selectedChipCountValues"
|
v-model="selectedValue"
|
||||||
:validationRules="validationRules"
|
:validationRules="validationRules"
|
||||||
isRequired
|
isRequired
|
||||||
/>
|
/>
|
||||||
|
|
@ -21,20 +21,12 @@ import buttonQuestion from "@/common-components/button-question/button-question"
|
||||||
export default ({
|
export default ({
|
||||||
name: "windshieldOptions",
|
name: "windshieldOptions",
|
||||||
props: {
|
props: {
|
||||||
modelValue: Array,
|
modelValue: [String, Number],
|
||||||
groupName: String,
|
groupName: String,
|
||||||
isAvailable: Boolean,
|
isAvailable: Boolean,
|
||||||
validationRules: String,
|
validationRules: String,
|
||||||
cmsWidgetName: String,
|
cmsWidgetName: String,
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
updateSelectedValues() {
|
|
||||||
// UPDATE SELECTEDVALUES IF ONLY ONE ANSWER
|
|
||||||
if(Array.isArray(this.answersToDisplay) && this.answersToDisplay.length === 1 && this.selectedValues) {
|
|
||||||
this.selectedValues = [this.answersToDisplay[0].Name];
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
questionText(){
|
questionText(){
|
||||||
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
|
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
|
||||||
|
|
@ -42,7 +34,7 @@ export default ({
|
||||||
answersFromCms(){
|
answersFromCms(){
|
||||||
return this.getCmsContent(this.cmsWidgetName, 'Answers');
|
return this.getCmsContent(this.cmsWidgetName, 'Answers');
|
||||||
},
|
},
|
||||||
selectedChipCountValues: {
|
selectedValue: {
|
||||||
get: function() {
|
get: function() {
|
||||||
return this.modelValue;
|
return this.modelValue;
|
||||||
},
|
},
|
||||||
|
|
@ -52,12 +44,6 @@ export default ({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
isAvailable(val) {
|
|
||||||
// CHECK TO UPDATE SELECTED VALUES WHEN ISAVAILABLE IS TRUE
|
|
||||||
val && this.updateSelectedValues();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
components: {
|
||||||
buttonQuestion,
|
buttonQuestion,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,22 +6,22 @@ import store from "@/store";
|
||||||
jest.mock("@/store", () => { return {}; }, {virtual: true});
|
jest.mock("@/store", () => { return {}; }, {virtual: true});
|
||||||
|
|
||||||
describe("windshield-damage-type-question.vue", () => {
|
describe("windshield-damage-type-question.vue", () => {
|
||||||
test("Selected chip count is emitted upon selection.", async () => {
|
test("Selected windshield damage is emitted upon selection.", async () => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper } = setupMocks({modelValueProp: ["Repair"]});
|
const { wrapper } = setupMocks({modelValueProp: "Repair"});
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
wrapper.setValue({ modelValue: ["Replace"] });
|
wrapper.setValue({ modelValue: "Replace" });
|
||||||
await wrapper.vm.$nextTick();
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
expect(wrapper.vm.selectedValues).toEqual(["Repair"]);
|
expect(wrapper.vm.selectedValues).toEqual("Repair");
|
||||||
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ modelValue: ["Replace"] }]);
|
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ modelValue: "Replace" }]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function setupMocks({
|
function setupMocks({
|
||||||
modelValueProp = ["Two"],
|
modelValueProp = "",
|
||||||
groupName = "WindshieldDamageTypeQuestion",
|
groupName = "WindshieldDamageTypeQuestion",
|
||||||
cmsQuestionText = "What's your windshield damage?",
|
cmsQuestionText = "What's your windshield damage?",
|
||||||
cmsAnswers = [{Name: "Repair"}, {Name: "Replace"}],
|
cmsAnswers = [{Name: "Repair"}, {Name: "Replace"}],
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,8 @@ defineRule("windshield-replace-options-required", required(errorMessages.WINSHIE
|
||||||
|
|
||||||
defineRule("check-for-repair-and-replace", (selectedWindshieldDamageType, selectedDamageLocations) => {
|
defineRule("check-for-repair-and-replace", (selectedWindshieldDamageType, selectedDamageLocations) => {
|
||||||
return selectedWindshieldDamageType.toString() != damageLocationsSelected.REPAIR ||
|
return selectedWindshieldDamageType.toString() != damageLocationsSelected.REPAIR ||
|
||||||
!selectedDamageLocations.includes(damageLocationsSelected.WINDSHIELD) ||
|
(!selectedDamageLocations.includes(damageLocationsSelected.WINDSHIELD) && !selectedDamageLocations[0]?.includes(damageLocationsSelected.WINDSHIELD)) ||
|
||||||
selectedDamageLocations.length === 1;
|
(selectedDamageLocations[0].length === 1);
|
||||||
});
|
});
|
||||||
defineRule("repair-only", (value) => {
|
defineRule("repair-only", (value) => {
|
||||||
return value.toString() === damageLocationsSelected.REPAIR;
|
return value.toString() === damageLocationsSelected.REPAIR;
|
||||||
|
|
@ -83,7 +83,7 @@ export default ({
|
||||||
},
|
},
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
modelValue: String,
|
modelValue: Object,
|
||||||
selectedDamageLocations: Array,
|
selectedDamageLocations: Array,
|
||||||
hasRepairReplaceConflict: Boolean,
|
hasRepairReplaceConflict: Boolean,
|
||||||
hasSplitSingleConflict: Boolean,
|
hasSplitSingleConflict: Boolean,
|
||||||
|
|
@ -114,7 +114,7 @@ export default ({
|
||||||
},
|
},
|
||||||
selectedWindshieldDamageTypeValue: {
|
selectedWindshieldDamageTypeValue: {
|
||||||
get: function() {
|
get: function() {
|
||||||
return this.selectedValues.selectedWindshieldDamageType;
|
return this.selectedDamageLocations.includes(damageLocationsSelected.WINDSHIELD) ? this.selectedValues.selectedWindshieldDamageType : null;
|
||||||
},
|
},
|
||||||
set: function(newValue) {
|
set: function(newValue) {
|
||||||
this.selectedValues = this.getWindshieldOptions(newValue, null, null);
|
this.selectedValues = this.getWindshieldOptions(newValue, null, null);
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export default {
|
||||||
name: "make-question",
|
name: "make-question",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
makes: Array,
|
makes: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
|
|
||||||
|
|
@ -5,150 +5,224 @@ import { settleAllPromises } from "@/helpers/layout-helper.js";
|
||||||
import { nextTick } from "vue";
|
import { nextTick } from "vue";
|
||||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
import baseMixin from "@/mixins/base-mixin.js";
|
import baseMixin from "@/mixins/base-mixin.js";
|
||||||
|
import store from "@/store";
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import vehicleMake from "@/layouts/vehicle-make/vehicle-make.vue";
|
import vehicleMake from "@/layouts/vehicle-make/vehicle-make.vue";
|
||||||
import makeQuestion from "@/layouts/vehicle-make/make-question/make-question";
|
import makeQuestion from "@/layouts/vehicle-make/make-question/make-question";
|
||||||
|
|
||||||
jest.mock("@/store", () => ({
|
jest.mock("@/store", () => ({
|
||||||
commit: jest.fn(),
|
commit: jest.fn(),
|
||||||
dispatch: jest.fn(),
|
dispatch: jest.fn(),
|
||||||
getters: {
|
// getters: jest.fn().mockImplementation(() => ({
|
||||||
vehicle: {
|
// vehicle: {
|
||||||
year: 2019,
|
// year: 2019,
|
||||||
},
|
// },
|
||||||
},
|
// })),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Mock fetchCmsContentForPage
|
// Mock fetchCmsContentForPage
|
||||||
jest.mock("@/helpers/cms-content-helper", () => ({
|
jest.mock("@/helpers/cms-content-helper", () => ({
|
||||||
fetchCmsContentForPage: jest.fn(),
|
fetchCmsContentForPage: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Mock our module for promises.
|
// Mock our module for promises.
|
||||||
jest.mock("@/helpers/layout-helper.js", () => ({
|
jest.mock("@/helpers/layout-helper.js", () => ({
|
||||||
settleAllPromises: jest.fn(),
|
settleAllPromises: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
describe("vehicle-make.vue", () => {
|
describe("vehicle-make.vue", () => {
|
||||||
test("Make question component is initized with api data", async (done) => {
|
test("Make question component is initized with api data", async (done) => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const makeQuestionInitialData = ["honda", "ford", "dodge"];
|
const makeQuestionInitialData = ["honda", "ford", "dodge"];
|
||||||
const { wrapper, apiPromise } = setupMocks({
|
const { wrapper, apiPromise } = setupMocks({
|
||||||
makeQuestionInitialData: makeQuestionInitialData,
|
makeQuestionInitialData: makeQuestionInitialData,
|
||||||
});
|
});
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
vehicleMake.beforeRouteEnter.call(
|
vehicleMake.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
{ query: { fmgPage: "vehicle-make" } },
|
{ query: { fmgPage: "vehicle-make" } },
|
||||||
undefined,
|
undefined,
|
||||||
(c) => c(wrapper.vm)
|
(c) => c(wrapper.vm)
|
||||||
);
|
);
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
apiPromise.finally(() => {
|
apiPromise.finally(() => {
|
||||||
expect(makeQuestion.methods.initializeComponent).toHaveBeenCalledWith(
|
expect(makeQuestion.methods.initializeComponent).toHaveBeenCalledWith(
|
||||||
makeQuestionInitialData
|
makeQuestionInitialData
|
||||||
);
|
);
|
||||||
done();
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("vehicle-make.vue", () => {
|
describe("vehicle-make.vue", () => {
|
||||||
test("BackButtonAction triggers a router.navigateWithoutSaving change", async (done) => {
|
test("BackButtonAction triggers a router.navigateWithoutSaving change", async (done) => {
|
||||||
//Arrange
|
//Arrange
|
||||||
const { wrapper, apiPromise } = setupMocks({
|
const { wrapper, apiPromise } = setupMocks({
|
||||||
pageHeaderWidgetHeaderText: "Select a make to get started",
|
pageHeaderWidgetHeaderText: "Select a make to get started",
|
||||||
mountOptionsMockData: {
|
mountOptionsMockData: {
|
||||||
router: {
|
router: {
|
||||||
navigate: jest.fn(),
|
navigate: jest.fn(),
|
||||||
navigateWithoutSaving: jest.fn(),
|
navigateWithoutSaving: jest.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
vehicleMake.beforeRouteEnter.call(
|
vehicleMake.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
{ query: { fmgPage: "vehicle-make" } },
|
{ query: { fmgPage: "vehicle-make" } },
|
||||||
undefined,
|
undefined,
|
||||||
(c) => c(wrapper.vm)
|
(c) => c(wrapper.vm)
|
||||||
);
|
);
|
||||||
wrapper.vm.backButtonAction();
|
wrapper.vm.backButtonAction();
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
apiPromise.finally(() => {
|
apiPromise.finally(() => {
|
||||||
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled();
|
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled();
|
||||||
done();
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("vehicle-make.vue", () => {
|
describe("vehicle-make.vue", () => {
|
||||||
test("Year set, arePagePrerequisitesValid should be true ", async () => {
|
describe("arePagePrerequisitesValue", () => {
|
||||||
//Arrange
|
test("Year set, arePagePrerequisitesValid should be true", async () => {
|
||||||
const { wrapper } = setupMocks({});
|
//Arrange
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
vehicleData: {
|
||||||
|
year: 2019
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
vehicleMake.beforeRouteEnter.call(
|
vehicleMake.beforeRouteEnter.call(
|
||||||
wrapper.vm,
|
wrapper.vm,
|
||||||
{ query: { fmgPage: "vehicle-make" } },
|
{ query: { fmgPage: "vehicle-make" } },
|
||||||
undefined,
|
undefined,
|
||||||
(c) => c(wrapper.vm)
|
(c) => c(wrapper.vm)
|
||||||
);
|
);
|
||||||
|
|
||||||
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
expect(arePagePrerequisitesValid).toBe(true);
|
expect(arePagePrerequisitesValid).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("Year not set, arePagePrerequisitesValid should be false", async () => {
|
||||||
|
//Arrange
|
||||||
|
store.getters.vehicle.year = jest.fn().mockReturnValueOnce(undefined);
|
||||||
|
const { wrapper } = setupMocks({});
|
||||||
|
|
||||||
|
//Act
|
||||||
|
let arePagePrerequisitesValid = wrapper.vm.arePagePrerequisitesValid();
|
||||||
|
|
||||||
|
//Assert
|
||||||
|
expect(arePagePrerequisitesValid).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("selectedMake changes => save make in store", async () => {
|
||||||
|
//Arrange
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
router: {
|
||||||
|
navigate: jest.fn(),
|
||||||
|
navigateWithSaving: jest.fn(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await wrapper.setData({
|
||||||
|
selectedMake: "Make",
|
||||||
|
});
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalledTimes(1);
|
||||||
|
expect(wrapper.vm.dispatchStoreAction).toHaveBeenCalledWith(
|
||||||
|
"saveVehicleMake",
|
||||||
|
"Make",
|
||||||
|
false
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("selectedMake changes => navigate with saving", async () => {
|
||||||
|
//Arrange
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
router: {
|
||||||
|
navigate: jest.fn(),
|
||||||
|
navigateWithSaving: jest.fn(),
|
||||||
|
},
|
||||||
|
route: {
|
||||||
|
fmgPage: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
await wrapper.setData({
|
||||||
|
selectedMake: "Make",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledTimes(1);
|
||||||
|
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalledWith(
|
||||||
|
"SELECTED_MAKE",
|
||||||
|
expect.anything()
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
function setupMocks({
|
function setupMocks({
|
||||||
vehicleMakeQuestionCmsContent = {},
|
vehicleMakeQuestionCmsContent = {},
|
||||||
makeQuestionInitialData = {},
|
makeQuestionInitialData = {},
|
||||||
pageHeaderWidgetHeaderText = {},
|
pageHeaderWidgetHeaderText = {},
|
||||||
mountOptionsMockData = {},
|
mountOptionsMockData = {},
|
||||||
|
vehicleData = {}
|
||||||
}) {
|
}) {
|
||||||
//Mock api responses
|
//Mock api responses
|
||||||
const apiResponses = {
|
const apiResponses = {
|
||||||
cmsContent: {
|
cmsContent: {
|
||||||
FunnelSubHeaderWidget: pageHeaderWidgetHeaderText,
|
FunnelSubHeaderWidget: pageHeaderWidgetHeaderText,
|
||||||
VehicleMakeQuestion: vehicleMakeQuestionCmsContent,
|
VehicleMakeQuestion: vehicleMakeQuestionCmsContent,
|
||||||
VehicleBannerWidget: {
|
VehicleBannerWidget: {
|
||||||
GenericVehicleImage:
|
GenericVehicleImage:
|
||||||
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
|
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/blurred-image.jpg?sfvrsn=a6ce3034_3",
|
||||||
},
|
},
|
||||||
FunnelHeaderWidget: {
|
FunnelHeaderWidget: {
|
||||||
LogoImage:
|
LogoImage:
|
||||||
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
|
"https://digitalconsumercms-dev.safelite.com/images/default-source/default-album/safelite-logo.svg?sfvrsn=45e7ed06_3",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
makeQuestionInitialData: makeQuestionInitialData,
|
makeQuestionInitialData: makeQuestionInitialData,
|
||||||
};
|
};
|
||||||
|
|
||||||
const apiPromise = Promise.resolve(apiResponses);
|
const apiPromise = Promise.resolve(apiResponses);
|
||||||
|
|
||||||
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
|
store.getters = {
|
||||||
settleAllPromises.mockImplementation(() => apiPromise);
|
vehicle: vehicleData
|
||||||
|
}
|
||||||
|
|
||||||
//Mock make question methods
|
fetchCmsContentForPage.mockImplementation(() => Promise.resolve());
|
||||||
makeQuestion.methods = {
|
settleAllPromises.mockImplementation(() => apiPromise);
|
||||||
loadInitialData: jest.fn(),
|
|
||||||
initializeComponent: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const mountOptions = getMountOptions(mountOptionsMockData);
|
//Mock make question methods
|
||||||
const wrapper = shallowMount(vehicleMake, mountOptions);
|
makeQuestion.methods = {
|
||||||
const makeQuestionWrapper = wrapper.findComponent({ name: "makeQuestion" });
|
loadInitialData: jest.fn(),
|
||||||
makeQuestionWrapper.vm.initializeComponent =
|
initializeComponent: jest.fn(),
|
||||||
makeQuestion.methods.initializeComponent;
|
};
|
||||||
|
|
||||||
|
const mountOptions = getMountOptions(mountOptionsMockData);
|
||||||
|
const wrapper = shallowMount(vehicleMake, mountOptions);
|
||||||
|
const makeQuestionWrapper = wrapper.findComponent({ name: "makeQuestion" });
|
||||||
|
makeQuestionWrapper.vm.initializeComponent = makeQuestion.methods.initializeComponent;
|
||||||
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
|
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
|
||||||
|
|
||||||
return { wrapper, apiPromise };
|
return { wrapper, apiPromise };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,14 +3,21 @@
|
||||||
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
<funnelHeader cmsWidgetName="FunnelHeaderWidget" />
|
||||||
<div class="select-car">
|
<div class="select-car">
|
||||||
<div class="select-car-form rounded text-center">
|
<div class="select-car-form rounded text-center">
|
||||||
<vehicleBanner cmsWidgetName="VehicleBannerWidget" displayGenericVehicleImage />
|
<vehicleBanner
|
||||||
|
cmsWidgetName="VehicleBannerWidget"
|
||||||
|
displayGenericVehicleImage
|
||||||
|
/>
|
||||||
<funnelSubHeader
|
<funnelSubHeader
|
||||||
cmsWidgetName="FunnelSubHeaderWidget"
|
cmsWidgetName="FunnelSubHeaderWidget"
|
||||||
:hasBackButton="true"
|
:hasBackButton="true"
|
||||||
@click-event="backButtonAction"
|
@click-event="backButtonAction"
|
||||||
/>
|
/>
|
||||||
<div class="fade-on-route-transition">
|
<div class="fade-on-route-transition">
|
||||||
<makeQuestion v-model="selectedMake" ref="makeQuestion" cmsWidgetName="VehicleMakeQuestion" />
|
<makeQuestion
|
||||||
|
v-model="selectedMake"
|
||||||
|
ref="makeQuestion"
|
||||||
|
cmsWidgetName="VehicleMakeQuestion"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -26,7 +33,6 @@ import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-he
|
||||||
// Supporting files
|
// Supporting files
|
||||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
import { storeMutations } from "@/constants/store-mutations";
|
|
||||||
import { storeActions } from "@/constants/store-actions";
|
import { storeActions } from "@/constants/store-actions";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
|
|
||||||
|
|
@ -75,7 +81,7 @@ export default {
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
arePagePrerequisitesValid() {
|
arePagePrerequisitesValid() {
|
||||||
if (store.getters.vehicle.year){
|
if (store.getters.vehicle.year) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -84,7 +90,7 @@ export default {
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
selectedMake(make) {
|
selectedMake(make) {
|
||||||
this.dispatchStoreAction(storeActions.SAVE_VEHICLE_MAKE, make, false);
|
this.dispatchStoreAction(storeActions.SAVE_VEHICLE_MAKE, make, false);
|
||||||
this.$router.navigateWithSaving(
|
this.$router.navigateWithSaving(
|
||||||
this.navigationScenarios.SELECTED_MAKE,
|
this.navigationScenarios.SELECTED_MAKE,
|
||||||
this.$route
|
this.$route
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export default {
|
||||||
name: "model-question",
|
name: "model-question",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
models: Array,
|
models: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,6 @@ const featureListData = {
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("glass-part-question.vue", () => {
|
describe("glass-part-question.vue", () => {
|
||||||
|
|
||||||
test("Part data passed in, should map data for ButtonQuestion (radio type)", async () => {
|
test("Part data passed in, should map data for ButtonQuestion (radio type)", async () => {
|
||||||
|
|
||||||
//Arrange
|
//Arrange
|
||||||
|
|
|
||||||
|
|
@ -13,9 +13,7 @@
|
||||||
altText=""
|
altText=""
|
||||||
isRequired
|
isRequired
|
||||||
:groupName="`${location}-${name}`"
|
:groupName="`${location}-${name}`"
|
||||||
@isCheckedChanged="ResetTintAndPartSelections"
|
:validationRules="tintValidationRules">
|
||||||
:validationRules="tintValidationRules"
|
|
||||||
>
|
|
||||||
<div class="row my-2" aria-live="polite">
|
<div class="row my-2" aria-live="polite">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<buttonQuestion
|
<buttonQuestion
|
||||||
|
|
@ -29,7 +27,7 @@
|
||||||
isRequired
|
isRequired
|
||||||
:groupName="`${location}-${name}-${selectedTint}`"
|
:groupName="`${location}-${name}-${selectedTint}`"
|
||||||
:validationRules="partValidationRules"
|
:validationRules="partValidationRules"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</buttonQuestion>
|
</buttonQuestion>
|
||||||
|
|
@ -64,7 +62,10 @@ export default {
|
||||||
location: String,
|
location: String,
|
||||||
colorAnswers: Array,
|
colorAnswers: Array,
|
||||||
modelValue: Object,
|
modelValue: Object,
|
||||||
alreadyPopulatedPartsData: Array
|
alreadyPopulatedPartsData: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.LoadPreselectedValues();
|
this.LoadPreselectedValues();
|
||||||
|
|
@ -75,12 +76,21 @@ export default {
|
||||||
computed: {
|
computed: {
|
||||||
tintValidationRules() {
|
tintValidationRules() {
|
||||||
const validationRuleName = `${this.location}-${this.name}-tint-required`;
|
const validationRuleName = `${this.location}-${this.name}-tint-required`;
|
||||||
defineRule(validationRuleName, required(errorMessages.OPTION_REQUIRED));
|
|
||||||
|
defineRule(
|
||||||
|
validationRuleName,
|
||||||
|
required(errorMessages.OPTION_REQUIRED)
|
||||||
|
);
|
||||||
|
|
||||||
return validationRuleName;
|
return validationRuleName;
|
||||||
},
|
},
|
||||||
partValidationRules() {
|
partValidationRules() {
|
||||||
const validationRuleName = `${this.location}-${this.name}-part-required`;
|
const validationRuleName = `${this.location}-${this.name}-part-required`;
|
||||||
defineRule(validationRuleName, required(errorMessages.OPTION_REQUIRED));
|
|
||||||
|
defineRule(
|
||||||
|
validationRuleName,
|
||||||
|
required(errorMessages.OPTION_REQUIRED)
|
||||||
|
);
|
||||||
return validationRuleName;
|
return validationRuleName;
|
||||||
},
|
},
|
||||||
colorQuestionText() {
|
colorQuestionText() {
|
||||||
|
|
@ -95,9 +105,9 @@ export default {
|
||||||
|
|
||||||
Object.keys(this.featureListData).forEach((tintOption) => {
|
Object.keys(this.featureListData).forEach((tintOption) => {
|
||||||
tintOptions.push({
|
tintOptions.push({
|
||||||
Name: tintOption,
|
value: tintOption,
|
||||||
Text: tintOption,
|
buttonLabel: tintOption,
|
||||||
AnswerImageUrl: require(`@/assets/img/tints/${this.getTintSourceImage(
|
buttonImage: require(`@/assets/img/tints/${this.getTintSourceImage(
|
||||||
this.location,
|
this.location,
|
||||||
tintOption
|
tintOption
|
||||||
)}`),
|
)}`),
|
||||||
|
|
@ -112,16 +122,29 @@ export default {
|
||||||
return this.modelValue?.partNumber;
|
return this.modelValue?.partNumber;
|
||||||
},
|
},
|
||||||
set(newValue) {
|
set(newValue) {
|
||||||
this.$emit("update:modelValue", this.partsForSelectedTint.filter(part => part.partNumber == newValue)[0]);
|
this.$emit(
|
||||||
|
"update:modelValue",
|
||||||
|
this.partsForSelectedTint.filter(
|
||||||
|
(part) => part.partNumber == newValue
|
||||||
|
)[0]
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
partsForSelectedTint() {
|
partsForSelectedTint() {
|
||||||
const matchingGlass = this.PartDataFromApi.partsOrQuestions?.filter(dataForlocationAndName =>
|
const matchingGlass = this.PartDataFromApi.partsOrQuestions?.filter(
|
||||||
dataForlocationAndName.name == this.name &&
|
(dataForGlassLocationAndName) =>
|
||||||
dataForlocationAndName.location == this.location);
|
dataForGlassLocationAndName.name == this.name &&
|
||||||
const matchingGlassParts = matchingGlass?.length == 1 ? matchingGlass[0].parts : [];
|
dataForGlassLocationAndName.location ==
|
||||||
return matchingGlassParts.filter(part => part.color == this.selectedTint) ?? [];
|
this.location
|
||||||
|
);
|
||||||
|
const matchingGlassParts =
|
||||||
|
matchingGlass?.length == 1 ? matchingGlass[0].parts : [];
|
||||||
|
return (
|
||||||
|
matchingGlassParts.filter(
|
||||||
|
(part) => part.color == this.selectedTint
|
||||||
|
) ?? []
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
// Creates a map of the feature list data in the correct Name/Value
|
// Creates a map of the feature list data in the correct Name/Value
|
||||||
|
|
@ -153,7 +176,9 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
PartDataFromApi() {
|
PartDataFromApi() {
|
||||||
return this.$store.getters.pageData(this.$route.query.fmgPage) ?? {};
|
return (
|
||||||
|
this.$store.getters.pageData(this.$route.query.fmgPage) ?? {}
|
||||||
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -190,7 +215,8 @@ export default {
|
||||||
// Check if only a single part is present for the tint and set the v-model if it is.
|
// Check if only a single part is present for the tint and set the v-model if it is.
|
||||||
AutoSelectIfSinglePart() {
|
AutoSelectIfSinglePart() {
|
||||||
if (this.partsForSelectedTint?.length == 1) {
|
if (this.partsForSelectedTint?.length == 1) {
|
||||||
this.selectedPartNumber = this.partsForSelectedTint[0].partNumber;
|
this.selectedPartNumber =
|
||||||
|
this.partsForSelectedTint[0].partNumber;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -199,7 +225,7 @@ export default {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
if (this.modelValue !== undefined) {
|
if (this.modelValue !== undefined) {
|
||||||
// Populate button-question model-value if parts data already exists in VueX
|
// Populate button-question model-value if parts data already exists in VueX
|
||||||
this.selectedTint = this.alreadyPopulatedPartsData?.filter(part => part.partNumber === this.selectedPartNumber)[0]?.color;
|
this.selectedTint = this.modelValue?.color
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -207,8 +233,8 @@ export default {
|
||||||
watch: {
|
watch: {
|
||||||
selectedTint() {
|
selectedTint() {
|
||||||
this.AutoSelectIfSinglePart();
|
this.AutoSelectIfSinglePart();
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -183,7 +183,16 @@ describe("vehicle-parts.vue", () => {
|
||||||
await nextTick();
|
await nextTick();
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
expect(wrapper.vm.selectedGlassParts).toEqual({ "Rear-Stationary": { "Rear": ['DB12209YPYNOEM'] } });
|
expect(wrapper.vm.selectedGlassParts).toEqual({
|
||||||
|
"Rear-Stationary": {
|
||||||
|
partNumber: "DB12209YPYNOEM",
|
||||||
|
description: "heated glass, solar, 1 hole",
|
||||||
|
color: "Gray Tint Privacy",
|
||||||
|
requiresRecalibration: false,
|
||||||
|
requiresCapabilityQuestions: false,
|
||||||
|
childParts: null
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("User had part questions > BackButtonAction triggers a router.navigateWithoutSaving change with correct scenario", async () => {
|
test("User had part questions > BackButtonAction triggers a router.navigateWithoutSaving change with correct scenario", async () => {
|
||||||
|
|
@ -509,6 +518,8 @@ function setupMocks({ pageHeaderWidgetHeaderText = {}, mountOptionsMockData = {}
|
||||||
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
|
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
|
||||||
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
||||||
wrapper.vm.$refs.loadingModal.showModal = jest.fn();
|
wrapper.vm.$refs.loadingModal.showModal = jest.fn();
|
||||||
|
// wrapper.vm.$refs.onSubmit = jest.fn();
|
||||||
|
// wrapper.vm.$refs.onInvalidSubmit = jest.fn();
|
||||||
|
|
||||||
return { wrapper, apiPromise };
|
return { wrapper, apiPromise };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,52 +1,45 @@
|
||||||
<template>
|
<template>
|
||||||
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm">
|
||||||
<div class="page-container-grouped-styles vehicle-parts">
|
<div class="page-container-grouped-styles vehicle-parts">
|
||||||
<loadingModal ref="loadingModal"/>
|
<loadingModal ref="loadingModal" />
|
||||||
<funnelHeader ref="funnelHeader" cmsWidgetName="FunnelHeaderWidget" />
|
<funnelHeader ref="funnelHeader" cmsWidgetName="FunnelHeaderWidget" />
|
||||||
<vehicleBanner
|
<vehicleBanner
|
||||||
ref="vehicleBanner"
|
ref="vehicleBanner"
|
||||||
cmsWidgetName="VehicleBannerWidget"
|
cmsWidgetName="VehicleBannerWidget"
|
||||||
:displayGenericVehicleImage="false"
|
:displayGenericVehicleImage="false" />
|
||||||
/>
|
<funnelSubHeader ref="funnelSubHeader" cmsWidgetName="FunnelSubHeaderWidget" />
|
||||||
<funnelSubHeader
|
<div class="fade-on-route-transition sub-container make-tall">
|
||||||
ref="funnelSubHeader"
|
<div class="prevent-squish my-5">
|
||||||
cmsWidgetName="FunnelSubHeaderWidget"
|
<div class="row">
|
||||||
/>
|
<div class="col">
|
||||||
<div class="fade-on-route-transition sub-container make-tall">
|
<alert
|
||||||
<div class="prevent-squish my-5">
|
class="rounded border-0 shadow-sm"
|
||||||
<div class="row">
|
alertClass="alert-warning"
|
||||||
<div class="col">
|
cmsWidgetName="AlertWidget"
|
||||||
<alert
|
:isDismissible="false" />
|
||||||
class="rounded border-0 shadow-sm"
|
</div>
|
||||||
alertClass="alert-warning"
|
</div>
|
||||||
cmsWidgetName="AlertWidget"
|
</div>
|
||||||
:isDismissible="false"
|
<div v-for="(item, i) in PartsOrQuestions" :key="i">
|
||||||
/>
|
<!-- Render horizontal lines if there is multi-glass (aka if i > 0) -->
|
||||||
|
<hr v-if="i > 0" />
|
||||||
|
<glassPartQuestion
|
||||||
|
:ref="`${RefPrefix}-${item.location}-${item.name}`"
|
||||||
|
v-model="selectedGlassParts[item.location + '-' + item.name]"
|
||||||
|
:location="item.location"
|
||||||
|
:name="item.name"
|
||||||
|
:colorAnswers="item.colorAnswers"
|
||||||
|
:alreadyPopulatedPartsData="alreadyPopulatedPartsData" />
|
||||||
|
</div>
|
||||||
|
<funnelFooter
|
||||||
|
cmsWidgetName="FunnelFooterWidget"
|
||||||
|
ref="funnelFooter"
|
||||||
|
:isForwardActionDisabled="isForwardActionDisabled"
|
||||||
|
@back-clicked="backButtonAction"
|
||||||
|
@ForwardClicked="forwardButtonAction" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-for="(item, i) in PartsOrQuestions" :key="i">
|
</Form>
|
||||||
<!-- Render horizontal lines if there is multi-glass (aka if i > 0) -->
|
|
||||||
<hr v-if="i > 0" />
|
|
||||||
<glassPartQuestion
|
|
||||||
:ref="`${RefPrefix}-${item.location}-${item.name}`"
|
|
||||||
v-model="selectedGlassParts[item.location + '-' + item.name]"
|
|
||||||
:location="item.location"
|
|
||||||
:name="item.name"
|
|
||||||
:colorAnswers="item.colorAnswers"
|
|
||||||
:alreadyPopulatedPartsData="alreadyPopulatedPartsData"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<funnelFooter
|
|
||||||
cmsWidgetName="FunnelFooterWidget"
|
|
||||||
ref="funnelFooter"
|
|
||||||
:isForwardActionDisabled="isForwardActionDisabled"
|
|
||||||
@back-clicked="backButtonAction"
|
|
||||||
@ForwardClicked="forwardButtonAction"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Form>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|
@ -57,17 +50,14 @@ import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||||
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||||
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
|
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
|
||||||
import alert from "@/ux-components/alert/alert";
|
import alert from "@/ux-components/alert/alert";
|
||||||
import loadingModal from '@/common-components/loading-modal/loading-modal.vue';
|
import loadingModal from "@/common-components/loading-modal/loading-modal.vue";
|
||||||
// Supporting Files
|
// Supporting Files
|
||||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||||
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
|
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
|
||||||
import { Form } from "vee-validate";
|
import { Form } from "vee-validate";
|
||||||
import store from "@/store";
|
import store from "@/store";
|
||||||
import { storeMutations } from "@/constants/store-mutations.js";
|
|
||||||
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
|
import vehicleQuestionsMixin from "@/mixins/vehicle-questions-mixin";
|
||||||
import { navigateToHeritageFunnel } from "@/helpers/heritage-integration/navigation-helper";
|
|
||||||
import { assertParenthesizedExpression } from "@babel/types";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "vehicle-parts",
|
name: "vehicle-parts",
|
||||||
|
|
@ -77,25 +67,25 @@ export default {
|
||||||
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
const cmsContentPromise = fetchCmsContentForPage(to.query.fmgPage);
|
||||||
// Settle promises and get results
|
// Settle promises and get results
|
||||||
const promiseResultMap = [
|
const promiseResultMap = [
|
||||||
{
|
{
|
||||||
resultKey: "cmsContent",
|
resultKey: "cmsContent",
|
||||||
promise: cmsContentPromise,
|
promise: cmsContentPromise,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const resultMap = await settleAllPromises(promiseResultMap);
|
const resultMap = await settleAllPromises(promiseResultMap);
|
||||||
// Call the "next" function to complete the transition to this page.
|
// Call the "next" function to complete the transition to this page.
|
||||||
next((vm) => {
|
next((vm) => {
|
||||||
vm.setCmsContent(resultMap.cmsContent);
|
vm.setCmsContent(resultMap.cmsContent);
|
||||||
|
|
||||||
// Glass Part Question dynamic component
|
// Glass Part Question dynamic component
|
||||||
Object.keys(vm.$refs)
|
Object.keys(vm.$refs)
|
||||||
.filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined)
|
.filter((r) => r.includes(vm.RefPrefix) && vm.$refs[r][0] !== undefined)
|
||||||
.forEach((c) =>
|
.forEach((c) =>
|
||||||
vm.$refs[c][0].initializeComponent({
|
vm.$refs[c][0].initializeComponent({
|
||||||
ColorQuestionWidget: resultMap.cmsContent.ColorQuestionWidget,
|
ColorQuestionWidget: resultMap.cmsContent.ColorQuestionWidget,
|
||||||
FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget,
|
FeatureQuestionWidget: resultMap.cmsContent.FeatureQuestionWidget,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
|
|
@ -111,11 +101,13 @@ export default {
|
||||||
this.selectedGlassPartNumbers.length !== this.PartsFromApi.partsOrQuestions.length
|
this.selectedGlassPartNumbers.length !== this.PartsFromApi.partsOrQuestions.length
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
selectedGlassPartNumbers () {
|
selectedGlassPartNumbers() {
|
||||||
// Compile all selected parts from the page.
|
// Compile all selected parts from the page.
|
||||||
const numberArray = [];
|
const numberArray = [];
|
||||||
for (let glassPart of Object.values(this.selectedGlassParts)) {
|
for (let glassPart of Object.values(this.selectedGlassParts)) {
|
||||||
if (glassPart?.partNumber) { numberArray.push(glassPart.partNumber) }
|
if (glassPart?.partNumber) {
|
||||||
|
numberArray.push(glassPart.partNumber);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return numberArray;
|
return numberArray;
|
||||||
},
|
},
|
||||||
|
|
@ -132,7 +124,8 @@ export default {
|
||||||
ColorAnswerText: p.color,
|
ColorAnswerText: p.color,
|
||||||
FeatureAnswers: [
|
FeatureAnswers: [
|
||||||
{
|
{
|
||||||
FeatureAnswerText: p.description === "" ? p.color : p.description,
|
FeatureAnswerText:
|
||||||
|
p.description === "" ? p.color : p.description,
|
||||||
PartNumber: p.partNumber,
|
PartNumber: p.partNumber,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
@ -156,16 +149,17 @@ export default {
|
||||||
methods: {
|
methods: {
|
||||||
arePagePrerequisitesValid() {
|
arePagePrerequisitesValid() {
|
||||||
// Check if isRepair is populated and if the pageData we need is here (Parts data)
|
// Check if isRepair is populated and if the pageData we need is here (Parts data)
|
||||||
return store.getters.damage.isRepair != null && store.getters.pageData(fmgPageValues.VEHICLE_PARTS) &&
|
return (
|
||||||
Object.keys(store.getters.pageData(fmgPageValues.VEHICLE_PARTS)).length !== 0;
|
store.getters.damage.isRepair != null &&
|
||||||
|
store.getters.pageData(fmgPageValues.VEHICLE_PARTS) &&
|
||||||
|
Object.keys(store.getters.pageData(fmgPageValues.VEHICLE_PARTS)).length !== 0
|
||||||
|
);
|
||||||
},
|
},
|
||||||
async forwardButtonAction() {
|
async forwardButtonAction() {
|
||||||
const matchedParts = [];
|
const matchedParts = [];
|
||||||
|
|
||||||
// Match them to the parts from the API.
|
// Match them to the parts from the API.
|
||||||
for (let [key, value] of Object.entries(
|
for (let [key, value] of Object.entries(this.PartsFromApi.partsOrQuestions)) {
|
||||||
this.PartsFromApi.partsOrQuestions
|
|
||||||
)) {
|
|
||||||
for (let [partKey, partValue] of Object.entries(value.parts)) {
|
for (let [partKey, partValue] of Object.entries(value.parts)) {
|
||||||
const currentPart = this.PartsFromApi.partsOrQuestions[key].parts[partKey];
|
const currentPart = this.PartsFromApi.partsOrQuestions[key].parts[partKey];
|
||||||
|
|
||||||
|
|
@ -177,7 +171,7 @@ export default {
|
||||||
matchedParts.push({
|
matchedParts.push({
|
||||||
location: value.location,
|
location: value.location,
|
||||||
name: value.name,
|
name: value.name,
|
||||||
parts: [currentPart]
|
parts: [currentPart],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -188,7 +182,11 @@ export default {
|
||||||
throw new Error("Could not match any parts to the selected parts");
|
throw new Error("Could not match any parts to the selected parts");
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.dispatchStoreAction(this.storeActions.RESET_MOLDING_AND_CAPABILITY_QUESTIONS_IF_NEEDED, matchedParts, false);
|
await this.dispatchStoreAction(
|
||||||
|
this.storeActions.RESET_MOLDING_AND_CAPABILITY_QUESTIONS_IF_NEEDED,
|
||||||
|
matchedParts,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
// Navigate to the next page
|
// Navigate to the next page
|
||||||
this.navigateForward(matchedParts);
|
this.navigateForward(matchedParts);
|
||||||
|
|
@ -196,20 +194,18 @@ export default {
|
||||||
|
|
||||||
LoadInitialPartsData() {
|
LoadInitialPartsData() {
|
||||||
const partsData = this.PartsFromApi;
|
const partsData = this.PartsFromApi;
|
||||||
const alreadyPopulatedPartsData =
|
this.alreadyPopulatedPartsData =
|
||||||
this.$store.getters.lineItems.glassParts === null
|
this.$store.getters.lineItems.glassParts === null
|
||||||
? []
|
? []
|
||||||
: this.$store.getters.lineItems.glassParts;
|
: this.$store.getters.lineItems.glassParts;
|
||||||
|
|
||||||
partsData.partsOrQuestions.map((g) => {
|
partsData.partsOrQuestions.map((g) => {
|
||||||
// If the part is already populated, use the value from the store and populate the v-model.
|
// If the part is already populated, use the value from the store and populate the v-model.
|
||||||
Object.keys(alreadyPopulatedPartsData).forEach((key) => {
|
Object.keys(this.alreadyPopulatedPartsData).forEach((key) => {
|
||||||
const partNumber = alreadyPopulatedPartsData[key].partNumber;
|
const partNumber = this.alreadyPopulatedPartsData[key].partNumber;
|
||||||
g.parts.forEach((p) => {
|
g.parts.forEach((p) => {
|
||||||
if (p.partNumber === partNumber) {
|
if (p.partNumber === partNumber) {
|
||||||
this.selectedGlassParts[g.location + "-" + g.name] = {
|
this.selectedGlassParts[g.location + "-" + g.name] = p;
|
||||||
[g.location]: [partNumber],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ export default {
|
||||||
name: "style-question",
|
name: "style-question",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
styles: Array,
|
styles: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ export default {
|
||||||
name: "year-question",
|
name: "year-question",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
years: Array,
|
years: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ export default {
|
||||||
pushEventToGA(category, action, label, pushToLogApp = false, valueToLogType = null) {
|
pushEventToGA(category, action, label, pushToLogApp = false, valueToLogType = null) {
|
||||||
const currentPageName = getPageNameByQueryString();
|
const currentPageName = getPageNameByQueryString();
|
||||||
const labelToLog = getValueToLog(label, valueToLogType);
|
const labelToLog = getValueToLog(label, valueToLogType);
|
||||||
|
|
||||||
const eventToBePushed = {
|
const eventToBePushed = {
|
||||||
'event': GaEvents.GENERIC_EVENT,
|
'event': GaEvents.GENERIC_EVENT,
|
||||||
'category': category,
|
'category': category,
|
||||||
|
|
@ -140,7 +141,7 @@ export default {
|
||||||
|
|
||||||
noSession() {
|
noSession() {
|
||||||
return getSessionKeyValue() === 0 || getSessionIdValue() === '00000000-0000-0000-0000-000000000000';
|
return getSessionKeyValue() === 0 || getSessionIdValue() === '00000000-0000-0000-0000-000000000000';
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
analyticsPageEvents() {
|
analyticsPageEvents() {
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,11 @@ import { vehicleCategories } from "@/constants/vehicle-categories.js";
|
||||||
import { routerParams } from "@/router/router-constants/router-params";
|
import { routerParams } from "@/router/router-constants/router-params";
|
||||||
import { queryStrings } from "@/constants/query-strings";
|
import { queryStrings } from "@/constants/query-strings";
|
||||||
import { dynamicStrings } from "@/constants/dynamic-strings";
|
import { dynamicStrings } from "@/constants/dynamic-strings";
|
||||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
cmsContentByWidget: {}
|
cmsContentByWidget: {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -19,7 +18,9 @@ export default {
|
||||||
this.$root.cmsContentByWidget = cmsContent;
|
this.$root.cmsContentByWidget = cmsContent;
|
||||||
},
|
},
|
||||||
getCmsContent(widgetName, fieldName) {
|
getCmsContent(widgetName, fieldName) {
|
||||||
return this.$root.cmsContentByWidget?.[widgetName]?.[fieldName] ? this.$root.cmsContentByWidget[widgetName][fieldName] : '';
|
return this.$root.cmsContentByWidget?.[widgetName]?.[fieldName]
|
||||||
|
? this.$root.cmsContentByWidget[widgetName][fieldName]
|
||||||
|
: "";
|
||||||
},
|
},
|
||||||
dispatchStoreAction(type, payload, encodePayload = true) {
|
dispatchStoreAction(type, payload, encodePayload = true) {
|
||||||
// Encode the payload if required
|
// Encode the payload if required
|
||||||
|
|
@ -32,7 +33,7 @@ export default {
|
||||||
savePageDataToStore(page, data) {
|
savePageDataToStore(page, data) {
|
||||||
store.commit(storeMutations.UPDATE_PAGE_DATA, { page: page, data: data });
|
store.commit(storeMutations.UPDATE_PAGE_DATA, { page: page, data: data });
|
||||||
},
|
},
|
||||||
onSubmit() { }, // DO NOT REMOVE; needed to prevent default form submit behavior
|
onSubmit() {}, // DO NOT REMOVE; needed to prevent default form submit behavior. Cannot use .prevent modifier for vee-validate Form
|
||||||
onInvalidSubmit({ values, errors, results }) {
|
onInvalidSubmit({ values, errors, results }) {
|
||||||
// identify the first error field and put focus on it
|
// identify the first error field and put focus on it
|
||||||
// get error names array
|
// get error names array
|
||||||
|
|
@ -49,14 +50,17 @@ export default {
|
||||||
return footerInfoBox ? footerInfoBox.offsetHeight : 0;
|
return footerInfoBox ? footerInfoBox.offsetHeight : 0;
|
||||||
},
|
},
|
||||||
async getZipCodeData(zipCode) {
|
async getZipCodeData(zipCode) {
|
||||||
const serviceZipValidationResponse = await this.dispatchStoreAction(storeActions.VALIDATE_ZIP, { zip: zipCode });
|
const serviceZipValidationResponse = await this.dispatchStoreAction(
|
||||||
|
storeActions.VALIDATE_ZIP,
|
||||||
return {
|
{ zip: zipCode }
|
||||||
isValid: serviceZipValidationResponse.data.isValid,
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isValid: serviceZipValidationResponse.data.isValid,
|
||||||
isServiceable: serviceZipValidationResponse.data.isServiceable,
|
isServiceable: serviceZipValidationResponse.data.isServiceable,
|
||||||
state: serviceZipValidationResponse.data.state
|
state: serviceZipValidationResponse.data.state,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
storeActions() {
|
storeActions() {
|
||||||
|
|
@ -74,13 +78,13 @@ export default {
|
||||||
routerParams() {
|
routerParams() {
|
||||||
return routerParams;
|
return routerParams;
|
||||||
},
|
},
|
||||||
queryStrings(){
|
queryStrings() {
|
||||||
return queryStrings;
|
return queryStrings;
|
||||||
},
|
},
|
||||||
dynamicStrings(){
|
dynamicStrings() {
|
||||||
return dynamicStrings;
|
return dynamicStrings;
|
||||||
},
|
},
|
||||||
cssClassNameForCmsWidget(){
|
cssClassNameForCmsWidget() {
|
||||||
return "widget-name-" + this.cmsWidgetName;
|
return "widget-name-" + this.cmsWidgetName;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
36
src/mixins/input-button-wrapper-mixin.js
Normal file
36
src/mixins/input-button-wrapper-mixin.js
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { inputButtonProps } from "@/common-components/base-input-button/button-functionality-props";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
model: {
|
||||||
|
prop: "modelValue",
|
||||||
|
event: "change",
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
...inputButtonProps,
|
||||||
|
buttonLabel: [Number, String],
|
||||||
|
buttonLabelSubCopy: String,
|
||||||
|
buttonImage: String,
|
||||||
|
buttonImageId: String,
|
||||||
|
altText: {
|
||||||
|
type: String,
|
||||||
|
default: "",
|
||||||
|
},
|
||||||
|
textPosition: String,
|
||||||
|
screenReaderOnlyText: String,
|
||||||
|
isWide: Boolean,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
selectedValue: {
|
||||||
|
get() {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
set(e) {
|
||||||
|
if (this.preHandleAnswerChange) {
|
||||||
|
this.preHandleAnswerChange(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit("update:modelValue", e);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
317
src/mixins/input-button-wrapper-mixin.spec.js
Normal file
317
src/mixins/input-button-wrapper-mixin.spec.js
Normal file
|
|
@ -0,0 +1,317 @@
|
||||||
|
import { mount } from "@vue/test-utils";
|
||||||
|
import baseInputButton from "@/common-components/base-input-button/base-input-button";
|
||||||
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
|
import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal";
|
||||||
|
import listButton from "@/ux-components/list-button/list-button";
|
||||||
|
import listCard from "@/ux-components/list-card/list-card";
|
||||||
|
import radio from "@/ux-components/radio/radio";
|
||||||
|
|
||||||
|
describe("input-button-wrapper-mixin", () => {
|
||||||
|
describe("mouse clicks", () => {
|
||||||
|
describe("checkbox", () => {
|
||||||
|
test("click on both => both are selected", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupBaseInputButtonWrapper({
|
||||||
|
mockData: {
|
||||||
|
isMultiSelect: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const buttonWrappers = wrapper.findAllComponents({
|
||||||
|
name: "baseInputButtonWrapper",
|
||||||
|
});
|
||||||
|
const inputButtonOne = buttonWrappers.at(0);
|
||||||
|
const inputButtonTwo = buttonWrappers.at(1);
|
||||||
|
expect(inputButtonOne.vm.isMultiSelect).toBe(true);
|
||||||
|
expect(inputButtonTwo.vm.isMultiSelect).toBe(true);
|
||||||
|
expect(wrapper.vm.value).toEqual([]);
|
||||||
|
|
||||||
|
await inputButtonOne.find("input").trigger("click");
|
||||||
|
await inputButtonTwo.find("input").trigger("click");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.value).toEqual(["value1", "value2"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("click input 1, 2, 1 => only input 2 selected", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupBaseInputButtonWrapper({
|
||||||
|
mockData: {
|
||||||
|
isMultiSelect: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const buttonWrappers = wrapper.findAllComponents({
|
||||||
|
name: "baseInputButtonWrapper",
|
||||||
|
});
|
||||||
|
const inputButtonOne = buttonWrappers.at(0);
|
||||||
|
const inputButtonTwo = buttonWrappers.at(1);
|
||||||
|
expect(inputButtonOne.vm.isMultiSelect).toBe(true);
|
||||||
|
expect(inputButtonTwo.vm.isMultiSelect).toBe(true);
|
||||||
|
expect(wrapper.vm.value).toEqual([]);
|
||||||
|
|
||||||
|
await inputButtonOne.find("input").trigger("click");
|
||||||
|
await inputButtonTwo.find("input").trigger("click");
|
||||||
|
await inputButtonOne.find("input").trigger("click");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.value).toEqual(["value2"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("radio", () => {
|
||||||
|
test("click on both => last clicked is selected", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupBaseInputButtonWrapper({
|
||||||
|
mockData: {
|
||||||
|
isMultiSelect: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const buttonWrappers = wrapper.findAllComponents({
|
||||||
|
name: "baseInputButtonWrapper",
|
||||||
|
});
|
||||||
|
const inputButtonOne = buttonWrappers.at(0);
|
||||||
|
const inputButtonTwo = buttonWrappers.at(1);
|
||||||
|
expect(inputButtonOne.vm.isMultiSelect).toBe(false);
|
||||||
|
expect(inputButtonTwo.vm.isMultiSelect).toBe(false);
|
||||||
|
expect(wrapper.vm.value).toEqual("");
|
||||||
|
|
||||||
|
await inputButtonOne.find("input").trigger("click");
|
||||||
|
await inputButtonTwo.find("input").trigger("click");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.value).toEqual("value2");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("click input 1, 2, 1 => input 1 is selected", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupBaseInputButtonWrapper({
|
||||||
|
mockData: {
|
||||||
|
isMultiSelect: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const buttonWrappers = wrapper.findAllComponents({
|
||||||
|
name: "baseInputButtonWrapper",
|
||||||
|
});
|
||||||
|
const inputButtonOne = buttonWrappers.at(0);
|
||||||
|
const inputButtonTwo = buttonWrappers.at(1);
|
||||||
|
expect(inputButtonOne.vm.isMultiSelect).toBe(false);
|
||||||
|
expect(inputButtonTwo.vm.isMultiSelect).toBe(false);
|
||||||
|
expect(wrapper.vm.value).toEqual("");
|
||||||
|
|
||||||
|
await inputButtonOne.find("input").trigger("click");
|
||||||
|
await inputButtonTwo.find("input").trigger("click");
|
||||||
|
await inputButtonOne.find("input").trigger("click");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.value).toEqual("value1");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("initial values", () => {
|
||||||
|
describe("checkbox", () => {
|
||||||
|
const defaultCheckedCases = [
|
||||||
|
[["value2"], false, true],
|
||||||
|
[["value1", "value2"], true, true],
|
||||||
|
[["value1"], true, false],
|
||||||
|
[[], false, false],
|
||||||
|
];
|
||||||
|
test.each(defaultCheckedCases)(
|
||||||
|
"initial value is %s => correct input buttons are selected",
|
||||||
|
async (modelValue, isInputButtonOneChecked, isInputButtonTwoChecked) => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupBaseInputButtonWrapper({
|
||||||
|
mockData: {
|
||||||
|
isMultiSelect: true,
|
||||||
|
initialValue: modelValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const buttonWrappers = wrapper.findAllComponents({
|
||||||
|
name: "baseInputButtonWrapper",
|
||||||
|
});
|
||||||
|
const inputs = wrapper.findAll("input");
|
||||||
|
const inputButtonOne = buttonWrappers.at(0);
|
||||||
|
const inputButtonTwo = buttonWrappers.at(1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(inputButtonOne.vm.isMultiSelect).toBe(true);
|
||||||
|
expect(inputButtonTwo.vm.isMultiSelect).toBe(true);
|
||||||
|
expect(inputs[0].element.checked).toBe(isInputButtonOneChecked);
|
||||||
|
expect(inputs[1].element.checked).toBe(isInputButtonTwoChecked);
|
||||||
|
expect(wrapper.vm.value).toEqual(modelValue);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("radio", () => {
|
||||||
|
const defaultCheckedCases = [
|
||||||
|
["", false, false],
|
||||||
|
["value1", true, false],
|
||||||
|
["value2", false, true],
|
||||||
|
[[], false, false],
|
||||||
|
];
|
||||||
|
test.each(defaultCheckedCases)(
|
||||||
|
"initial value is %s => correct input buttons are selected",
|
||||||
|
async (modelValue, isInputButtonOneChecked, isInputButtonTwoChecked) => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupBaseInputButtonWrapper({
|
||||||
|
mockData: {
|
||||||
|
isMultiSelect: false,
|
||||||
|
initialValue: modelValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const buttonWrappers = wrapper.findAllComponents({
|
||||||
|
name: "baseInputButtonWrapper",
|
||||||
|
});
|
||||||
|
const inputs = wrapper.findAll("input");
|
||||||
|
const inputButtonOne = buttonWrappers.at(0);
|
||||||
|
const inputButtonTwo = buttonWrappers.at(1);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(inputButtonOne.vm.isMultiSelect).toBe(false);
|
||||||
|
expect(inputButtonTwo.vm.isMultiSelect).toBe(false);
|
||||||
|
expect(inputs[0].element.checked).toBe(isInputButtonOneChecked);
|
||||||
|
expect(inputs[1].element.checked).toBe(isInputButtonTwoChecked);
|
||||||
|
expect(wrapper.vm.value).toEqual(modelValue);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// shared checks between components that use input-button-wrapper-mixin
|
||||||
|
describe("shared checks", () => {
|
||||||
|
const inputButtonComponents = [listButtonHorizontal, listButton, listCard, radio];
|
||||||
|
test.each(inputButtonComponents.map((x) => [x.name, x]))(
|
||||||
|
"%s - should return input type checkbox if isMultiSelect is true",
|
||||||
|
async (name, inputButtonComponent) => {
|
||||||
|
// Act
|
||||||
|
const { wrapper } = setupMocksForComponentsUsingInputButtonWrapperMixin({
|
||||||
|
component: inputButtonComponent,
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
isMultiSelect: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const input = wrapper.find("input");
|
||||||
|
expect(input.attributes().type).toEqual("checkbox");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
test.each(inputButtonComponents.map((x) => [x.name, x]))(
|
||||||
|
"%s - return input type radio if isMultiSelect is false or not specified",
|
||||||
|
async (name, inputButtonComponent) => {
|
||||||
|
// Act
|
||||||
|
const { wrapper } = setupMocksForComponentsUsingInputButtonWrapperMixin({
|
||||||
|
component: inputButtonComponent,
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
isMultiSelect: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const input = wrapper.find("input");
|
||||||
|
expect(input.attributes().type).toEqual("radio");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const selectedValues = ["something", ["test"]];
|
||||||
|
inputButtonComponents.forEach((inputButtonComponent) => {
|
||||||
|
test.each(selectedValues)(
|
||||||
|
`${inputButtonComponent.name} with selectedValue %s - should emit button value on click`,
|
||||||
|
async (selectedValue) => {
|
||||||
|
// Act
|
||||||
|
const { wrapper } = setupMocksForComponentsUsingInputButtonWrapperMixin({
|
||||||
|
component: inputButtonComponent,
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
isRadioHorizontal: true,
|
||||||
|
buttonLabel: "Windshield",
|
||||||
|
value: "List Card Checkbox",
|
||||||
|
groupID: "radio-demo-1",
|
||||||
|
groupName: "radio 1",
|
||||||
|
buttonImage: "windshield-damage.svg",
|
||||||
|
isRequired: true,
|
||||||
|
isWide: false,
|
||||||
|
modelValue: ["List Card Checkbox"],
|
||||||
|
buttonID: "list-card-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.selectedValue = selectedValue;
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual(selectedValue);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupMocksForComponentsUsingInputButtonWrapperMixin({ mockData, component }) {
|
||||||
|
const wrapper = mount(component, {
|
||||||
|
...mockData,
|
||||||
|
propsData: {
|
||||||
|
...mockData.propsData,
|
||||||
|
groupName: "my-group",
|
||||||
|
modelValue: mockData.propsData?.isMultiSelect ? ["5"] : "5",
|
||||||
|
value: "4",
|
||||||
|
},
|
||||||
|
mixins: [inputButtonWrapperMixin],
|
||||||
|
});
|
||||||
|
|
||||||
|
return { wrapper };
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupBaseInputButtonWrapper({ mockData = {} }) {
|
||||||
|
baseInputButton.methods.pushClickEventToGA = jest.fn();
|
||||||
|
|
||||||
|
const baseInputButtonWrapper = {
|
||||||
|
name: "baseInputButtonWrapper",
|
||||||
|
components: { baseInputButton },
|
||||||
|
template: '<div><baseInputButton v-bind="$props" v-model="selectedValue" /></div>',
|
||||||
|
mixins: [inputButtonWrapperMixin],
|
||||||
|
};
|
||||||
|
|
||||||
|
let parentComponentTemplate = `<div>`;
|
||||||
|
parentComponentTemplate += `<baseInputButtonWrapper v-model="value" groupName="myGroupName" :isMultiSelect="${mockData.isMultiSelect}" value="value1" />`;
|
||||||
|
parentComponentTemplate += `<baseInputButtonWrapper v-model="value" groupName="myGroupName" :isMultiSelect="${mockData.isMultiSelect}" value="value2" />`;
|
||||||
|
parentComponentTemplate += `</div>`;
|
||||||
|
const wrapper = mount(
|
||||||
|
{
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
value: mockData.initialValue ?? (mockData.isMultiSelect ? [] : ""),
|
||||||
|
$route: {
|
||||||
|
query: {
|
||||||
|
fmgPage: "myPage",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
template: parentComponentTemplate,
|
||||||
|
components: { baseInputButtonWrapper },
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
return { wrapper };
|
||||||
|
}
|
||||||
|
|
@ -177,7 +177,6 @@ export default {
|
||||||
// if single parts only
|
// if single parts only
|
||||||
const collectedGlassParts = this.reducedGlassPartsArray(partsOrQuestions);
|
const collectedGlassParts = this.reducedGlassPartsArray(partsOrQuestions);
|
||||||
// save to store lineItems.glassParts
|
// save to store lineItems.glassParts
|
||||||
// TODO KO
|
|
||||||
self.$store.commit(storeMutations.UPDATE_GLASS_PARTS, collectedGlassParts);
|
self.$store.commit(storeMutations.UPDATE_GLASS_PARTS, collectedGlassParts);
|
||||||
|
|
||||||
self.$refs.loadingModal.showModal();
|
self.$refs.loadingModal.showModal();
|
||||||
|
|
|
||||||
|
|
@ -238,7 +238,7 @@ function navigateToUrl(url, optionalQuery = {}) {
|
||||||
for (const queryKey in optionalQuery) {
|
for (const queryKey in optionalQuery) {
|
||||||
externalUrl.searchParams.append(queryKey, optionalQuery[queryKey]);
|
externalUrl.searchParams.append(queryKey, optionalQuery[queryKey]);
|
||||||
}
|
}
|
||||||
|
|
||||||
window.location.assign(externalUrl);
|
window.location.assign(externalUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { createStore } from "vuex";
|
import { createStore, Store } from "vuex";
|
||||||
import { endpoints } from "@/constants/endpoints.js";
|
import { endpoints } from "@/constants/endpoints.js";
|
||||||
import { storeMutations } from "@/constants/store-mutations";
|
import { storeMutations } from "@/constants/store-mutations";
|
||||||
import { getDateForSavedSessionTimeout } from "@/helpers/heritage-integration/session-helper";
|
import { getDateForSavedSessionTimeout } from "@/helpers/heritage-integration/session-helper";
|
||||||
|
|
@ -50,22 +50,22 @@ const getDefaultState = () => {
|
||||||
glassToReplace: null,
|
glassToReplace: null,
|
||||||
partQuestionAnswers: null,
|
partQuestionAnswers: null,
|
||||||
moldingQuestionAnswers: null,
|
moldingQuestionAnswers: null,
|
||||||
capabilityQuestionAnswers: null
|
capabilityQuestionAnswers: null,
|
||||||
},
|
},
|
||||||
lineItems: {
|
lineItems: {
|
||||||
glassParts: null
|
glassParts: null,
|
||||||
},
|
},
|
||||||
payment: {
|
payment: {
|
||||||
isInsurance: null,
|
isInsurance: null,
|
||||||
insuranceCoverage: {
|
insuranceCoverage: {
|
||||||
isVerified: null
|
isVerified: null,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
referralNumber: null,
|
referralNumber: null,
|
||||||
referralDate: null,
|
referralDate: null,
|
||||||
referralCorrelationId: null,
|
referralCorrelationId: null,
|
||||||
accountNumber: 0,
|
accountNumber: 0,
|
||||||
eon: null
|
eon: null,
|
||||||
},
|
},
|
||||||
applicationUser: {
|
applicationUser: {
|
||||||
eventBus: [],
|
eventBus: [],
|
||||||
|
|
@ -76,9 +76,15 @@ const getDefaultState = () => {
|
||||||
crmCustomerId: null,
|
crmCustomerId: null,
|
||||||
lastPageVisited: null,
|
lastPageVisited: null,
|
||||||
experiments: [],
|
experiments: [],
|
||||||
triggeredSiteEntry: false
|
triggeredSiteEntry: false,
|
||||||
},
|
},
|
||||||
}
|
// gaClickInformation: {
|
||||||
|
// currentlySelectedValues: {},
|
||||||
|
// firedGaClickEventValues: {},
|
||||||
|
// lastFocusedInputGroup: "",
|
||||||
|
// wasLastFocusedInputMultiselect: undefined,
|
||||||
|
// },
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const state = getDefaultState();
|
export const state = getDefaultState();
|
||||||
|
|
@ -195,7 +201,6 @@ export const mutations = {
|
||||||
state.order.customer.emailAddress = customerEmailAddress;
|
state.order.customer.emailAddress = customerEmailAddress;
|
||||||
},
|
},
|
||||||
updateVehicle(state, vehicleInfo) {
|
updateVehicle(state, vehicleInfo) {
|
||||||
|
|
||||||
state.order.vehicle.year = vehicleInfo.year;
|
state.order.vehicle.year = vehicleInfo.year;
|
||||||
state.order.vehicle.make = vehicleInfo.make;
|
state.order.vehicle.make = vehicleInfo.make;
|
||||||
state.order.vehicle.model = vehicleInfo.model;
|
state.order.vehicle.model = vehicleInfo.model;
|
||||||
|
|
@ -209,7 +214,8 @@ export const mutations = {
|
||||||
state.order.vehicle.imageColor = vehicleInfo.imageVifColor;
|
state.order.vehicle.imageColor = vehicleInfo.imageVifColor;
|
||||||
},
|
},
|
||||||
updateRegistration(state, registrationInfo) {
|
updateRegistration(state, registrationInfo) {
|
||||||
state.order.vehicle.registration.licensePlate = registrationInfo?.licensePlate;
|
state.order.vehicle.registration.licensePlate =
|
||||||
|
registrationInfo?.licensePlate;
|
||||||
state.order.vehicle.registration.address = registrationInfo?.address;
|
state.order.vehicle.registration.address = registrationInfo?.address;
|
||||||
state.order.vehicle.registration.city = registrationInfo?.city;
|
state.order.vehicle.registration.city = registrationInfo?.city;
|
||||||
state.order.vehicle.registration.state = registrationInfo?.state;
|
state.order.vehicle.registration.state = registrationInfo?.state;
|
||||||
|
|
@ -235,7 +241,7 @@ export const mutations = {
|
||||||
state.applicationUser.crmCustomerId = crmCustomerId;
|
state.applicationUser.crmCustomerId = crmCustomerId;
|
||||||
},
|
},
|
||||||
updateLastPageVisited(state, lastPageVisited) {
|
updateLastPageVisited(state, lastPageVisited) {
|
||||||
state.applicationUser.lastPageVisited = lastPageVisited
|
state.applicationUser.lastPageVisited = lastPageVisited;
|
||||||
},
|
},
|
||||||
// EVENT BUS MUTATIONS
|
// EVENT BUS MUTATIONS
|
||||||
addEventToBus(state, event) {
|
addEventToBus(state, event) {
|
||||||
|
|
@ -244,8 +250,7 @@ export const mutations = {
|
||||||
removeEventFromBus(state, eventData) {
|
removeEventFromBus(state, eventData) {
|
||||||
const matchedEvent = state.applicationUser.eventBus.find(
|
const matchedEvent = state.applicationUser.eventBus.find(
|
||||||
({ category, subCategory }) =>
|
({ category, subCategory }) =>
|
||||||
category === eventData.category &&
|
category === eventData.category && subCategory === eventData.subCategory
|
||||||
subCategory === eventData.subCategory
|
|
||||||
);
|
);
|
||||||
const itemIndex = state.applicationUser.eventBus.indexOf(matchedEvent);
|
const itemIndex = state.applicationUser.eventBus.indexOf(matchedEvent);
|
||||||
|
|
||||||
|
|
@ -331,7 +336,7 @@ export const mutations = {
|
||||||
state: orderInformation.vehicle.registration.state,
|
state: orderInformation.vehicle.registration.state,
|
||||||
zipCode: orderInformation.vehicle.registration.zipCode,
|
zipCode: orderInformation.vehicle.registration.zipCode,
|
||||||
licensePlate: orderInformation.vehicle.registration.licensePlateNumber,
|
licensePlate: orderInformation.vehicle.registration.licensePlateNumber,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
state.order.damage.glassToReplace = orderInformation.damage.glassToReplace;
|
state.order.damage.glassToReplace = orderInformation.damage.glassToReplace;
|
||||||
|
|
@ -340,13 +345,18 @@ export const mutations = {
|
||||||
|
|
||||||
state.order.lineItems.glassParts = orderInformation.parts;
|
state.order.lineItems.glassParts = orderInformation.parts;
|
||||||
state.order.accountNumber = orderInformation.accountNumber;
|
state.order.accountNumber = orderInformation.accountNumber;
|
||||||
state.order.serviceLocation.address = orderInformation.serviceLocation.streetAddress,
|
(state.order.serviceLocation.address =
|
||||||
state.order.serviceLocation.city = orderInformation.serviceLocation.city,
|
orderInformation.serviceLocation.streetAddress),
|
||||||
state.order.serviceLocation.state = orderInformation.serviceLocation.state,
|
(state.order.serviceLocation.city =
|
||||||
state.order.serviceLocation.zipCode = orderInformation.serviceLocation.zipCode;
|
orderInformation.serviceLocation.city),
|
||||||
|
(state.order.serviceLocation.state =
|
||||||
|
orderInformation.serviceLocation.state),
|
||||||
|
(state.order.serviceLocation.zipCode =
|
||||||
|
orderInformation.serviceLocation.zipCode);
|
||||||
|
|
||||||
state.order.payment.isInsurance = orderInformation.IsInsuranceOrder;
|
state.order.payment.isInsurance = orderInformation.IsInsuranceOrder;
|
||||||
state.order.payment.insuranceCoverage.isVerified = orderInformation?.insuranceInfo.coverageVerified;
|
state.order.payment.insuranceCoverage.isVerified =
|
||||||
|
orderInformation?.insuranceInfo.coverageVerified;
|
||||||
|
|
||||||
state.order.customer.emailAddress = orderInformation.customer.emailAddress;
|
state.order.customer.emailAddress = orderInformation.customer.emailAddress;
|
||||||
state.applicationUser.experiments = orderInformation.experiments;
|
state.applicationUser.experiments = orderInformation.experiments;
|
||||||
|
|
@ -356,8 +366,23 @@ export const mutations = {
|
||||||
},
|
},
|
||||||
updateTriggeredSiteEntry(state, wasSiteEntryTriggered) {
|
updateTriggeredSiteEntry(state, wasSiteEntryTriggered) {
|
||||||
state.applicationUser.triggeredSiteEntry = wasSiteEntryTriggered;
|
state.applicationUser.triggeredSiteEntry = wasSiteEntryTriggered;
|
||||||
}
|
},
|
||||||
}
|
// START GA click event mutations
|
||||||
|
// updateCurrentlySelectedValues(state, groupName, value) {
|
||||||
|
// state.gaClickInformation.currentlySelectedValues[groupName] = value;
|
||||||
|
// },
|
||||||
|
// updateFiredGaClickEventValues(state, groupName, value) {
|
||||||
|
// state.gaClickInformation.firedGaClickEventValues[groupName] = value;
|
||||||
|
// },
|
||||||
|
// updateLastFocusedInputGroup(state, groupName) {
|
||||||
|
// state.gaClickInformation.lastFocusedInputGroup = groupName;
|
||||||
|
// },
|
||||||
|
// updateWasLastFocusedInputMultiselect(state, wasLastFocusedInputMultiselect) {
|
||||||
|
// state.gaClickInformation.wasLastFocusedInputMultiselect =
|
||||||
|
// wasLastFocusedInputMultiselect;
|
||||||
|
// },
|
||||||
|
// END GA click event mutations
|
||||||
|
};
|
||||||
|
|
||||||
// Export Getters
|
// Export Getters
|
||||||
export const getters = {
|
export const getters = {
|
||||||
|
|
@ -373,7 +398,9 @@ export const getters = {
|
||||||
eventBus: (state) => state.applicationUser.eventBus,
|
eventBus: (state) => state.applicationUser.eventBus,
|
||||||
damage: (state) => state.order.damage,
|
damage: (state) => state.order.damage,
|
||||||
lineItems: (state) => state.order.lineItems,
|
lineItems: (state) => state.order.lineItems,
|
||||||
pageData: (state) => (page) => { return state.applicationUser.pageData[page]; },
|
pageData: (state) => (page) => {
|
||||||
|
return state.applicationUser.pageData[page];
|
||||||
|
},
|
||||||
applicationUser: (state) => state.applicationUser,
|
applicationUser: (state) => state.applicationUser,
|
||||||
order: (state) => state.order,
|
order: (state) => state.order,
|
||||||
payment: (state) => state.order.payment,
|
payment: (state) => state.order.payment,
|
||||||
|
|
@ -390,7 +417,8 @@ export const getters = {
|
||||||
funnelServiceState: state.order.serviceLocation.state,
|
funnelServiceState: state.order.serviceLocation.state,
|
||||||
funnelServiceZipCode: state.order.serviceLocation.zipCode,
|
funnelServiceZipCode: state.order.serviceLocation.zipCode,
|
||||||
funnelParentAccountNumber: state.order.accountNumber,
|
funnelParentAccountNumber: state.order.accountNumber,
|
||||||
funnelIsCoverageVerified: state.order.payment.insuranceCoverage.isVerified,
|
funnelIsCoverageVerified:
|
||||||
|
state.order.payment.insuranceCoverage.isVerified,
|
||||||
funnelHasRecalibrationPart: getHasRecalibrationPart(state),
|
funnelHasRecalibrationPart: getHasRecalibrationPart(state),
|
||||||
funnelSelectedMultiGlass: state.order.damage.glassToReplace?.length > 1,
|
funnelSelectedMultiGlass: state.order.damage.glassToReplace?.length > 1,
|
||||||
funnelSelectedWindshieldGlass: getNonFalseValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, "glassLocation").includes(damageLocationsSelected.WINDSHIELD),
|
funnelSelectedWindshieldGlass: getNonFalseValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, "glassLocation").includes(damageLocationsSelected.WINDSHIELD),
|
||||||
|
|
@ -403,8 +431,12 @@ export const getters = {
|
||||||
funnelOrderPartTypes: [...getNonFalseValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, "recalibrationType"), ...getNonFalseValuesOfPropertyInArrayOfObjects(state.order.lineItems.otherParts, "recalibrationType")],
|
funnelOrderPartTypes: [...getNonFalseValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, "recalibrationType"), ...getNonFalseValuesOfPropertyInArrayOfObjects(state.order.lineItems.otherParts, "recalibrationType")],
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
experimentSettings: (state) => state.applicationUser.experiments.map(x => x.settings).reduce((r, c) => Object.assign(r, c), {}) ?? {}
|
experimentSettings: (state) =>
|
||||||
}
|
state.applicationUser.experiments
|
||||||
|
.map((x) => x.settings)
|
||||||
|
.reduce((r, c) => Object.assign(r, c), {}) ?? {},
|
||||||
|
// gaClickInformation: (state) => state.gaClickInformation,
|
||||||
|
};
|
||||||
|
|
||||||
function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
|
function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
|
||||||
return (array ?? []).map(x => x[propertyName]).filter(x => x);
|
return (array ?? []).map(x => x[propertyName]).filter(x => x);
|
||||||
|
|
@ -412,7 +444,6 @@ function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
|
||||||
|
|
||||||
// Export Actions
|
// Export Actions
|
||||||
export const actions = {
|
export const actions = {
|
||||||
|
|
||||||
// Vehicle API Actions
|
// Vehicle API Actions
|
||||||
getVehicleYears(context) {
|
getVehicleYears(context) {
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
|
|
@ -443,11 +474,14 @@ export const actions = {
|
||||||
endpoint: endpoints.LookupVinByPlate.url,
|
endpoint: endpoints.LookupVinByPlate.url,
|
||||||
payload: {
|
payload: {
|
||||||
licensePlate: licensePlate,
|
licensePlate: licensePlate,
|
||||||
licenseState: licenseState
|
licenseState: licenseState,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
lookupVinByAddress(context, { licenseLastName, licenseStreetAddress, licenseZip, licenseState }) {
|
lookupVinByAddress(
|
||||||
|
context,
|
||||||
|
{ licenseLastName, licenseStreetAddress, licenseZip, licenseState }
|
||||||
|
) {
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.LookupVinByAddress.method,
|
method: endpoints.LookupVinByAddress.method,
|
||||||
endpoint: endpoints.LookupVinByAddress.url,
|
endpoint: endpoints.LookupVinByAddress.url,
|
||||||
|
|
@ -455,7 +489,7 @@ export const actions = {
|
||||||
licenseLastName: licenseLastName,
|
licenseLastName: licenseLastName,
|
||||||
licenseStreetAddress: licenseStreetAddress,
|
licenseStreetAddress: licenseStreetAddress,
|
||||||
licenseZip: licenseZip,
|
licenseZip: licenseZip,
|
||||||
licenseState: licenseState
|
licenseState: licenseState,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -489,10 +523,22 @@ export const actions = {
|
||||||
})
|
})
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
context.commit(storeMutations.UPDATE_CAR_ID, response.data.carId);
|
context.commit(storeMutations.UPDATE_CAR_ID, response.data.carId);
|
||||||
context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, response.data.category);
|
context.commit(
|
||||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, response.data.imageUrl);
|
storeMutations.UPDATE_VEHICLE_CATEGORY,
|
||||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, response.data.imageVifNumber);
|
response.data.category
|
||||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, response.data.imageVifColor);
|
);
|
||||||
|
context.commit(
|
||||||
|
storeMutations.UPDATE_VEHICLE_IMAGE_URL,
|
||||||
|
response.data.imageUrl
|
||||||
|
);
|
||||||
|
context.commit(
|
||||||
|
storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER,
|
||||||
|
response.data.imageVifNumber
|
||||||
|
);
|
||||||
|
context.commit(
|
||||||
|
storeMutations.UPDATE_VEHICLE_IMAGE_COLOR,
|
||||||
|
response.data.imageVifColor
|
||||||
|
);
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -506,8 +552,8 @@ export const actions = {
|
||||||
validateZip(context, { zip }) {
|
validateZip(context, { zip }) {
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
methods: endpoints.ValidateZip.method,
|
methods: endpoints.ValidateZip.method,
|
||||||
endpoint: `${endpoints.ValidateZip.url}/${zip}`
|
endpoint: `${endpoints.ValidateZip.url}/${zip}`,
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Dependency Actions
|
// Dependency Actions
|
||||||
|
|
@ -522,7 +568,7 @@ export const actions = {
|
||||||
},
|
},
|
||||||
resetRegistrationAndDependencies(context) {
|
resetRegistrationAndDependencies(context) {
|
||||||
context.commit(storeMutations.RESET_REGISTRATION_STATE);
|
context.commit(storeMutations.RESET_REGISTRATION_STATE);
|
||||||
context.commit(storeMutations.RESET_GLASS_PARTS_STATE)
|
context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
|
||||||
},
|
},
|
||||||
resetPartsAndDependencies(context) {
|
resetPartsAndDependencies(context) {
|
||||||
context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
|
context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
|
||||||
|
|
@ -578,8 +624,8 @@ export const actions = {
|
||||||
assignmentId: experiment.assignmentId,
|
assignmentId: experiment.assignmentId,
|
||||||
sessionKey: sessionKey,
|
sessionKey: sessionKey,
|
||||||
pageName: pageName,
|
pageName: pageName,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -587,13 +633,28 @@ export const actions = {
|
||||||
updateStoreWithSaveSessionResponse(context, { referralNumber, referralDate, referralCorrelationId, eon, accountNumber, savedSessionId, crmCustomerId }) {
|
updateStoreWithSaveSessionResponse(context, { referralNumber, referralDate, referralCorrelationId, eon, accountNumber, savedSessionId, crmCustomerId }) {
|
||||||
context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
|
context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
|
||||||
context.commit(storeMutations.UPDATE_REFERRAL_DATE, referralDate);
|
context.commit(storeMutations.UPDATE_REFERRAL_DATE, referralDate);
|
||||||
context.commit(storeMutations.UPDATE_REFERRAL_CORRELATION_ID, referralCorrelationId);
|
context.commit(
|
||||||
|
storeMutations.UPDATE_REFERRAL_CORRELATION_ID,
|
||||||
|
referralCorrelationId
|
||||||
|
);
|
||||||
context.commit(storeMutations.UPDATE_EON, eon);
|
context.commit(storeMutations.UPDATE_EON, eon);
|
||||||
context.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, accountNumber);
|
context.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, accountNumber);
|
||||||
context.commit(storeMutations.UPDATE_SAVED_SESSION_ID, savedSessionId);
|
context.commit(storeMutations.UPDATE_SAVED_SESSION_ID, savedSessionId);
|
||||||
context.commit(storeMutations.UPDATE_CRM_CUSTOMER_ID, crmCustomerId);
|
context.commit(storeMutations.UPDATE_CRM_CUSTOMER_ID, crmCustomerId);
|
||||||
},
|
},
|
||||||
logPageView(context, { userId, sessionKey, pageName, sessionId, action, event, shouldUseSessionId, experimentsForUser }) {
|
logPageView(
|
||||||
|
context,
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
sessionKey,
|
||||||
|
pageName,
|
||||||
|
sessionId,
|
||||||
|
action,
|
||||||
|
event,
|
||||||
|
shouldUseSessionId,
|
||||||
|
experimentsForUser,
|
||||||
|
}
|
||||||
|
) {
|
||||||
var payload = {
|
var payload = {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
sessionKey: sessionKey,
|
sessionKey: sessionKey,
|
||||||
|
|
@ -603,17 +664,31 @@ export const actions = {
|
||||||
action: action,
|
action: action,
|
||||||
event: event,
|
event: event,
|
||||||
shouldUseSessionId: shouldUseSessionId,
|
shouldUseSessionId: shouldUseSessionId,
|
||||||
experimentsForUser: experimentsForUser
|
experimentsForUser: experimentsForUser,
|
||||||
};
|
};
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.LogPageView.method,
|
method: endpoints.LogPageView.method,
|
||||||
endpoint: endpoints.LogPageView.url,
|
endpoint: endpoints.LogPageView.url,
|
||||||
payload: payload,
|
payload: payload,
|
||||||
logApiCall: false
|
logApiCall: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
logCustomEvent(context, { userId, sessionKey, pageName, sessionId, category, action, label, value, shouldUseSessionId, experimentsForUser }) {
|
logCustomEvent(
|
||||||
|
context,
|
||||||
|
{
|
||||||
|
userId,
|
||||||
|
sessionKey,
|
||||||
|
pageName,
|
||||||
|
sessionId,
|
||||||
|
category,
|
||||||
|
action,
|
||||||
|
label,
|
||||||
|
value,
|
||||||
|
shouldUseSessionId,
|
||||||
|
experimentsForUser,
|
||||||
|
}
|
||||||
|
) {
|
||||||
var payload = {
|
var payload = {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
sessionKey: sessionKey,
|
sessionKey: sessionKey,
|
||||||
|
|
@ -625,14 +700,14 @@ export const actions = {
|
||||||
label: label,
|
label: label,
|
||||||
value: value,
|
value: value,
|
||||||
shouldUseSessionId: shouldUseSessionId,
|
shouldUseSessionId: shouldUseSessionId,
|
||||||
experimentsForUser: experimentsForUser
|
experimentsForUser: experimentsForUser,
|
||||||
};
|
};
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.LogCustomEvent.method,
|
method: endpoints.LogCustomEvent.method,
|
||||||
endpoint: endpoints.LogCustomEvent.url,
|
endpoint: endpoints.LogCustomEvent.url,
|
||||||
payload: payload,
|
payload: payload,
|
||||||
logApiCall: false
|
logApiCall: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
initializeSession(context, { userId, sessionId, userAgent, referrer }) {
|
initializeSession(context, { userId, sessionId, userAgent, referrer }) {
|
||||||
|
|
@ -644,22 +719,28 @@ export const actions = {
|
||||||
userAgent: userAgent,
|
userAgent: userAgent,
|
||||||
operatorId: "WEB",
|
operatorId: "WEB",
|
||||||
userName: "SafeliteConceptFunnel",
|
userName: "SafeliteConceptFunnel",
|
||||||
referrer: referrer
|
referrer: referrer,
|
||||||
};
|
};
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.InitializeSession.method,
|
method: endpoints.InitializeSession.method,
|
||||||
endpoint: endpoints.InitializeSession.url,
|
endpoint: endpoints.InitializeSession.url,
|
||||||
payload: payload,
|
payload: payload,
|
||||||
logApiCall: false
|
logApiCall: false,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Misc Actions
|
// Misc Actions
|
||||||
setReferralInformation(context, { referralNumber, referralDate, referralCorrelationId, eon }) {
|
setReferralInformation(
|
||||||
|
context,
|
||||||
|
{ referralNumber, referralDate, referralCorrelationId, eon }
|
||||||
|
) {
|
||||||
context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
|
context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
|
||||||
context.commit(storeMutations.UPDATE_REFERRAL_DATE, referralDate);
|
context.commit(storeMutations.UPDATE_REFERRAL_DATE, referralDate);
|
||||||
context.commit(storeMutations.UPDATE_REFERRAL_CORRELATION_ID, referralCorrelationId);
|
context.commit(
|
||||||
|
storeMutations.UPDATE_REFERRAL_CORRELATION_ID,
|
||||||
|
referralCorrelationId
|
||||||
|
);
|
||||||
context.commit(storeMutations.UPDATE_EON, eon);
|
context.commit(storeMutations.UPDATE_EON, eon);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
@ -667,11 +748,14 @@ export const actions = {
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.GetExperimentsByUser.method,
|
method: endpoints.GetExperimentsByUser.method,
|
||||||
endpoint: `${endpoints.GetExperimentsByUser.url}/${userId}`,
|
endpoint: `${endpoints.GetExperimentsByUser.url}/${userId}`,
|
||||||
payload: {}
|
payload: {},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
async runExperimentsForTrigger(context, { userId, triggerEvent, triggerValue }) {
|
async runExperimentsForTrigger(
|
||||||
|
context,
|
||||||
|
{ userId, triggerEvent, triggerValue }
|
||||||
|
) {
|
||||||
if (triggerEvent == experimentTriggers.SITE_ENTRY) {
|
if (triggerEvent == experimentTriggers.SITE_ENTRY) {
|
||||||
context.commit(storeMutations.UPDATE_TRIGGERED_SITE_ENTRY, true);
|
context.commit(storeMutations.UPDATE_TRIGGERED_SITE_ENTRY, true);
|
||||||
}
|
}
|
||||||
|
|
@ -681,7 +765,7 @@ export const actions = {
|
||||||
userId: userId,
|
userId: userId,
|
||||||
triggerEvent: triggerEvent,
|
triggerEvent: triggerEvent,
|
||||||
triggerValue: triggerValue,
|
triggerValue: triggerValue,
|
||||||
experimentOrder: context.getters.experimentOrder
|
experimentOrder: context.getters.experimentOrder,
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await globalMethods.callHttpClient({
|
const response = await globalMethods.callHttpClient({
|
||||||
|
|
@ -690,7 +774,10 @@ export const actions = {
|
||||||
payload: payload,
|
payload: payload,
|
||||||
});
|
});
|
||||||
|
|
||||||
context.commit(storeMutations.UPDATE_EXPERIMENTS, response.data.experiments);
|
context.commit(
|
||||||
|
storeMutations.UPDATE_EXPERIMENTS,
|
||||||
|
response.data.experiments
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
getEvoxImage(context, { relativeUrl }) {
|
getEvoxImage(context, { relativeUrl }) {
|
||||||
|
|
@ -719,7 +806,7 @@ export const actions = {
|
||||||
carId: carId,
|
carId: carId,
|
||||||
glassPieces: glassArray ?? [],
|
glassPieces: glassArray ?? [],
|
||||||
zip: zipCode,
|
zip: zipCode,
|
||||||
vin: vin
|
vin: vin,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -755,7 +842,7 @@ export const actions = {
|
||||||
glassPieces: glassArray,
|
glassPieces: glassArray,
|
||||||
answerResults: resultsArray,
|
answerResults: resultsArray,
|
||||||
zip: zipCode,
|
zip: zipCode,
|
||||||
vin: vin
|
vin: vin,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -773,24 +860,31 @@ export const actions = {
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.GetCapabilityQuestions.method,
|
method: endpoints.GetCapabilityQuestions.method,
|
||||||
endpoint: `${endpoints.GetCapabilityQuestions.url}/${carId}/${partNumber}`,
|
endpoint: `${endpoints.GetCapabilityQuestions.url}/${carId}/${partNumber}`,
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getPartFromCapabilityQuestionAnswer(context, location) {
|
getPartFromCapabilityQuestionAnswer(context, glassLocation) {
|
||||||
const pageData = context.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS);
|
const pageData = context.getters.pageData(
|
||||||
|
fmgPageValues.CAPABILITY_QUESTIONS
|
||||||
|
);
|
||||||
|
|
||||||
const part = pageData.partsOrQuestions.find(x => x.location === location).parts[0];
|
const part = pageData.partsOrQuestions.find(
|
||||||
const capabilityQuestionAnswers = context.getters.damage.capabilityQuestionAnswers;
|
(x) => x.location === glassLocation
|
||||||
const capabilityQuestionAnswersForPart = capabilityQuestionAnswers.find(x => x.location === location);
|
).parts[0];
|
||||||
|
const capabilityQuestionAnswers =
|
||||||
|
context.getters.damage.capabilityQuestionAnswers;
|
||||||
|
const capabilityQuestionAnswersForPart = capabilityQuestionAnswers.find(
|
||||||
|
(x) => x.location === glassLocation
|
||||||
|
);
|
||||||
|
|
||||||
return globalMethods.callHttpClient({
|
return globalMethods.callHttpClient({
|
||||||
method: endpoints.GetPartFromCapabilityAnswer.method,
|
method: endpoints.GetPartFromCapabilityAnswer.method,
|
||||||
endpoint: endpoints.GetPartFromCapabilityAnswer.url,
|
endpoint: endpoints.GetPartFromCapabilityAnswer.url,
|
||||||
payload: {
|
payload: {
|
||||||
part,
|
part,
|
||||||
capabilityAnswerResults: capabilityQuestionAnswersForPart
|
capabilityAnswerResults: capabilityQuestionAnswersForPart,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Session API Actions
|
// Session API Actions
|
||||||
|
|
@ -825,21 +919,21 @@ export const actions = {
|
||||||
damage: {
|
damage: {
|
||||||
numberOfChips: damage.numberOfChips,
|
numberOfChips: damage.numberOfChips,
|
||||||
glassToReplace: damage.glassToReplace,
|
glassToReplace: damage.glassToReplace,
|
||||||
isRepair: damage.isRepair
|
isRepair: damage.isRepair,
|
||||||
},
|
},
|
||||||
customer: {
|
customer: {
|
||||||
emailAddress: order.customer.emailAddress,
|
emailAddress: order.customer.emailAddress,
|
||||||
},
|
},
|
||||||
lineItems: {
|
lineItems: {
|
||||||
glassParts: lineItems.glassParts
|
glassParts: lineItems.glassParts,
|
||||||
},
|
},
|
||||||
serviceLocation: {
|
serviceLocation: {
|
||||||
streetAddress: order.serviceLocation.address,
|
streetAddress: order.serviceLocation.address,
|
||||||
city: order.serviceLocation.city,
|
city: order.serviceLocation.city,
|
||||||
state: order.serviceLocation.state,
|
state: order.serviceLocation.state,
|
||||||
zipCode: order.serviceLocation.zipCode
|
zipCode: order.serviceLocation.zipCode,
|
||||||
},
|
},
|
||||||
referralNumber: order.referralNumber?.toString(), // TODO It'd be nice to save these as strings in the first place
|
referralNumber: order.referralNumber?.toString(), // TODO It'd be nice to save these as strings in the first place
|
||||||
referralDate: order.referralDate,
|
referralDate: order.referralDate,
|
||||||
accountNumber: order.accountNumber?.toString(),
|
accountNumber: order.accountNumber?.toString(),
|
||||||
existingPromoCode: null,
|
existingPromoCode: null,
|
||||||
|
|
@ -874,8 +968,7 @@ export const actions = {
|
||||||
|
|
||||||
// Vehicle
|
// Vehicle
|
||||||
saveVehicleYear(context, year) {
|
saveVehicleYear(context, year) {
|
||||||
|
//Reset dependent state when changing
|
||||||
//Reset dependent state when changing
|
|
||||||
if (context.state.order.vehicle.year !== year) {
|
if (context.state.order.vehicle.year !== year) {
|
||||||
context.commit(storeMutations.UPDATE_MAKE, null);
|
context.commit(storeMutations.UPDATE_MAKE, null);
|
||||||
context.commit(storeMutations.UPDATE_MODEL, null);
|
context.commit(storeMutations.UPDATE_MODEL, null);
|
||||||
|
|
@ -896,7 +989,6 @@ export const actions = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
saveVehicleMake(context, make) {
|
saveVehicleMake(context, make) {
|
||||||
|
|
||||||
//Reset dependent state when changing
|
//Reset dependent state when changing
|
||||||
if (context.state.order.vehicle.make !== make) {
|
if (context.state.order.vehicle.make !== make) {
|
||||||
context.commit(storeMutations.UPDATE_MODEL, null);
|
context.commit(storeMutations.UPDATE_MODEL, null);
|
||||||
|
|
@ -917,8 +1009,7 @@ export const actions = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
saveVehicleModel(context, model) {
|
saveVehicleModel(context, model) {
|
||||||
|
//Reset dependent state when changing
|
||||||
//Reset dependent state when changing
|
|
||||||
if (context.state.order.vehicle.model !== model) {
|
if (context.state.order.vehicle.model !== model) {
|
||||||
context.commit(storeMutations.UPDATE_STYLE, null);
|
context.commit(storeMutations.UPDATE_STYLE, null);
|
||||||
context.commit(storeMutations.UPDATE_CAR_ID, null);
|
context.commit(storeMutations.UPDATE_CAR_ID, null);
|
||||||
|
|
@ -937,7 +1028,7 @@ export const actions = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
saveVehicleStyle(context, style) {
|
saveVehicleStyle(context, style) {
|
||||||
//Reset dependent state when changing
|
//Reset dependent state when changing
|
||||||
if (context.state.order.vehicle.style !== style) {
|
if (context.state.order.vehicle.style !== style) {
|
||||||
context.commit(storeMutations.UPDATE_CAR_ID, null);
|
context.commit(storeMutations.UPDATE_CAR_ID, null);
|
||||||
context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, null);
|
context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, null);
|
||||||
|
|
@ -954,20 +1045,32 @@ export const actions = {
|
||||||
context.commit(storeMutations.UPDATE_STYLE, style);
|
context.commit(storeMutations.UPDATE_STYLE, style);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
saveVehicleDamage(context, { isWindshieldRepair, selectedGlassToReplace, selectedWindshieldChipCount }) {
|
saveVehicleDamage(
|
||||||
|
context,
|
||||||
|
{ isWindshieldRepair, selectedGlassToReplace, selectedWindshieldChipCount }
|
||||||
|
) {
|
||||||
const selectedGlassPassedInSorted = selectedGlassToReplace.slice().sort();
|
const selectedGlassPassedInSorted = selectedGlassToReplace.slice().sort();
|
||||||
const isGlassToReplaceTheSame = (context.state.order.damage.glassToReplace?.length === selectedGlassToReplace.length)
|
const isGlassToReplaceTheSame =
|
||||||
&& context.state.order.damage.glassToReplace
|
context.state.order.damage.glassToReplace?.length ===
|
||||||
|
selectedGlassToReplace.length &&
|
||||||
|
context.state.order.damage.glassToReplace
|
||||||
.slice()
|
.slice()
|
||||||
.sort()
|
.sort()
|
||||||
.every((obj, index) => obj.glassLocation === selectedGlassPassedInSorted[index].glassLocation && obj.glassName === selectedGlassPassedInSorted[index].glassName);
|
.every(
|
||||||
const isWindshieldRepairTheSame = isWindshieldRepair === context.state.order.damage.isRepair;
|
(obj, index) =>
|
||||||
const isChipCountTheSame = Array.isArray(selectedWindshieldChipCount) //TODO: fix the underlying components so this is never an array
|
obj.glassLocation ===
|
||||||
? selectedWindshieldChipCount[0] === context.state.order.damage.numberOfChips
|
selectedGlassPassedInSorted[index].glassLocation &&
|
||||||
: selectedWindshieldChipCount === context.state.order.damage.numberOfChips;
|
obj.glassName === selectedGlassPassedInSorted[index].glassName
|
||||||
|
);
|
||||||
|
const isWindshieldRepairTheSame =
|
||||||
|
isWindshieldRepair === context.state.order.damage.isRepair;
|
||||||
|
|
||||||
const isDamageChanging = !isGlassToReplaceTheSame || !isWindshieldRepairTheSame || (isWindshieldRepair && !isChipCountTheSame);
|
const isChipCountTheSame = selectedWindshieldChipCount === context.state.order.damage.numberOfChips;
|
||||||
|
|
||||||
|
const isDamageChanging =
|
||||||
|
!isGlassToReplaceTheSame ||
|
||||||
|
!isWindshieldRepairTheSame ||
|
||||||
|
(isWindshieldRepair && !isChipCountTheSame);
|
||||||
|
|
||||||
if (isDamageChanging) {
|
if (isDamageChanging) {
|
||||||
//Reset dependent state when changing
|
//Reset dependent state when changing
|
||||||
|
|
@ -975,14 +1078,23 @@ export const actions = {
|
||||||
|
|
||||||
// Save new values
|
// Save new values
|
||||||
context.commit(storeMutations.UPDATE_IS_REPAIR, isWindshieldRepair);
|
context.commit(storeMutations.UPDATE_IS_REPAIR, isWindshieldRepair);
|
||||||
context.commit(storeMutations.UPDATE_NUMBER_OF_CHIPS, isWindshieldRepair ? parseInt(selectedWindshieldChipCount) : null);
|
context.commit(
|
||||||
context.commit(storeMutations.UPDATE_GLASS_TO_REPLACE, selectedGlassToReplace);
|
storeMutations.UPDATE_NUMBER_OF_CHIPS,
|
||||||
|
isWindshieldRepair ? parseInt(selectedWindshieldChipCount) : null
|
||||||
|
);
|
||||||
|
context.commit(
|
||||||
|
storeMutations.UPDATE_GLASS_TO_REPLACE,
|
||||||
|
selectedGlassToReplace
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Vin lookup
|
// Vin lookup
|
||||||
saveVinLookup(context, { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }) {
|
saveVinLookup(
|
||||||
//Reset dependent state when changing
|
context,
|
||||||
|
{ isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }
|
||||||
|
) {
|
||||||
|
//Reset dependent state when changing
|
||||||
if (vehicleInfo.vin !== context.state.order.vehicle.vin) {
|
if (vehicleInfo.vin !== context.state.order.vehicle.vin) {
|
||||||
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
||||||
context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
|
context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
|
||||||
|
|
@ -996,10 +1108,15 @@ export const actions = {
|
||||||
context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo);
|
context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
saveRegistrationLicensePlateLookup(context, { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }) {
|
saveRegistrationLicensePlateLookup(
|
||||||
//Reset dependent state when changing
|
context,
|
||||||
if (registrationInfo?.licensePlate !== context.state.order.vehicle.registration?.licensePlate) {
|
{ isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }
|
||||||
|
) {
|
||||||
|
//Reset dependent state when changing
|
||||||
|
if (
|
||||||
|
registrationInfo?.licensePlate !==
|
||||||
|
context.state.order.vehicle.registration?.licensePlate
|
||||||
|
) {
|
||||||
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
||||||
|
|
||||||
if (!isSelectedGlassAvailableForVehicle) {
|
if (!isSelectedGlassAvailableForVehicle) {
|
||||||
|
|
@ -1012,10 +1129,25 @@ export const actions = {
|
||||||
context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo);
|
context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
saveRegistrationAddressLookup(context, { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }) {
|
saveRegistrationAddressLookup(
|
||||||
//Reset dependent state when changing
|
context,
|
||||||
if (registrationInfo?.address !== context.state.order.vehicle.registration?.address || registrationInfo?.city !== context.state.order.vehicle.registration?.city || registrationInfo?.state !== context.state.order.vehicle.registration?.state || registrationInfo?.zipCode !== context.state.order.vehicle.registration?.zipCode || registrationInfo?.firstName !== context.state.order.vehicle.registration?.firstName || registrationInfo?.lastName !== context.state.order.vehicle.registration?.lastName) {
|
{ isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }
|
||||||
|
) {
|
||||||
|
//Reset dependent state when changing
|
||||||
|
if (
|
||||||
|
registrationInfo?.address !==
|
||||||
|
context.state.order.vehicle.registration?.address ||
|
||||||
|
registrationInfo?.city !==
|
||||||
|
context.state.order.vehicle.registration?.city ||
|
||||||
|
registrationInfo?.state !==
|
||||||
|
context.state.order.vehicle.registration?.state ||
|
||||||
|
registrationInfo?.zipCode !==
|
||||||
|
context.state.order.vehicle.registration?.zipCode ||
|
||||||
|
registrationInfo?.firstName !==
|
||||||
|
context.state.order.vehicle.registration?.firstName ||
|
||||||
|
registrationInfo?.lastName !==
|
||||||
|
context.state.order.vehicle.registration?.lastName
|
||||||
|
) {
|
||||||
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
||||||
|
|
||||||
if (!isSelectedGlassAvailableForVehicle) {
|
if (!isSelectedGlassAvailableForVehicle) {
|
||||||
|
|
@ -1030,72 +1162,141 @@ export const actions = {
|
||||||
},
|
},
|
||||||
savePartQuestionAnswers(context, partQuestionAnswersArray) {
|
savePartQuestionAnswers(context, partQuestionAnswersArray) {
|
||||||
// if part question answers have changed, reset subsequent question answers
|
// if part question answers have changed, reset subsequent question answers
|
||||||
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(context.getters.damage.partQuestionAnswers, "result");
|
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(
|
||||||
const sortedPartQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(partQuestionAnswersArray, "result")
|
context.getters.damage.partQuestionAnswers,
|
||||||
const havePartQuestionAnswersChanged = sortedPreviousResultsArray?.length !== sortedPartQuestionAnswersArray.length ||
|
"result"
|
||||||
!sortedPreviousResultsArray?.every((x, i) => x.result === sortedPartQuestionAnswersArray[i].result);
|
);
|
||||||
|
const sortedPartQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(
|
||||||
|
partQuestionAnswersArray,
|
||||||
|
"result"
|
||||||
|
);
|
||||||
|
const havePartQuestionAnswersChanged =
|
||||||
|
sortedPreviousResultsArray?.length !==
|
||||||
|
sortedPartQuestionAnswersArray.length ||
|
||||||
|
!sortedPreviousResultsArray?.every(
|
||||||
|
(x, i) => x.result === sortedPartQuestionAnswersArray[i].result
|
||||||
|
);
|
||||||
|
|
||||||
if (havePartQuestionAnswersChanged) {
|
if (havePartQuestionAnswersChanged) {
|
||||||
context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
|
context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
|
||||||
context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null);
|
context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null);
|
||||||
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
|
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
|
||||||
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.VEHICLE_PARTS, data: null });
|
context.commit(storeMutations.UPDATE_PAGE_DATA, {
|
||||||
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.MOLDING_QUESTIONS, data: null });
|
page: fmgPageValues.VEHICLE_PARTS,
|
||||||
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.CAPABILITY_QUESTIONS, data: null });
|
data: null,
|
||||||
|
});
|
||||||
|
context.commit(storeMutations.UPDATE_PAGE_DATA, {
|
||||||
|
page: fmgPageValues.MOLDING_QUESTIONS,
|
||||||
|
data: null,
|
||||||
|
});
|
||||||
|
context.commit(storeMutations.UPDATE_PAGE_DATA, {
|
||||||
|
page: fmgPageValues.CAPABILITY_QUESTIONS,
|
||||||
|
data: null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//Save new values
|
//Save new values
|
||||||
context.commit(storeMutations.UPDATE_PART_QUESTION_ANSWERS, partQuestionAnswersArray);
|
context.commit(
|
||||||
|
storeMutations.UPDATE_PART_QUESTION_ANSWERS,
|
||||||
|
partQuestionAnswersArray
|
||||||
|
);
|
||||||
},
|
},
|
||||||
resetMoldingAndCapabilityQuestionAnswersIfNeeded(context, matchedParts) {
|
resetMoldingAndCapabilityQuestionAnswersIfNeeded(context, matchedParts) {
|
||||||
const partsOrQuestionsDataToCompareWith = context.getters.pageData(fmgPageValues.MOLDING_QUESTIONS)?.partsOrQuestions ?? context.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS)?.partsOrQuestions ?? [];
|
const partsOrQuestionsDataToCompareWith =
|
||||||
|
context.getters.pageData(fmgPageValues.MOLDING_QUESTIONS)
|
||||||
|
?.partsOrQuestions ??
|
||||||
|
context.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS)
|
||||||
|
?.partsOrQuestions ??
|
||||||
|
[];
|
||||||
|
|
||||||
function getAllPartNumbers(partsOrQuestions) {
|
function getAllPartNumbers(partsOrQuestions) {
|
||||||
return partsOrQuestions[0]?.parts
|
return partsOrQuestions[0]?.parts
|
||||||
? [...partsOrQuestions].map(glass => glass.parts).flat().map(part => part.partNumber).filter(partNumber => !partNumber.toUpperCase().includes("FEE")).sort().join(",")
|
? [...partsOrQuestions]
|
||||||
: []
|
.map((glass) => glass.parts)
|
||||||
|
.flat()
|
||||||
|
.map((part) => part.partNumber)
|
||||||
|
.filter((partNumber) => !partNumber.toUpperCase().includes("FEE"))
|
||||||
|
.sort()
|
||||||
|
.join(",")
|
||||||
|
: [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const previouslySelectedPartNumbers = getAllPartNumbers(partsOrQuestionsDataToCompareWith);
|
const previouslySelectedPartNumbers = getAllPartNumbers(
|
||||||
|
partsOrQuestionsDataToCompareWith
|
||||||
|
);
|
||||||
const currentlySelectedPartNumbers = getAllPartNumbers(matchedParts);
|
const currentlySelectedPartNumbers = getAllPartNumbers(matchedParts);
|
||||||
|
|
||||||
const haveSelectedVehiclePartsChanged = previouslySelectedPartNumbers !== currentlySelectedPartNumbers;
|
const haveSelectedVehiclePartsChanged =
|
||||||
|
previouslySelectedPartNumbers !== currentlySelectedPartNumbers;
|
||||||
|
|
||||||
if (haveSelectedVehiclePartsChanged) {
|
if (haveSelectedVehiclePartsChanged) {
|
||||||
context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
|
context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
|
||||||
context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null);
|
context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null);
|
||||||
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
|
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
|
||||||
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.MOLDING_QUESTIONS, data: null });
|
context.commit(storeMutations.UPDATE_PAGE_DATA, {
|
||||||
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.CAPABILITY_QUESTIONS, data: null });
|
page: fmgPageValues.MOLDING_QUESTIONS,
|
||||||
|
data: null,
|
||||||
|
});
|
||||||
|
context.commit(storeMutations.UPDATE_PAGE_DATA, {
|
||||||
|
page: fmgPageValues.CAPABILITY_QUESTIONS,
|
||||||
|
data: null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
saveMoldingQuestionAnswers(context, moldingQuestionAnswers) {
|
saveMoldingQuestionAnswers(context, moldingQuestionAnswers) {
|
||||||
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(context.getters.damage.moldingQuestionAnswers, "partNum");
|
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(
|
||||||
const sortedMoldingQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(moldingQuestionAnswers, "partNum");
|
context.getters.damage.moldingQuestionAnswers,
|
||||||
const haveMoldingQuestionAnswersChanged = sortedPreviousResultsArray?.length !== sortedMoldingQuestionAnswersArray.length ||
|
"partNum"
|
||||||
!sortedPreviousResultsArray?.every((x, i) => x.partNum === sortedMoldingQuestionAnswersArray[i].partNum);
|
);
|
||||||
|
const sortedMoldingQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(
|
||||||
|
moldingQuestionAnswers,
|
||||||
|
"partNum"
|
||||||
|
);
|
||||||
|
const haveMoldingQuestionAnswersChanged =
|
||||||
|
sortedPreviousResultsArray?.length !==
|
||||||
|
sortedMoldingQuestionAnswersArray.length ||
|
||||||
|
!sortedPreviousResultsArray?.every(
|
||||||
|
(x, i) => x.partNum === sortedMoldingQuestionAnswersArray[i].partNum
|
||||||
|
);
|
||||||
|
|
||||||
if (haveMoldingQuestionAnswersChanged) {
|
if (haveMoldingQuestionAnswersChanged) {
|
||||||
context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
|
context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
|
||||||
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
|
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
|
||||||
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.CAPABILITY_QUESTIONS, data: null });
|
context.commit(storeMutations.UPDATE_PAGE_DATA, {
|
||||||
|
page: fmgPageValues.CAPABILITY_QUESTIONS,
|
||||||
|
data: null,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
//Save new values
|
//Save new values
|
||||||
context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, moldingQuestionAnswers);
|
context.commit(
|
||||||
|
storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS,
|
||||||
|
moldingQuestionAnswers
|
||||||
|
);
|
||||||
},
|
},
|
||||||
saveCapabilityQuestionAnswers(context, capabilityQuestionAnswers) {
|
saveCapabilityQuestionAnswers(context, capabilityQuestionAnswers) {
|
||||||
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(context.getters.damage.capabilityQuestionAnswers, "result");
|
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(
|
||||||
const sortedCapabilityQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(capabilityQuestionAnswers, "result");
|
context.getters.damage.capabilityQuestionAnswers,
|
||||||
const haveCapabilityQuestionAnswersChanged = sortedPreviousResultsArray?.length !== sortedCapabilityQuestionAnswersArray.length ||
|
"result"
|
||||||
!sortedPreviousResultsArray?.every((x, i) => x.result === sortedCapabilityQuestionAnswersArray[i].result);
|
);
|
||||||
|
const sortedCapabilityQuestionAnswersArray =
|
||||||
|
sortArrayOfObjectsByPropertyValue(capabilityQuestionAnswers, "result");
|
||||||
|
const haveCapabilityQuestionAnswersChanged =
|
||||||
|
sortedPreviousResultsArray?.length !==
|
||||||
|
sortedCapabilityQuestionAnswersArray.length ||
|
||||||
|
!sortedPreviousResultsArray?.every(
|
||||||
|
(x, i) => x.result === sortedCapabilityQuestionAnswersArray[i].result
|
||||||
|
);
|
||||||
|
|
||||||
if (haveCapabilityQuestionAnswersChanged) {
|
if (haveCapabilityQuestionAnswersChanged) {
|
||||||
context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
|
context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Save new values
|
//Save new values
|
||||||
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, capabilityQuestionAnswers);
|
context.commit(
|
||||||
|
storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS,
|
||||||
|
capabilityQuestionAnswers
|
||||||
|
);
|
||||||
},
|
},
|
||||||
// Misc order actions
|
// Misc order actions
|
||||||
saveServiceLocation(context, serviceLocationInfo) {
|
saveServiceLocation(context, serviceLocationInfo) {
|
||||||
|
|
@ -1107,7 +1308,6 @@ export const actions = {
|
||||||
saveVin(context, { isSelectedGlassAvailableForVehicle, vehicleInfo }) {
|
saveVin(context, { isSelectedGlassAvailableForVehicle, vehicleInfo }) {
|
||||||
//Reset dependent state when changing
|
//Reset dependent state when changing
|
||||||
if (vehicleInfo.vin !== context.state.order.vehicle.vin) {
|
if (vehicleInfo.vin !== context.state.order.vehicle.vin) {
|
||||||
|
|
||||||
if (!isSelectedGlassAvailableForVehicle) {
|
if (!isSelectedGlassAvailableForVehicle) {
|
||||||
context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
|
context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
|
||||||
context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
|
context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
|
||||||
|
|
@ -1122,12 +1322,11 @@ export const actions = {
|
||||||
},
|
},
|
||||||
clearVin(context) {
|
clearVin(context) {
|
||||||
context.commit(storeMutations.UPDATE_VEHICLE_VIN, null);
|
context.commit(storeMutations.UPDATE_VEHICLE_VIN, null);
|
||||||
}
|
},
|
||||||
}
|
};
|
||||||
|
|
||||||
export default createStore({
|
export default createStore({
|
||||||
plugins: [createPersistedState()],
|
plugins: [createPersistedState()],
|
||||||
|
|
||||||
// IMPORTANT: Be VERY careful when modifying these fields for at least a few reasons:
|
// IMPORTANT: Be VERY careful when modifying these fields for at least a few reasons:
|
||||||
// * The CMS can reference the fields by name
|
// * The CMS can reference the fields by name
|
||||||
// * Return users may have a previous "version" of the model, and we don't want
|
// * Return users may have a previous "version" of the model, and we don't want
|
||||||
|
|
@ -1150,21 +1349,18 @@ function getHasRecalibrationPart(state) {
|
||||||
} else { // Has 'requiresRecalibration' but no 'recalibrationType' at all
|
} else { // Has 'requiresRecalibration' but no 'recalibrationType' at all
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else { // Does not have 'requiresRecalibration'
|
} else {
|
||||||
|
// Does not have 'requiresRecalibration'
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {
|
function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {
|
||||||
if (!arrayOfObjects) return null;
|
if (!arrayOfObjects) return null;
|
||||||
|
|
||||||
return arrayOfObjects.sort((a, b) => {
|
return arrayOfObjects.sort((a, b) => {
|
||||||
if (a[propertyName] < b[propertyName])
|
if (a[propertyName] < b[propertyName]) return -1;
|
||||||
return -1;
|
else if (a[propertyName] > b[propertyName]) return 1;
|
||||||
else if (a[propertyName] > b[propertyName])
|
else return 0;
|
||||||
return 1;
|
});
|
||||||
else
|
}
|
||||||
return 0;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,6 @@ html {
|
||||||
&.list-button,
|
&.list-button,
|
||||||
&.list-card,
|
&.list-card,
|
||||||
&.list-card.list-button {
|
&.list-card.list-button {
|
||||||
border: none;
|
|
||||||
color: $red;
|
color: $red;
|
||||||
input[type=checkbox]:focus + label,
|
input[type=checkbox]:focus + label,
|
||||||
input[type=radio]:focus + label {
|
input[type=radio]:focus + label {
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,17 @@
|
||||||
<button
|
<button
|
||||||
:aria-disabled="isDisabled"
|
:aria-disabled="isDisabled"
|
||||||
class="btn d-flex align-items-center py-3 px-4 delay"
|
class="btn d-flex align-items-center py-3 px-4 delay"
|
||||||
:class="[isPrimary ? 'btn-primary' : 'btn-secondary',isFloat ? 'float-end' : '', isLoaderDisplayed ? 'has-loader' : '']"
|
:class="[
|
||||||
@click="clicked()"
|
isPrimary ? 'btn-primary' : 'btn-secondary',
|
||||||
>
|
isFloat ? 'float-end' : '',
|
||||||
|
isLoaderDisplayed ? 'has-loader' : '',
|
||||||
|
]"
|
||||||
|
@click="clicked">
|
||||||
<span class="m-0">{{ this.buttonText }}</span>
|
<span class="m-0">{{ this.buttonText }}</span>
|
||||||
<loader
|
<loader
|
||||||
class="ms-2"
|
class="ms-2"
|
||||||
v-if="isLoaderDisplayed"
|
v-if="isLoaderDisplayed"
|
||||||
v-bind:class="[this.loaderColor, this.loaderPosition]"
|
v-bind:class="[this.loaderColor, this.loaderPosition]" />
|
||||||
/>
|
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
@ -33,11 +35,16 @@ export default {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
removeLoader(){
|
removeLoader() {
|
||||||
this.isLoaderDisplayed = false;
|
this.isLoaderDisplayed = false;
|
||||||
},
|
},
|
||||||
clicked() {
|
clicked() {
|
||||||
this.pushEventToGA(this.$route.query[this.queryStrings.FMG_PAGE], this.GaActions.CLICKED, this.buttonText, true);
|
this.pushEventToGA(
|
||||||
|
this.$route.query[this.queryStrings.FMG_PAGE],
|
||||||
|
this.GaActions.CLICKED,
|
||||||
|
this.buttonText,
|
||||||
|
true
|
||||||
|
);
|
||||||
if (!this.isDisabled) {
|
if (!this.isDisabled) {
|
||||||
this.isLoaderDisplayed = true;
|
this.isLoaderDisplayed = true;
|
||||||
this.$emit("click-event");
|
this.$emit("click-event");
|
||||||
|
|
@ -98,7 +105,8 @@ export default {
|
||||||
background: $blue-700;
|
background: $blue-700;
|
||||||
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
|
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
|
||||||
}
|
}
|
||||||
&.delay {// fixes flicker while transitioning between states
|
&.delay {
|
||||||
|
// fixes flicker while transitioning between states
|
||||||
transition: background 0s 0s ease-in-out;
|
transition: background 0s 0s ease-in-out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -137,7 +145,8 @@ export default {
|
||||||
color: $white;
|
color: $white;
|
||||||
@include blue-gradient;
|
@include blue-gradient;
|
||||||
}
|
}
|
||||||
&.delay {// fixes flicker while transitioning between states
|
&.delay {
|
||||||
|
// fixes flicker while transitioning between states
|
||||||
transition: background 0s 0s ease-in-out;
|
transition: background 0s 0s ease-in-out;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,271 +1,139 @@
|
||||||
import { shallowMount } from "@vue/test-utils";
|
import { mount } from "@vue/test-utils";
|
||||||
import listButtonHorizontal from "./list-button-horizontal";
|
import listButtonHorizontal from "./list-button-horizontal";
|
||||||
import { nextTick } from "vue";
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
import { GaActions } from "@/constants/analytics";
|
|
||||||
|
|
||||||
describe("list-button-horizontal.vue", () => {
|
describe("list-button-horizontal.vue", () => {
|
||||||
it("Should return input type checkbox if isMultiSelect is true", async () => {
|
describe("styling/UI", () => {
|
||||||
// Act
|
it("Should return screen reader text", async () => {
|
||||||
const wrapper = shallowMount(listButtonHorizontal, {
|
// Act
|
||||||
propsData: {
|
const { wrapper } = setupMocks({
|
||||||
isMultiSelect: true,
|
mockData: {
|
||||||
},
|
propsData: {
|
||||||
|
screenReaderOnlyText: "Screen Reader Only Text",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const paragraph = wrapper.find("span.sr-only");
|
||||||
|
|
||||||
|
expect(paragraph.text()).toEqual("Screen Reader Only Text");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should return text alignment class", async () => {
|
||||||
|
// Act
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
textPosition: "text-center",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const paragraph = wrapper.find("span.m-0");
|
||||||
|
|
||||||
|
expect(paragraph.attributes("class")).toContain("text-center");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should return aria-required state", async () => {
|
||||||
|
// Act
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
isRequired: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const input = wrapper.find("input");
|
||||||
|
|
||||||
|
expect(input.attributes()["aria-required"]).toEqual("true");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("is cash or insurance button => has 'radio-fancy' class", () => {
|
||||||
|
// Arrange/Act
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
isCashOrInsurance: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const label = wrapper.find("label");
|
||||||
|
|
||||||
|
expect(label.classes()).toContain("radio-fancy");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("has buttonLabel => displays buttonLabel", () => {
|
||||||
|
// Arrange/Act
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
buttonLabel: "Surprise!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const content = wrapper.find(".list-button-horizontal-content");
|
||||||
|
expect(content.isVisible()).toBe(true);
|
||||||
|
expect(content.text()).toContain("Surprise!");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("has buttonLabelSubCopy => displays buttonLabelSubCopy", () => {
|
||||||
|
// Arrange/Act
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
buttonLabel: "Surprise!",
|
||||||
|
buttonLabelSubCopy: "Super duper surprise :)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const content = wrapper.find(".list-button-horizontal-content");
|
||||||
|
expect(content.isVisible()).toBe(true);
|
||||||
|
expect(content.text()).toContain("Super duper surprise :)");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("has screenReaderOnlyText => displays screenReaderOnlyText", () => {
|
||||||
|
// Arrange/Act
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
buttonLabel: "Surprise!",
|
||||||
|
buttonLabelSubCopy: "Super duper surprise :)",
|
||||||
|
screenReaderOnlyText: "Tests are fun!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const content = wrapper.find(".list-button-horizontal-content");
|
||||||
|
const screenReaderOnlyText = wrapper.find(".sr-only");
|
||||||
|
expect(content.isVisible()).toBe(true);
|
||||||
|
expect(screenReaderOnlyText.exists()).toBe(true);
|
||||||
|
expect(screenReaderOnlyText.text()).toContain("Tests are fun!");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assert
|
|
||||||
const input = wrapper.find("input");
|
|
||||||
|
|
||||||
expect(input.attributes().type).toEqual("checkbox");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return input type radio if isMultiSelect is false or not specified", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButtonHorizontal, {
|
|
||||||
propsData: {
|
|
||||||
isMultiSelect: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
const input = wrapper.find("input");
|
|
||||||
|
|
||||||
expect(input.attributes().type).toEqual("radio");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return primary label text (buttonID)", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButtonHorizontal, {
|
|
||||||
propsData: {
|
|
||||||
buttonID: "List Card Checkbox",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
const label = wrapper.find("label");
|
|
||||||
|
|
||||||
expect(label.attributes().for).toEqual("List Card Checkbox");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return screen reader text", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButtonHorizontal, {
|
|
||||||
propsData: {
|
|
||||||
screenReaderOnlyText: "Screen Reader Only Text",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
const paragraph = wrapper.find("span.sr-only");
|
|
||||||
|
|
||||||
expect(paragraph.text()).toEqual("Screen Reader Only Text");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return text alignment class", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButtonHorizontal, {
|
|
||||||
propsData: {
|
|
||||||
textPosition: "text-center",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
const paragraph = wrapper.find("span.m-0");
|
|
||||||
|
|
||||||
expect(paragraph.attributes("class")).toContain("text-center");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return aria-required state", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButtonHorizontal, {
|
|
||||||
propsData: {
|
|
||||||
isRequired: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
const input = wrapper.find("input");
|
|
||||||
|
|
||||||
expect(input.attributes()["aria-required"]).toEqual("true");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return loader enabled true", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButtonHorizontal, {
|
|
||||||
global: {
|
|
||||||
mocks: {
|
|
||||||
'$route': { query: { fmgPage: 'page-name' } },
|
|
||||||
GaActions: GaActions,
|
|
||||||
pushEventToGA: jest.fn(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
propsData: {
|
|
||||||
selectingInitiatesLoad: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
|
|
||||||
const label = wrapper.find("label");
|
|
||||||
|
|
||||||
wrapper.vm.handleCheckChange = jest.fn();
|
|
||||||
wrapper.vm.triggerButton();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
const loader = wrapper.find("loader-stub");
|
|
||||||
|
|
||||||
expect(loader.exists()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return loader color", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButtonHorizontal, {
|
|
||||||
global: {
|
|
||||||
mocks: {
|
|
||||||
'$route': { query: { fmgPage: 'page-name' } },
|
|
||||||
GaActions: GaActions,
|
|
||||||
pushEventToGA: jest.fn(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
propsData: {
|
|
||||||
loaderColor: "blue",
|
|
||||||
selectingInitiatesLoad: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
|
|
||||||
const label = wrapper.find("label");
|
|
||||||
|
|
||||||
wrapper.vm.handleCheckChange = jest.fn();
|
|
||||||
wrapper.vm.triggerButton();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
const loader = wrapper.find("loader-stub");
|
|
||||||
|
|
||||||
expect(loader.attributes("class")).toContain("blue");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return loader position", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButtonHorizontal, {
|
|
||||||
global: {
|
|
||||||
mocks: {
|
|
||||||
'$route': { query: { fmgPage: 'page-name' } },
|
|
||||||
GaActions: GaActions,
|
|
||||||
pushEventToGA: jest.fn(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
propsData: {
|
|
||||||
loaderPosition: "right",
|
|
||||||
selectingInitiatesLoad: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
|
|
||||||
const label = wrapper.find("label");
|
|
||||||
|
|
||||||
wrapper.vm.handleCheckChange = jest.fn();
|
|
||||||
wrapper.vm.triggerButton();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
const loader = wrapper.find("loader-stub");
|
|
||||||
|
|
||||||
expect(loader.attributes("class")).toContain("right");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should emit button value on click", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButtonHorizontal, {
|
|
||||||
propsData: {
|
|
||||||
isRadioHorizontal: true,
|
|
||||||
buttonLabel: "Windshield",
|
|
||||||
value: "List Card Checkbox",
|
|
||||||
groupID: "radio-demo-1",
|
|
||||||
groupName: "radio 1",
|
|
||||||
buttonImage: "windshield-damage.svg",
|
|
||||||
isRequired: true,
|
|
||||||
isWide: false,
|
|
||||||
modelValue: ["List Card Checkbox"],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
wrapper.vm.handleCheckChange();
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: Boolean, buttonId: undefined, checkValue: false}]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should set checkValue data if selectedButtonIDs has value(s)", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButtonHorizontal, {
|
|
||||||
propsData: {
|
|
||||||
isRadioHorizontal: true,
|
|
||||||
buttonLabel: "Windshield",
|
|
||||||
buttonID: "List Card Checkbox",
|
|
||||||
groupID: "radio-demo-1",
|
|
||||||
groupName: "radio 1",
|
|
||||||
buttonImage: "windshield-damage.svg",
|
|
||||||
isRequired: true,
|
|
||||||
isWide: false,
|
|
||||||
modelValue: ["List Card Checkbox"],
|
|
||||||
isMultiSelect: false,
|
|
||||||
value: "Car-Front",
|
|
||||||
selectedValues: ["Car-Front"]
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.vm.checkValue).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should run handleCheckChange if selectingInitiatesLoad is false and handleInputChange is triggered", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButtonHorizontal, {
|
|
||||||
propsData: {
|
|
||||||
selectingInitiatesLoad: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
wrapper.vm.handleInputChange();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
expect(wrapper.vm.handleCheckChange).toBeCalled;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should do nothing if isMultiSelect is true and handleKeyupArrow is triggered", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButtonHorizontal, {
|
|
||||||
propsData: {
|
|
||||||
isMultiSelect: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
wrapper.vm.handleKeyupArrow();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
expect(wrapper.vm.handleKeyupArrow).toHaveReturned;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should run handleCheckChange if selectingInitiatesLoad is false and handleKeyupArrow is triggered", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButtonHorizontal, {
|
|
||||||
propsData: {
|
|
||||||
selectingInitiatesLoad: false,
|
|
||||||
isMultiSelect: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
wrapper.vm.handleKeyupArrow();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
expect(wrapper.vm.handleCheckChange).toBeCalled;
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function setupMocks({ mockData }) {
|
||||||
|
const wrapper = mount(listButtonHorizontal, {
|
||||||
|
...mockData,
|
||||||
|
propsData: {
|
||||||
|
...mockData.propsData,
|
||||||
|
groupName: "my-group",
|
||||||
|
modelValue: mockData.propsData?.isMultiSelect ? ["5"] : "5",
|
||||||
|
value: mockData.propsData?.isMultiSelect ? ["4"] : "4",
|
||||||
|
},
|
||||||
|
mixins: [inputButtonWrapperMixin],
|
||||||
|
});
|
||||||
|
|
||||||
|
return { wrapper };
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,345 +1,222 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<baseInputButton
|
||||||
class="list-group list-button-horizontal d-flex flex-column w-100"
|
v-bind="$props"
|
||||||
:class="[(errors.length > 0 || hasError) ? 'has-error' : '']"
|
:buttonWrapperClasses="[
|
||||||
@keyup.space="triggerButton()"
|
'list-group list-button-horizontal d-flex flex-column w-100',
|
||||||
@keyup.up="handleKeyupArrow()"
|
{ 'radio-fancy': isCashOrInsurance },
|
||||||
@keyup.down="handleKeyupArrow()"
|
]"
|
||||||
@keyup.left="handleKeyupArrow()"
|
v-model="selectedValue">
|
||||||
@keyup.right="handleKeyupArrow()"
|
<div
|
||||||
>
|
class="list-button-horizontal-content d-flex flex-column justify-content-center p-3">
|
||||||
<input
|
<span class="m-0" :class="textPosition">
|
||||||
:type="isMultiSelect ? 'checkbox' : 'radio'"
|
{{ buttonLabel }}
|
||||||
:id="buttonID"
|
</span>
|
||||||
:name="groupName"
|
<span
|
||||||
:aria-required="isRequired"
|
v-if="buttonLabelSubCopy"
|
||||||
v-model="checkValue"
|
class="m-0 small"
|
||||||
:checked="checkValue"
|
:class="textPosition">
|
||||||
@change="handleInputChange()"
|
{{ buttonLabelSubCopy }}
|
||||||
/>
|
</span>
|
||||||
<label
|
<span v-if="screenReaderOnlyText" class="sr-only">
|
||||||
tabindex="-1"
|
{{ screenReaderOnlyText }}
|
||||||
:for="buttonID"
|
</span>
|
||||||
:aria-label="buttonLabel"
|
</div>
|
||||||
class="d-flex flex-column justify-content-center py-3 px-4"
|
</baseInputButton>
|
||||||
@mouseup="triggerButton()"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="m-0"
|
|
||||||
:class="textPosition"
|
|
||||||
>
|
|
||||||
{{buttonLabel}}
|
|
||||||
</span>
|
|
||||||
<span v-if="buttonLabelSubCopy" class="m-0 small" :class="textPosition">
|
|
||||||
{{ buttonLabelSubCopy }}
|
|
||||||
</span>
|
|
||||||
<span v-if="screenReaderOnlyText" class="sr-only">
|
|
||||||
{{ screenReaderOnlyText }}
|
|
||||||
</span>
|
|
||||||
<loader v-if="isLoaderDisplayed && selectingInitiatesLoad" :class="[loaderColor, loaderPosition]" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { useField } from "vee-validate";
|
import baseInputButton from "@/common-components/base-input-button/base-input-button";
|
||||||
import loader from "@/ux-components/loader/loader";
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
import { toRef } from "vue";
|
|
||||||
import { queryStrings } from "@/constants/query-strings";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "listButtonHorizontal",
|
name: "listButtonHorizontal",
|
||||||
props: {
|
mixins: [inputButtonWrapperMixin],
|
||||||
isMultiSelect: Boolean,
|
props: {
|
||||||
groupName: String,
|
isCashOrInsurance: Boolean,
|
||||||
buttonID: String,
|
|
||||||
buttonLabel: String,
|
|
||||||
buttonLabelSubCopy: String,
|
|
||||||
screenReaderOnlyText: String,
|
|
||||||
textPosition: String,
|
|
||||||
selectingInitiatesLoad: Boolean,
|
|
||||||
loaderColor: String,
|
|
||||||
loaderPosition: String,
|
|
||||||
isRequired: Boolean,
|
|
||||||
isCashOrInsurance: Boolean,
|
|
||||||
value: {
|
|
||||||
// Field initial value
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
},
|
||||||
validationRules: String,
|
components: {
|
||||||
selectedValues: [Array, String],
|
baseInputButton,
|
||||||
hasError: Boolean,
|
|
||||||
valueToLogType: String,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isLoaderDisplayed: false,
|
|
||||||
checkValue: Boolean,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (Array.isArray(this.selectedValues)) {
|
|
||||||
this.checkValue = this.isMultiSelect
|
|
||||||
? this.selectedValues.includes(this.value)
|
|
||||||
: this.selectedValues[0] == this.value;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.checkValue = this.selectedValues === this.value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
isValueSelectedByArray(arr) {
|
|
||||||
return this.isMultiSelect
|
|
||||||
? arr.includes(this.value)
|
|
||||||
: arr[0];
|
|
||||||
},
|
},
|
||||||
displayLoader() {
|
|
||||||
this.isLoaderDisplayed = true;
|
|
||||||
},
|
|
||||||
handleInputChange() {
|
|
||||||
if (!this.selectingInitiatesLoad) {
|
|
||||||
this.handleCheckChange();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleKeyupArrow() {
|
|
||||||
if (this.isMultiSelect) {
|
|
||||||
return; // Prevent arrow keys from doing anything if element is a checkbox
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.selectingInitiatesLoad) {
|
|
||||||
this.handleCheckChange();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
triggerButton() {
|
|
||||||
if (this.selectingInitiatesLoad) {
|
|
||||||
this.displayLoader();
|
|
||||||
this.handleCheckChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pushEventToGA(this.$route.query[queryStrings.FMG_PAGE], this.GaActions.CLICKED, this.value.toString(), true, this.valueToLogType);
|
|
||||||
},
|
|
||||||
handleCheckChange() {
|
|
||||||
const emitEvent = {
|
|
||||||
checkValue: this.checkValue, // only read on checkboxes, on handleCheckedChanged on button-question
|
|
||||||
value: this.value,
|
|
||||||
buttonId: this.buttonID && this.buttonID.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.handleChange(this.value);
|
|
||||||
this.$emit("isCheckedChanged", emitEvent);
|
|
||||||
this.$emit("update:modelValue", emitEvent);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
loader,
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const inputType = props.isMultiSelect ? "checkbox" : "radio";
|
|
||||||
|
|
||||||
const fieldOptions = {
|
|
||||||
type: inputType,
|
|
||||||
checkedValue: props.value,
|
|
||||||
potentialInitialValue: props.selectedValues,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
handleChange,
|
|
||||||
errors,
|
|
||||||
value
|
|
||||||
} = useField(toRef(props, "groupName"), toRef(props, "validationRules"), fieldOptions);
|
|
||||||
|
|
||||||
const validateValue = value;
|
|
||||||
return {
|
|
||||||
handleChange,
|
|
||||||
errors,
|
|
||||||
validateValue,
|
|
||||||
fieldOptions, // only need to expose this for unit test purposes
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.list-button-horizontal {
|
.list-button-horizontal {
|
||||||
|
|
||||||
input[type="radio"],
|
|
||||||
input[type="checkbox"] {
|
|
||||||
position: absolute;
|
|
||||||
height: 0;
|
|
||||||
opacity: 0;
|
|
||||||
width: 0;
|
|
||||||
|
|
||||||
&:focus-visible+label {
|
|
||||||
box-shadow: 0 0 0 2.5px $blue;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:focus+label {
|
|
||||||
box-shadow: 0 0 0 2.5px $blue;
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:checked+label {
|
|
||||||
background: $blue-100;
|
|
||||||
box-shadow: 0 0 0 1px $blue;
|
|
||||||
outline: none;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:checked:focus+label {
|
|
||||||
box-shadow: 0 0 0 2.5px $blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:checked+label p:first-child {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
outline: none;
|
|
||||||
position: relative;
|
|
||||||
background: $white;
|
|
||||||
transition: all 150ms linear;
|
|
||||||
border: 1px solid $gray-500;
|
|
||||||
border-radius: 0;
|
|
||||||
width: 100%;
|
|
||||||
color: $gray-600;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
@include media-breakpoint-up(sm) {
|
|
||||||
box-shadow: 0 0 0 4px $blue-300;
|
|
||||||
cursor: pointer;
|
|
||||||
z-index: 4 !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+p {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: .875rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cash/Insurance option radio button styling
|
|
||||||
&.radio-fancy {
|
|
||||||
label {
|
|
||||||
border: 1px solid $blue-700;
|
|
||||||
z-index: 2;
|
|
||||||
color: $blue;
|
|
||||||
|
|
||||||
span {
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
label:hover {
|
|
||||||
background-color: $blue-700;
|
|
||||||
color: $white;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="radio"],
|
input[type="radio"],
|
||||||
input[type="checkbox"] {
|
input[type="checkbox"] {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
height: 0;
|
height: 0;
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
width: 0;
|
width: 0;
|
||||||
|
|
||||||
&:focus-visible+label {
|
.list-button-horizontal-content {
|
||||||
border-radius: 0.5rem;
|
cursor: pointer;
|
||||||
z-index: 2;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&:focus+label {
|
&:focus-visible + .list-button-horizontal-content {
|
||||||
z-index: 3;
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
}
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
&:checked+label {
|
&:focus + .list-button-horizontal-content {
|
||||||
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + .list-button-horizontal-content {
|
||||||
|
background: $blue-100;
|
||||||
|
box-shadow: 0 0 0 1px $blue;
|
||||||
|
outline: none;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked:focus + .list-button-horizontal-content {
|
||||||
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + .list-button-horizontal-content p:first-child {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-button-horizontal-content {
|
||||||
outline: none;
|
outline: none;
|
||||||
box-shadow: none;
|
position: relative;
|
||||||
color: $white;
|
background: $white;
|
||||||
background: linear-gradient(84.45deg, #125B7E 0%, #3B8FB8 100%);
|
transition: all 150ms linear;
|
||||||
border-radius: 0.5rem;
|
border: 1px solid $gray-500;
|
||||||
z-index: 5;
|
border-radius: 0;
|
||||||
}
|
width: 100%;
|
||||||
|
color: $gray-600;
|
||||||
|
|
||||||
&:checked:focus+label {
|
&:hover {
|
||||||
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
|
@include media-breakpoint-up(sm) {
|
||||||
}
|
box-shadow: 0 0 0 4px $blue-300;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 4 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:checked+label p:first-child {
|
+ p {
|
||||||
font-weight: 500;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
&.list-button-horizontal {
|
// Cash/Insurance option radio button styling
|
||||||
height: 100%;
|
&.radio-fancy {
|
||||||
label {
|
.list-button-horizontal-content {
|
||||||
height: 100%;
|
border: 1px solid $blue-700;
|
||||||
|
z-index: 2;
|
||||||
|
color: $blue;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-button-horizontal-content:hover {
|
||||||
|
background-color: $blue-700;
|
||||||
|
color: $white;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"],
|
||||||
|
input[type="checkbox"] {
|
||||||
|
position: absolute;
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
|
||||||
|
&:focus-visible + .list-button-horizontal-content {
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus + .list-button-horizontal-content {
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + .list-button-horizontal-content {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: none;
|
||||||
|
color: $white;
|
||||||
|
background: linear-gradient(84.45deg, #125b7e 0%, #3b8fb8 100%);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked:focus + .list-button-horizontal-content {
|
||||||
|
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + .list-button-horizontal-content p:first-child {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&.list-button-horizontal {
|
||||||
|
height: 100%;
|
||||||
|
label {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.col {
|
.col {
|
||||||
&:first-of-type {
|
&:first-of-type {
|
||||||
.list-button-horizontal {
|
.list-button-horizontal {
|
||||||
label {
|
.list-button-horizontal-content {
|
||||||
border-bottom-left-radius: 0.5rem;
|
border-bottom-left-radius: 0.5rem;
|
||||||
border-top-left-radius: 0.5rem;
|
border-top-left-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:last-of-type {
|
|
||||||
.list-button-horizontal {
|
|
||||||
label {
|
|
||||||
border-bottom-right-radius: 0.5rem;
|
|
||||||
border-top-right-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Cash/insurance styling
|
|
||||||
&:first-of-type {
|
|
||||||
.list-button-horizontal.radio-fancy {
|
|
||||||
input[type="radio"] {
|
|
||||||
&:checked+label {
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:checked:focus+label {
|
|
||||||
border-bottom-right-radius: 0.5rem;
|
|
||||||
border-top-right-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&:last-of-type {
|
&:last-of-type {
|
||||||
.list-button-horizontal.radio-fancy {
|
.list-button-horizontal {
|
||||||
input[type="radio"] {
|
.list-button-horizontal-content {
|
||||||
&:checked+label {
|
border-bottom-right-radius: 0.5rem;
|
||||||
border-bottom-left-radius: 0;
|
border-top-right-radius: 0.5rem;
|
||||||
border-top-left-radius: 0;
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Cash/insurance styling
|
||||||
|
&:first-of-type {
|
||||||
|
.list-button-horizontal.radio-fancy {
|
||||||
|
input[type="radio"] {
|
||||||
|
&:checked + .list-button-horizontal-content {
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked:focus + .list-button-horizontal-content {
|
||||||
|
border-bottom-right-radius: 0.5rem;
|
||||||
|
border-top-right-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
.list-button-horizontal.radio-fancy {
|
||||||
|
input[type="radio"] {
|
||||||
|
&:checked + .list-button-horizontal-content {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked:focus + .list-button-horizontal-content {
|
||||||
|
border-bottom-left-radius: 0.5rem;
|
||||||
|
border-top-left-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:checked:focus+label {
|
|
||||||
border-bottom-left-radius: 0.5rem;
|
|
||||||
border-top-left-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,263 +1,304 @@
|
||||||
import { shallowMount } from "@vue/test-utils";
|
import { mount } from "@vue/test-utils";
|
||||||
import listButton from "./list-button";
|
import listButton from "./list-button";
|
||||||
import { nextTick } from "vue";
|
|
||||||
import { GaActions } from "@/constants/analytics";
|
import { GaActions } from "@/constants/analytics";
|
||||||
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
|
|
||||||
describe("list-button.vue", () => {
|
describe("list-button.vue", () => {
|
||||||
it("Should return input type checkbox if isMultiSelect is true", async () => {
|
describe("loader", () => {
|
||||||
// Act
|
it("selectingInitiatesLoad is true and answer is changed => show the loader", async () => {
|
||||||
const wrapper = shallowMount(listButton, {
|
// Act
|
||||||
propsData: {
|
const { wrapper } = setupMocks({
|
||||||
isMultiSelect: true,
|
mockData: {
|
||||||
},
|
global: {
|
||||||
|
mocks: {
|
||||||
|
$route: { query: { fmgPage: "page-name" } },
|
||||||
|
GaActions: GaActions,
|
||||||
|
pushEventToGA: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
propsData: {
|
||||||
|
selectingInitiatesLoad: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.selectedValue = "something";
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const loader = wrapper.findComponent({ name: "loader" });
|
||||||
|
expect(loader.exists()).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should return loader color", async () => {
|
||||||
|
// Arrange
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
global: {
|
||||||
|
mocks: {
|
||||||
|
$route: { query: { fmgPage: "page-name" } },
|
||||||
|
GaActions: GaActions,
|
||||||
|
pushEventToGA: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
propsData: {
|
||||||
|
loaderColor: "blue",
|
||||||
|
selectingInitiatesLoad: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.selectedValue = "something";
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const loader = wrapper.findComponent({ name: "loader" });
|
||||||
|
expect(loader.attributes("class")).toContain("blue");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should return loader position", async () => {
|
||||||
|
// Act
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
global: {
|
||||||
|
mocks: {
|
||||||
|
$route: { query: { fmgPage: "page-name" } },
|
||||||
|
GaActions: GaActions,
|
||||||
|
pushEventToGA: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
propsData: {
|
||||||
|
loaderPosition: "right",
|
||||||
|
selectingInitiatesLoad: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.selectedValue = "something";
|
||||||
|
await wrapper.vm.$nextTick();
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const loader = wrapper.findComponent({ name: "loader" });
|
||||||
|
expect(loader.attributes("class")).toContain("right");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assert
|
describe("baseInputButton checks", () => {
|
||||||
const input = wrapper.find("input");
|
it("Should return input type checkbox if isMultiSelect is true", async () => {
|
||||||
|
// Act
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
isMultiSelect: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
expect(input.attributes().type).toEqual("checkbox");
|
// Assert
|
||||||
});
|
const input = wrapper.find("input");
|
||||||
|
|
||||||
it("Should return input type radio if isMultiSelect is false or not specified", async () => {
|
expect(input.attributes().type).toEqual("checkbox");
|
||||||
// Act
|
});
|
||||||
const wrapper = shallowMount(listButton, {
|
|
||||||
propsData: {
|
it("Should return input type radio if isMultiSelect is false or not specified", async () => {
|
||||||
isMultiSelect: false,
|
// Act
|
||||||
},
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
isMultiSelect: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const input = wrapper.find("input");
|
||||||
|
expect(input.attributes().type).toEqual("radio");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("(Radio) Should emit button value on selectedValue change", async () => {
|
||||||
|
// Act
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
isRadioHorizontal: true,
|
||||||
|
buttonLabel: "Windshield",
|
||||||
|
value: "List Card Checkbox",
|
||||||
|
groupID: "radio-demo-1",
|
||||||
|
groupName: "radio1",
|
||||||
|
buttonImage: "windshield-damage.svg",
|
||||||
|
isRequired: true,
|
||||||
|
isWide: false,
|
||||||
|
modelValue: "List Card Checkbox",
|
||||||
|
buttonID: "list-card-id",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.selectedValue = "test";
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual("test");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("(Checkbox) Should emit button value on selectedValue change", async () => {
|
||||||
|
// Act
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
isRadioHorizontal: true,
|
||||||
|
buttonLabel: "Windshield",
|
||||||
|
value: "List Card Checkbox",
|
||||||
|
groupID: "radio-demo-1",
|
||||||
|
groupName: "radio 1",
|
||||||
|
buttonImage: "windshield-damage.svg",
|
||||||
|
isRequired: true,
|
||||||
|
isWide: false,
|
||||||
|
modelValue: "List Card Checkbox",
|
||||||
|
buttonID: "list-card-id",
|
||||||
|
isMultiSelect: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
wrapper.vm.selectedValue = ["test"];
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual(["test"]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assert
|
describe("styling/UI", () => {
|
||||||
const input = wrapper.find("input");
|
test("has buttonLabel => displays buttonLabel", () => {
|
||||||
|
// Arrange/Act
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
buttonLabel: "Surprise!",
|
||||||
|
modelValue: "",
|
||||||
|
groupName: "groupName",
|
||||||
|
value: "myValue"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
expect(input.attributes().type).toEqual("radio");
|
// Assert
|
||||||
});
|
const content = wrapper.find(".list-button-content");
|
||||||
|
expect(content.isVisible()).toBe(true);
|
||||||
|
expect(content.text()).toContain("Surprise!");
|
||||||
|
});
|
||||||
|
|
||||||
it("Should return primary label text (buttonID)", async () => {
|
test("has buttonLabelSubCopy => displays buttonLabelSubCopy", () => {
|
||||||
// Act
|
// Arrange/Act
|
||||||
const wrapper = shallowMount(listButton, {
|
const { wrapper } = setupMocks({
|
||||||
propsData: {
|
mockData: {
|
||||||
buttonID: "List Card Checkbox",
|
propsData: {
|
||||||
},
|
buttonLabel: "Surprise!",
|
||||||
|
buttonLabelSubCopy: "Super duper surprise :)",
|
||||||
|
modelValue: "",
|
||||||
|
groupName: "groupName",
|
||||||
|
value: "myValue"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const content = wrapper.find(".list-button-content");
|
||||||
|
expect(content.isVisible()).toBe(true);
|
||||||
|
expect(content.text()).toContain("Super duper surprise :)");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("has screenReaderOnlyText => displays screenReaderOnlyText", () => {
|
||||||
|
// Arrange/Act
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
buttonLabel: "Surprise!",
|
||||||
|
buttonLabelSubCopy: "Super duper surprise :)",
|
||||||
|
screenReaderOnlyText: "Tests are fun!",
|
||||||
|
modelValue: "",
|
||||||
|
groupName: "groupName",
|
||||||
|
value: "myValue"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const content = wrapper.find(".list-button-content");
|
||||||
|
const screenReaderOnlyText = wrapper.find(".sr-only");
|
||||||
|
expect(content.isVisible()).toBe(true);
|
||||||
|
expect(screenReaderOnlyText.exists()).toBe(true);
|
||||||
|
expect(screenReaderOnlyText.text()).toContain("Tests are fun!");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should return screen reader text", async () => {
|
||||||
|
// Act
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
screenReaderOnlyText: "Screen Reader Only Text",
|
||||||
|
modelValue: "",
|
||||||
|
groupName: "groupName",
|
||||||
|
value: "myValue"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const paragraph = wrapper.find("span.sr-only");
|
||||||
|
|
||||||
|
expect(paragraph.text()).toEqual("Screen Reader Only Text");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should return text alignment class", async () => {
|
||||||
|
// Act
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
textPosition: "text-center",
|
||||||
|
modelValue: "",
|
||||||
|
groupName: "groupName",
|
||||||
|
value: "myValue"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const paragraph = wrapper.find("span.m-0");
|
||||||
|
|
||||||
|
expect(paragraph.attributes("class")).toContain("text-center");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Should return aria-required state", async () => {
|
||||||
|
// Act
|
||||||
|
const { wrapper } = setupMocks({
|
||||||
|
mockData: {
|
||||||
|
propsData: {
|
||||||
|
isRequired: true,
|
||||||
|
groupName: "groupName",
|
||||||
|
modelValue: "",
|
||||||
|
value: "myValue",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const input = wrapper.find("input");
|
||||||
|
|
||||||
|
expect(input.attributes()["aria-required"]).toEqual("true");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assert
|
|
||||||
const label = wrapper.find("label");
|
|
||||||
|
|
||||||
expect(label.attributes().for).toEqual("List Card Checkbox");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return screen reader text", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButton, {
|
|
||||||
propsData: {
|
|
||||||
screenReaderOnlyText: "Screen Reader Only Text",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
const paragraph = wrapper.find("span.sr-only");
|
|
||||||
|
|
||||||
expect(paragraph.text()).toEqual("Screen Reader Only Text");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return text alignment class", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButton, {
|
|
||||||
propsData: {
|
|
||||||
textPosition: "text-center",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
const paragraph = wrapper.find("span.m-0");
|
|
||||||
|
|
||||||
expect(paragraph.attributes("class")).toContain("text-center");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return aria-required state", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButton, {
|
|
||||||
propsData: {
|
|
||||||
isRequired: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
const input = wrapper.find("input");
|
|
||||||
|
|
||||||
expect(input.attributes()["aria-required"]).toEqual("true");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return loader enabled true", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButton, {
|
|
||||||
global: {
|
|
||||||
mocks: {
|
|
||||||
'$route': { query: { fmgPage: 'page-name' } },
|
|
||||||
GaActions: GaActions,
|
|
||||||
pushEventToGA: jest.fn(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
propsData: {
|
|
||||||
selectingInitiatesLoad: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
wrapper.vm.handleCheckChange = jest.fn();
|
|
||||||
wrapper.vm.triggerButton();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
const loader = wrapper.find("loader-stub");
|
|
||||||
|
|
||||||
expect(loader.exists()).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return loader color", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButton, {
|
|
||||||
global: {
|
|
||||||
mocks: {
|
|
||||||
'$route': { query: { fmgPage: 'page-name' } },
|
|
||||||
GaActions: GaActions,
|
|
||||||
pushEventToGA: jest.fn(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
propsData: {
|
|
||||||
loaderColor: "blue",
|
|
||||||
selectingInitiatesLoad: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
wrapper.vm.handleCheckChange = jest.fn();
|
|
||||||
wrapper.vm.triggerButton();
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
const loader = wrapper.find("loader-stub");
|
|
||||||
|
|
||||||
expect(loader.attributes("class")).toContain("blue");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return loader position", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButton, {
|
|
||||||
global: {
|
|
||||||
mocks: {
|
|
||||||
'$route': { query: { fmgPage: 'page-name' } },
|
|
||||||
GaActions: GaActions,
|
|
||||||
pushEventToGA: jest.fn(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
propsData: {
|
|
||||||
loaderPosition: "right",
|
|
||||||
selectingInitiatesLoad: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
wrapper.vm.handleCheckChange = jest.fn();
|
|
||||||
wrapper.vm.triggerButton();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
const loader = wrapper.find("loader-stub");
|
|
||||||
|
|
||||||
expect(loader.attributes("class")).toContain("right");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should emit button value on click", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButton, {
|
|
||||||
propsData: {
|
|
||||||
isRadioHorizontal: true,
|
|
||||||
buttonLabel: "Windshield",
|
|
||||||
value: "List Card Checkbox",
|
|
||||||
groupID: "radio-demo-1",
|
|
||||||
groupName: "radio 1",
|
|
||||||
buttonImage: "windshield-damage.svg",
|
|
||||||
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: false, buttonId: 'list-card-id'}]);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should set checkValue data if selectedButtonIDs has value(s)", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButton, {
|
|
||||||
propsData: {
|
|
||||||
isRadioHorizontal: true,
|
|
||||||
buttonLabel: "Windshield",
|
|
||||||
buttonID: "List Card Checkbox",
|
|
||||||
groupID: "radio-demo-1",
|
|
||||||
groupName: "radio 1",
|
|
||||||
buttonImage: "windshield-damage.svg",
|
|
||||||
isRequired: true,
|
|
||||||
isWide: false,
|
|
||||||
modelValue: ["List Card Checkbox"],
|
|
||||||
selectedValues: "Car-Front"
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.componentVM.checkValue).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should run handleCheckChange if selectingInitiatesLoad is false and handleInputChange is triggered", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButton, {
|
|
||||||
propsData: {
|
|
||||||
selectingInitiatesLoad: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
wrapper.vm.handleInputChange();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
expect(wrapper.vm.handleCheckChange).toBeCalled;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should do nothing if isMultiSelect is true and handleKeyupArrow is triggered", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButton, {
|
|
||||||
propsData: {
|
|
||||||
isMultiSelect: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
wrapper.vm.handleKeyupArrow();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
expect(wrapper.vm.handleKeyupArrow).toHaveReturned;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should run handleCheckChange if selectingInitiatesLoad is false and handleKeyupArrow is triggered", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listButton, {
|
|
||||||
propsData: {
|
|
||||||
selectingInitiatesLoad: false,
|
|
||||||
isMultiSelect: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
wrapper.vm.handleKeyupArrow();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
expect(wrapper.vm.handleCheckChange).toBeCalled;
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function setupMocks({ mockData }) {
|
||||||
|
const wrapper = mount(listButton, {
|
||||||
|
...mockData,
|
||||||
|
mixins: [inputButtonWrapperMixin],
|
||||||
|
});
|
||||||
|
|
||||||
|
return { wrapper };
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,242 +1,130 @@
|
||||||
<template>
|
<template>
|
||||||
<div
|
<baseInputButton
|
||||||
class="list-group list-button rounded-3 d-flex flex-column w-100 mb-2"
|
v-bind="$props"
|
||||||
:class="[(errors.length > 0 || hasError) ? 'has-error' : '']"
|
buttonWrapperClasses="list-group list-button rounded-3 d-flex flex-column w-100 mb-2"
|
||||||
@keyup.space="triggerButton"
|
v-model="selectedValue">
|
||||||
@keyup.enter="triggerButton"
|
<div
|
||||||
@keyup.up="handleKeyupArrow"
|
:aria-label="buttonLabel"
|
||||||
@keyup.down="handleKeyupArrow"
|
class="list-button-content d-flex flex-column justify-content-center py-3 px-4">
|
||||||
@keyup.left="handleKeyupArrow"
|
<span class="m-0" :class="textPosition">
|
||||||
@keyup.right="handleKeyupArrow">
|
{{ buttonLabel }}
|
||||||
<input
|
</span>
|
||||||
:type="isMultiSelect ? 'checkbox' : 'radio'"
|
<span
|
||||||
:id="buttonID"
|
v-if="buttonLabelSubCopy"
|
||||||
:name="groupName"
|
class="m-0 small"
|
||||||
:value="value"
|
:class="textPosition">
|
||||||
:aria-required="isRequired"
|
{{ buttonLabelSubCopy }}
|
||||||
v-model="checkValue"
|
</span>
|
||||||
:checked="checkValue"
|
<span v-if="screenReaderOnlyText" class="sr-only">
|
||||||
@change="handleInputChange"
|
{{ screenReaderOnlyText }}
|
||||||
>
|
</span>
|
||||||
<label
|
<loader
|
||||||
tabindex="-1"
|
v-if="isLoaderDisplayed && selectingInitiatesLoad"
|
||||||
:for="buttonID"
|
:class="[this.loaderColor, this.loaderPosition]" />
|
||||||
:aria-label="buttonLabel"
|
</div>
|
||||||
class="d-flex flex-column justify-content-center py-3 px-4"
|
</baseInputButton>
|
||||||
@mouseup="triggerButton"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
class="m-0"
|
|
||||||
:class="textPosition"
|
|
||||||
>
|
|
||||||
{{ buttonLabel }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-if="buttonLabelSubCopy"
|
|
||||||
class="m-0 small"
|
|
||||||
:class="textPosition"
|
|
||||||
>
|
|
||||||
{{ buttonLabelSubCopy }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
v-if="screenReaderOnlyText"
|
|
||||||
class="sr-only"
|
|
||||||
>
|
|
||||||
{{ screenReaderOnlyText }}
|
|
||||||
</span>
|
|
||||||
<loader
|
|
||||||
v-if="isLoaderDisplayed && selectingInitiatesLoad"
|
|
||||||
:class="[this.loaderColor, this.loaderPosition]"
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { useField } from "vee-validate";
|
|
||||||
import { toRef } from "vue";
|
|
||||||
import loader from "@/ux-components/loader/loader";
|
import loader from "@/ux-components/loader/loader";
|
||||||
import { queryStrings } from "@/constants/query-strings";
|
import baseInputButton from "@/common-components/base-input-button/base-input-button";
|
||||||
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "listButton",
|
name: "listButton",
|
||||||
props: {
|
mixins: [inputButtonWrapperMixin],
|
||||||
isMultiSelect: Boolean,
|
props: {
|
||||||
groupName: String,
|
selectingInitiatesLoad: Boolean,
|
||||||
buttonLabel: [Number, String],
|
loaderColor: String,
|
||||||
buttonID: [Number, String],
|
loaderPosition: {
|
||||||
isRequired: Boolean,
|
type: String,
|
||||||
textPosition: String,
|
default: "right",
|
||||||
buttonLabelSubCopy: String,
|
},
|
||||||
screenReaderOnlyText: String,
|
|
||||||
selectingInitiatesLoad: Boolean,
|
|
||||||
loaderColor: String,
|
|
||||||
loaderPosition: String,
|
|
||||||
value: {
|
|
||||||
// Field initial value
|
|
||||||
type: [String, Number],
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
|
|
||||||
validationRules: String,
|
|
||||||
selectedValues: [Array, String],
|
|
||||||
hasError: Boolean,
|
|
||||||
valueToLogType: String,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
isLoaderDisplayed: false,
|
|
||||||
checkValue: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
mounted() {
|
|
||||||
if (Array.isArray(this.validateValue)) {
|
|
||||||
this.checkValue = this.isValueSelectedByArray(this.selectedValues);
|
|
||||||
const isSelectedByValidator = this.isValueSelectedByArray(this.validateValue);
|
|
||||||
|
|
||||||
if (this.checkValue != isSelectedByValidator) {
|
|
||||||
this.handleChange(this.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.checkValue = this.selectedValues == this.value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
isValueSelectedByArray(arr) {
|
|
||||||
return this.isMultiSelect
|
|
||||||
? arr.includes(this.value)
|
|
||||||
: arr[0];
|
|
||||||
},
|
},
|
||||||
displayLoader() {
|
data() {
|
||||||
this.isLoaderDisplayed = true;
|
return {
|
||||||
|
isLoaderDisplayed: false,
|
||||||
|
};
|
||||||
},
|
},
|
||||||
handleInputChange() {
|
methods: {
|
||||||
if(!this.selectingInitiatesLoad) {
|
displayLoader() {
|
||||||
this.handleCheckChange();
|
this.isLoaderDisplayed = true;
|
||||||
}
|
},
|
||||||
|
preHandleAnswerChange() {
|
||||||
|
if (this.selectingInitiatesLoad) {
|
||||||
|
this.displayLoader();
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
handleKeyupArrow() {
|
components: {
|
||||||
if (this.isMultiSelect) {
|
loader,
|
||||||
return; // Prevent arrow keys from doing anything if element is a checkbox
|
baseInputButton,
|
||||||
}
|
|
||||||
},
|
},
|
||||||
triggerButton() {
|
|
||||||
if(this.selectingInitiatesLoad) {
|
|
||||||
this.displayLoader();
|
|
||||||
this.handleCheckChange();
|
|
||||||
}
|
|
||||||
this.pushEventToGA(this.$route.query[queryStrings.FMG_PAGE], this.GaActions.CLICKED, this.value.toString(), true, this.valueToLogType);
|
|
||||||
},
|
|
||||||
handleCheckChange() {
|
|
||||||
const emitEvent = {
|
|
||||||
checkValue: this.checkValue, // only read on checkboxes, on handleCheckedChanged on button-question
|
|
||||||
value: this.value.toString(),
|
|
||||||
buttonId: this.buttonID && this.buttonID.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.handleChange(this.value);
|
|
||||||
this.$emit("isCheckedChanged", emitEvent);
|
|
||||||
this.$emit("update:modelValue", emitEvent);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
components: {
|
|
||||||
loader,
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const inputType = props.isMultiSelect ? "checkbox" : "radio";
|
|
||||||
|
|
||||||
const fieldOptions = {
|
|
||||||
type: inputType,
|
|
||||||
checkedValue: props.value,
|
|
||||||
potentialInitialValue: props.selectedValues,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
handleChange,
|
|
||||||
errors,
|
|
||||||
value
|
|
||||||
} = useField(toRef(props, "groupName"), toRef(props, "validationRules"), fieldOptions);
|
|
||||||
|
|
||||||
const validateValue = value;
|
|
||||||
|
|
||||||
return {
|
|
||||||
handleChange,
|
|
||||||
errors,
|
|
||||||
validateValue,
|
|
||||||
fieldOptions, // only need to expose this for unit test purposes
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.list-group {
|
.list-group {
|
||||||
&.list-button {
|
.loader {
|
||||||
outline: none;
|
position: absolute;
|
||||||
input[type="radio"],
|
}
|
||||||
input[type="checkbox"] {
|
&.list-button {
|
||||||
position: static; //override bootstrap
|
outline: none;
|
||||||
height: 0;
|
input[type="radio"],
|
||||||
opacity: 0;
|
input[type="checkbox"] {
|
||||||
|
position: static; //override bootstrap
|
||||||
|
|
||||||
&:focus-visible + label {
|
&:focus-visible + .list-button-content {
|
||||||
box-shadow: 0 0 0 2.5px $blue;
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
}
|
}
|
||||||
&:focus + label {
|
&:focus + .list-button-content {
|
||||||
box-shadow: 0 0 0 2.5px $blue;
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
}
|
}
|
||||||
&:checked + label {
|
&:checked + .list-button-content {
|
||||||
color: $black;
|
color: $black;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
background: $blue-100;
|
background: $blue-100;
|
||||||
box-shadow: 0 0 0 1px $blue;
|
box-shadow: 0 0 0 1px $blue;
|
||||||
}
|
}
|
||||||
&:checked:focus + label {
|
&:checked:focus + .list-button-content {
|
||||||
box-shadow: 0 0 0 2.5px $blue;
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
}
|
}
|
||||||
&:checked + label p,
|
&:checked + .list-button-content p,
|
||||||
&:checked + label span {
|
&:checked + .list-button-content span {
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
&:checked + label span:nth-child(2) {
|
&:checked + .list-button-content span:nth-child(2) {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
color: $gray-600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.list-button-content {
|
||||||
color: $gray-600;
|
color: $gray-600;
|
||||||
}
|
position: relative;
|
||||||
}
|
background: $white;
|
||||||
}
|
transition: all 150ms linear;
|
||||||
label {
|
border-radius: $border-radius-lg;
|
||||||
color: $gray-600;
|
border: 1px solid $gray-500;
|
||||||
position: relative;
|
width: 100%;
|
||||||
background: $white;
|
outline: none;
|
||||||
transition: all 150ms linear;
|
|
||||||
border-radius: $border-radius-lg;
|
|
||||||
border: 1px solid $gray-500;
|
|
||||||
width: 100%;
|
|
||||||
outline: none;
|
|
||||||
|
|
||||||
span {
|
span {
|
||||||
&.small {
|
&.small {
|
||||||
font-size: .75rem;
|
font-size: 0.75rem;
|
||||||
color: $gray-550;
|
color: $gray-550;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@include media-breakpoint-up(sm) {
|
@include media-breakpoint-up(sm) {
|
||||||
box-shadow: 0 0 0 4px $blue-300;
|
box-shadow: 0 0 0 4px $blue-300;
|
||||||
}
|
}
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
+ p {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+ p {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { shallowMount } from "@vue/test-utils";
|
import { mount } from "@vue/test-utils";
|
||||||
import listCard from "./list-card";
|
import listCard from "./list-card";
|
||||||
import { nextTick } from "vue";
|
import { nextTick } from "vue";
|
||||||
import { GaActions } from "@/constants/analytics";
|
import { GaActions } from "@/constants/analytics";
|
||||||
|
|
||||||
describe("list-card.vue", () => {
|
describe("list-card.vue", () => {
|
||||||
it("Should return input type checkbox if isMultiSelect is true", async () => {
|
it("Should return input type checkbox if isMultiSelect is true", () => {
|
||||||
// Act
|
// Act
|
||||||
const wrapper = shallowMount(listCard, {
|
const wrapper = mount(listCard, {
|
||||||
propsData: {
|
propsData: {
|
||||||
isMultiSelect: true,
|
isMultiSelect: true,
|
||||||
buttonLabel: "Windshield",
|
buttonLabel: "Windshield",
|
||||||
|
|
@ -14,6 +14,7 @@ describe("list-card.vue", () => {
|
||||||
groupID: "checkbox-demo-1",
|
groupID: "checkbox-demo-1",
|
||||||
groupName: "Checkbox 1",
|
groupName: "Checkbox 1",
|
||||||
buttonImage: "windshield-damage.svg",
|
buttonImage: "windshield-damage.svg",
|
||||||
|
value: "test value",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -22,9 +23,9 @@ describe("list-card.vue", () => {
|
||||||
expect(input.attributes().type).toEqual("checkbox");
|
expect(input.attributes().type).toEqual("checkbox");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return primary label text", async () => {
|
it("Should return primary label text", () => {
|
||||||
// Act
|
// Act
|
||||||
const wrapper = shallowMount(listCard, {
|
const wrapper = mount(listCard, {
|
||||||
propsData: {
|
propsData: {
|
||||||
isRadioHorizontal: true,
|
isRadioHorizontal: true,
|
||||||
buttonLabel: "Windshield",
|
buttonLabel: "Windshield",
|
||||||
|
|
@ -32,6 +33,7 @@ describe("list-card.vue", () => {
|
||||||
groupID: "radio-demo-1",
|
groupID: "radio-demo-1",
|
||||||
groupName: "radio 1",
|
groupName: "radio 1",
|
||||||
buttonImage: "windshield-damage.svg",
|
buttonImage: "windshield-damage.svg",
|
||||||
|
value: "test value",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -40,9 +42,9 @@ describe("list-card.vue", () => {
|
||||||
expect(paragraph.text()).toEqual("Windshield");
|
expect(paragraph.text()).toEqual("Windshield");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return secondary (sub) label text", async () => {
|
it("Should return secondary (sub) label text", () => {
|
||||||
// Act
|
// Act
|
||||||
const wrapper = shallowMount(listCard, {
|
const wrapper = mount(listCard, {
|
||||||
propsData: {
|
propsData: {
|
||||||
isRadioHorizontal: true,
|
isRadioHorizontal: true,
|
||||||
buttonLabel: "Windshield",
|
buttonLabel: "Windshield",
|
||||||
|
|
@ -51,6 +53,7 @@ describe("list-card.vue", () => {
|
||||||
groupName: "radio 1",
|
groupName: "radio 1",
|
||||||
buttonImage: "windshield-damage.svg",
|
buttonImage: "windshield-damage.svg",
|
||||||
buttonLabelSubCopy: "Test",
|
buttonLabelSubCopy: "Test",
|
||||||
|
value: "test value",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -59,28 +62,9 @@ describe("list-card.vue", () => {
|
||||||
expect(paragraph.text()).toEqual("Test");
|
expect(paragraph.text()).toEqual("Test");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return value used for various text settings including the label 'for' and input id", async () => {
|
it("Should return input group name used for radio or checkbox", () => {
|
||||||
// Act
|
// Act
|
||||||
const wrapper = shallowMount(listCard, {
|
const wrapper = mount(listCard, {
|
||||||
propsData: {
|
|
||||||
isRadioHorizontal: true,
|
|
||||||
buttonLabel: "Windshield",
|
|
||||||
buttonID: "List Card Checkbox",
|
|
||||||
groupID: "radio-demo-1",
|
|
||||||
groupName: "radio 1",
|
|
||||||
buttonImage: "windshield-damage.svg",
|
|
||||||
buttonLabelSubCopy: "Test",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
const label = wrapper.find("label");
|
|
||||||
expect(label.attributes().for).toEqual("List Card Checkbox");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return input group name used for radio or checkbox", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listCard, {
|
|
||||||
propsData: {
|
propsData: {
|
||||||
isRadioHorizontal: true,
|
isRadioHorizontal: true,
|
||||||
buttonLabel: "Windshield",
|
buttonLabel: "Windshield",
|
||||||
|
|
@ -89,6 +73,7 @@ describe("list-card.vue", () => {
|
||||||
groupName: "radio 1",
|
groupName: "radio 1",
|
||||||
buttonImage: "windshield-damage.svg",
|
buttonImage: "windshield-damage.svg",
|
||||||
buttonLabelSubCopy: "Test",
|
buttonLabelSubCopy: "Test",
|
||||||
|
value: "test value",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -97,9 +82,9 @@ describe("list-card.vue", () => {
|
||||||
expect(input.attributes().name).toEqual("radio 1");
|
expect(input.attributes().name).toEqual("radio 1");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return aria-required state", async () => {
|
it("Should return aria-required state", () => {
|
||||||
// Act
|
// Act
|
||||||
const wrapper = shallowMount(listCard, {
|
const wrapper = mount(listCard, {
|
||||||
propsData: {
|
propsData: {
|
||||||
isRadioHorizontal: true,
|
isRadioHorizontal: true,
|
||||||
buttonLabel: "Windshield",
|
buttonLabel: "Windshield",
|
||||||
|
|
@ -108,6 +93,7 @@ describe("list-card.vue", () => {
|
||||||
groupName: "radio 1",
|
groupName: "radio 1",
|
||||||
buttonImage: "windshield-damage.svg",
|
buttonImage: "windshield-damage.svg",
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
|
value: "test value",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -116,9 +102,9 @@ describe("list-card.vue", () => {
|
||||||
expect(input.attributes()["aria-required"]).toEqual("true");
|
expect(input.attributes()["aria-required"]).toEqual("true");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return flex row classes if isWide is true", async () => {
|
it("Should return flex row classes if isWide is true", () => {
|
||||||
// Act
|
// Act
|
||||||
const wrapper = shallowMount(listCard, {
|
const wrapper = mount(listCard, {
|
||||||
propsData: {
|
propsData: {
|
||||||
isRadioHorizontal: true,
|
isRadioHorizontal: true,
|
||||||
buttonLabel: "Windshield",
|
buttonLabel: "Windshield",
|
||||||
|
|
@ -129,17 +115,21 @@ describe("list-card.vue", () => {
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
isWide: true,
|
isWide: true,
|
||||||
buttonLabelSubCopy: "",
|
buttonLabelSubCopy: "",
|
||||||
|
value: "test value",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
const label = wrapper.find("label");
|
const label = wrapper.find(".list-card-content");
|
||||||
expect(label.classes()).toEqual(["d-flex", "w-100", "align-items-center", "px-2", "h-100", "flex-row", "py-2", "ps-4", "pe-4"]);
|
expect(label.exists()).toBe(true);
|
||||||
|
const labelClasses = wrapper.vm.labelClasses;
|
||||||
|
expect(labelClasses).toContain("flex-row");
|
||||||
|
expect(label.classes()).toContain("flex-row");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return flex row classes if isWide is true and checkboxTop if buttonLabelSubCopy is true", async () => {
|
it("Should return flex row classes if isWide is true and checkboxTop if buttonLabelSubCopy is provided", () => {
|
||||||
// Act
|
// Act
|
||||||
const wrapper = shallowMount(listCard, {
|
const wrapper = mount(listCard, {
|
||||||
propsData: {
|
propsData: {
|
||||||
isRadioHorizontal: true,
|
isRadioHorizontal: true,
|
||||||
buttonLabel: "Windshield",
|
buttonLabel: "Windshield",
|
||||||
|
|
@ -150,17 +140,23 @@ describe("list-card.vue", () => {
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
isWide: true,
|
isWide: true,
|
||||||
buttonLabelSubCopy: "Button Subcopy",
|
buttonLabelSubCopy: "Button Subcopy",
|
||||||
|
value: "test value",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
const label = wrapper.find("label");
|
const label = wrapper.find(".list-card-content");
|
||||||
expect(label.classes()).toEqual(["d-flex", "w-100", "align-items-center", "px-2", "h-100", "flex-row", "py-2", "ps-4", "pe-4", "checkboxTop"]);
|
expect(label.exists()).toBe(true);
|
||||||
|
const labelClasses = wrapper.vm.labelClasses;
|
||||||
|
expect(labelClasses).toContain("flex-row");
|
||||||
|
expect(label.classes()).toContain("flex-row");
|
||||||
|
expect(labelClasses).toContain("checkboxTop");
|
||||||
|
expect(label.classes()).toContain("checkboxTop");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should return flex column classes if isWide is false", async () => {
|
it("Should return flex column classes if isWide is false", () => {
|
||||||
// Act
|
// Act
|
||||||
const wrapper = shallowMount(listCard, {
|
const wrapper = mount(listCard, {
|
||||||
propsData: {
|
propsData: {
|
||||||
isRadioHorizontal: true,
|
isRadioHorizontal: true,
|
||||||
buttonLabel: "Windshield",
|
buttonLabel: "Windshield",
|
||||||
|
|
@ -170,165 +166,15 @@ describe("list-card.vue", () => {
|
||||||
buttonImage: "windshield-damage.svg",
|
buttonImage: "windshield-damage.svg",
|
||||||
isRequired: true,
|
isRequired: true,
|
||||||
isWide: false,
|
isWide: false,
|
||||||
|
value: "test value",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
const label = wrapper.find("label");
|
const label = wrapper.find(".list-card-content");
|
||||||
expect(label.classes()).toEqual(["d-flex", "w-100", "align-items-center", "px-2", "h-100", "flex-column", "pt-4", "pb-3"]);
|
expect(label.exists()).toBe(true);
|
||||||
|
const labelClasses = wrapper.vm.labelClasses;
|
||||||
|
expect(labelClasses).toContain("flex-column");
|
||||||
|
expect(label.classes()).toContain("flex-column");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("Should emit button value on click", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listCard, {
|
|
||||||
propsData: {
|
|
||||||
isRadioHorizontal: true,
|
|
||||||
buttonLabel: "Windshield",
|
|
||||||
value: "List Card Checkbox",
|
|
||||||
groupID: "radio-demo-1",
|
|
||||||
groupName: "radio 1",
|
|
||||||
buttonImage: "windshield-damage.svg",
|
|
||||||
isRequired: true,
|
|
||||||
isWide: false,
|
|
||||||
buttonID: 'list-card-id',
|
|
||||||
selectedValues: "List Card Checkbox"
|
|
||||||
},
|
|
||||||
});
|
|
||||||
wrapper.vm.handleCheckChange();
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: true, buttonId: 'list-card-id'}]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should set checkValue data if selectedButtonIDs has value(s)", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listCard, {
|
|
||||||
propsData: {
|
|
||||||
isRadioHorizontal: true,
|
|
||||||
buttonLabel: "Windshield",
|
|
||||||
value: "List Card Checkbox",
|
|
||||||
groupID: "radio-demo-1",
|
|
||||||
groupName: "radio 1",
|
|
||||||
buttonImage: "windshield-damage.svg",
|
|
||||||
isRequired: true,
|
|
||||||
isWide: false,
|
|
||||||
selectedValues: "Car-Front"
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.componentVM.checkValue).toEqual(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
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",
|
|
||||||
selectedValues: ["Windshield"],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.vm.fieldOptions.initialValue).toEqual([ 'Windshield' ]);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should run handleCheckChange if selectingInitiatesLoad is false and handleInputChange is triggered", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listCard, {
|
|
||||||
propsData: {
|
|
||||||
selectingInitiatesLoad: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
wrapper.vm.handleInputChange();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
expect(wrapper.vm.handleCheckChange).toBeCalled;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should do nothing if isMultiSelect is true and handleKeyupArrow is triggered", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listCard, {
|
|
||||||
propsData: {
|
|
||||||
isMultiSelect: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
wrapper.vm.handleKeyupArrow();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
expect(wrapper.vm.handleKeyupArrow).toHaveReturned;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should run handleCheckChange if selectingInitiatesLoad is false and handleKeyupArrow is triggered", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listCard, {
|
|
||||||
propsData: {
|
|
||||||
selectingInitiatesLoad: false,
|
|
||||||
isMultiSelect: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
wrapper.vm.handleKeyupArrow();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
expect(wrapper.vm.handleCheckChange).toBeCalled;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should run handleChange if triggerButton is triggered", async () => {
|
|
||||||
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listCard, {
|
|
||||||
global: {
|
|
||||||
mocks: {
|
|
||||||
'$route': { query: { fmgPage: 'page-name' } },
|
|
||||||
GaActions: GaActions,
|
|
||||||
pushEventToGA: jest.fn(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
propsData: {
|
|
||||||
selectingInitiatesLoad: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
wrapper.vm.triggerButton();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
expect(wrapper.vm.handleChange).toBeCalled;
|
|
||||||
expect(wrapper.vm.handleCheckChange).not.toBeCalled;
|
|
||||||
expect(wrapper.vm.displayLoader).not.toBeCalled;
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should run handleCheckChange and displayLoader if triggerButton is triggered and seletingInitiatesLoad is true", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(listCard, {
|
|
||||||
global: {
|
|
||||||
mocks: {
|
|
||||||
'$route': { query: { fmgPage: 'page-name' } },
|
|
||||||
GaActions: GaActions,
|
|
||||||
pushEventToGA: jest.fn(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
propsData: {
|
|
||||||
selectingInitiatesLoad: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
wrapper.vm.triggerButton();
|
|
||||||
|
|
||||||
await nextTick();
|
|
||||||
|
|
||||||
expect(wrapper.vm.handleCheckChange).toBeCalled;
|
|
||||||
expect(wrapper.vm.displayLoader).toBeCalled;
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,400 +1,252 @@
|
||||||
<template>
|
<template>
|
||||||
<div :class="{'h-100': !isWide}">
|
<baseInputButton
|
||||||
<div
|
v-bind="$props"
|
||||||
class="list-card w-100 rounded-3 d-flex align-items-center"
|
:buttonWrapperClasses="[
|
||||||
:class="[
|
'list-card w-100 rounded-3 d-flex align-items-center h-100',
|
||||||
'h-100',
|
{ horizontal: isWide },
|
||||||
isWide ? 'horizontal' : '',
|
]"
|
||||||
(errors.length > 0 || hasError) ? 'has-error' : '',
|
v-model="selectedValue">
|
||||||
]"
|
<div
|
||||||
@keyup.space="triggerButton"
|
class="d-flex w-100 align-items-center px-2 h-100 list-card-content"
|
||||||
@keyup.up="handleKeyupArrow"
|
:class="labelClasses">
|
||||||
@keyup.down="handleKeyupArrow"
|
<img
|
||||||
@keyup.left="handleKeyupArrow"
|
:id="buttonImageId"
|
||||||
@keyup.right="handleKeyupArrow"
|
:class="!isWide ? 'order-1' : 'ms-auto order-3'"
|
||||||
>
|
:src="buttonImage"
|
||||||
<input
|
:alt="altText" />
|
||||||
:type="isMultiSelect ? 'checkbox' : 'radio'"
|
<p
|
||||||
:id="buttonID"
|
v-if="!isWide"
|
||||||
:name="groupName"
|
class="small order-3"
|
||||||
:value="value"
|
:class="isMultiSelect ? 'm-0' : 'mt-2 mb-0'">
|
||||||
:aria-required="isRequired"
|
{{ buttonLabel }}
|
||||||
v-model="checkValue"
|
</p>
|
||||||
:checked="checkValue"
|
<p
|
||||||
@change="handleInputChange"
|
v-if="buttonLabelSubCopy && !isWide"
|
||||||
/>
|
class="fs-7 m-0 order-4 sub-copy">
|
||||||
<label
|
{{ buttonLabelSubCopy }}
|
||||||
tabindex="-1"
|
</p>
|
||||||
:for="buttonID"
|
<div v-if="isWide" class="order-2">
|
||||||
:aria-label="buttonLabel"
|
<p class="m-0 small">{{ buttonLabel }}</p>
|
||||||
class="d-flex w-100 align-items-center px-2 h-100"
|
<p v-if="buttonLabelSubCopy" class="m-0 fs-7 sub-copy">
|
||||||
:class="getLabelClasses"
|
{{ buttonLabelSubCopy }}
|
||||||
@mouseup="triggerButton"
|
</p>
|
||||||
>
|
</div>
|
||||||
<img
|
|
||||||
:id="buttonImageId"
|
|
||||||
:class="!isWide ? 'order-1' : 'ms-auto order-3'"
|
|
||||||
:src="buttonImage"
|
|
||||||
:alt="altText"
|
|
||||||
/>
|
|
||||||
<p
|
|
||||||
v-if="!isWide"
|
|
||||||
class="small order-3"
|
|
||||||
:class="isMultiSelect ? 'm-0' : 'mt-2 mb-0'"
|
|
||||||
>
|
|
||||||
{{ buttonLabel }}
|
|
||||||
</p>
|
|
||||||
<p
|
|
||||||
v-if="buttonLabelSubCopy && !isWide"
|
|
||||||
class="fs-7 m-0 order-4 sub-copy"
|
|
||||||
>
|
|
||||||
{{ buttonLabelSubCopy }}
|
|
||||||
</p>
|
|
||||||
<div v-if="isWide" class="order-2">
|
|
||||||
<p class="m-0 small">{{ buttonLabel }}</p>
|
|
||||||
<p v-if="buttonLabelSubCopy" class="m-0 fs-7 sub-copy">
|
|
||||||
{{ buttonLabelSubCopy }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</label>
|
</baseInputButton>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { useField } from "vee-validate";
|
import baseInputButton from "@/common-components/base-input-button/base-input-button";
|
||||||
import { toRef } from "vue";
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
import { queryStrings } from "@/constants/query-strings";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "listCard",
|
name: "listCard",
|
||||||
props: {
|
mixins: [inputButtonWrapperMixin],
|
||||||
isMultiSelect: Boolean, //Defines use as checkbox
|
components: {
|
||||||
isWide: Boolean,
|
baseInputButton,
|
||||||
buttonImage: String, //Required: File name of image
|
|
||||||
buttonImageId: String,
|
|
||||||
buttonLabel: String, //Required: Label text
|
|
||||||
isRequired: Boolean, //Required: is aria-required required or not?
|
|
||||||
altText: String, //Leave empty. Screen readers read the buttonLabel text. If alt has content, it will repeat unnecessarily.
|
|
||||||
buttonID: String, //Required: Unique
|
|
||||||
groupName: String, //Rquired: Unique
|
|
||||||
buttonLabelSubCopy: String, //Optional: sub text
|
|
||||||
value: {
|
|
||||||
// Field initial value
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
},
|
||||||
colLength: String,
|
computed: {
|
||||||
validationRules: String,
|
labelClasses() {
|
||||||
selectedValues: [Array, String],
|
if (this.isWide) {
|
||||||
hasError: Boolean,
|
let classes = "flex-row py-2 ps-4 pe-4";
|
||||||
valueToLogType: String,
|
if (this.buttonLabelSubCopy) {
|
||||||
},
|
classes += " checkboxTop";
|
||||||
data() {
|
}
|
||||||
return {
|
return classes;
|
||||||
checkValue: null,
|
} else {
|
||||||
}
|
return "flex-column pt-4 pb-3";
|
||||||
},
|
}
|
||||||
mounted() {
|
},
|
||||||
if (Array.isArray(this.validateValue)) {
|
|
||||||
this.checkValue = this.isValueSelectedByArray(this.selectedValues);
|
|
||||||
const isSelectedByValidator = this.isValueSelectedByArray(this.validateValue);
|
|
||||||
|
|
||||||
if (this.checkValue != isSelectedByValidator) {
|
|
||||||
this.handleChange(this.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this.checkValue = this.selectedValues == this.value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
getLabelClasses() {
|
|
||||||
if (this.isWide) {
|
|
||||||
let classes = "flex-row py-2 ps-4 pe-4";
|
|
||||||
if (this.buttonLabelSubCopy) {
|
|
||||||
classes += " checkboxTop";
|
|
||||||
}
|
|
||||||
return classes;
|
|
||||||
} else {
|
|
||||||
return "flex-column pt-4 pb-3";
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
isValueSelectedByArray(arr) {
|
|
||||||
return this.isMultiSelect
|
|
||||||
? arr.includes(this.value)
|
|
||||||
: arr[0];
|
|
||||||
},
|
|
||||||
handleInputChange() {
|
|
||||||
if(!this.selectingInitiatesLoad) {
|
|
||||||
this.handleCheckChange();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleKeyupArrow() {
|
|
||||||
if (this.isMultiSelect) {
|
|
||||||
return; // Prevent arrow keys from doing anything if element is a checkbox
|
|
||||||
}
|
|
||||||
|
|
||||||
if(!this.selectingInitiatesLoad) {
|
|
||||||
this.handleCheckChange();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
triggerButton() {
|
|
||||||
if(this.selectingInitiatesLoad) {
|
|
||||||
this.displayLoader();
|
|
||||||
this.handleCheckChange();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.pushEventToGA(this.$route.query[queryStrings.FMG_PAGE], this.GaActions.CLICKED, this.value.toString(), true, this.valueToLogType);
|
|
||||||
},
|
|
||||||
handleCheckChange() {
|
|
||||||
const emitEvent = {
|
|
||||||
checkValue: this.checkValue, // only read on checkboxes, on handleCheckedChanged on button-question
|
|
||||||
value: this.value.toString(),
|
|
||||||
buttonId: this.buttonID && this.buttonID.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.handleChange(this.value);
|
|
||||||
this.$emit("isCheckedChanged", emitEvent);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
// Changing this will impact pre-selection data loads on vehicle-parts.
|
|
||||||
// If changed, please regression test that vehicle-parts data still loads correctly with previous selections.
|
|
||||||
selectedValues(newVal) {
|
|
||||||
if (typeof newVal === "string") {
|
|
||||||
this.checkValue = newVal == this.value;
|
|
||||||
}
|
|
||||||
else if (newVal !== undefined) {
|
|
||||||
this.checkValue = newVal.value;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const inputType = props.isMultiSelect ? "checkbox" : "radio";
|
|
||||||
|
|
||||||
const fieldOptions = {
|
|
||||||
type: inputType,
|
|
||||||
checkedValue: props.value, // EX: "Single" or "Passenger"
|
|
||||||
potentialInitialValue: props.selectedValues,
|
|
||||||
};
|
|
||||||
|
|
||||||
// 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 {
|
|
||||||
handleChange,
|
|
||||||
errors,
|
|
||||||
value
|
|
||||||
} = useField(toRef(props, "groupName"), toRef(props, "validationRules"), fieldOptions);
|
|
||||||
|
|
||||||
// First land on the blank, unselected page, no handleChange
|
|
||||||
// Land on page with initial values, handleChange
|
|
||||||
const validateValue = value;
|
|
||||||
return {
|
|
||||||
handleChange,
|
|
||||||
errors,
|
|
||||||
validateValue,
|
|
||||||
fieldOptions, // only need to expose this for unit test purposes
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.list-card {
|
.list-card {
|
||||||
border: 1px solid $gray-500;
|
border: 1px solid $gray-500;
|
||||||
|
|
||||||
&.invalid {
|
&.has-error {
|
||||||
//Red border if invalid
|
//Red border if invalid
|
||||||
border: 1px solid $red;
|
border: 1px solid $red;
|
||||||
}
|
|
||||||
|
|
||||||
img {
|
|
||||||
// svg's should be constructed on the same canvas size/viewbox to ensure they occupy the same space in the DOM. This will allow easy/proper alignment of elements. See exisitng svg's for examples.
|
|
||||||
height: auto;
|
|
||||||
width: 6.5rem;
|
|
||||||
margin-bottom: 2.2rem;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
@include media-breakpoint-up(sm) {
|
|
||||||
box-shadow: 0px 0px 0px 4px $blue-300;
|
|
||||||
border: 1px solid transparent;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="checkbox"],
|
|
||||||
input[type="radio"] {
|
|
||||||
opacity: 0;
|
|
||||||
width: 0;
|
|
||||||
height: 0.1px; // NOTE: cannot be zero or safari can't put focus on it
|
|
||||||
position: absolute;
|
|
||||||
|
|
||||||
+ label {
|
|
||||||
outline: none;
|
|
||||||
display: block;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: $gray-600;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
&.sub-copy {
|
|
||||||
color: $gray-550;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:checked + label {
|
|
||||||
background: $blue-100;
|
|
||||||
box-shadow: 0 0 0 1px $blue;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
}
|
|
||||||
&:focus-visible + label {
|
|
||||||
box-shadow: 0 0 0 2.5px $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;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sub-copy {
|
|
||||||
color: $gray-600;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+ label::before {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
display: flex;
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 1rem;
|
|
||||||
height: 1rem;
|
|
||||||
margin: 3rem 0 0 0;
|
|
||||||
background: white;
|
|
||||||
border: 1px solid $gray-500;
|
|
||||||
border-radius: 2px;
|
|
||||||
order: 2;
|
|
||||||
flex-shrink: 0;
|
|
||||||
color: $gray-600;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ label.checkboxTop::before {
|
|
||||||
margin: -1.25rem 0.5rem 0 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ label.checkboxTop::after {
|
|
||||||
margin: -1.5rem 0.5rem 0 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:checked + label::before {
|
|
||||||
background: $blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:checked + label::after {
|
|
||||||
content: "";
|
|
||||||
position: absolute;
|
|
||||||
margin: 3.2rem 0 0 0;
|
|
||||||
border-left: 2px solid $white;
|
|
||||||
border-bottom: 2px solid $white;
|
|
||||||
height: 6px;
|
|
||||||
width: 11px;
|
|
||||||
transform: rotate(-45deg);
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type="radio"] {
|
|
||||||
+ label::before {
|
|
||||||
content: "";
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ label::after {
|
|
||||||
content: "";
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
+ label {
|
|
||||||
img {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&.horizontal {
|
|
||||||
img {
|
img {
|
||||||
margin-bottom: 0;
|
// svg's should be constructed on the same canvas size/viewbox to ensure they occupy the same space in the DOM. This will allow easy/proper alignment of elements. See exisitng svg's for examples.
|
||||||
width: 5.5rem;
|
height: auto;
|
||||||
|
width: 6.5rem;
|
||||||
|
margin-bottom: 2.2rem;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
@include media-breakpoint-up(sm) {
|
||||||
|
box-shadow: 0px 0px 0px 4px $blue-300;
|
||||||
|
border: 1px solid transparent;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="checkbox"],
|
input[type="checkbox"],
|
||||||
input[type="radio"] {
|
input[type="radio"] {
|
||||||
+ label::before {
|
position: absolute;
|
||||||
content: "";
|
|
||||||
position: relative;
|
|
||||||
margin: 0 0.5rem 0 0;
|
|
||||||
order: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:checked + label::after {
|
+ .list-card-content {
|
||||||
content: "";
|
outline: none;
|
||||||
margin: -0.15rem 0 0 0;
|
display: block;
|
||||||
left: 1.175rem;
|
position: relative;
|
||||||
}
|
|
||||||
|
|
||||||
&:checked + label {
|
&:hover {
|
||||||
p {
|
cursor: pointer;
|
||||||
color: $black;
|
}
|
||||||
font-weight: 500;
|
|
||||||
|
p {
|
||||||
|
color: $gray-600;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
&.sub-copy {
|
||||||
|
color: $gray-550;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sub-copy {
|
&:checked + .list-card-content {
|
||||||
color: $gray-600;
|
background: $blue-100;
|
||||||
font-weight: 400;
|
box-shadow: 0 0 0 1px $blue;
|
||||||
|
border-radius: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
&:focus-visible + .list-card-content {
|
||||||
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
&:focus + .list-card-content {
|
||||||
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
&:checked:focus + .list-card-content {
|
||||||
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
|
}
|
||||||
|
&:checked + .list-card-content {
|
||||||
|
p {
|
||||||
|
color: $black;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
+ label {
|
.sub-copy {
|
||||||
outline: none;
|
color: $gray-600;
|
||||||
min-height: 48px;
|
font-weight: 400;
|
||||||
color: $gray-600;
|
}
|
||||||
|
|
||||||
img {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
+ .list-card-content::before {
|
||||||
color: $gray-600;
|
content: "";
|
||||||
text-align: left;
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
&.sub-copy {
|
margin: 0 auto;
|
||||||
color: $gray-550;
|
width: 1rem;
|
||||||
}
|
height: 1rem;
|
||||||
|
margin: 3rem 0 0 0;
|
||||||
|
background: white;
|
||||||
|
border: 1px solid $gray-500;
|
||||||
|
border-radius: 2px;
|
||||||
|
order: 2;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: $gray-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ .list-card-content.checkboxTop::before {
|
||||||
|
margin: -1.25rem 0.5rem 0 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ .list-card-content.checkboxTop::after {
|
||||||
|
margin: -1.5rem 0.5rem 0 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + .list-card-content::before {
|
||||||
|
background: $blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + .list-card-content::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
margin: 3.2rem 0 0 0;
|
||||||
|
border-left: 2px solid $white;
|
||||||
|
border-bottom: 2px solid $white;
|
||||||
|
height: 6px;
|
||||||
|
width: 11px;
|
||||||
|
transform: rotate(-45deg);
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"] {
|
||||||
|
+ .list-card-content::before {
|
||||||
|
content: "";
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ .list-card-content::after {
|
||||||
|
content: "";
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
+ .list-card-content {
|
||||||
|
img {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.horizontal {
|
||||||
|
img {
|
||||||
|
margin-bottom: 0;
|
||||||
|
width: 5.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="checkbox"],
|
||||||
|
input[type="radio"] {
|
||||||
|
+ .list-card-content::before {
|
||||||
|
content: "";
|
||||||
|
position: relative;
|
||||||
|
margin: 0 0.5rem 0 0;
|
||||||
|
order: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + .list-card-content::after {
|
||||||
|
content: "";
|
||||||
|
margin: -0.15rem 0 0 0;
|
||||||
|
left: 1.175rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + .list-card-content {
|
||||||
|
p {
|
||||||
|
color: $black;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-copy {
|
||||||
|
color: $gray-600;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+ .list-card-content {
|
||||||
|
outline: none;
|
||||||
|
min-height: 48px;
|
||||||
|
color: $gray-600;
|
||||||
|
|
||||||
|
img {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: $gray-600;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
&.sub-copy {
|
||||||
|
color: $gray-550;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ export default {
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.loader {
|
.loader {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
||||||
//Open an overlay to prevent page interaction
|
//Open an overlay to prevent page interaction
|
||||||
&:before {
|
&:before {
|
||||||
content: "";
|
content: "";
|
||||||
|
|
@ -56,16 +57,13 @@ export default {
|
||||||
}
|
}
|
||||||
//Spinner position
|
//Spinner position
|
||||||
&.center {
|
&.center {
|
||||||
position: absolute;
|
|
||||||
right: 50%;
|
right: 50%;
|
||||||
transform: translateX(50%);
|
transform: translateX(50%);
|
||||||
}
|
}
|
||||||
&.right {
|
&.right {
|
||||||
position: absolute;
|
|
||||||
right: 1rem;
|
right: 1rem;
|
||||||
}
|
}
|
||||||
&.left {
|
&.left {
|
||||||
position: absolute;
|
|
||||||
left: 1rem;
|
left: 1rem;
|
||||||
}
|
}
|
||||||
//Spinner color
|
//Spinner color
|
||||||
|
|
|
||||||
|
|
@ -1,117 +1,66 @@
|
||||||
import { shallowMount } from "@vue/test-utils";
|
import { mount } from "@vue/test-utils";
|
||||||
import radio from "./radio";
|
import radio from "./radio";
|
||||||
import { nextTick } from "vue";
|
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||||
import { GaActions } from "@/constants/analytics";
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
|
|
||||||
describe("radio.vue", () => {
|
describe("radio.vue", () => {
|
||||||
it("Should return group name", async () => {
|
it("Should have correct group name", async () => {
|
||||||
// Act
|
// Arrange
|
||||||
const wrapper = shallowMount(radio, {
|
let { wrapper } = setupMocks({
|
||||||
propsData: {
|
mountOptionsMockData: {
|
||||||
groupName: "radio-button-test",
|
propsData: {
|
||||||
},
|
groupName: "radio-button-test",
|
||||||
|
value: "test value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Act
|
||||||
|
const input = wrapper.find("input");
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(input.attributes().name).toEqual("radio-button-test");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assert
|
it("Should have correct label text", async () => {
|
||||||
const input = wrapper.find("input");
|
// Act
|
||||||
|
let { wrapper } = setupMocks({
|
||||||
|
mountOptionsMockData: {
|
||||||
|
propsData: {
|
||||||
|
buttonLabel: "label text",
|
||||||
|
value: "test value",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Expect
|
// Arrange
|
||||||
expect(input.attributes().name).toEqual("radio-button-test");
|
const paragraph = wrapper.find("p");
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return checkbox id", async () => {
|
// Assert
|
||||||
// Act
|
expect(paragraph.text()).toEqual("label text");
|
||||||
const wrapper = shallowMount(radio, {
|
|
||||||
propsData: {
|
|
||||||
buttonID: "Radio ID",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assert
|
it("Should have correct screenreader-only text", async () => {
|
||||||
const input = wrapper.find("input");
|
// Act
|
||||||
|
let { wrapper } = setupMocks({});
|
||||||
|
|
||||||
// Expect
|
// Arrange
|
||||||
expect(input.attributes().id).toEqual("Radio ID");
|
await wrapper.setProps({
|
||||||
});
|
screenReaderOnlyText: "screenreader text",
|
||||||
|
value: "test value",
|
||||||
|
});
|
||||||
|
const paragraph = wrapper.find(".sr-only");
|
||||||
|
|
||||||
it("Should return label text", async () => {
|
// Assert
|
||||||
// Act
|
expect(paragraph.text()).toEqual("screenreader text");
|
||||||
const wrapper = shallowMount(radio, {
|
|
||||||
propsData: {
|
|
||||||
buttonLabel: "label text",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Assert
|
|
||||||
const paragraph = wrapper.find("p");
|
|
||||||
|
|
||||||
expect(paragraph.text()).toEqual("label text");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should return label text", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(radio, {
|
|
||||||
propsData: {
|
|
||||||
screenReaderOnlyText: "screenreader text",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Assert
|
|
||||||
const paragraph = wrapper.find("span");
|
|
||||||
|
|
||||||
expect(paragraph.text()).toEqual("screenreader text");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should emit button value on click", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(radio, {
|
|
||||||
global: {
|
|
||||||
mocks: {
|
|
||||||
'$route': { query: { fmgPage: 'page-name' } },
|
|
||||||
GaActions: GaActions,
|
|
||||||
pushEventToGA: jest.fn(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
propsData: {
|
|
||||||
buttonLabel: "Windshield",
|
|
||||||
value: "List Card Checkbox",
|
|
||||||
buttonID: "List Card Checkbox",
|
|
||||||
groupID: "radio-demo-1",
|
|
||||||
groupName: "radio 1",
|
|
||||||
isRequired: true,
|
|
||||||
isWide: false,
|
|
||||||
modelValue: ["List Card Checkbox"],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
wrapper.vm.handleCheckChange();
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{"buttonID": "List Card Checkbox", value: "List Card Checkbox", checkValue: false}]);;
|
|
||||||
expect(wrapper.vm.pushEventToGA).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("Should set checkValue data if selectedButtonIDs has value(s)", async () => {
|
|
||||||
// Act
|
|
||||||
const wrapper = shallowMount(radio, {
|
|
||||||
global: {
|
|
||||||
mocks: {
|
|
||||||
'$route': { query: { fmgPage: 'page-name' } },
|
|
||||||
GaActions: GaActions,
|
|
||||||
pushEventToGA: jest.fn(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
propsData: {
|
|
||||||
buttonLabel: "Windshield",
|
|
||||||
buttonID: "List Card Checkbox",
|
|
||||||
groupID: "radio-demo-1",
|
|
||||||
groupName: "radio 1",
|
|
||||||
buttonImage: "windshield-damage.svg",
|
|
||||||
isRequired: true,
|
|
||||||
modelValue: ["List Card Checkbox"],
|
|
||||||
value: "Car-Front",
|
|
||||||
selectedValues: "Car-Front"
|
|
||||||
},
|
|
||||||
});
|
|
||||||
// Assert
|
|
||||||
expect(wrapper.componentVM.checkValue).toEqual(true);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function setupMocks({ mountOptionsMockData = {} }) {
|
||||||
|
const wrapper = mount(radio, {
|
||||||
|
...mountOptionsMockData,
|
||||||
|
mixins: [inputButtonWrapperMixin],
|
||||||
|
});
|
||||||
|
|
||||||
|
return { wrapper };
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,140 +1,79 @@
|
||||||
<template>
|
<template>
|
||||||
<!-- Checkbox groups MUST be wrapped in a <fieldset> and <legend> tag and must contain tabindex -->
|
<baseInputButton
|
||||||
<div class="ui-radio form-check" :class="[(errors.length > 0 || hasError) ? 'has-error' : '']">
|
v-bind="$props"
|
||||||
<input
|
buttonWrapperClasses="ui-radio form-check"
|
||||||
type="radio"
|
inputClasses="form-check-input"
|
||||||
class="form-check-input"
|
v-model="selectedValue">
|
||||||
aria-checked="false"
|
<div class="d-flex align-items-start form-check-label">
|
||||||
:name="groupName"
|
<p v-if="buttonLabel" class="m-0">{{ buttonLabel }}</p>
|
||||||
:id="buttonID"
|
<span v-if="screenReaderOnlyText" class="sr-only">{{
|
||||||
:aria-required="isRequired"
|
screenReaderOnlyText
|
||||||
:value="value"
|
}}</span>
|
||||||
:v-model="checkValue"
|
</div>
|
||||||
@change="handleCheckChange"
|
</baseInputButton>
|
||||||
:checked="checkValue"
|
|
||||||
:validationRules="validationRules"
|
|
||||||
/>
|
|
||||||
<label class="d-flex align-items-start form-check-label" :for="buttonID">
|
|
||||||
<p v-if="buttonLabel" class="m-0">{{ buttonLabel }}</p>
|
|
||||||
<span v-if="screenReaderOnlyText" class="sr-only">{{
|
|
||||||
screenReaderOnlyText
|
|
||||||
}}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { useField } from "vee-validate";
|
import baseInputButton from "@/common-components/base-input-button/base-input-button";
|
||||||
import { queryStrings } from "@/constants/query-strings";
|
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "radio",
|
name: "radio",
|
||||||
props: {
|
mixins: [inputButtonWrapperMixin],
|
||||||
groupName: String,
|
components: {
|
||||||
buttonLabel: String,
|
baseInputButton,
|
||||||
buttonID: String,
|
|
||||||
isRequired: Boolean,
|
|
||||||
value: {
|
|
||||||
type: [String, Number],
|
|
||||||
default: "",
|
|
||||||
},
|
},
|
||||||
screenReaderOnlyText: String,
|
|
||||||
selectedValues: String,
|
|
||||||
hasError: Boolean,
|
|
||||||
validationRules: String,
|
|
||||||
valueToLogType: String,
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
checkValue: Boolean,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
if (this.selectedValues) {
|
|
||||||
this.checkValue = this.selectedValues === this.value;
|
|
||||||
this.handleCheckChange();
|
|
||||||
} else{
|
|
||||||
this.checkValue = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
handleCheckChange() {
|
|
||||||
this.handleChange(this.value);
|
|
||||||
const emitEvent = {
|
|
||||||
checkValue: this.checkValue,
|
|
||||||
value: this.value.toString(),
|
|
||||||
buttonID: this.buttonID && this.buttonID.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
this.$emit("isCheckedChanged", emitEvent);
|
|
||||||
this.$emit("update:modelValue", emitEvent);
|
|
||||||
|
|
||||||
this.pushEventToGA(this.$route.query[queryStrings.FMG_PAGE], this.GaActions.CLICKED, this.value.toString(), true, this.valueToLogType);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const inputType = "radio";
|
|
||||||
const {
|
|
||||||
value: inputValue,
|
|
||||||
handleChange,
|
|
||||||
errors,
|
|
||||||
} = useField(props.groupName, props.validationRules,
|
|
||||||
{
|
|
||||||
type: inputType,
|
|
||||||
checkedValue: props.value,
|
|
||||||
});
|
|
||||||
return {
|
|
||||||
handleChange,
|
|
||||||
errors,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss">
|
||||||
.form-check {
|
.form-check {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
.form-check-input {
|
|
||||||
border: 1px solid $gray-500;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin-right: 0.5rem;
|
|
||||||
|
|
||||||
&:checked {
|
|
||||||
background-color: $white;
|
|
||||||
background-size: 71%;
|
|
||||||
background-position: center;
|
|
||||||
border: 1px solid $blue;
|
|
||||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 50 50' xml:space='preserve'%3e%3ccircle cx='25' cy='25' r='25' fill='%231574A1'/%3e%3c/svg%3e");
|
|
||||||
+ label {
|
|
||||||
p {
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: .875rem;
|
|
||||||
color: $black;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&:focus {
|
|
||||||
box-shadow: 0 0 0 2.5px $blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
&, & + label {
|
|
||||||
margin-top: 0;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.form-check-input {
|
.form-check-input {
|
||||||
box-shadow: 0 0 0 4px $blue-300;
|
border: 1px solid $gray-500;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
opacity: 1;
|
||||||
|
height: 1em;
|
||||||
|
width: 1em;
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
background-color: $white;
|
||||||
|
background-size: 71%;
|
||||||
|
background-position: center;
|
||||||
|
border: 1px solid $blue;
|
||||||
|
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 50 50' xml:space='preserve'%3e%3ccircle cx='25' cy='25' r='25' fill='%231574A1'/%3e%3c/svg%3e");
|
||||||
|
+ .form-check-label {
|
||||||
|
p {
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: $black;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:focus {
|
||||||
|
box-shadow: 0 0 0 2.5px $blue;
|
||||||
|
}
|
||||||
|
|
||||||
|
&,
|
||||||
|
& + .form-check-label {
|
||||||
|
margin-top: 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.form-check-input {
|
||||||
|
box-shadow: 0 0 0 4px $blue-300;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: $gray-600;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
p {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: .875rem;
|
|
||||||
color: $gray-600;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue