Creating damage location question and refactoring button question methods

This commit is contained in:
Max 2022-02-02 11:10:59 -05:00
parent 5a3850948a
commit 228d4a15e4
18 changed files with 252 additions and 62 deletions

View file

@ -1,25 +1,6 @@
import { shallowMount } from "@vue/test-utils";
import buttonQuestion from "@/common-components/button-question/button-question";
describe("buttonQuestion.vue", () => {
it("Should render the 'questionText' prop value as a span value for the button question and the 'answer' values should render as text values for button components.", async () => {
// Act
const wrapper = shallowMount(buttonQuestion);
await wrapper.setProps({
questionText: "Question Text",
answers: ["2023", "2022", "2021"],
modelValue: "2020",
});
wrapper.vm.chooseAnswer("2021");
// Assert
expect(wrapper.find(".text-center").text()).toEqual("Question Text");
const buttonButtons = wrapper.findAllComponents('[data-test="button"]');
expect(buttonButtons.length).toBe(3);
expect(wrapper.props().modelValue).toBe("2020");
});
});
describe("buttonQuestion.vue", () => {
it("Should show overflow classes on fieldset if isOverflowScrollable is true", async () => {
// Act
@ -34,7 +15,7 @@ describe("buttonQuestion.vue", () => {
});
describe("buttonQuestion.vue", () => {
it("Should show appropriate classes for button type", async () => {
it("Fieldset classes should contain row if button type is listCard", async () => {
// Act
const wrapper = shallowMount(buttonQuestion);
await wrapper.setProps({
@ -46,6 +27,19 @@ describe("buttonQuestion.vue", () => {
});
});
describe("buttonQuestion.vue", () => {
it("Fieldset classes should contain d-flex if button type is listButtonHorizontal", async () => {
// Act
const wrapper = shallowMount(buttonQuestion);
await wrapper.setProps({
buttonType: "listButtonHorizontal",
});
// Assert
const Div = wrapper.find('fieldset div');
expect(Div.classes()).toContain("d-flex");
});
});
describe("buttonQuestion.vue", () => {
it("Should trigger event modelValue change on select", async () => {
// Act
@ -68,7 +62,7 @@ describe("buttonQuestion.vue", () => {
await wrapper.setData({
modelValueAnswers: ["Windshield", "BackDoor"]
});
wrapper.vm.isItemChecked(true);
wrapper.vm.handleCheckedChanged(true);
// Assert
expect(typeof wrapper.emitted()["update:modelValue"][0]).toEqual('object');
});
@ -78,8 +72,13 @@ describe("buttonQuestion.vue", () => {
it("Should not trigger event modelValue change on select if checked is false", async () => {
// Act
const wrapper = shallowMount(buttonQuestion);
wrapper.vm.isItemChecked(false);
wrapper.setData({
modelValueAnswers: ["Front-door"]
})
const val = {isChecked : false, buttonId: "Front-door"}
wrapper.vm.handleCheckedChanged(val);
// Assert
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([undefined]);
});
});
});

View file

@ -11,9 +11,7 @@
:is="buttonType"
v-for="answer in answers"
:key="answer.Name ? answer.Name : answer"
@mouseup="chooseAnswer(answer.Name ? answer.Name : answer)"
@keyup.space="chooseAnswer(answer.Name ? answer.Name : answer)"
@isChecked="isItemChecked"
@isChecked="handleCheckedChanged"
:buttonID="answer.Name ? answer.Name : answer"
:value="answer.Name ? answer.Name : answer"
:buttonLabel="answer.Text ? answer.Text : answer"
@ -32,6 +30,7 @@
:altText="answer.Name ? answer.Name : answer"
screenReaderOnlyText="(opens new window)"
:colLength="this.answers.length < 3 ? '' : '-4'"
v-model="modelValue"
data-test="button"
/>
</div>
@ -76,7 +75,7 @@ export default {
isRequired: Boolean,
isOverflowScrollable: Boolean,
isWide: Boolean,
modelValue: [Array, String],
modelValue: Array,
},
computed: {
getFieldSetClasses() {
@ -103,23 +102,19 @@ export default {
data(){
return {
modelValueAnswers: [],
chosenAnswer: String
}
},
created(){
if(Array.isArray(this.answers) && this.answers.length === 1) {
const onlyAnswer = typeof(this.answers[0]) === 'object' ? this.answers[0].Name : this.answers[0];
this.$emit("update:modelValue", onlyAnswer);
}
this.modelValueAnswers.push(typeof(this.answers[0]) === 'object' ? this.answers[0].Name : this.answers[0]);
this.$emit("update:modelValue", this.modelValueAnswers);
};
this.modelValueAnswers = this.modelValue;
},
methods: {
chooseAnswer(answer) {
this.chosenAnswer = answer;
},
isItemChecked(val) {
handleCheckedChanged(val) {
// Add or remove item to array of data to emit
val ? this.modelValueAnswers.push(this.chosenAnswer) : this.modelValueAnswers.splice(this.modelValueAnswers.indexOf(this.chosenAnswer), 1);
val.isChecked ? this.modelValueAnswers.push(val.buttonId) : this.modelValueAnswers.splice(this.modelValueAnswers.indexOf(val.buttonId), 1);
this.$emit("update:modelValue", this.modelValueAnswers.length ? this.modelValueAnswers : undefined);
},
},

View file

@ -0,0 +1,83 @@
import { shallowMount } from "@vue/test-utils";
import damageLocationQuestion from "@/layouts/vehicle-damage/damage-location-question/damage-location-question";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import store from "@/store";
jest.mock("@/store", () => { return {}; }, {virtual: true});
describe("damage-location-question.vue", () => {
test("Selected location option is emitted upon selection.", async () => {
//Arrange
const { wrapper } = setupMocks({ modelValueProp: ["Windshield"] });
const locationToSelect = ["Backseat"];
//Act
wrapper.setValue({ modelValue: locationToSelect });
await wrapper.vm.$nextTick();
//Assert
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{modelValue: ["Backseat"]}]);
});
});
describe("damage-location-question.vue", () => {
test("Answers to display filtered by data from api.", async () => {
//Arrange
const { wrapper, cmsContent, damageOptions } = setupMocks({ dataFromStoreApi: {
driverSideOptions: {
availableReplacementOptions: ['Quarter', 'Front']
},
windshieldOptions: {
availableReplacementOptions: ['Single']
},
backGlassOptions: {
availableReplacementOptions: []
}
}
});
//Act
damageLocationQuestion.methods.initializeComponent.call(wrapper.vm, cmsContent, damageOptions, "car-group");
//Assert
expect(wrapper.vm.answersToDisplay).toStrictEqual([ { Name: 'car-Windshield' }, { Name: 'car-SideDoor' } ])
});
});
function setupMocks({
modelValueProp = ["Windshield", "SideDoor"],
isMultiSelect = false,
groupName = "damageQuestion",
cmsQuestionText = "CMS text goes here",
cmsAnswers = [{Name: "car-Windshield"}, {Name: "car-SideDoor"}, {Name: "car-RearWindow"}],
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,
isMultiSelect: isMultiSelect,
};
const wrapper = shallowMount(damageLocationQuestion, mountOptions);
//Mock CMS content
const cmsContent = {
groupName: groupName,
QuestionText: cmsQuestionText,
Answers: cmsAnswers,
};
const damageOptions = dataFromStoreApi;
return { wrapper, cmsContent, damageOptions };
}

View file

@ -0,0 +1,68 @@
<template>
<div class="replace-options-question">
<buttonQuestion
v-if="answersToDisplay.length > 1"
:questionText="questionText"
:isMultiSelect="isMultiSelect"
:answers="answersToDisplay"
:groupName="groupName"
buttonType="listCard"
v-model="modelValue"
/>
</div>
</template>
<script>
import buttonQuestion from "@/common-components/button-question/button-question";
import store from "@/store";
export default ({
name: "damageLocationQuestion",
data(){
return {
questionText: String,
answersFromCms: Array,
damageOptions: Object,
groupName: String,
}
},
props: {
isMultiSelect: Boolean,
modelValue: Array,
},
methods: {
initializeComponent(cmsContent, damageOptions, groupName){
this.groupName = groupName;
this.questionText = cmsContent.QuestionText;
this.answersFromCms = cmsContent.Answers;
this.damageOptions = damageOptions;
}
},
computed: {
damageOptionsMap(){
return {
Windshield: true,
SideDoor: this.damageOptions.driverSideOptions.availableReplacementOptions || this.damageOptions.passengerSideOptions.availableReplacementOption ? true : false,
RearWindow: this.damageOptions.backGlassOptions.availableReplacementOption ? true : false,
}
},
answersToDisplay(){
return 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]];
})
: [];
},
},
watch: {
modelValue(val) {
this.$emit("update:modelValue", val);
}
},
components: {
buttonQuestion,
}
})
</script>

View file

@ -3,7 +3,8 @@ import vehicleDamage from "@/layouts/vehicle-damage/vehicle-damage.vue";
import funnelHeader from "@/common-components/funnel-header/funnel-header";
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 replaceOptionsQuestion from "@/layouts/vehicle-damage/replace-options-question/replace-options-question";
import damageLocationQuestion from "@/layouts/vehicle-damage/damage-location-question/damage-location-question";
// Supporting Files
import { settleAllPromises } from "@/helpers/layout-helper.js";
@ -192,6 +193,10 @@ function setupMocks({
initializeComponent: jest.fn(),
};
damageLocationQuestion.methods = {
initializeComponent: jest.fn(),
}
const mountOptions = getMountOptions(mountOptionsMockData);
const wrapper = shallowMount(vehicleDamage, mountOptions);
@ -215,5 +220,11 @@ function setupMocks({
driverSideOptionsWrapper.vm.initializeComponent =
replaceOptionsQuestion.methods.initializeComponent;
const damageLocationQuestionWrapper = wrapper.findComponent({
name: "damageLocationQuestion",
});
damageLocationQuestionWrapper.vm.initializeComponent =
damageLocationQuestion.methods.initializeComponent;
return { wrapper, apiPromise };
}

View file

@ -5,7 +5,8 @@
<funnelSubHeader
ref="funnelSubHeader"
/>
<replaceOptionsQuestion ref="driverSideOptions" isAvailable isMultiSelect />
<damageLocationQuestion ref="damageLocation" isMultiSelect v-model="damageLocationQuestionData" />
<replaceOptionsQuestion ref="driverSideOptions" isAvailable isMultiSelect v-model="driverSideOptionsData" />
</div>
</template>
@ -15,6 +16,7 @@ import funnelHeader from "@/common-components/funnel-header/funnel-header";
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 damageLocationQuestion from"@/layouts/vehicle-damage/damage-location-question/damage-location-question";
// Supporting files
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
@ -61,10 +63,20 @@ export default {
vm.$refs.driverSideOptions.initializeComponent(
resultMap.cmsContent.DriverSideReplaceOptionsQuestion, resultMap.damageOptions.driverSideOptions.availableReplacementOptions, 'DriverSideReplaceOptionsQuestion'
);
vm.$refs.damageLocation.initializeComponent(
resultMap.cmsContent.DamageLocationQuestion, resultMap.damageOptions, 'DamageLocationQuestion'
);
//console.log(resultMap) for damageOptions and cms content data
// for damageOptions and cms content data
//console.log(resultMap)
});
},
data(){
return {
damageLocationQuestionData: [],
driverSideOptionsData: [],
}
},
methods: {
arePagePrerequisitesValid() {
return store.getters.vehicle.carId !== null;
@ -75,6 +87,7 @@ export default {
vehicleBanner,
funnelSubHeader,
replaceOptionsQuestion,
damageLocationQuestion,
},
};
</script>

View file

@ -27,7 +27,7 @@ export default {
};
},
props: {
modelValue: String,
modelValue: Array,
},
components: {
buttonQuestion,

View file

@ -30,7 +30,7 @@ export default {
name: "vehicle-make",
data() {
return {
selectedMake: null,
selectedMake: [],
};
},
computed: {},

View file

@ -27,7 +27,7 @@ export default {
};
},
props: {
modelValue: String,
modelValue: Array,
},
components: {
buttonQuestion,

View file

@ -31,7 +31,7 @@ export default {
name: "vehicle-model",
data() {
return {
selectedModel: null,
selectedModel: [],
};
},
computed: {},

View file

@ -27,7 +27,7 @@ export default {
};
},
props: {
modelValue: String,
modelValue: Array,
},
components: {
buttonQuestion,

View file

@ -31,7 +31,7 @@ export default {
name: "vehicle-style",
data() {
return {
selectedStyle: null,
selectedStyle: [],
};
},
computed: {},

View file

@ -26,7 +26,7 @@ export default {
name: "vehicle-year",
data() {
return {
selectedYear: null,
selectedYear: [],
};
},
computed: {},

View file

@ -26,7 +26,7 @@ export default {
};
},
props: {
modelValue: String,
modelValue: Array,
},
emits: ['update:modelValue'],
components: {

View file

@ -11,7 +11,7 @@
:value="buttonID"
:aria-required="isRequired"
:data-focus-target="groupName"
v-model="modelValue"
v-model="checkValue"
/>
<label
tabindex="-1"
@ -74,6 +74,7 @@ export default {
data() {
return {
isLoaderDisplayed: false,
checkValue: Boolean,
};
},
methods: {
@ -88,8 +89,8 @@ export default {
},
},
watch: {
modelValue(){
this.$emit('isChecked', this.modelValue);
checkValue(){
this.$emit('isChecked', {isChecked: this.checkValue, buttonId: this.buttonID});
}
},
components: {

View file

@ -11,7 +11,7 @@
:value="buttonID"
:aria-required="isRequired"
:data-focus-target="groupName"
v-model="modelValue"
v-model="checkValue"
>
<label
tabindex="-1"
@ -71,13 +71,17 @@ export default {
type: [String, Number],
default: "",
},
modelValue: false
modelValue: [Array, String],
},
data() {
return {
isLoaderDisplayed: false,
checkValue: Boolean,
};
},
created(){
this.checkValue = this.modelValue ? this.modelValue.includes(this.buttonID) : false;
},
methods: {
displayLoader() {
this.isLoaderDisplayed = true;
@ -90,8 +94,8 @@ export default {
},
},
watch: {
modelValue(){
this.$emit('isChecked', this.modelValue);
checkValue(){
this.$emit('isChecked', {isChecked: this.checkValue, buttonId: this.buttonID});
}
},
components: {

View file

@ -12,6 +12,7 @@ describe("list-card.vue", () => {
groupID: "checkbox-demo-1",
groupName: "Checkbox 1",
buttonImage: "windshield-damage.svg",
modelValue: ["List Card Checkbox"],
},
});
@ -31,6 +32,7 @@ describe("list-card.vue", () => {
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
modelValue: ["List Card Checkbox"],
},
});
@ -51,6 +53,7 @@ describe("list-card.vue", () => {
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
buttonLabelSubCopy: "Test",
modelValue: ["List Card Checkbox"],
},
});
@ -71,6 +74,7 @@ describe("list-card.vue", () => {
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
buttonLabelSubCopy: "Test",
modelValue: ["List Card Checkbox"],
},
});
@ -91,6 +95,7 @@ describe("list-card.vue", () => {
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
buttonLabelSubCopy: "Test",
modelValue: ["List Card Checkbox"],
},
});
@ -111,6 +116,7 @@ describe("list-card.vue", () => {
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
isRequired: true,
modelValue: ["List Card Checkbox"],
},
});
@ -132,7 +138,8 @@ describe("list-card.vue", () => {
buttonImage: "windshield-damage.svg",
isRequired: true,
isWide: true,
buttonLabelSubCopy: ""
buttonLabelSubCopy: "",
modelValue: ["List Card Checkbox"],
},
});
@ -153,7 +160,8 @@ describe("list-card.vue", () => {
buttonImage: "windshield-damage.svg",
isRequired: true,
isWide: true,
buttonLabelSubCopy: "Button Subcopy"
buttonLabelSubCopy: "Button Subcopy",
modelValue: ["List Card Checkbox"],
},
});
@ -174,6 +182,7 @@ describe("list-card.vue", () => {
buttonImage: "windshield-damage.svg",
isRequired: true,
isWide: false,
modelValue: ["List Card Checkbox"],
},
});

View file

@ -1,5 +1,4 @@
<template>
<!-- Heavily documented below -->
<div :class="'col' + colLength">
<div class="list-card w-100 rounded-3 d-flex align-items-center h-100"
:class="isWide ? 'horizontal' : ''"
@ -12,7 +11,7 @@
:value="buttonID"
:aria-required="isRequired"
:data-focus-target="groupName"
v-model="modelValue"
v-model="checkValue"
/>
<label
:for="buttonID"
@ -71,7 +70,15 @@ export default {
default: "",
},
colLength: String,
modelValue: false,
modelValue: [Array, String],
},
data(){
return {
checkValue: Boolean,
}
},
created(){
this.checkValue = this.modelValue.includes(this.buttonID);
},
computed: {
getLabelClasses() {
@ -87,8 +94,8 @@ export default {
},
},
watch: {
modelValue(){
this.$emit('isChecked', this.modelValue);
checkValue(){
this.$emit('isChecked', {isChecked: this.checkValue, buttonId: this.buttonID});
}
},
setup(props) {