Merged from develop
This commit is contained in:
commit
ed8cd94ac0
40 changed files with 2548 additions and 1463 deletions
|
|
@ -16,6 +16,7 @@ module.exports = {
|
|||
"!src/layouts/vehicle-damage/windshield-damage-type-question/windshield-damage-type-question.vue",
|
||||
"!src/layouts/vehicle-damage/windshield-options/windshield-options.vue",
|
||||
"!src/layouts/address-poc/address-poc.vue",
|
||||
"!src/layouts/nested-radio-poc/nested-radio.vue",
|
||||
], //! means exclude from coverage.
|
||||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||
coverageThreshold: {
|
||||
|
|
|
|||
|
|
@ -7,4 +7,5 @@
|
|||
@import "@/styles/common-styles.scss";
|
||||
@import "@/styles/common-typography-styles.scss";
|
||||
@import "@/styles/common-error-styles.scss";
|
||||
@import "@/styles/common-animations.scss";
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -3,12 +3,14 @@ import buttonQuestion from "@/common-components/button-question/button-question"
|
|||
import { nextTick } from "vue";
|
||||
|
||||
describe("buttonQuestion.vue", () => {
|
||||
it("Should show overflow classes on fieldset if isOverflowScrollable is true", async () => {
|
||||
it("Should show overflow classes on fieldset if isOverflowScrollable is true", () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(buttonQuestion);
|
||||
await wrapper.setProps({
|
||||
isOverflowScrollable: true,
|
||||
const wrapper = shallowMount(buttonQuestion, {
|
||||
propsData: {
|
||||
isOverflowScrollable: true,
|
||||
}
|
||||
});
|
||||
|
||||
// Assert
|
||||
const fieldSet = wrapper.find('fieldset');
|
||||
expect(fieldSet.classes()).toContain("overflow-scroll");
|
||||
|
|
@ -16,11 +18,12 @@ describe("buttonQuestion.vue", () => {
|
|||
});
|
||||
|
||||
describe("buttonQuestion.vue", () => {
|
||||
it("Fieldset classes should contain row if button type is listCard", async () => {
|
||||
it("Fieldset classes should contain row if button type is listCard", () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(buttonQuestion);
|
||||
await wrapper.setProps({
|
||||
buttonType: "listCard",
|
||||
const wrapper = shallowMount(buttonQuestion, {
|
||||
propsData: {
|
||||
buttonType: "listCard",
|
||||
}
|
||||
});
|
||||
// Assert
|
||||
const Div = wrapper.find('fieldset div');
|
||||
|
|
@ -29,11 +32,12 @@ describe("buttonQuestion.vue", () => {
|
|||
});
|
||||
|
||||
describe("buttonQuestion.vue", () => {
|
||||
it("Fieldset classes should contain d-flex if button type is listButtonHorizontal", async () => {
|
||||
it("Fieldset classes should contain d-flex if button type is listButtonHorizontal", () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(buttonQuestion);
|
||||
await wrapper.setProps({
|
||||
buttonType: "listButtonHorizontal",
|
||||
const wrapper = shallowMount(buttonQuestion, {
|
||||
propsData: {
|
||||
buttonType: "listButtonHorizontal",
|
||||
}
|
||||
});
|
||||
// Assert
|
||||
const Div = wrapper.find('fieldset div');
|
||||
|
|
@ -49,7 +53,7 @@ describe("buttonQuestion.vue", () => {
|
|||
answers: ["2022", "2021", "2020"],
|
||||
isMultiSelect: false
|
||||
});
|
||||
const val = {isChecked: true, buttonId: "2021", }
|
||||
const val = {checkValue: true, value: "2021", }
|
||||
wrapper.vm.handleCheckedChanged(val);
|
||||
// Assert
|
||||
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([["2021"]]);
|
||||
|
|
@ -57,14 +61,15 @@ describe("buttonQuestion.vue", () => {
|
|||
});
|
||||
|
||||
describe("buttonQuestion.vue", () => {
|
||||
it("Should add values to array on checkbox click", async () => {
|
||||
it("Should add values to array on checkbox click", () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(buttonQuestion);
|
||||
await wrapper.setProps({
|
||||
modelValue: ["2022", "2021", "2020"],
|
||||
isMultiSelect: true
|
||||
const wrapper = shallowMount(buttonQuestion, {
|
||||
propsData: {
|
||||
modelValue: ["2022", "2021", "2020"],
|
||||
isMultiSelect: true,
|
||||
}
|
||||
});
|
||||
const val = {isChecked: true, buttonId: "2019", }
|
||||
const val = {checkValue: true, value: "2019", }
|
||||
wrapper.vm.handleCheckedChanged(val);
|
||||
// Assert
|
||||
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([["2022", "2021", "2020", "2019"]]);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
<span class="text-center fs-6 fw-bold w-100">{{ questionText }}</span>
|
||||
</div>
|
||||
<div class="w-100 d-flex justify-content-center">
|
||||
<fieldset class="w-100" :class="getFieldSetClasses" role="radiogroup" :aria-labelledby="groupName ? groupName + '-radio-group' : ''">
|
||||
<fieldset class="w-100" :class="getFieldSetClasses" :role="isMultiSelect ? 'group' : 'radiogroup'" :aria-labelledby="groupName ? groupName + '-radio-group' : ''">
|
||||
<legend class="sr-only">{{groupName}}</legend>
|
||||
<div :class="getComponentWrapperClasses">
|
||||
<component
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
v-for="answer in answers"
|
||||
:key="answer.Name ? answer.Name : answer"
|
||||
@isCheckedChanged="handleCheckedChanged"
|
||||
:buttonID="answer.Name ? answer.Name : answer"
|
||||
:buttonID="answer.Name ? groupName + '-' + answer.Name : groupName + '-' + answer"
|
||||
:value="answer.Name ? answer.Name : answer"
|
||||
:buttonLabel="answer.Text ? answer.Text : answer"
|
||||
:buttonLabelSubCopy="answer.SubText"
|
||||
|
|
@ -22,15 +22,14 @@
|
|||
:selectingInitiatesLoad="selectingInitiatesLoad"
|
||||
:loaderColor="loaderColor"
|
||||
:loaderPosition="loaderPosition"
|
||||
:sizeInRem="sizeInRem"
|
||||
:isWide="isWide"
|
||||
:isRequired="isRequired"
|
||||
:buttonImage="answer.AnswerImageUrl"
|
||||
:buttonImageId="answer.ImageId"
|
||||
:altText="answer.Name ? answer.Name : answer"
|
||||
screenReaderOnlyText="(opens new window)"
|
||||
:colLength="this.answers.length < 3 ? '' : '-4'"
|
||||
:selectedButtonIDs="selectedValues"
|
||||
:colLength="getColLength"
|
||||
:selectedValues="selectedValues"
|
||||
data-test="button"
|
||||
:validationRules="validationRules"
|
||||
/>
|
||||
|
|
@ -74,17 +73,11 @@ export default {
|
|||
type: String,
|
||||
default: "right",
|
||||
},
|
||||
sizeInRem: {
|
||||
type: [String, Number],
|
||||
default: 1.5,
|
||||
},
|
||||
isRequired: Boolean,
|
||||
isOverflowScrollable: Boolean,
|
||||
isWide: Boolean,
|
||||
modelValue: Array,
|
||||
validationRules: String,
|
||||
name: String,
|
||||
value: String,
|
||||
suppressError: Boolean,
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -111,6 +104,13 @@ export default {
|
|||
}
|
||||
return classes;
|
||||
},
|
||||
getColLength(){
|
||||
if(this.isWide) {
|
||||
return "12"
|
||||
} else {
|
||||
return this.answers.length < 3 ? '' : '-4';
|
||||
}
|
||||
},
|
||||
selectedValues: {
|
||||
get: function() {
|
||||
return this.modelValue;
|
||||
|
|
@ -120,25 +120,17 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
mounted(){
|
||||
if(Array.isArray(this.answers) && this.answers.length === 1) {
|
||||
const newSelectedValues = this.selectedValues;
|
||||
newSelectedValues.push(typeof(this.answers[0]) === 'object' ? this.answers[0].Name : this.answers[0]);
|
||||
this.selectedValues = newSelectedValues;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
chooseAnswer(answer) {
|
||||
this.$emit("update:modelValue", answer);
|
||||
},
|
||||
handleCheckedChanged(val) {
|
||||
if(this.isMultiSelect) {
|
||||
if(this.isMultiSelect && this.selectedValues) {
|
||||
// Add or remove item to array of data to emit
|
||||
const newSelectedValues = this.selectedValues;
|
||||
val.isChecked ? newSelectedValues.push(val.buttonId) : newSelectedValues.splice(newSelectedValues.indexOf(val.buttonId), 1);
|
||||
this.selectedValues = newSelectedValues;
|
||||
if(Array.isArray(this.selectedValues)) {
|
||||
val.checkValue ? newSelectedValues.push(val.value) : newSelectedValues.splice(newSelectedValues.indexOf(val.value), 1);
|
||||
this.selectedValues = newSelectedValues;
|
||||
}
|
||||
} else {
|
||||
this.selectedValues = [val.buttonId];
|
||||
this.selectedValues = [val.value];
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import dropdownQuestion from "./dropdown-question";
|
||||
import { nextTick } from "vue";
|
||||
|
||||
describe("dropdownQuestion.vue", () => {
|
||||
|
||||
it("Should return aria-disabled state", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(dropdownQuestion, {
|
||||
propsData: {
|
||||
isDisabled: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("select");
|
||||
|
||||
// Expect
|
||||
expect(input.attributes()["aria-disabled"]).toEqual("true");
|
||||
});
|
||||
|
||||
it("Should render a text input", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(dropdownQuestion, {
|
||||
propsData: {
|
||||
name: "test",
|
||||
label: "unit test label",
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("select");
|
||||
|
||||
expect(input.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("Should return input id", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(dropdownQuestion, {
|
||||
propsData: {
|
||||
inputId: "input ID",
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("select");
|
||||
|
||||
// Expect
|
||||
expect(input.attributes().id).toEqual("input ID");
|
||||
});
|
||||
|
||||
it("Should return label text", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(dropdownQuestion, {
|
||||
propsData: {
|
||||
labelText: "label text",
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const label = wrapper.find("label");
|
||||
|
||||
expect(label.text()).toEqual("label text");
|
||||
});
|
||||
|
||||
it("Should return input id", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(dropdownQuestion, {
|
||||
propsData: {
|
||||
inputId: "input ID",
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("select");
|
||||
|
||||
// Expect
|
||||
expect(input.attributes().id).toEqual("input ID");
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -1,13 +1,19 @@
|
|||
<template>
|
||||
<label :for="inputId">{{ labelText }}</label>
|
||||
<div class="dropdown-question">
|
||||
<label :for="inputId" class="form-label">{{labelText}}</label>
|
||||
<select v-model="selectedOption"
|
||||
:ref="inputId"
|
||||
:id="inputId"
|
||||
class="form-control">
|
||||
<option v-for="(value, name, index) in options" :value="name" :key="index">
|
||||
{{ value }}
|
||||
</option>
|
||||
class="form-select"
|
||||
aria-label="Default select example"
|
||||
:id="inputId"
|
||||
:aria-disabled="isDisabled"
|
||||
:disabled="isDisabled"
|
||||
:aria-required="isRequired">
|
||||
<option v-for="(value, name, index) in options" :value="name" :key="index">
|
||||
{{ value }}
|
||||
</option>
|
||||
</select>
|
||||
<p class="mt-1 mb-0">There has been an error!</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
|
@ -39,3 +45,41 @@ export default {
|
|||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.dropdown-question {
|
||||
.form-label {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
.form-select {
|
||||
color: $gray-500;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.89' xml:space='preserve'%3e%3cpath d='M8 8.89c-.24 0-.46-.09-.63-.26L.26 1.53a.901.901 0 0 1 0-1.27C.43.1.66 0 .9 0s.47.1.64.26L8 6.74 14.47.27c.17-.17.4-.27.64-.27s.47.1.63.27c.17.17.26.4.26.64s-.1.47-.27.63l-7.1 7.09a.86.86 0 0 1-.63.26z' fill='%231474a2'/%3e%3c/svg%3e");
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: .5rem;
|
||||
min-height: 3rem;
|
||||
option[selected] {
|
||||
color: $gray-500;
|
||||
background-color: red;
|
||||
}
|
||||
&:focus,
|
||||
&:focus-visible {
|
||||
box-shadow: 0 0 0 2px $blue;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
background-color: $gray-100;
|
||||
filter: grayscale(100%);
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 4px transparent;
|
||||
border: 1px solid $gray-500;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
isPrimary
|
||||
:buttonText="buttonText"
|
||||
loaderColor="white"
|
||||
sizeInRem="1"
|
||||
:class="isDisabled && 'form-test-invalid'"
|
||||
:aria-disabled="isDisabled"
|
||||
:isDisabled="isDisabled"
|
||||
|
|
@ -97,6 +96,10 @@ export default {
|
|||
.btn-primary {
|
||||
width: 100%;
|
||||
}
|
||||
a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
@media only screen and (min-width: 340px) {
|
||||
.button-col,
|
||||
.link-col {
|
||||
|
|
@ -105,6 +108,10 @@ export default {
|
|||
.btn-primary {
|
||||
width: auto;
|
||||
}
|
||||
a {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ export default {
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.funnel-header {
|
||||
height: 56px;
|
||||
padding: 0.97rem 0;
|
||||
}
|
||||
|
||||
.logo-image {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,81 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import textboxQuestion from "./textbox-question";
|
||||
import { nextTick } from "vue";
|
||||
|
||||
describe("textboxQuestion.vue", () => {
|
||||
|
||||
it("Should return aria-disabled state", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(textboxQuestion, {
|
||||
propsData: {
|
||||
isDisabled: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
|
||||
// Expect
|
||||
expect(input.attributes()["aria-disabled"]).toEqual("true");
|
||||
});
|
||||
|
||||
it("Should render a text input", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(textboxQuestion, {
|
||||
propsData: {
|
||||
name: "test",
|
||||
label: "unit test label",
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
|
||||
expect(input.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("Should return input id", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(textboxQuestion, {
|
||||
propsData: {
|
||||
inputId: "input ID",
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
|
||||
// Expect
|
||||
expect(input.attributes().id).toEqual("input ID");
|
||||
});
|
||||
|
||||
it("Should return label text", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(textboxQuestion, {
|
||||
propsData: {
|
||||
labelText: "label text",
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const label = wrapper.find("label");
|
||||
|
||||
expect(label.text()).toEqual("label text");
|
||||
});
|
||||
|
||||
it("Should return input id", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(textboxQuestion, {
|
||||
propsData: {
|
||||
inputId: "input ID",
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
|
||||
// Expect
|
||||
expect(input.attributes().id).toEqual("input ID");
|
||||
});
|
||||
|
||||
});
|
||||
52
src/common-components/textbox-question/textbox-question.vue
Normal file
52
src/common-components/textbox-question/textbox-question.vue
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<template>
|
||||
<div class="textbox-question">
|
||||
<label :for="inputId" class="form-label">{{labelText}}</label>
|
||||
<input type="text" class="form-control" :id="inputId" :placeholder="placeholderText" :aria-disabled="isDisabled" :disabled="isDisabled" :aria-required="isRequired">
|
||||
<p class="mt-1 mb-0">There has been an error!</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "textboxQuestion",
|
||||
props: {
|
||||
isDisabled: Boolean,
|
||||
modelValue: String,
|
||||
labelText: String,
|
||||
placeholderText: String,
|
||||
inputId: String,
|
||||
isRequired: Boolean,
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.textbox-question {
|
||||
.form-label {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
.form-control {
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: .5rem;
|
||||
min-height: 3rem;
|
||||
&::placeholder {
|
||||
color: $gray-500;
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2px $blue;
|
||||
}
|
||||
&:disabled,
|
||||
&.disabled {
|
||||
background-color: $gray-100;
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 4px transparent;
|
||||
border: 1px solid $gray-500;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
border: 1px solid transparent;
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
File diff suppressed because it is too large
Load diff
75
src/layouts/nested-radio-poc/nested-radio.vue
Normal file
75
src/layouts/nested-radio-poc/nested-radio.vue
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
|
||||
<div class="container-fluid nested-radio">
|
||||
<div class="row mb-1">
|
||||
<div class="col">
|
||||
<listCard
|
||||
groupName="listcard1"
|
||||
isWide="true"
|
||||
buttonImage="https://digitalconsumercms-dev.safelite.com/images/default-source/damage-options/car.svg?sfvrsn=1468d7f1_3"
|
||||
buttonLabel="Green Tint, Blue Shade"
|
||||
buttonID="radio1"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-1">
|
||||
<div class="col">
|
||||
<p class="mb-0 fw-bold">Ok, select the features</p>
|
||||
<buttonQuestion
|
||||
buttonType="radio"
|
||||
groupName="Demo Group"
|
||||
buttonID="button1"
|
||||
isRequired
|
||||
value="test button"
|
||||
screenReaderOnlyText="rain sensor, solar, 3rd visor band"
|
||||
:answers="demoArray"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col">
|
||||
<listCard
|
||||
groupName="listcard1"
|
||||
isWide="true"
|
||||
buttonImage="https://digitalconsumercms-dev.safelite.com/images/default-source/damage-options/car.svg?sfvrsn=1468d7f1_3"
|
||||
buttonLabel="Green Tint"
|
||||
buttonID="radio2"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import buttonQuestion from "@/common-components/button-question/button-question";
|
||||
import listCard from "@/ux-components/list-card/list-card";
|
||||
|
||||
export default {
|
||||
name: "nestedRadio",
|
||||
components: {
|
||||
buttonQuestion,
|
||||
listCard,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
demoArray: [
|
||||
"rain sensor, solar, 3rd visor band",
|
||||
"rain sensor, heated glass, solar, 3rd visor band",
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.nested-radio {
|
||||
.ui-radio {
|
||||
flex-direction: column;
|
||||
margin: .25rem 0;
|
||||
}
|
||||
p {
|
||||
font-size: .875rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -41,7 +41,7 @@ describe("damage-location-question.vue", () => {
|
|||
damageLocationQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent, damageOptions, "car-group");
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.answersToDisplay).toStrictEqual([ { Name: 'car-Windshield' }, { Name: 'car-SideDoor' } ])
|
||||
expect(wrapper.vm.answersToDisplay).toStrictEqual([ { Name: 'Windshield' }, { Name: 'SideDoor' } ])
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
<template>
|
||||
<div class="damage-location-question">
|
||||
<buttonQuestion
|
||||
v-if="answersToDisplay.length > 1"
|
||||
:questionText="questionText"
|
||||
isMultiSelect
|
||||
:answers="answersToDisplay"
|
||||
|
|
@ -68,13 +67,18 @@ export default ({
|
|||
}
|
||||
},
|
||||
answersToDisplay(){
|
||||
return Array.isArray(this.answersFromCms)
|
||||
const filteredAnswers = Array.isArray(this.answersFromCms)
|
||||
? this.answersFromCms.filter(ans =>
|
||||
{
|
||||
const name = ans.Name.split('-');
|
||||
return name[0].toUpperCase() === store.getters.vehicle.category && this.damageOptionsMap[name[1]];
|
||||
})
|
||||
: [];
|
||||
return filteredAnswers.map(ans => {
|
||||
const newName = ans.Name.includes('-') ? ans.Name.split('-')[1] : ans.Name;
|
||||
ans.Name = newName;
|
||||
return ans;
|
||||
});
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -1,76 +1,124 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-question/replace-options-question";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import { nextTick } from "vue";
|
||||
import store from "@/store";
|
||||
jest.mock("@/store", () => { return {}; }, {virtual: true});
|
||||
|
||||
describe("replace-options-question.vue", () => {
|
||||
test("Selected damage option is emitted upon selection.", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({ modelValueProp: ["Windshield"] });
|
||||
const damageToSelect = ["Backseat"];
|
||||
|
||||
//Act
|
||||
wrapper.setValue({ modelValue: damageToSelect });
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{modelValue: ["Backseat"]}]);
|
||||
});
|
||||
});
|
||||
test("Selected damage option is emitted upon selection.", async () => {
|
||||
|
||||
describe("replace-options-question.vue", () => {
|
||||
test("Answers to display filtered by data from api.", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper, cmsContent, replaceOptions } = setupMocks({ dataFromStoreApi: ["Windshield", "FrontDoor"]});
|
||||
|
||||
//Act
|
||||
replaceOptionsQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent, replaceOptions, "car-group");
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.answersToDisplay).toStrictEqual([ { Name: 'car-Windshield' }, { Name: 'car-FrontDoor' } ])
|
||||
});
|
||||
});
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({ modelValueProp: ["Windshield"] });
|
||||
const damageToSelect = ["Backseat"];
|
||||
|
||||
function setupMocks({
|
||||
modelValueProp = ["Windshield"],
|
||||
isAvailale = true,
|
||||
isMultiSelect = false,
|
||||
filterByVehicleCategory = false,
|
||||
groupName = "damageQuestion",
|
||||
cmsQuestionText = "CMS text goes here",
|
||||
cmsAnswers = [{Name: "car-Windshield"}, {Name: "car-BackDoor"}, {Name: "car-FrontDoor"}],
|
||||
dataFromStoreApi = [],
|
||||
}) {
|
||||
|
||||
//Mock store
|
||||
store.dispatch = jest.fn(() => dataFromStoreApi);
|
||||
store.getters = { vehicle: {year: 2019, make: 'honda', model: 'civc', style: '2 Door', category: 'CAR'} };
|
||||
const mountOptions = getMountOptions({
|
||||
store: {
|
||||
dispatch: store.dispatch,
|
||||
getters: store.getters,
|
||||
//Act
|
||||
wrapper.setValue({ modelValue: damageToSelect });
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{modelValue: ["Backseat"]}]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("replace-options-question.vue", () => {
|
||||
test("Answers to display filtered by data from api.", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper, cmsContent, replaceOptions } = setupMocks({ dataFromStoreApi: ["Windshield", "FrontDoor"], filterByVehicleCategory: true});
|
||||
|
||||
//Act
|
||||
replaceOptionsQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent, replaceOptions, "car-group");
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.answersToDisplay).toStrictEqual([ { Name: 'Windshield' }, { Name: 'FrontDoor' } ])
|
||||
});
|
||||
});
|
||||
|
||||
describe("replace-options-question.vue", () => {
|
||||
test("when updateSelectedValues method is called with a single answerToDisplay it will call to update this.selectedValues", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper, cmsContent, replaceOptions
|
||||
} = setupMocks({
|
||||
modelValueProp: [],
|
||||
});
|
||||
wrapper.setData({answersFromCms: [
|
||||
{
|
||||
"Name": "Stationary",
|
||||
},
|
||||
});
|
||||
{
|
||||
"Name": "Slider",
|
||||
}
|
||||
]});
|
||||
wrapper.setData({replaceOptions: ["Stationary"]});
|
||||
|
||||
//Act
|
||||
wrapper.vm.$options.methods.updateSelectedValues.call(wrapper.vm);
|
||||
|
||||
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([['Stationary']]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("replace-options-question.vue", () => {
|
||||
test("when isAvailable is true, will run updateSelectedValues method", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper, cmsContent, replaceOptions } = setupMocks({ isAvailable: false, methodsToMock: ["updateSelectedValues"] });
|
||||
|
||||
//Act
|
||||
wrapper.vm.$options.methods.initializeComponent.call(wrapper.vm, cmsContent, replaceOptions, "car-group");
|
||||
wrapper.vm.$options.watch.isAvailable.call(wrapper.vm, true);
|
||||
|
||||
//Assert
|
||||
expect(replaceOptionsQuestion.methods.updateSelectedValues).toHaveBeenCalled();
|
||||
wrapper.unmount();
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks({
|
||||
modelValueProp = ["Windshield"],
|
||||
isAvailable = true,
|
||||
isMultiSelect = false,
|
||||
filterByVehicleCategory = false,
|
||||
groupName = "damageQuestion",
|
||||
cmsQuestionText = "CMS text goes here",
|
||||
cmsAnswers = [{Name: "car-Windshield"}, {Name: "car-BackDoor"}, {Name: "car-FrontDoor"}],
|
||||
dataFromStoreApi = [],
|
||||
methodsToMock = [],
|
||||
}) {
|
||||
|
||||
//Mock props
|
||||
mountOptions.propsData = {
|
||||
modelValue: modelValueProp,
|
||||
isAvailable: isAvailale,
|
||||
isMultiSelect: isMultiSelect,
|
||||
filterByVehicleCategory: filterByVehicleCategory
|
||||
};
|
||||
|
||||
const wrapper = shallowMount(replaceOptionsQuestion, mountOptions);
|
||||
//Mock store
|
||||
store.dispatch = jest.fn(() => dataFromStoreApi);
|
||||
store.getters = { vehicle: {year: 2019, make: 'honda', model: 'civc', style: '2 Door', category: 'CAR'} };
|
||||
const mountOptions = getMountOptions({
|
||||
store: {
|
||||
dispatch: store.dispatch,
|
||||
getters: store.getters,
|
||||
},
|
||||
});
|
||||
|
||||
//Mock props
|
||||
mountOptions.propsData = {
|
||||
modelValue: modelValueProp,
|
||||
isAvailable: isAvailable,
|
||||
isMultiSelect: isMultiSelect,
|
||||
filterByVehicleCategory: filterByVehicleCategory
|
||||
};
|
||||
|
||||
//Mock methods
|
||||
methodsToMock.forEach((methodName) => {
|
||||
replaceOptionsQuestion.methods[methodName] = jest.fn();
|
||||
});
|
||||
|
||||
//Mock CMS content
|
||||
const cmsContent = {
|
||||
groupName: groupName,
|
||||
QuestionText: cmsQuestionText,
|
||||
Answers: cmsAnswers,
|
||||
};
|
||||
const replaceOptions = dataFromStoreApi;
|
||||
return { wrapper, cmsContent, replaceOptions };
|
||||
}
|
||||
const wrapper = shallowMount(replaceOptionsQuestion, mountOptions);
|
||||
|
||||
//Mock CMS content
|
||||
const cmsContent = {
|
||||
groupName: groupName,
|
||||
QuestionText: cmsQuestionText,
|
||||
Answers: cmsAnswers,
|
||||
};
|
||||
const replaceOptions = dataFromStoreApi;
|
||||
return { wrapper, cmsContent, replaceOptions };
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<transition name="fade">
|
||||
<div class="replace-options-question">
|
||||
<transition name="fade" mode="out-in">
|
||||
<div v-if="isAvailable && this.answersToDisplay.length > 0" class="replace-options-question" :class="this.answersToDisplay.length < 2 ? 'd-none' : ''" aria-live="polite">
|
||||
<buttonQuestion
|
||||
v-if="isAvailable && answersToDisplay.length > 1"
|
||||
isWide
|
||||
:questionText="questionText"
|
||||
isMultiSelect
|
||||
:isMultiSelect="isMultiSelect"
|
||||
:answers="answersToDisplay"
|
||||
:groupName="groupName"
|
||||
buttonType="listCard"
|
||||
|
|
@ -42,6 +42,7 @@ export default ({
|
|||
filterByVehicleCategory: Boolean,
|
||||
groupName: String,
|
||||
modelValue: Array,
|
||||
isMultiSelect: Boolean,
|
||||
},
|
||||
methods: {
|
||||
initializeComponent(cmsContent, replaceOptions){
|
||||
|
|
@ -49,6 +50,12 @@ export default ({
|
|||
this.answersFromCms = cmsContent.Answers;
|
||||
this.replaceOptions = replaceOptions;
|
||||
},
|
||||
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: {
|
||||
selectedValues: {
|
||||
|
|
@ -60,29 +67,29 @@ export default ({
|
|||
}
|
||||
},
|
||||
answersToDisplay(){
|
||||
return Array.isArray(this.answersFromCms)
|
||||
const filteredAnswers = Array.isArray(this.answersFromCms)
|
||||
? this.answersFromCms.filter(ans =>
|
||||
{
|
||||
const name = ans.Name.split('-');
|
||||
return this.filterByVehicleCategory ? name[0].toUpperCase() === store.getters.vehicle.category : name[0].toUpperCase() === store.getters.vehicle.category && this.replaceOptions.includes(name[1])
|
||||
return this.filterByVehicleCategory ? name[0].toUpperCase() === store.getters.vehicle.category && this.replaceOptions.includes(name[1]) : this.replaceOptions.includes(ans.Name);
|
||||
})
|
||||
: [];
|
||||
|
||||
return filteredAnswers.map(ans => {
|
||||
const newName = ans.Name.includes('-') ? ans.Name.split('-')[1] : ans.Name;
|
||||
ans.Name = newName;
|
||||
return ans;
|
||||
});
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
isAvailable(val) {
|
||||
// CHECK TO UPDATE SELECTED VALUES WHEN ISAVAILABLE IS TRUE
|
||||
val && this.updateSelectedValues();
|
||||
}
|
||||
},
|
||||
components: {
|
||||
buttonQuestion,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.5s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,126 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import sideDoorOptions from "@/layouts/vehicle-damage/side-door-options/side-door-options";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-question/replace-options-question";
|
||||
import store from "@/store";
|
||||
jest.mock("@/store", () => { return {}; }, {virtual: true});
|
||||
|
||||
describe("replace-options-question.vue", () => {
|
||||
test("Selected side door option is emitted upon selection.", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({ modelValueProp: ["Windshield"] });
|
||||
const sideDoorOptions = ["Backseat"];
|
||||
|
||||
//Act
|
||||
wrapper.setValue({ modelValue: sideDoorOptions });
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{modelValue: ["Backseat"]}]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("replace-options-question.vue", () => {
|
||||
test("Selected door side option is updated when selection made.", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
wrapper.vm.selectedDoorSidesValues = ["DriverSide"]
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.getSideDoorReplacementOptions()).toHaveBeenCalled;
|
||||
});
|
||||
});
|
||||
|
||||
describe("replace-options-question.vue", () => {
|
||||
test("Selected driver side option is updated when selection made.", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
wrapper.vm.selectedDriverSideReplaceOptionsValues = ["FrontDoor"]
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.getSideDoorReplacementOptions()).toHaveBeenCalled;
|
||||
});
|
||||
});
|
||||
|
||||
describe("replace-options-question.vue", () => {
|
||||
test("Selected passenger side option is updated when selection made.", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
wrapper.vm.selectedPassengerSideReplaceOptionsValues = ["BacktDoor"]
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.getSideDoorReplacementOptions()).toHaveBeenCalled;
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe("replace-options-question.vue", () => {
|
||||
test("Answers to display filtered by data from api.", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper, cmsContent, driverSideReplaceOptions, passengerSideReplaceOptions, driverSideOptions, passengerSideOptions } = setupMocks({});
|
||||
|
||||
//Act
|
||||
sideDoorOptions.methods.initializeComponent.call(wrapper.vm, cmsContent, driverSideReplaceOptions, passengerSideReplaceOptions, driverSideOptions, passengerSideOptions, "car-group");
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.answersToDisplay).toStrictEqual([ { Name: 'DriverSide' }, { Name: 'PassengerSide' } ])
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
function setupMocks({
|
||||
modelValueProp = ["DriverSide"],
|
||||
groupName = "sideDoorOptions",
|
||||
cmsQuestionText = "CMS text goes here",
|
||||
cmsAnswers = [{Name: "Car-DriverSide"}, {Name: "Car-PassengerSide"}],
|
||||
driverSideReplaceOptions = ["FrontDoor", "BackDoor"],
|
||||
passengerSideReplaceOptions = ["FrontDoor", "BackDoor"],
|
||||
driverSideOptions = ["Car-FrontDoor", "Car-BackDoor"],
|
||||
passengerSideOptions = ["Car-FrontDoor", "Car-BackDoor"],
|
||||
selectedDamageLocations = ["SideDoor"]
|
||||
}) {
|
||||
|
||||
//Mock store
|
||||
store.dispatch = jest.fn(() => dataFromStoreApi);
|
||||
store.getters = { vehicle: {year: 2019, make: 'honda', model: 'civc', style: '2 Door', category: 'CAR'} };
|
||||
const mountOptions = getMountOptions({
|
||||
store: {
|
||||
dispatch: store.dispatch,
|
||||
getters: store.getters,
|
||||
},
|
||||
});
|
||||
|
||||
//Mock props
|
||||
mountOptions.propsData = {
|
||||
modelValue: modelValueProp,
|
||||
groupName: groupName,
|
||||
selectedDamageLocations: selectedDamageLocations,
|
||||
};
|
||||
|
||||
const wrapper = shallowMount(sideDoorOptions, mountOptions);
|
||||
|
||||
const OptionsWrapper = wrapper.findAllComponents({name: "replaceOptionsQuestion"});
|
||||
OptionsWrapper[0].vm.initializeComponent = replaceOptionsQuestion.methods.initializeComponent;
|
||||
OptionsWrapper[1].vm.initializeComponent = replaceOptionsQuestion.methods.initializeComponent;
|
||||
//Mock CMS content
|
||||
const cmsContent = {
|
||||
groupName: groupName,
|
||||
QuestionText: cmsQuestionText,
|
||||
Answers: cmsAnswers,
|
||||
};
|
||||
return { wrapper, cmsContent, driverSideReplaceOptions, passengerSideReplaceOptions, driverSideOptions, passengerSideOptions };
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
<template>
|
||||
<div class="side-door-options">
|
||||
<transition name="fade" mode="out-in">
|
||||
<div class="side-doors" v-if="selectedDamageLocations.includes('SideDoor')" aria-live="polite">
|
||||
<buttonQuestion
|
||||
:questionText="questionText"
|
||||
isMultiSelect
|
||||
:answers="answersToDisplay"
|
||||
:groupName="groupName"
|
||||
buttonType="listCard"
|
||||
v-model="selectedDoorSidesValues"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
<replaceOptionsQuestion ref="driverSideOptions" :isAvailable="isDriverSideReplaceOptionsQuestionAvailable" groupName="driverSideOptions" filterByVehicleCategory isMultiSelect v-model="selectedDriverSideReplaceOptionsValues" />
|
||||
|
||||
<replaceOptionsQuestion ref="passengerSideOptions" :isAvailable="isPassengerSideReplaceOptionsQuestionAvailable" groupName="passengerSideOptions" filterByVehicleCategory isMultiSelect v-model="selectedPassengerSideReplaceOptionsValues" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import buttonQuestion from "@/common-components/button-question/button-question";
|
||||
import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-question/replace-options-question";
|
||||
import store from "@/store";
|
||||
|
||||
export default ({
|
||||
name: "sideDoorOptions",
|
||||
data(){
|
||||
return {
|
||||
questionText: String,
|
||||
answersFromCms: Array,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
groupName: String,
|
||||
modelValue: Array,
|
||||
selectedDamageLocations: Array,
|
||||
},
|
||||
methods: {
|
||||
initializeComponent(cmsContent, driverSideReplaceOptions, passengerSideReplaceOptions, driverSideOptions, passengerSideOptions){
|
||||
this.questionText = cmsContent.QuestionText;
|
||||
this.answersFromCms = cmsContent.Answers;
|
||||
// Child Components intialize
|
||||
this.$refs.driverSideOptions.initializeComponent(driverSideReplaceOptions, driverSideOptions);
|
||||
this.$refs.passengerSideOptions.initializeComponent(passengerSideReplaceOptions, passengerSideOptions);
|
||||
},
|
||||
getSideDoorReplacementOptions(selectedDoorSides, selectedDriverSideReplaceOptions, selectedPassengerSideReplaceOptions){
|
||||
return {
|
||||
selectedDoorSides: selectedDoorSides,
|
||||
selectedDriverSideReplaceOptions: selectedDriverSideReplaceOptions,
|
||||
selectedPassengerSideReplaceOptions: selectedPassengerSideReplaceOptions
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
selectedValues: {
|
||||
get: function() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
}
|
||||
},
|
||||
selectedDoorSidesValues: {
|
||||
get: function() {
|
||||
return this.selectedValues.selectedDoorSides;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.selectedValues = this.getSideDoorReplacementOptions(newValue, this.selectedValues.selectedDriverSideReplaceOptions, this.selectedValues.selectedPassengerSideReplaceOptions);
|
||||
}
|
||||
},
|
||||
selectedDriverSideReplaceOptionsValues: {
|
||||
get: function() {
|
||||
return this.selectedValues.selectedDriverSideReplaceOptions;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.selectedValues = this.getSideDoorReplacementOptions(this.selectedValues.selectedDoorSides, newValue, this.selectedValues.selectedPassengerSideReplaceOptions);
|
||||
}
|
||||
},
|
||||
selectedPassengerSideReplaceOptionsValues: {
|
||||
get: function() {
|
||||
return this.selectedValues.selectedPassengerSideReplaceOptions;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.selectedValues = this.getSideDoorReplacementOptions(this.selectedValues.selectedDoorSides, this.selectedValues.selectedDriverSideReplaceOptions, newValue);
|
||||
}
|
||||
},
|
||||
answersToDisplay(){
|
||||
const filteredAnswers = Array.isArray(this.answersFromCms)
|
||||
? this.answersFromCms.filter(ans =>
|
||||
{
|
||||
const name = ans.Name.split('-');
|
||||
return name[0].toUpperCase() === store.getters.vehicle.category;
|
||||
})
|
||||
: [];
|
||||
|
||||
return filteredAnswers.map(ans => {
|
||||
const newName = ans.Name.includes('-') ? ans.Name.split('-')[1] : ans.Name;
|
||||
ans.Name = newName;
|
||||
return ans;
|
||||
});
|
||||
},
|
||||
isDriverSideReplaceOptionsQuestionAvailable(){
|
||||
return (Array.isArray(this.selectedDoorSidesValues) && this.selectedDoorSidesValues.includes("DriverSide") && (Array.isArray(this.selectedDamageLocations) && this.selectedDamageLocations.includes("SideDoor")));
|
||||
},
|
||||
isPassengerSideReplaceOptionsQuestionAvailable(){
|
||||
return (Array.isArray(this.selectedDoorSidesValues) && this.selectedDoorSidesValues.includes("PassengerSide") && (Array.isArray(this.selectedDamageLocations) && this.selectedDamageLocations.includes("SideDoor")));
|
||||
},
|
||||
},
|
||||
components: {
|
||||
buttonQuestion,
|
||||
replaceOptionsQuestion,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -4,8 +4,10 @@ import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
|||
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
|
||||
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||
import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-question/replace-options-question";
|
||||
import sideDoorOptions from "@/layouts/vehicle-damage/side-door-options/side-door-options";
|
||||
import damageLocationQuestion from "@/layouts/vehicle-damage/damage-location-question/damage-location-question";
|
||||
import windshieldOptions from "@/layouts/vehicle-damage/windshield-options/windshield-options";
|
||||
import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-question/replace-options-question";
|
||||
|
||||
// Supporting Files
|
||||
import { settleAllPromises } from "@/helpers/layout-helper.js";
|
||||
|
|
@ -165,19 +167,60 @@ describe("vehicle-damage.vue", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("vehicle-damage.vue", () => {
|
||||
test("BackButtonAction triggers a router.navigate change", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
vehicleDamage.beforeRouteEnter.call(
|
||||
wrapper.vm,
|
||||
{ query: { fmgPage: "vehicle-damage" } },
|
||||
undefined,
|
||||
(c) => c(wrapper.vm)
|
||||
);
|
||||
|
||||
wrapper.vm.backButtonAction();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.$router.navigate).toHaveBeenCalled();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe("vehicle-damage.vue", () => {
|
||||
test("isRearWindowDamageLocation is true if Rear Window damage is selected", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({});
|
||||
|
||||
//Act
|
||||
vehicleDamage.beforeRouteEnter.call(
|
||||
wrapper.vm,
|
||||
{ query: { fmgPage: "vehicle-damage" } },
|
||||
undefined,
|
||||
(c) => c(wrapper.vm)
|
||||
);
|
||||
|
||||
wrapper.vm.selectedDamageLocations = ["RearWindow"];
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.isRearWindowDamageLocation).toEqual(true);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks({
|
||||
pageHeaderWidgetHeaderText = {},
|
||||
mountOptionsMockData = {},
|
||||
mountOptionsMockData = {
|
||||
router: {
|
||||
navigate: jest.fn(),
|
||||
},
|
||||
},
|
||||
}) {
|
||||
//Mock api responses
|
||||
baseMixin.methods.dispatchNonBlockingStoreAction = jest.fn();
|
||||
baseMixin.methods.dispatchNonBlockingStoreAction.mockImplementation(() => {
|
||||
return Promise.resolve({
|
||||
driverSideOptions: {
|
||||
availableReplacementOptions: ["Front", "Back", "Side"],
|
||||
}
|
||||
});
|
||||
});
|
||||
const apiResponses = {
|
||||
cmsContent: {
|
||||
FunnelSubHeaderWidget: pageHeaderWidgetHeaderText,
|
||||
|
|
@ -193,7 +236,16 @@ function setupMocks({
|
|||
damageOptions: {
|
||||
driverSideOptions: {
|
||||
availableReplacementOptions: ["Front", "Back", "Side"],
|
||||
}
|
||||
},
|
||||
passengerSideOptions: {
|
||||
availableReplacementOptions: ["Front", "Back", "Side"],
|
||||
},
|
||||
windshieldOptions: {
|
||||
availableReplacementOptions: ["Single", "Driver", "Passenger"],
|
||||
},
|
||||
backGlassOptions: {
|
||||
availableReplacementOptions: ["Front", "Back", "Side"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -215,12 +267,19 @@ function setupMocks({
|
|||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
const driverSideOptions = replaceOptionsQuestion
|
||||
driverSideOptions.methods = {
|
||||
damageLocationQuestion.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
damageLocationQuestion.methods = {
|
||||
sideDoorOptions.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
windshieldOptions.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
replaceOptionsQuestion.methods = {
|
||||
initializeComponent: jest.fn(),
|
||||
};
|
||||
|
||||
|
|
@ -239,16 +298,27 @@ function setupMocks({
|
|||
vehicleBannerWrapper.vm.initializeComponent =
|
||||
vehicleBanner.methods.initializeComponent;
|
||||
|
||||
const sideDoorOptionsWrapper = wrapper.findComponent({ name: "sideDoorOptions" });
|
||||
sideDoorOptionsWrapper.vm.initializeComponent =
|
||||
sideDoorOptions.methods.initializeComponent;
|
||||
|
||||
const windshieldOptionsWrapper = wrapper.findComponent({
|
||||
name: "windshieldOptions",
|
||||
});
|
||||
|
||||
windshieldOptionsWrapper.vm.initializeComponent =
|
||||
windshieldOptions.methods.initializeComponent;
|
||||
|
||||
const funnelSubHeaderWrapper = wrapper.findComponent({
|
||||
name: "funnelSubHeader",
|
||||
});
|
||||
funnelSubHeaderWrapper.vm.initializeComponent =
|
||||
funnelSubHeader.methods.initializeComponent;
|
||||
|
||||
const driverSideOptionsWrapper = wrapper.findComponent({
|
||||
const backGlassOptionsWrapper = wrapper.findComponent({
|
||||
name: "replaceOptionsQuestion",
|
||||
});
|
||||
driverSideOptionsWrapper.vm.initializeComponent =
|
||||
backGlassOptionsWrapper.vm.initializeComponent =
|
||||
replaceOptionsQuestion.methods.initializeComponent;
|
||||
|
||||
const damageLocationQuestionWrapper = wrapper.findComponent({
|
||||
|
|
|
|||
|
|
@ -3,8 +3,28 @@
|
|||
<funnelHeader ref="funnelHeader" />
|
||||
<vehicleBanner ref="vehicleBanner" :displayGenericVehicleImage=false />
|
||||
<funnelSubHeader ref="funnelSubHeader" />
|
||||
<damageLocationQuestion ref="damageLocation" v-model="selectedDamageLocations" groupName="DamageLocationQuestion" />
|
||||
<replaceOptionsQuestion ref="driverSideOptions" isAvailable v-model="driverSideOptionsData" groupName="DriverSideReplaceOptionsQuestion" />
|
||||
<damageLocationQuestion
|
||||
ref="damageLocation"
|
||||
v-model="selectedDamageLocations"
|
||||
groupName="DamageLocationQuestion"
|
||||
/>
|
||||
<windshieldOptions
|
||||
ref="windshieldOptions"
|
||||
v-model="selectedWindshieldOptions"
|
||||
:selectedDamageLocations="selectedDamageLocations"
|
||||
/>
|
||||
<sideDoorOptions
|
||||
ref="sideDoorOptions"
|
||||
groupName="SideDoorSideQuestion"
|
||||
v-model="sideDoorOptionsData"
|
||||
:selectedDamageLocations="selectedDamageLocations"
|
||||
/>
|
||||
<replaceOptionsQuestion
|
||||
ref="backGlassOptions"
|
||||
:isAvailable="isRearWindowDamageLocation"
|
||||
v-model="selectedRearReplaceOptions"
|
||||
groupName="BackGlassReplaceOptionsQuestion"
|
||||
/>
|
||||
<funnelFooter ref="funnelFooter" @back-clicked="backButtonAction" />
|
||||
</div>
|
||||
</template>
|
||||
|
|
@ -15,8 +35,10 @@ import funnelHeader from "@/common-components/funnel-header/funnel-header";
|
|||
import funnelFooter from "@/common-components/funnel-footer/funnel-footer";
|
||||
import vehicleBanner from "@/common-components/vehicle-banner/vehicle-banner";
|
||||
import funnelSubHeader from "@/common-components/funnel-sub-header/funnel-sub-header";
|
||||
import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-question/replace-options-question";
|
||||
import sideDoorOptions from "@/layouts/vehicle-damage/side-door-options/side-door-options";
|
||||
import damageLocationQuestion from "@/layouts/vehicle-damage/damage-location-question/damage-location-question";
|
||||
import windshieldOptions from "@/layouts/vehicle-damage/windshield-options/windshield-options";
|
||||
import replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-question/replace-options-question";
|
||||
|
||||
// Supporting files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
|
|
@ -62,21 +84,42 @@ export default {
|
|||
vm.$refs.funnelSubHeader.initializeComponent(
|
||||
resultMap.cmsContent.FunnelSubHeaderWidget
|
||||
);
|
||||
vm.$refs.driverSideOptions.initializeComponent(
|
||||
resultMap.cmsContent.DriverSideReplaceOptionsQuestion, resultMap.damageOptions.driverSideOptions.availableReplacementOptions
|
||||
);
|
||||
vm.$refs.damageLocation.initializeComponent(
|
||||
resultMap.cmsContent.DamageLocationQuestion, resultMap.damageOptions
|
||||
);
|
||||
vm.$refs.sideDoorOptions.initializeComponent(
|
||||
resultMap.cmsContent.SideDoorSideQuestion, resultMap.cmsContent.DriverSideReplaceOptionsQuestion, resultMap.cmsContent.PassengerSideReplaceOptionsQuestion, resultMap.damageOptions.driverSideOptions.availableReplacementOptions, resultMap.damageOptions.passengerSideOptions.availableReplacementOptions
|
||||
);
|
||||
vm.$refs.windshieldOptions.initializeComponent(
|
||||
resultMap.cmsContent.WindshieldDamageTypeQuestion, resultMap.cmsContent.WindshieldChipCountQuestion,
|
||||
resultMap.cmsContent.WindshieldReplaceOptionsQuestion, resultMap.damageOptions.windshieldOptions.availableReplacementOptions
|
||||
);
|
||||
vm.$refs.backGlassOptions.initializeComponent(
|
||||
resultMap.cmsContent.RearReplaceOptionsQuestion, resultMap.damageOptions.backGlassOptions.availableReplacementOptions
|
||||
);
|
||||
vm.$refs.funnelFooter.initializeComponent(
|
||||
resultMap.cmsContent.FunnelFooterWidget
|
||||
);
|
||||
});
|
||||
},
|
||||
computed: {
|
||||
isRearWindowDamageLocation() {
|
||||
return this.selectedDamageLocations.some(selectedDamages =>
|
||||
{
|
||||
return selectedDamages.toUpperCase() === "REARWINDOW";
|
||||
});
|
||||
},
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
selectedDamageLocations: [],
|
||||
driverSideOptionsData: [],
|
||||
sideDoorOptionsData: {
|
||||
selectedDoorSides: [],
|
||||
selectedDriverSideReplaceOptions: [],
|
||||
selectedPassengerSideReplaceOptions: []
|
||||
},
|
||||
selectedWindshieldOptions: [],
|
||||
selectedRearReplaceOptions: [],
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -100,8 +143,10 @@ export default {
|
|||
funnelFooter,
|
||||
vehicleBanner,
|
||||
funnelSubHeader,
|
||||
replaceOptionsQuestion,
|
||||
sideDoorOptions,
|
||||
damageLocationQuestion,
|
||||
windshieldOptions,
|
||||
replaceOptionsQuestion,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
<template>
|
||||
<div class="windshield-damage-type-question">
|
||||
<buttonQuestion
|
||||
v-if="isAvailable && answersToDisplay.length > 1"
|
||||
:questionText="questionText"
|
||||
:answers="answersToDisplay"
|
||||
:groupName="groupName"
|
||||
buttonType="listCard"
|
||||
v-model="selectedValues"
|
||||
validationRules="windshield-damage-required|checkForRepairAndReplace:@DamageLocationQuestion"
|
||||
:suppressError="suppressError"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import buttonQuestion from "@/common-components/button-question/button-question";
|
||||
import store from "@/store";
|
||||
import { defineRule } from "vee-validate";
|
||||
|
||||
// DEFINE VALIDATION RULES
|
||||
defineRule("windshield-damage-required", (value) => {
|
||||
if (!value || value.length < 1) {
|
||||
return "Please select windshield damage";
|
||||
}
|
||||
return true;
|
||||
});
|
||||
defineRule("checkForRepairAndReplace", (value, [other]) => {
|
||||
if (value === "Windshield-Chip" && other.toString().includes("Windshield") && other.length > 1) {
|
||||
return "checkForRepairAndReplace error"
|
||||
}
|
||||
return true;
|
||||
});
|
||||
|
||||
export default ({
|
||||
name: "windshieldDamageTypeQuestion",
|
||||
data(){
|
||||
return {
|
||||
questionText: String,
|
||||
answersFromCms: Array,
|
||||
damageOptions: Object,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
isMultiSelect: Boolean,
|
||||
modelValue: Array,
|
||||
isAvailable: Boolean,
|
||||
name: String,
|
||||
groupName: String,
|
||||
suppressError: Boolean,
|
||||
},
|
||||
methods: {
|
||||
initializeComponent(cmsContent, damageOptions){
|
||||
this.questionText = cmsContent.QuestionText;
|
||||
this.answersFromCms = cmsContent.Answers;
|
||||
this.damageOptions = damageOptions;
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
selectedValues: {
|
||||
get: function() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
}
|
||||
},
|
||||
damageOptionsMap(){
|
||||
return {
|
||||
Windshield: true,
|
||||
SideDoor: this.damageOptions.driverSideOptions.availableReplacementOptions || this.damageOptions.passengerSideOptions.availableReplacementOption ? true : false,
|
||||
RearWindow: this.damageOptions.backGlassOptions.availableReplacementOptions ? true : false,
|
||||
}
|
||||
},
|
||||
answersToDisplay(){
|
||||
// HARD CODED MOCK DATA FOR NOW
|
||||
return [
|
||||
{
|
||||
"Name": "Windshield-Crack",
|
||||
"Text": "Crack",
|
||||
"SubText": "My damage is larger than six inches.",
|
||||
"ImageId": "2cf13fbb-22d8-4b61-bd3d-3d413ea58c87",
|
||||
"AnswerImageUrl": "https://fixmyglass.safelite.com/Shared/images/icon-replace-desktop-ds.png"
|
||||
},
|
||||
{
|
||||
"Name": "Windshield-Chip",
|
||||
"Text": "Chip(s)",
|
||||
"SubText": "I have three or fewer chips smaller than six inches.",
|
||||
"ImageId": "faaa14d9-3650-4949-a687-7665962337b8",
|
||||
"AnswerImageUrl": "https://fixmyglass.safelite.com/Shared/images/icon-repair-desktop-ds.png"
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
components: {
|
||||
buttonQuestion,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
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 store from "@/store";
|
||||
|
||||
jest.mock("@/store", () => { return {}; }, {virtual: true});
|
||||
|
||||
describe("windshield-chip-count-question.vue", () => {
|
||||
test("Selected chip count is emitted upon selection.", async () => {
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({modelValueProp: ["One"]});
|
||||
|
||||
//Act
|
||||
wrapper.setValue({ modelValue: ["Two"] });
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.selectedChipCountValues).toEqual(["One"]);
|
||||
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ modelValue: ["Two"] }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Windshield-chip-count-question.vue", () => {
|
||||
test("Should display question and answers from api.", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper, cmsContent } = setupMocks({modelValueProp: ["One"]});
|
||||
|
||||
//Act
|
||||
windshieldChipCountQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent);
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.questionText).toStrictEqual("How many chips are we repairing?");
|
||||
expect(wrapper.vm.answersFromCms).toStrictEqual([ { Name: 'One' }, { Name: 'Two' }, { Name: 'Three'} ]);
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks({
|
||||
modelValueProp = ["Two"],
|
||||
groupName = "WindshieldChipCountQuestion",
|
||||
cmsQuestionText = "How many chips are we repairing?",
|
||||
cmsAnswers = [{Name: "One"}, {Name: "Two"}, {Name: "Three"}],
|
||||
dataFromStoreApi = [],
|
||||
}) {
|
||||
|
||||
//Mock store
|
||||
store.dispatch = jest.fn(() => dataFromStoreApi);
|
||||
store.getters = { vehicle: {year: 2019, make: 'honda', model: 'civc', style: '2 Door', category: 'CAR'} };
|
||||
const mountOptions = getMountOptions({
|
||||
store: {
|
||||
dispatch: store.dispatch,
|
||||
getters: store.getters,
|
||||
},
|
||||
});
|
||||
|
||||
//Mock props
|
||||
mountOptions.propsData = {
|
||||
modelValue: modelValueProp
|
||||
};
|
||||
|
||||
const wrapper = shallowMount(windshieldChipCountQuestion, mountOptions);
|
||||
|
||||
//Mock CMS content
|
||||
const cmsContent = {
|
||||
groupName: groupName,
|
||||
QuestionText: cmsQuestionText,
|
||||
Answers: cmsAnswers,
|
||||
};
|
||||
const damageOptions = dataFromStoreApi;
|
||||
return { wrapper, cmsContent, damageOptions };
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<transition name="fade" mode="out-in">
|
||||
<div class="windshield-chip-count-question" v-if="isAvailable" aria-live="polite">
|
||||
<buttonQuestion
|
||||
:questionText="questionText"
|
||||
:answers="answersFromCms"
|
||||
:groupName="groupName"
|
||||
buttonType="listButtonHorizontal"
|
||||
v-model="selectedChipCountValues"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import buttonQuestion from "@/common-components/button-question/button-question";
|
||||
|
||||
export default ({
|
||||
name: "windshieldOptions",
|
||||
data(){
|
||||
return {
|
||||
questionText: String,
|
||||
answersFromCms: Array
|
||||
}
|
||||
},
|
||||
props: {
|
||||
modelValue: Array,
|
||||
groupName: String,
|
||||
isAvailable: Boolean,
|
||||
},
|
||||
methods: {
|
||||
initializeComponent(cmsContent){
|
||||
this.questionText = cmsContent.QuestionText;
|
||||
this.answersFromCms = cmsContent.Answers;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedChipCountValues: {
|
||||
get: function() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
buttonQuestion,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import windshieldDamageTypeQuestion from"@/layouts/vehicle-damage/windshield-options/windshield-damage-type-question/windshield-damage-type-question";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
import store from "@/store";
|
||||
|
||||
jest.mock("@/store", () => { return {}; }, {virtual: true});
|
||||
|
||||
describe("windshield-damage-type-question.vue", () => {
|
||||
test("Selected chip count is emitted upon selection.", async () => {
|
||||
//Arrange
|
||||
const { wrapper } = setupMocks({modelValueProp: ["Repair"]});
|
||||
|
||||
//Act
|
||||
wrapper.setValue({ modelValue: ["Replace"] });
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.selectedValues).toEqual(["Repair"]);
|
||||
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ modelValue: ["Replace"] }]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("windshield-damage-type-question.vue", () => {
|
||||
test("Should display question and answers from api.", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper, cmsContent } = setupMocks({modelValueProp: ["Repair"]});
|
||||
|
||||
//Act
|
||||
windshieldDamageTypeQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent);
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.questionText).toStrictEqual("What's your windshield damage?");
|
||||
expect(wrapper.vm.answersFromCms).toStrictEqual([ { Name: 'Repair' }, { Name: 'Replace' } ]);
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks({
|
||||
modelValueProp = ["Two"],
|
||||
groupName = "WindshieldDamageTypeQuestion",
|
||||
cmsQuestionText = "What's your windshield damage?",
|
||||
cmsAnswers = [{Name: "Repair"}, {Name: "Replace"}],
|
||||
dataFromStoreApi = [],
|
||||
}) {
|
||||
|
||||
//Mock store
|
||||
store.dispatch = jest.fn(() => dataFromStoreApi);
|
||||
store.getters = { vehicle: {year: 2019, make: 'honda', model: 'civc', style: '2 Door', category: 'CAR'} };
|
||||
const mountOptions = getMountOptions({
|
||||
store: {
|
||||
dispatch: store.dispatch,
|
||||
getters: store.getters,
|
||||
},
|
||||
});
|
||||
|
||||
//Mock props
|
||||
mountOptions.propsData = {
|
||||
modelValue: modelValueProp
|
||||
};
|
||||
|
||||
const wrapper = shallowMount(windshieldDamageTypeQuestion, mountOptions);
|
||||
|
||||
//Mock CMS content
|
||||
const cmsContent = {
|
||||
groupName: groupName,
|
||||
QuestionText: cmsQuestionText,
|
||||
Answers: cmsAnswers,
|
||||
};
|
||||
const damageOptions = dataFromStoreApi;
|
||||
return { wrapper, cmsContent, damageOptions };
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
<template>
|
||||
<transition name="fade" mode="out-in">
|
||||
<div class="windshield-damage-type-question" v-if="isAvailable" aria-live="polite">
|
||||
<buttonQuestion
|
||||
:questionText="questionText"
|
||||
:answers="answersFromCms"
|
||||
:groupName="groupName"
|
||||
buttonType="listCard"
|
||||
v-model="selectedValues"
|
||||
/>
|
||||
</div>
|
||||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import buttonQuestion from "@/common-components/button-question/button-question";
|
||||
//import { defineRule } from "vee-validate";
|
||||
|
||||
//DEFINE VALIDATION RULES
|
||||
// add these to the button component above:
|
||||
// validationRules="windshield-damage-required|checkForRepairAndReplace:@DamageLocationQuestion"
|
||||
// :suppressError="suppressError"
|
||||
// defineRule("windshield-damage-required", (value) => {
|
||||
// if (!value || value.length < 1) {
|
||||
// return "Please select windshield damage";
|
||||
// }
|
||||
// return true;
|
||||
// });
|
||||
// defineRule("checkForRepairAndReplace", (value, [other]) => {
|
||||
// if (value === "Windshield-Chip" && other.toString().includes("Windshield") && other.length > 1) {
|
||||
// return "checkForRepairAndReplace error"
|
||||
// }
|
||||
// return true;
|
||||
// });
|
||||
|
||||
export default ({
|
||||
name: "windshieldDamageTypeQuestion",
|
||||
data(){
|
||||
return {
|
||||
questionText: String,
|
||||
answersFromCms: Array
|
||||
}
|
||||
},
|
||||
props: {
|
||||
modelValue: Array,
|
||||
groupName: String,
|
||||
isAvailable: Boolean,
|
||||
suppressError: Boolean,
|
||||
},
|
||||
methods: {
|
||||
initializeComponent(cmsContent){
|
||||
this.questionText = cmsContent.QuestionText;
|
||||
this.answersFromCms = cmsContent.Answers;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
selectedValues: {
|
||||
get: function() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
}
|
||||
},
|
||||
},
|
||||
components: {
|
||||
buttonQuestion,
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
|
@ -1,26 +1,110 @@
|
|||
<template>
|
||||
<div class="windshield-options">
|
||||
<windshieldDamageTypeQuestion
|
||||
ref="windshieldDamageType"
|
||||
v-model="selectedWindshieldDamageTypes"
|
||||
groupName="windshieldDamageTypeQuestion"
|
||||
:isAvailable="showWindshieldDamageTypeQuestion"
|
||||
<windshieldDamageTypeQuestion ref="windshieldDamageTypeQuestion"
|
||||
:isAvailable=isWindshieldDamageLocation
|
||||
:suppressError="suppressError"
|
||||
groupName="WindshieldDamageTypeQuestion"
|
||||
v-model="selectedWindshieldDamageTypeValues"
|
||||
/>
|
||||
<windshieldChipCountQuestion ref="windshieldChipCountQuestion"
|
||||
:isAvailable=isRepairOptionSelected
|
||||
groupName="WindshieldChipCountQuestion"
|
||||
v-model="selectedWindshieldChipCountValues"
|
||||
/>
|
||||
<replaceOptionsQuestion ref="replaceOptionsQuestion"
|
||||
:isAvailable=isReplaceOptionSelected
|
||||
isMultiSelect
|
||||
groupName="WindshieldReplaceOptions"
|
||||
v-model="selectedWindshieldReplaceOptionsValues"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import windshieldDamageTypeQuestion from "@/layouts/vehicle-damage/windshield-damage-type-question/windshield-damage-type-question";
|
||||
|
||||
import windshieldDamageTypeQuestion from"@/layouts/vehicle-damage/windshield-options/windshield-damage-type-question/windshield-damage-type-question";
|
||||
import windshieldChipCountQuestion from"@/layouts/vehicle-damage/windshield-options/windshield-chip-count-question/windshield-chip-count-question";
|
||||
import replaceOptionsQuestion from"@/layouts/vehicle-damage/replace-options-question/replace-options-question";
|
||||
export default ({
|
||||
name: "windshieldOptions",
|
||||
|
||||
props: {
|
||||
showWindshieldDamageTypeQuestion: Boolean,
|
||||
suppressError: Boolean,
|
||||
modelValue: Array,
|
||||
selectedDamageLocations: Array,
|
||||
selectedChipCount: Array,
|
||||
selectedReplaceOptions: Array,
|
||||
suppressError: Boolean
|
||||
},
|
||||
|
||||
methods: {
|
||||
initializeComponent(windshieldDamageTypeQuestionFromCms, windshieldChipCountQuestionFromCms,
|
||||
windshieldReplaceOptionsQuestionFromCms, windshieldAvailableReplacementOptions){
|
||||
|
||||
this.$refs.windshieldDamageTypeQuestion.initializeComponent(windshieldDamageTypeQuestionFromCms);
|
||||
this.$refs.windshieldChipCountQuestion.initializeComponent(windshieldChipCountQuestionFromCms);
|
||||
this.$refs.replaceOptionsQuestion.initializeComponent(windshieldReplaceOptionsQuestionFromCms, windshieldAvailableReplacementOptions);
|
||||
},
|
||||
getWindshieldOptions(selectedWindshieldDamageType, selectedWindshieldChipCount, selectedWindshieldReplaceOptions){
|
||||
return {
|
||||
selectedWindshieldDamageType: selectedWindshieldDamageType,
|
||||
selectedWindshieldChipCount: selectedWindshieldChipCount,
|
||||
selectedWindshieldReplaceOptions: selectedWindshieldReplaceOptions
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
selectedValues: {
|
||||
get: function() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.$emit("update:modelValue", newValue);
|
||||
}
|
||||
},
|
||||
selectedWindshieldDamageTypeValues:{
|
||||
get: function() {
|
||||
return this.selectedValues.selectedWindshieldDamageType;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.selectedValues = this.getWindshieldOptions(newValue, null, null);
|
||||
}
|
||||
},
|
||||
selectedWindshieldChipCountValues: {
|
||||
get: function() {
|
||||
return this.selectedValues.selectedChipCount;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.selectedValues = this.getWindshieldOptions(this.selectedWindshieldDamageTypeValues, newValue, null);
|
||||
}
|
||||
},
|
||||
selectedWindshieldReplaceOptionsValues: {
|
||||
get: function() {
|
||||
return this.selectedValues.selectedWindshieldReplaceOptions;
|
||||
},
|
||||
set: function(newValue) {
|
||||
this.selectedValues = this.getWindshieldOptions(this.selectedWindshieldDamageTypeValues, null, newValue);
|
||||
}
|
||||
},
|
||||
isWindshieldDamageLocation() {
|
||||
return this.selectedDamageLocations.some(selectedDamages =>
|
||||
{
|
||||
return Boolean(selectedDamages.toUpperCase() === "WINDSHIELD");
|
||||
});
|
||||
},
|
||||
isRepairOptionSelected(){
|
||||
if (!this.selectedWindshieldDamageTypeValues) return false;
|
||||
|
||||
return this.selectedWindshieldDamageTypeValues.some(val => val.toUpperCase() === "REPAIR") && this.isWindshieldDamageLocation;
|
||||
},
|
||||
isReplaceOptionSelected(){
|
||||
if (!this.selectedWindshieldDamageTypeValues) return false;
|
||||
|
||||
return this.selectedWindshieldDamageTypeValues.some(val => val.toUpperCase() === "REPLACE") && this.isWindshieldDamageLocation;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
windshieldDamageTypeQuestion,
|
||||
windshieldChipCountQuestion,
|
||||
replaceOptionsQuestion
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
@ -11,6 +11,7 @@ import store from "@/store";
|
|||
import ComponentTest from "@/layouts/component-test/component-test.vue";
|
||||
import AddressPOC from "@/layouts/address-poc/address-poc.vue";
|
||||
import FormTest from "@/layouts/form-test/form-test.vue";
|
||||
import NestedRadio from "@/layouts/nested-radio-poc/nested-radio.vue";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
|
|
@ -33,6 +34,11 @@ const routes = [
|
|||
name: "FormTest",
|
||||
component: FormTest,
|
||||
},
|
||||
{
|
||||
path: "/nested-radio", // This is a temporary route for testing.
|
||||
name: "NestedRadio",
|
||||
component: NestedRadio,
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
name: "root",
|
||||
|
|
|
|||
12
src/styles/common-animations.scss
Normal file
12
src/styles/common-animations.scss
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
.fade-enter-active{
|
||||
transition: opacity 0.6s ease;
|
||||
}
|
||||
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
.has-error {
|
||||
&.list-button,
|
||||
&.list-card {
|
||||
border: 1px solid transparent;
|
||||
border: 1px solid $gray-500;
|
||||
color: $red;
|
||||
label {
|
||||
border: 1px solid $red;
|
||||
box-shadow: 0 0 1px $red;
|
||||
border-radius: .5rem;
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,31 @@
|
|||
border: 1px solid $red;
|
||||
}
|
||||
}
|
||||
&.ui-radio,
|
||||
&.ui-checkbox {
|
||||
input[type=checkbox],
|
||||
input[type=radio],
|
||||
input[type=radio]+label:before,
|
||||
input[type=checkbox]+label:before {
|
||||
border: 1px solid $red;
|
||||
}
|
||||
input[type=checkbox]:checked + label:before {
|
||||
border: 1px solid $blue;
|
||||
}
|
||||
}
|
||||
&.textbox-question,
|
||||
&.dropdown-question {
|
||||
p {
|
||||
color: $red;
|
||||
}
|
||||
input,
|
||||
select {
|
||||
border: 1px solid $red;
|
||||
&:focus {
|
||||
border: 1px solid transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-test-error {
|
||||
|
|
|
|||
|
|
@ -76,26 +76,4 @@ describe("buttonMain.vue", () => {
|
|||
|
||||
expect(loader.attributes("class")).toContain("right");
|
||||
});
|
||||
|
||||
it("Should return loader size in rem", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(buttonMain, {
|
||||
propsData: {
|
||||
sizeInRem: 1,
|
||||
loaderEnabled: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
||||
const label = wrapper.find("label");
|
||||
|
||||
wrapper.vm.clicked();
|
||||
|
||||
await nextTick();
|
||||
|
||||
const loader = wrapper.find("loader-stub");
|
||||
|
||||
expect(loader.attributes("style")).toContain("1rem");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
<loader
|
||||
class="ms-2"
|
||||
v-if="isLoaderDisplayed"
|
||||
v-bind:style="{ width: `${sizeInRem}rem`, height: `${sizeInRem}rem` }"
|
||||
v-bind:class="[this.loaderColor, this.loaderPosition]"
|
||||
/>
|
||||
</button>
|
||||
|
|
@ -25,7 +24,6 @@ export default {
|
|||
isDisabled: Boolean,
|
||||
loaderColor: String,
|
||||
loaderPosition: String,
|
||||
sizeInRem: [Number, String],
|
||||
isFloat: Boolean,
|
||||
},
|
||||
data() {
|
||||
|
|
@ -56,6 +54,7 @@ export default {
|
|||
border-radius: $border-radius-lg;
|
||||
color: $white;
|
||||
justify-content: center;
|
||||
font-weight: 500;
|
||||
&:hover {
|
||||
background: linear-gradient(
|
||||
270deg,
|
||||
|
|
@ -82,7 +81,8 @@ export default {
|
|||
$gray-200 0%,
|
||||
$gray-200 100%
|
||||
) !important;
|
||||
color: $gray-550 !important;
|
||||
color: $gray-600 !important;
|
||||
font-weight: 400;
|
||||
height: 48px;
|
||||
border: none;
|
||||
border-radius: $border-radius-lg;
|
||||
|
|
@ -103,6 +103,7 @@ export default {
|
|||
border: 1px solid $blue;
|
||||
border-radius: $border-radius-lg;
|
||||
color: $blue;
|
||||
font-weight: 500;
|
||||
transition: all 150ms linear;
|
||||
&:hover {
|
||||
color: $white;
|
||||
|
|
@ -118,10 +119,13 @@ export default {
|
|||
}
|
||||
&:disabled {
|
||||
background: transparent;
|
||||
color: $gray !important;
|
||||
color: $gray-550 !important;
|
||||
font-weight: 400;
|
||||
height: 48px;
|
||||
border: 1px solid $gray-300;
|
||||
border: 1px solid $gray-550;
|
||||
border-radius: $border-radius-lg;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
&.has-loader {
|
||||
color: $white;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
<template>
|
||||
<!-- Checkbox groups MUST be wrapped in a <fieldset> and <legend> tag -->
|
||||
<div class="ui-checkbox d-flex">
|
||||
<input
|
||||
type="checkbox"
|
||||
aria-checked="false"
|
||||
:name="checkboxName"
|
||||
:id="buttonID"
|
||||
:tabindex="tabIndex"
|
||||
:aria-required="isRequired"
|
||||
<div class="form-check ui-checkbox" :class="[hasError ? 'has-error' : '']">
|
||||
<input class="form-check-input"
|
||||
type="checkbox"
|
||||
aria-checked="false"
|
||||
:name="checkboxName"
|
||||
:id="buttonID"
|
||||
:tabindex="tabIndex"
|
||||
:aria-required="isRequired"
|
||||
/>
|
||||
<label class="d-flex align-items-center" :for="buttonID">
|
||||
<label class="d-flex align-items-start" :for="buttonID">
|
||||
<p v-if="checkboxLabel" class="m-0">{{ checkboxLabel }}</p>
|
||||
<span v-if="screenReaderOnlyText" class="sr-only">{{
|
||||
screenReaderOnlyText
|
||||
|
|
@ -28,65 +28,27 @@ export default {
|
|||
checkboxLabel: String,
|
||||
screenReaderOnlyText: String,
|
||||
isRequired: Boolean,
|
||||
hasError: Boolean
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ui-checkbox {
|
||||
position: relative;
|
||||
input[type="checkbox"] {
|
||||
position: absolute !important;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
+ label {
|
||||
display: block;
|
||||
position: relative;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
<style lang="scss" scoped>
|
||||
.form-check {
|
||||
.form-check-input {
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: 2px;
|
||||
&:checked {
|
||||
background-size: 125%;
|
||||
border: 1px solid $blue;
|
||||
}
|
||||
+ label::before {
|
||||
content: "";
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: white;
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: 2px;
|
||||
}
|
||||
&:checked + label::before {
|
||||
background: $blue;
|
||||
}
|
||||
&:checked + label::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 2px;
|
||||
border-left: 2px solid $white;
|
||||
border-bottom: 2px solid $white;
|
||||
height: 6px;
|
||||
width: 12px;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
&:hover + label::before {
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
&:focus + label::before {
|
||||
box-shadow: 0 0 0 2px $blue;
|
||||
}
|
||||
&:focus:checked + label::before {
|
||||
box-shadow: 0 0 0 2px transparent;
|
||||
}
|
||||
&:disabled + label {
|
||||
color: $gray-200;
|
||||
}
|
||||
&:disabled + label::before {
|
||||
background: $gray-200;
|
||||
}
|
||||
&:hover {
|
||||
.form-check-input {
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -155,36 +155,13 @@ describe("list-button-horizontal.vue", () => {
|
|||
expect(loader.attributes("class")).toContain("right");
|
||||
});
|
||||
|
||||
it("Should return loader size in rem", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButtonHorizontal, {
|
||||
propsData: {
|
||||
sizeInRem: 1,
|
||||
selectingInitiatesLoad: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
||||
const label = wrapper.find("label");
|
||||
|
||||
wrapper.vm.handleCheckChange = jest.fn();
|
||||
wrapper.vm.handleClick();
|
||||
|
||||
await nextTick();
|
||||
|
||||
const loader = wrapper.find("loader-stub");
|
||||
|
||||
expect(loader.attributes("style")).toContain("1rem");
|
||||
});
|
||||
|
||||
it("Should emit button value on click", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButtonHorizontal, {
|
||||
propsData: {
|
||||
isRadioHorizontal: true,
|
||||
buttonLabel: "Windshield",
|
||||
buttonID: "List Card Checkbox",
|
||||
value: "List Card Checkbox",
|
||||
groupID: "radio-demo-1",
|
||||
groupName: "radio 1",
|
||||
buttonImage: "windshield-damage.svg",
|
||||
|
|
@ -195,7 +172,7 @@ describe("list-button-horizontal.vue", () => {
|
|||
});
|
||||
wrapper.vm.handleCheckChange();
|
||||
// Assert
|
||||
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{buttonId: "List Card Checkbox", isChecked: Boolean}]);
|
||||
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: Boolean}]);
|
||||
});
|
||||
|
||||
it("Should set checkValue data if selectedButtonIDs has value(s)", async () => {
|
||||
|
|
@ -212,7 +189,7 @@ describe("list-button-horizontal.vue", () => {
|
|||
isWide: false,
|
||||
modelValue: ["List Card Checkbox"],
|
||||
isMultiSelect: false,
|
||||
selectedButtonIDs: ["Car-Front"]
|
||||
selectedValues: ["Car-Front"]
|
||||
},
|
||||
});
|
||||
// Assert
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
class="list-group list-button-horizontal d-flex flex-column w-100 mb-2"
|
||||
:class="hasError ? 'has-error' : ''"
|
||||
@mouseup="handleClick(value)"
|
||||
@keyup.space="handleClick(value)"
|
||||
>
|
||||
|
|
@ -8,11 +9,11 @@
|
|||
:type="isMultiSelect ? 'checkbox' : 'radio'"
|
||||
:id="buttonID"
|
||||
:name="groupName"
|
||||
:value="buttonID"
|
||||
:value="value"
|
||||
:aria-required="isRequired"
|
||||
:data-focus-target="groupName"
|
||||
v-model="checkValue"
|
||||
@change="!selectingInitiatesLoad ? handleCheckChange : ''"
|
||||
@change="!selectingInitiatesLoad ? handleCheckChange() : ''"
|
||||
/>
|
||||
<label
|
||||
tabindex="-1"
|
||||
|
|
@ -38,7 +39,6 @@
|
|||
</span>
|
||||
<loader
|
||||
v-if="isLoaderDisplayed && !isMultiSelect"
|
||||
:style="{width: `${sizeInRem}rem`, height: `${sizeInRem}rem`}"
|
||||
:class="[loaderColor, loaderPosition]"
|
||||
/>
|
||||
</label>
|
||||
|
|
@ -63,14 +63,14 @@ export default {
|
|||
selectingInitiatesLoad: Boolean,
|
||||
loaderColor: String,
|
||||
loaderPosition: String,
|
||||
sizeInRem: [Number, String],
|
||||
isRequired: Boolean,
|
||||
value: {
|
||||
// Field initial value
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
selectedButtonIDs: [Array, String],
|
||||
selectedValues: [Array, String],
|
||||
hasError: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -79,8 +79,8 @@ export default {
|
|||
};
|
||||
},
|
||||
created(){
|
||||
if(this.selectedButtonIDs){
|
||||
this.checkValue = this.isMultiSelect ? this.selectedButtonIDs.includes(this.buttonID) : this.selectedButtonIDs[0];
|
||||
if(Array.isArray(this.selectedValues)){
|
||||
this.checkValue = this.isMultiSelect ? this.selectedValues.includes(this.value) : this.selectedValues[0];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -97,7 +97,7 @@ export default {
|
|||
handleCheckChange(newValue, oldValue){
|
||||
const isInitialization = typeof(oldValue) === 'function';
|
||||
if (!isInitialization) {
|
||||
this.$emit('isCheckedChanged', { isChecked: this.checkValue, buttonId: this.buttonID.toString() });
|
||||
this.$emit('isCheckedChanged', { checkValue: this.checkValue, value: this.value.toString() });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -133,13 +133,16 @@ export default {
|
|||
height: 0;
|
||||
&:focus-visible + label {
|
||||
box-shadow: 0 0 0 2px $blue;
|
||||
z-index: 2;
|
||||
}
|
||||
&:focus + label {
|
||||
box-shadow: 0 0 0 2px $blue;
|
||||
z-index: 3;
|
||||
}
|
||||
&:checked + label {
|
||||
background: $blue-100;
|
||||
box-shadow: 0 0 0 1px $blue;
|
||||
z-index: 2;
|
||||
}
|
||||
&:checked + label p:first-child {
|
||||
font-weight: 500;
|
||||
|
|
@ -156,11 +159,14 @@ export default {
|
|||
&:hover {
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
cursor: pointer;
|
||||
z-index: 2;
|
||||
z-index: 4 !important;
|
||||
}
|
||||
+ p {
|
||||
display: none;
|
||||
}
|
||||
span {
|
||||
font-size: .875rem;
|
||||
}
|
||||
}
|
||||
&:first-of-type {
|
||||
label {
|
||||
|
|
|
|||
|
|
@ -153,36 +153,13 @@ describe("list-button.vue", () => {
|
|||
expect(loader.attributes("class")).toContain("right");
|
||||
});
|
||||
|
||||
it("Should return loader size in rem", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButton, {
|
||||
propsData: {
|
||||
sizeInRem: 1,
|
||||
selectingInitiatesLoad: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
||||
const label = wrapper.find("label");
|
||||
|
||||
wrapper.vm.handleCheckChange = jest.fn();
|
||||
wrapper.vm.handleClick();
|
||||
|
||||
await nextTick();
|
||||
|
||||
const loader = wrapper.find("loader-stub");
|
||||
|
||||
expect(loader.attributes("style")).toContain("1rem");
|
||||
});
|
||||
|
||||
it("Should emit button value on click", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButton, {
|
||||
propsData: {
|
||||
isRadioHorizontal: true,
|
||||
buttonLabel: "Windshield",
|
||||
buttonID: "List Card Checkbox",
|
||||
value: "List Card Checkbox",
|
||||
groupID: "radio-demo-1",
|
||||
groupName: "radio 1",
|
||||
buttonImage: "windshield-damage.svg",
|
||||
|
|
@ -193,7 +170,7 @@ describe("list-button.vue", () => {
|
|||
});
|
||||
wrapper.vm.handleCheckChange();
|
||||
// Assert
|
||||
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{buttonId: "List Card Checkbox", isChecked: Boolean}]);
|
||||
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: Boolean}]);
|
||||
});
|
||||
|
||||
it("Should set checkValue data if selectedButtonIDs has value(s)", async () => {
|
||||
|
|
@ -209,7 +186,7 @@ describe("list-button.vue", () => {
|
|||
isRequired: true,
|
||||
isWide: false,
|
||||
modelValue: ["List Card Checkbox"],
|
||||
selectedButtonIDs: ["Car-Front"]
|
||||
selectedValues: ["Car-Front"]
|
||||
},
|
||||
});
|
||||
// Assert
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<template>
|
||||
<div
|
||||
class="list-group list-button d-flex flex-column w-100 mb-2"
|
||||
:class="hasError ? 'has-error' : ''"
|
||||
@mouseup="handleClick(value)"
|
||||
@keyup.space="handleClick(value)"
|
||||
>
|
||||
|
|
@ -8,11 +9,11 @@
|
|||
:type="isMultiSelect ? 'checkbox' : 'radio'"
|
||||
:id="buttonID"
|
||||
:name="groupName"
|
||||
:value="buttonID"
|
||||
:value="value"
|
||||
:aria-required="isRequired"
|
||||
:data-focus-target="groupName"
|
||||
v-model="checkValue"
|
||||
@change="!selectingInitiatesLoad ? handleCheckChange : ''"
|
||||
@change="!selectingInitiatesLoad ? handleCheckChange() : ''"
|
||||
>
|
||||
<label
|
||||
tabindex="-1"
|
||||
|
|
@ -41,7 +42,6 @@
|
|||
</span>
|
||||
<loader
|
||||
v-if="isLoaderDisplayed && !isMultiSelect"
|
||||
:style="{ width: `${sizeInRem}rem`, height: `${sizeInRem}rem` }"
|
||||
:class="[this.loaderColor, this.loaderPosition]"
|
||||
/>
|
||||
</label>
|
||||
|
|
@ -66,13 +66,13 @@ export default {
|
|||
selectingInitiatesLoad: Boolean,
|
||||
loaderColor: String,
|
||||
loaderPosition: String,
|
||||
sizeInRem: [Number,String],
|
||||
value: {
|
||||
// Field initial value
|
||||
type: [String, Number],
|
||||
default: "",
|
||||
},
|
||||
selectedButtonIDs: [Array, String],
|
||||
selectedValues: [Array, String],
|
||||
hasError: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -81,8 +81,8 @@ export default {
|
|||
};
|
||||
},
|
||||
created(){
|
||||
if(this.selectedButtonIDs){
|
||||
this.checkValue = this.isMultiSelect ? this.selectedButtonIDs.includes(this.buttonID) : this.selectedButtonIDs[0];
|
||||
if(Array.isArray(this.selectedValues)){
|
||||
this.checkValue = this.isMultiSelect ? this.selectedValues.includes(this.value) : this.selectedValues[0];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -99,7 +99,7 @@ export default {
|
|||
handleCheckChange(newValue, oldValue){
|
||||
const isInitialization = typeof(oldValue) === 'function';
|
||||
if (!isInitialization) {
|
||||
this.$emit('isCheckedChanged', { isChecked: this.checkValue, buttonId: this.buttonID.toString() });
|
||||
this.$emit('isCheckedChanged', { checkValue: this.checkValue, value: this.value.toString() });
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -126,7 +126,7 @@ export default {
|
|||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
<style lang="scss" scoped>
|
||||
.list-group {
|
||||
&.list-button {
|
||||
input[type="radio"],
|
||||
|
|
@ -147,9 +147,14 @@ export default {
|
|||
background: $blue-100;
|
||||
box-shadow: 0 0 0 1px $blue;
|
||||
}
|
||||
&:checked + label p:first-child {
|
||||
&:checked + label p,
|
||||
&:checked + label span {
|
||||
font-weight: 500;
|
||||
}
|
||||
&:checked + label span:nth-child(2) {
|
||||
font-weight: 400;
|
||||
color: $gray-600;
|
||||
}
|
||||
}
|
||||
}
|
||||
label {
|
||||
|
|
@ -161,6 +166,13 @@ export default {
|
|||
border: 1px solid $gray-500;
|
||||
width: 100%;
|
||||
|
||||
span {
|
||||
&.small {
|
||||
font-size: .75rem;
|
||||
color: $gray-550;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@include media-breakpoint-up(sm) {
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
|
|
|
|||
|
|
@ -197,7 +197,7 @@ describe("list-card.vue", () => {
|
|||
propsData: {
|
||||
isRadioHorizontal: true,
|
||||
buttonLabel: "Windshield",
|
||||
buttonID: "List Card Checkbox",
|
||||
value: "List Card Checkbox",
|
||||
groupID: "radio-demo-1",
|
||||
groupName: "radio 1",
|
||||
buttonImage: "windshield-damage.svg",
|
||||
|
|
@ -208,7 +208,7 @@ describe("list-card.vue", () => {
|
|||
});
|
||||
wrapper.vm.handleCheckChange();
|
||||
// Assert
|
||||
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{buttonId: "List Card Checkbox", isChecked: Boolean}]);
|
||||
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: Boolean}]);
|
||||
});
|
||||
|
||||
it("Should set checkValue data if selectedButtonIDs has value(s)", async () => {
|
||||
|
|
@ -217,14 +217,14 @@ describe("list-card.vue", () => {
|
|||
propsData: {
|
||||
isRadioHorizontal: true,
|
||||
buttonLabel: "Windshield",
|
||||
buttonID: "List Card Checkbox",
|
||||
value: "List Card Checkbox",
|
||||
groupID: "radio-demo-1",
|
||||
groupName: "radio 1",
|
||||
buttonImage: "windshield-damage.svg",
|
||||
isRequired: true,
|
||||
isWide: false,
|
||||
modelValue: ["List Card Checkbox"],
|
||||
selectedButtonIDs: ["Car-Front"]
|
||||
selectedValues: ["Car-Front"]
|
||||
},
|
||||
});
|
||||
// Assert
|
||||
|
|
|
|||
|
|
@ -2,18 +2,18 @@
|
|||
<div :class="'col' + colLength">
|
||||
<div
|
||||
class="list-card w-100 rounded-3 d-flex align-items-center h-100"
|
||||
:class="[isWide ? 'horizontal' : '', errors.length > 0 ? 'has-error' : '']"
|
||||
:class="[isWide ? 'horizontal' : '', errors.length > 0 ? 'has-error' : '', hasError ? 'has-error' : '']"
|
||||
>
|
||||
<input
|
||||
:type="isMultiSelect ? 'checkbox' : 'radio'"
|
||||
:id="buttonID"
|
||||
:name="groupName"
|
||||
:value="buttonID"
|
||||
:value="value"
|
||||
:aria-required="isRequired"
|
||||
:data-focus-target="groupName"
|
||||
@click="handleChange(value)"
|
||||
v-model="checkValue"
|
||||
@change="handleCheckChange"
|
||||
@change="handleCheckChange()"
|
||||
/>
|
||||
<label
|
||||
:for="buttonID"
|
||||
|
|
@ -53,10 +53,8 @@ import { useField } from "vee-validate";
|
|||
export default {
|
||||
name: "listCard",
|
||||
props: {
|
||||
//Must choose one of the following four options
|
||||
isMultiSelect: Boolean, //Defines use as checkbox
|
||||
isWide: Boolean,
|
||||
//end must choose
|
||||
buttonImage: String, //Required: File name of image
|
||||
buttonImageId: String,
|
||||
buttonLabel: String, //Required: Label text
|
||||
|
|
@ -72,7 +70,8 @@ export default {
|
|||
},
|
||||
colLength: String,
|
||||
validationRules: String,
|
||||
selectedButtonIDs: [Array, String],
|
||||
selectedValues: [Array, String],
|
||||
hasError: Boolean,
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
|
|
@ -80,8 +79,8 @@ export default {
|
|||
}
|
||||
},
|
||||
created(){
|
||||
if(this.selectedButtonIDs){
|
||||
this.checkValue = this.isMultiSelect ? this.selectedButtonIDs.includes(this.buttonID) : this.selectedButtonIDs[0];
|
||||
if(Array.isArray(this.selectedValues)){
|
||||
this.checkValue = this.isMultiSelect ? this.selectedValues.includes(this.value) : this.selectedValues[0];
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -101,7 +100,7 @@ export default {
|
|||
handleCheckChange(newValue, oldValue){
|
||||
const isInitialization = typeof(oldValue) === 'function';
|
||||
if (!isInitialization) {
|
||||
this.$emit('isCheckedChanged', { isChecked: this.checkValue, buttonId: this.buttonID.toString() });
|
||||
this.$emit('isCheckedChanged', { checkValue: this.checkValue, value: this.value.toString() });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -174,9 +173,11 @@ export default {
|
|||
&:checked + label {
|
||||
p {
|
||||
color: $black;
|
||||
font-weight: 500;
|
||||
}
|
||||
.sub-copy {
|
||||
color: $gray-600;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
+ label::before {
|
||||
|
|
@ -247,6 +248,16 @@ export default {
|
|||
margin: -0.25rem 0 0 0;
|
||||
left: 0.875rem;
|
||||
}
|
||||
&:checked + label {
|
||||
p {
|
||||
color: $black;
|
||||
font-weight: 500;
|
||||
}
|
||||
.sub-copy {
|
||||
color: $gray-600;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
+ label {
|
||||
min-height: 48px;
|
||||
color: $gray-600;
|
||||
|
|
@ -254,6 +265,7 @@ export default {
|
|||
margin-bottom: 0;
|
||||
}
|
||||
p {
|
||||
color: $gray-600;
|
||||
text-align: left;
|
||||
&.sub-copy {
|
||||
color: $gray-550;
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@
|
|||
class="loader"
|
||||
role="alert"
|
||||
aria-label="Loading new page"
|
||||
v-bind:style="{ width: `${sizeInRem}rem`, height: `${sizeInRem}rem` }"
|
||||
v-bind:class="[this.loaderColor, this.loaderPosition]"
|
||||
></div>
|
||||
</template>
|
||||
|
|
@ -13,10 +12,6 @@ export default {
|
|||
name: "loader",
|
||||
/* Specify size in number value which translates to rem value. For example, 1.5 = 1.5rem = 24px */
|
||||
props: {
|
||||
sizeInRem: {
|
||||
type: Number,
|
||||
default: 1,
|
||||
},
|
||||
/* Color options: red, green, blue, white, black */
|
||||
loaderColor: {
|
||||
type: String,
|
||||
|
|
@ -50,8 +45,8 @@ export default {
|
|||
mask: url(../../assets/img/icons/spinner.svg);
|
||||
mask-size: cover;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
animation: rotation 1s infinite linear;
|
||||
@keyframes rotation {
|
||||
100% {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
<template>
|
||||
<!-- Checkbox groups MUST be wrapped in a <fieldset> and <legend> tag and must contain tabindex -->
|
||||
<div class="ui-radio d-flex">
|
||||
<div class="ui-radio form-check" :class="[hasError ? 'has-error' : '']">
|
||||
<input
|
||||
type="radio"
|
||||
class="form-check-input"
|
||||
aria-checked="false"
|
||||
:name="groupName"
|
||||
:id="buttonID"
|
||||
:aria-required="isRequired"
|
||||
:value="value"
|
||||
:v-model="checkValue"
|
||||
@change="handleCheckChanged"
|
||||
@change="handleCheckChange()"
|
||||
/>
|
||||
<label class="d-flex align-items-center" :for="buttonID">
|
||||
<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
|
||||
|
|
@ -35,6 +36,7 @@ export default {
|
|||
default: "",
|
||||
},
|
||||
screenReaderOnlyText: String,
|
||||
hasError: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -53,7 +55,7 @@ export default {
|
|||
handleCheckChange(newValue, oldValue){
|
||||
const isInitialization = typeof(oldValue) === 'function';
|
||||
if (!isInitialization) {
|
||||
this.$emit('isCheckedChanged', { isChecked: this.checkValue, buttonId: this.buttonID.toString() });
|
||||
this.$emit('isCheckedChanged', { checkValue: this.checkValue, value: this.value.toString() });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -76,62 +78,25 @@ export default {
|
|||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.ui-radio {
|
||||
position: relative;
|
||||
input[type="radio"] {
|
||||
position: absolute !important;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
+ label {
|
||||
display: block;
|
||||
position: relative;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
+ label::before {
|
||||
content: "";
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-right: 8px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: white;
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: 50%;
|
||||
}
|
||||
&:checked + label::before {
|
||||
background: $white;
|
||||
<style lang="scss" scoped>
|
||||
.form-check {
|
||||
.form-check-input {
|
||||
border: 1px solid $gray-500;
|
||||
border-radius: 100%;
|
||||
margin-right: .5rem;
|
||||
&:checked {
|
||||
background-color: $white;
|
||||
background-size: 71%;
|
||||
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");
|
||||
}
|
||||
&:checked + label::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 3px;
|
||||
height: 6px;
|
||||
width: 6px;
|
||||
transform: rotate(-45deg);
|
||||
border: 5px solid $blue;
|
||||
border-radius: 50%;
|
||||
}
|
||||
&:hover + label::before {
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
&:focus + label::before {
|
||||
box-shadow: 0 0 0 2px $blue;
|
||||
}
|
||||
&:focus:checked + label::before {
|
||||
box-shadow: 0 0 0 2px transparent;
|
||||
}
|
||||
&:disabled + label {
|
||||
color: $gray-200;
|
||||
}
|
||||
&:disabled + label::before {
|
||||
background: $gray-200;
|
||||
}
|
||||
&:hover {
|
||||
.form-check-input {
|
||||
box-shadow: 0 0 0 4px $blue-300;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue