commit
This commit is contained in:
parent
7ec4cfd3e9
commit
92dfa35521
10 changed files with 443 additions and 0 deletions
190
src/common-components/button-question/button-question.spec.js
Normal file
190
src/common-components/button-question/button-question.spec.js
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
import { shallowMount } from "@vue/test-utils";
|
||||||
|
import buttonQuestion from "@/common-components/button-question/button-question";
|
||||||
|
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||||
|
|
||||||
|
jest.mock("@/store", () => { return {}; }, { virtual: true });
|
||||||
|
|
||||||
|
describe("buttonQuestion.vue", () => {
|
||||||
|
it("Should show overflow classes on fieldset if isOverflowScrollable is true", () => {
|
||||||
|
// Act
|
||||||
|
const wrapper = shallowMount(buttonQuestion, {
|
||||||
|
propsData: {
|
||||||
|
isOverflowScrollable: true,
|
||||||
|
groupName: "group-name"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
const fieldSet = wrapper.find('fieldset');
|
||||||
|
expect(fieldSet.classes()).toContain("overflow-scroll");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("buttonQuestion.vue", () => {
|
||||||
|
it("Fieldset classes should contain row if button type is listCard", () => {
|
||||||
|
// Act
|
||||||
|
const wrapper = shallowMount(buttonQuestion, {
|
||||||
|
propsData: {
|
||||||
|
buttonType: "listCard",
|
||||||
|
groupName: "group-name"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Assert
|
||||||
|
const Div = wrapper.find('fieldset div');
|
||||||
|
expect(Div.classes()).toContain("row");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("buttonQuestion.vue", () => {
|
||||||
|
it("Fieldset classes should contain d-flex if button type is listButtonHorizontal", () => {
|
||||||
|
// Act
|
||||||
|
const wrapper = shallowMount(buttonQuestion, {
|
||||||
|
propsData: {
|
||||||
|
buttonType: "listButtonHorizontal",
|
||||||
|
groupName: "group-name"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Assert
|
||||||
|
const Div = wrapper.find('fieldset div');
|
||||||
|
expect(Div.classes()).toContain("d-flex");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("buttonQuestion.vue", () => {
|
||||||
|
it("Fieldset classes should contain ui-radio if button type is radio", () => {
|
||||||
|
// Act
|
||||||
|
const wrapper = shallowMount(buttonQuestion, {
|
||||||
|
propsData: {
|
||||||
|
buttonType: "radio",
|
||||||
|
groupName: "group-name"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// Assert
|
||||||
|
const Div = wrapper.find('fieldset div');
|
||||||
|
expect(Div.classes()).toContain("ui-radio");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// testing a computed property
|
||||||
|
describe("buttonQuestion.vue", () => {
|
||||||
|
it("getColLength should return '12' if prop isWide is set to true", () => {
|
||||||
|
// Act
|
||||||
|
const localThis = { isWide: true }
|
||||||
|
|
||||||
|
expect(buttonQuestion.computed.getColLength.call(localThis)).toBe("12");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("buttonQuestion.vue", () => {
|
||||||
|
it("getColLength should return '' if prop isWide is set to false", () => {
|
||||||
|
// Act
|
||||||
|
const localThis = {
|
||||||
|
isWide: false,
|
||||||
|
answers: ['a', 'b'],
|
||||||
|
groupName: "group-name"
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(buttonQuestion.computed.getColLength.call(localThis)).toBe("");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("buttonQuestion.vue", () => {
|
||||||
|
it("Should return answer.Text if prop useTextForValue is true", async () => {
|
||||||
|
// Act
|
||||||
|
const localThis = { useTextForValue: true };
|
||||||
|
const answer = { 'Name': 'testName', 'Text': 'testText' };
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(buttonQuestion.methods.getValue.call(localThis, answer)).toBe('testText');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("buttonQuestion.vue", () => {
|
||||||
|
it("Should return answer.Name if prop useTextForValue is false and answer.Name exists", async () => {
|
||||||
|
// Act
|
||||||
|
const localThis = { useTextForValue: false };
|
||||||
|
const answer = { 'Name': 'testName', 'Text': 'testText' };
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(buttonQuestion.methods.getValue.call(localThis, answer)).toBe('testName');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("buttonQuestion.vue", () => {
|
||||||
|
it("Should trigger event modelValue change to new value on when radio button selected", async () => {
|
||||||
|
// Act
|
||||||
|
const wrapper = shallowMount(buttonQuestion, setupMocks({propsData: {groupName: "group-name"}}));
|
||||||
|
await wrapper.setProps({
|
||||||
|
answers: ["2022", "2021", "2020"],
|
||||||
|
isMultiSelect: false,
|
||||||
|
modelValue: []
|
||||||
|
});
|
||||||
|
const val = { checkValue: true, value: "2021", }
|
||||||
|
wrapper.vm.handleCheckedChanged(val);
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([["2021"]]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("buttonQuestion.vue", () => {
|
||||||
|
it("Should add values to array on checkbox click", () => {
|
||||||
|
// Act
|
||||||
|
const wrapper = shallowMount(buttonQuestion, setupMocks({
|
||||||
|
propsData: {
|
||||||
|
modelValue: ["2022", "2021", "2020"],
|
||||||
|
isMultiSelect: true,
|
||||||
|
groupName: "group-name"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
const val = { checkValue: true, value: "2019", }
|
||||||
|
wrapper.vm.handleCheckedChanged(val);
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([["2022", "2021", "2020", "2019"]]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("buttonQuestion.vue", () => {
|
||||||
|
it("Should add a value to this.selectedValues if prop isMultiSelect is true, checkValue is true and this.selectedValues already exists", () => {
|
||||||
|
// Act
|
||||||
|
const wrapper = shallowMount(buttonQuestion, setupMocks({
|
||||||
|
propsData: {
|
||||||
|
isMultiSelect: true,
|
||||||
|
modelValue: ['a', 'b'],
|
||||||
|
groupName: "group-name"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
const val = { checkValue: true, value: "2021", }
|
||||||
|
wrapper.vm.handleCheckedChanged(val);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.selectedValues).toEqual(["a", "b", "2021"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("buttonQuestion.vue", () => {
|
||||||
|
it("Should remove a value to this.selectedValues if prop isMultiSelect is true, checkValue is false and this.selectedValues already exists", () => {
|
||||||
|
// Act
|
||||||
|
const wrapper = shallowMount(buttonQuestion, setupMocks({
|
||||||
|
propsData: {
|
||||||
|
isMultiSelect: true,
|
||||||
|
modelValue: ['a', 'b'],
|
||||||
|
groupName: "group-name"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
const val = { checkValue: false, value: "a", }
|
||||||
|
wrapper.vm.handleCheckedChanged(val);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
expect(wrapper.vm.selectedValues).toEqual(["b"]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function setupMocks(mountOptionsMockData = {}) {
|
||||||
|
const defaultMountOptions = { route: { query: { fmgPage: 'page-name' } } };
|
||||||
|
const baseMountOptions = getMountOptions(Object.assign(defaultMountOptions, mountOptionsMockData));
|
||||||
|
const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions);
|
||||||
|
|
||||||
|
return allMountOptions;
|
||||||
|
}
|
||||||
253
src/common-components/button-question/button-question.vue
Normal file
253
src/common-components/button-question/button-question.vue
Normal file
|
|
@ -0,0 +1,253 @@
|
||||||
|
<template>
|
||||||
|
<div :class="isOverflowScrollable ? 'button-question button-question-overflow' : 'button-question'">
|
||||||
|
<div v-if="questionText && answers && answers.length > 0" class="question-text d-flex">
|
||||||
|
<span class="fw-bold w-100">{{ questionText }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="w-100 d-flex justify-content-center">
|
||||||
|
<fieldset class="w-100" :aria-required=isRequired :class="getFieldSetClasses" :role="isMultiSelect ? 'group' : 'radiogroup'" :aria-labelledby="formatString(groupName)">
|
||||||
|
<legend class="sr-only" :data-focus-target="formatString(groupName)" :id="formatString(groupName)" tabindex="-1">
|
||||||
|
{{ questionText }}
|
||||||
|
{{(isMultiSelect && answers && answers.length > 1) ? 'Select one or more options below.' : 'Select an option below.' }}
|
||||||
|
</legend>
|
||||||
|
<div :class="getComponentLoopWrapperClasses">
|
||||||
|
<div :class="getComponentWrapperClasses" v-for="answer in answers" :key="answer.Name ? answer.Name : answer">
|
||||||
|
<component
|
||||||
|
:is="buttonType"
|
||||||
|
@isCheckedChanged="handleCheckedChanged"
|
||||||
|
:buttonID="answer.Name ? formatString(groupName) + '-' + answer.Name : formatString(groupName) + '-' + getAnswerString(answer, 'Text')"
|
||||||
|
:value="getValue(answer)"
|
||||||
|
:buttonLabel="answer.Text ? answer.Text : getAnswerString(answer, 'Name')"
|
||||||
|
:buttonLabelSubCopy="answer.SubText"
|
||||||
|
:textPosition="textPosition"
|
||||||
|
:isMultiSelect="isMultiSelect"
|
||||||
|
:groupName="formatString(groupName)"
|
||||||
|
:selectingInitiatesLoad="selectingInitiatesLoad"
|
||||||
|
:loaderColor="loaderColor"
|
||||||
|
:loaderPosition="loaderPosition"
|
||||||
|
:isWide=isWide
|
||||||
|
:isCashOrInsurance=isCashOrInsurance
|
||||||
|
:isRequired=isRequired
|
||||||
|
:buttonImage="answer.AnswerImageUrl"
|
||||||
|
:buttonImageId="answer.ImageId"
|
||||||
|
:altText="answer.Name ? answer.Name : answer"
|
||||||
|
screenReaderOnlyText="(opens new window)"
|
||||||
|
:colLength="getColLength"
|
||||||
|
:selectedValues="selectedValues"
|
||||||
|
data-test="button"
|
||||||
|
:validationRules="validationRules"
|
||||||
|
:class="[suppressError ? 'alertError' : '' , isCashOrInsurance ? 'radio-fancy' : '']"
|
||||||
|
:valueToLogType="valueToLogType"
|
||||||
|
/>
|
||||||
|
<!-- For nested questions -->
|
||||||
|
<transition name="fade" mode="out-in">
|
||||||
|
<div v-if="typeof selectedValues == 'string' && selectedValues == answer.Name">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
<div class="row form-test-error mt-1">
|
||||||
|
<error-message :name="formatString(groupName)" v-if="!suppressError"></error-message>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import listButton from "@/ux-components/list-button/list-button";
|
||||||
|
import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal";
|
||||||
|
import listCard from "@/ux-components/list-card/list-card";
|
||||||
|
import { ErrorMessage } from 'vee-validate';
|
||||||
|
import radio from "@/ux-components/radio/radio";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "buttonQuestion",
|
||||||
|
props: {
|
||||||
|
buttonType: {
|
||||||
|
type: String,
|
||||||
|
default: "listButton",
|
||||||
|
},
|
||||||
|
isMultiSelect: Boolean,
|
||||||
|
groupName: String,
|
||||||
|
questionText: String,
|
||||||
|
answers: Array,
|
||||||
|
textPosition: {
|
||||||
|
type: String,
|
||||||
|
default: "text-center",
|
||||||
|
},
|
||||||
|
selectingInitiatesLoad: Boolean,
|
||||||
|
loaderColor: {
|
||||||
|
type: String,
|
||||||
|
default: "blue",
|
||||||
|
},
|
||||||
|
loaderPosition: {
|
||||||
|
type: String,
|
||||||
|
default: "right",
|
||||||
|
},
|
||||||
|
isRequired: Boolean,
|
||||||
|
isOverflowScrollable: Boolean,
|
||||||
|
isWide: Boolean,
|
||||||
|
isCashOrInsurance: Boolean,
|
||||||
|
modelValue: [Array, String],
|
||||||
|
validationRules: String,
|
||||||
|
suppressError: Boolean,
|
||||||
|
useTextForValue: Boolean,
|
||||||
|
valueToLogType: String,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
getFieldSetClasses() {
|
||||||
|
if (this.isOverflowScrollable) {
|
||||||
|
return "container-fluid overflow-scroll position-absolute px-5 pt-1 py-0";
|
||||||
|
}
|
||||||
|
else if (this.buttonType == "listCard") {
|
||||||
|
return "w-100";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getComponentLoopWrapperClasses() {
|
||||||
|
let classes;
|
||||||
|
switch (this.buttonType) {
|
||||||
|
case "listButton":
|
||||||
|
classes = "w-100";
|
||||||
|
break;
|
||||||
|
case "listButtonHorizontal":
|
||||||
|
classes = "d-flex flex-row p-0";
|
||||||
|
break;
|
||||||
|
case 'listCard':
|
||||||
|
classes = "row g-2 justify-content-center";
|
||||||
|
break;
|
||||||
|
case 'radio':
|
||||||
|
classes = 'ui-radio d-flex'
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return classes;
|
||||||
|
},
|
||||||
|
getComponentWrapperClasses() {
|
||||||
|
let classes = "";
|
||||||
|
|
||||||
|
classes += this.isWide ? "col-12" : "col";
|
||||||
|
|
||||||
|
if (this.buttonType == "radio") {
|
||||||
|
classes += " radio-button-container";
|
||||||
|
}
|
||||||
|
|
||||||
|
return classes;
|
||||||
|
},
|
||||||
|
getColLength(){
|
||||||
|
if(this.isWide) {
|
||||||
|
return "12"
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
},
|
||||||
|
selectedValues: {
|
||||||
|
get: function() {
|
||||||
|
return this.modelValue;
|
||||||
|
},
|
||||||
|
set: function(newValue) {
|
||||||
|
this.$emit("update:modelValue", newValue);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
formatString(str) {
|
||||||
|
return str.replace(" ", "-");
|
||||||
|
},
|
||||||
|
getValue(answer){
|
||||||
|
if (this.useTextForValue) { return answer.Text }
|
||||||
|
return answer.Name ? answer.Name : answer;
|
||||||
|
},
|
||||||
|
getAnswerString(answer, prop = "Name") {
|
||||||
|
switch (typeof answer) {
|
||||||
|
case "string":
|
||||||
|
case "number":
|
||||||
|
case "boolean":
|
||||||
|
return this.formatString(answer.toString());
|
||||||
|
default:
|
||||||
|
return answer[prop] ? this.formatString(answer[prop]) : this.formatString(answer.toString());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handleCheckedChanged(val) {
|
||||||
|
if(this.selectingInitiatesLoad) {
|
||||||
|
this.selectedValues = val.value;
|
||||||
|
} else {
|
||||||
|
if(this.isMultiSelect) {
|
||||||
|
const newSelectedValues = this.selectedValues;
|
||||||
|
val.checkValue ? newSelectedValues.push(val.value) : newSelectedValues.splice(newSelectedValues.indexOf(val.value), 1);
|
||||||
|
this.selectedValues = newSelectedValues;
|
||||||
|
}
|
||||||
|
else if (Array.isArray(this.selectedValues)) {
|
||||||
|
this.selectedValues[0] = val.value;
|
||||||
|
const temp = this.selectedValues;
|
||||||
|
this.selectedValues = temp;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.selectedValues = val.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.$emit("isCheckedChanged", val);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
listButton,
|
||||||
|
listButtonHorizontal,
|
||||||
|
listCard,
|
||||||
|
ErrorMessage,
|
||||||
|
radio,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.button-question-overflow {
|
||||||
|
height: calc(100vh - 274px);
|
||||||
|
|
||||||
|
.overflow-scroll {
|
||||||
|
// Height will be determined by overall height of content above list
|
||||||
|
height: calc(100% - 314px);
|
||||||
|
overflow-x: hidden !important;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.button-question {
|
||||||
|
color: $black;
|
||||||
|
|
||||||
|
.radio-button-container {
|
||||||
|
&:not(:last-child) {
|
||||||
|
padding-bottom: map-get($spacers, 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.question-text {
|
||||||
|
margin-top: 1.5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1.625rem;
|
||||||
|
|
||||||
|
& > span {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.vehicle-parts {
|
||||||
|
.question-text {
|
||||||
|
span {
|
||||||
|
font-size: .875rem;
|
||||||
|
text-align: left;
|
||||||
|
margin: 0 0 .5rem 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.question-text {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
fieldset {
|
||||||
|
.ui-radio {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
0
src/ux-components/list-button/list-button-vue
Normal file
0
src/ux-components/list-button/list-button-vue
Normal file
0
src/ux-components/list-button/list-button.spec.js
Normal file
0
src/ux-components/list-button/list-button.spec.js
Normal file
0
src/ux-components/list-card/list-card.spec.js
Normal file
0
src/ux-components/list-card/list-card.spec.js
Normal file
0
src/ux-components/list-card/list-card.vue
Normal file
0
src/ux-components/list-card/list-card.vue
Normal file
0
src/ux-components/radio/radio.spec.js
Normal file
0
src/ux-components/radio/radio.spec.js
Normal file
0
src/ux-components/radio/radio.vue
Normal file
0
src/ux-components/radio/radio.vue
Normal file
Loading…
Reference in a new issue