Merge remote-tracking branch 'origin/feature/CSR-762' into feature/CSR-731

This commit is contained in:
Scott Kiener 2022-10-19 14:08:35 -04:00
commit 65cf2d3b84
52 changed files with 2559 additions and 1649 deletions

View file

@ -28,8 +28,7 @@ module.exports = {
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
coverageThreshold: {
global: {
// TODO KO
statements: 55,
statements: 80,
// Got the go ahead from Mark to temporarily lower this. Taking out initialize component made the year,make,model and style coverage drop a bit. Once unit tests for license plate lookup, vin lookup and address lookup are in the coverage should go back up to 90
},
},

View file

@ -1,16 +1,29 @@
<template>
<router-view v-slot="{ Component }">
<transition :duration="{ enter: 200, leave: 200 }" name="route-fade" mode="out-in">
<!-- The above durations should be kept in sync with the global css class "fade-on-route-transition" -->
<component :is="Component" />
</transition>
</router-view>
<router-view v-slot="{ Component }">
<transition
:duration="{ enter: 200, leave: 200 }"
name="route-fade"
mode="out-in">
<!-- The above durations should be kept in sync with the global css class "fade-on-route-transition" -->
<component :is="Component" @focusin="handleAnyComponentFocus" />
</transition>
</router-view>
</template>
<script>
import { handleAnyComponentFocus } from "@/helpers/button-question-focus-helper"
export default {
name: "app",
methods: {
handleAnyComponentFocus: handleAnyComponentFocus
}
};
</script>
<style lang="scss">
@import "./node_modules/bootstrap/scss/bootstrap";
@import "@/styles/common-styles.scss";
@import "@/styles/common-typography-styles.scss";
@import "@/styles/common-error-styles.scss";
@import "@/styles/common-animations.scss";
@import "./node_modules/bootstrap/scss/bootstrap";
@import "@/styles/common-styles.scss";
@import "@/styles/common-typography-styles.scss";
@import "@/styles/common-error-styles.scss";
@import "@/styles/common-animations.scss";
</style>

View file

@ -0,0 +1,736 @@
import { shallowMount, mount } from "@vue/test-utils";
import { EXPECTATION_FAILED } from "http-status-codes";
import inputButtonWrapperMixin from "../../mixins/input-button-wrapper-mixin";
import baseInputButton from "./base-input-button";
// TODO KO
describe("baseInputButton.vue", () => {
describe("general", () => {
describe("checkbox", () => {
test("isMultiSelect => baseInputButton is a checkbox", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: true,
},
},
});
// Assert
const inputElement = wrapper.find("input");
expect(inputElement.attributes().type).toEqual("checkbox");
});
});
describe("radio", () => {
test("!isMultiSelect => baseInputButton is a radio button", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: false,
},
},
});
// Assert
const inputElement = wrapper.find("input");
expect(inputElement.attributes().type).toEqual("radio");
});
});
// tests in here should be test.each
describe("shared", () => {
const isMultiSelectOptions = [true, false];
test.each(isMultiSelectOptions)("groupName", (isMultiSelect) => {
const { wrapper } = setupMocks({
mockData: {
propsData: {
groupName: "boogly",
isMultiSelect: isMultiSelect,
},
},
});
// Assert
const inputElement = wrapper.find("input");
const inputElementAttributes = inputElement.attributes();
expect(inputElementAttributes.name).toEqual("boogly");
});
});
});
describe("mouse clicks", () => {
describe("checkbox", () => {
test("clicked => correct event and value are emitted", async () => {
// Arrange
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: true,
value: "X",
},
},
});
// Act
// await wrapper.trigger("mousedown.left");
await wrapper.trigger("click");
// Assert
expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual([
"X",
]);
});
});
describe("radio", () => {
test("clicked => correct event and value are emitted", async () => {
// Arrange
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: false,
value: "X",
},
},
});
// Act
await wrapper.trigger("mousedown.left");
await wrapper.trigger("click");
// Assert
expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual(
"X"
);
});
});
});
describe("keyboard navigation and events", () => {
describe("checkbox", () => {
test.todo(
"focus on a checkbox => inputButtonClicked is not emitted"
);
test.todo(
"blur from a checkbox => inputButtonClicked is not emitted"
);
test("change event fired from checkbox => inputButtonClicked is emitted with correct value", async () => {
// Arrange
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: true,
value: "Hi",
},
},
});
const input = wrapper.find("input");
// Act
await input.trigger("change");
// Assert
expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual([
"Hi",
]);
});
test("focus and click space on a checkbox => inputButtonClicked is not emitted", async () => {
// Arrange
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: true,
value: "Hi",
},
},
});
const input = wrapper.find("input");
// Act
await input.trigger("keypress", { key: "space" });
// Assert
expect(wrapper.emitted()).not.toHaveProperty(
"update:modelValue"
);
});
test("focus and click enter on a checkbox => inputButtonClicked is emitted with correct value", async () => {
// Arrange
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: true,
value: "Hi",
},
},
});
const input = wrapper.find("input");
// Act
await input.trigger("keypress", { key: "enter" });
// Assert
expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual([
"Hi",
]);
});
});
describe("radio", () => {
test.todo(
"focus on a radio button => inputButtonClicked is not emitted"
);
test.todo(
"blur from a radio button => inputButtonClicked is not emitted"
);
test("focus and click space on a radio button => inputButtonClicked is emitted with correct value", async () => {
// Arrange
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: false,
value: "Hi",
},
},
});
const input = wrapper.find("input");
// Act
await input.trigger("keypress", { key: "space" });
// Assert
expect(wrapper.emitted()).toHaveProperty("update:modelValue");
expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual(
"Hi"
);
});
test("focus and click enter on a radio button => inputButtonClicked is emitted with correct value", async () => {
// Arrange
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: false,
value: "Hi",
},
},
});
const input = wrapper.find("input");
// Act
await input.trigger("keypress", { key: "enter" });
// Assert
expect(wrapper.emitted()).toHaveProperty("update:modelValue");
expect(wrapper.emitted()["update:modelValue"][0][0]).toEqual(
"Hi"
);
});
});
});
describe("methods", () => {
describe("handleEventAction", () => {});
describe("handleSelectionChange", () => {
test.todo("handleChange is called with valueToEmit");
describe("checkbox", () => {
test.todo("modelValue is null => valueToEmit is correct value");
test.todo(
"modelValue is undefined => valueToEmit is correct value"
);
test.todo(
"modelValue is empty => valueToEmit is correct value"
);
test.todo(
"modelValue is not empty and does not contain this button's value => valueToEmit is correct value"
);
test.todo(
"modelValue is not empty and does contain this button's value => valueToEmit is correct value"
);
});
describe("radio", () => {});
});
describe("handleClick", () => {
test.todo("handleSelectChange is also called");
test.todo("inputButtonClicked is emitted with valueToEmit");
});
});
describe("computed", () => {
describe("isChecked", () => {
describe("checkbox", () => {
const falsyModelValues = [[], null, undefined];
test.each(falsyModelValues)(
"modelValue is falsy/empty => checkbox isn't checked",
(modelValue) => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: true,
modelValue,
},
},
});
// Assert
const inputElement = wrapper.find("input");
expect(wrapper.vm.isChecked).toEqual(false);
expect(inputElement.element.checked).toBe(false);
}
);
test("modelValue doesn't contain this button's value => checkbox isn't checked", async () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: true,
modelValue: ["Aaa", "Bbb", "Ccc"],
value: "Ddd",
},
},
});
await wrapper.vm.$nextTick();
// Assert
const inputElement = wrapper.find("input");
expect(wrapper.vm.isChecked).toEqual(false);
expect(inputElement.element.checked).toBe(false);
});
test("modelValue contains this button's value => checkbox is checked", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: true,
modelValue: ["Aaa", "Bbb", "Ddd", "Ccc"],
value: "Ddd",
},
},
});
// Assert
const inputElement = wrapper.find("input");
expect(wrapper.vm.isChecked).toEqual(true);
expect(inputElement.element.checked).toBe(true);
});
});
describe("radio", () => {
const falsyModelValues = ["", null, undefined, []];
test.each(falsyModelValues)(
"modelValue is falsy/empty => radio button isn't checked",
(modelValue) => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: false,
modelValue,
value: "Aaa",
},
},
});
// Assert
const inputElement = wrapper.find("input");
expect(wrapper.vm.isChecked).toEqual(false);
expect(inputElement.element.checked).toBe(false);
}
);
test("modelValue equals this button's value => radio button is checked", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: false,
modelValue: "Ddd",
value: "Ddd",
},
},
});
// Assert
const inputElement = wrapper.find("input");
expect(wrapper.vm.isChecked).toEqual(true);
expect(inputElement.element.checked).toBe(true);
});
test("modelValue doesn't equal this button's value => radio button isn't checked", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: false,
modelValue: "Aaa",
value: "Ddd",
},
},
});
// Assert
const inputElement = wrapper.find("input");
expect(wrapper.vm.isChecked).toEqual(false);
expect(inputElement.element.checked).toBe(false);
});
});
});
describe("buttonId", () => {
test("groupName and value combo yield correct id for input button with string value", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
groupName: "my test-name",
value: "Aaa-BBB CcC",
},
},
});
// Assert
const inputElement = wrapper.find("input");
expect(wrapper.vm.buttonId).toBe("my-test-name-Aaa-BBB-CcC");
expect(inputElement.attributes().id).toBe(
"my-test-name-Aaa-BBB-CcC"
);
});
test("groupName and value combo yield correct id for input button with number value", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
groupName: "my test-name",
value: 2,
},
},
});
// Assert
const inputElement = wrapper.find("input");
expect(wrapper.vm.buttonId).toBe("my-test-name-2");
expect(inputElement.attributes().id).toBe("my-test-name-2");
});
});
describe("inputType", () => {
test("isMultiSelect is true => inputType is 'checkbox'", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: true,
},
},
});
// Assert
const inputElement = wrapper.find("input");
expect(wrapper.vm.inputType).toEqual("checkbox");
expect(inputElement.attributes().type).toBe("checkbox");
});
test("isMultiSelect is false => inputType is 'radio'", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: false,
},
},
});
// Assert
const inputElement = wrapper.find("input");
expect(wrapper.vm.inputType).toEqual("radio");
expect(inputElement.attributes().type).toBe("radio");
});
});
});
describe("integration testing", () => {
describe("checkbox", () => {
test("click on both => both are selected", async () => {
// Arrange
const { wrapper } = setupBaseInputButtonWrapper({
mockData: {
isMultiSelect: true,
},
});
// Act
const buttonWrappers = wrapper.findAllComponents({
name: "baseInputButtonWrapper",
});
const inputButtonOne = buttonWrappers.at(0);
const inputButtonTwo = buttonWrappers.at(1);
expect(inputButtonOne.vm.isMultiSelect).toBe(true);
expect(inputButtonTwo.vm.isMultiSelect).toBe(true);
expect(wrapper.vm.value).toEqual([]);
await inputButtonOne.find("input").trigger("click");
await inputButtonTwo.find("input").trigger("click");
// Assert
expect(wrapper.vm.value).toEqual(["value1", "value2"]);
});
test("click input 1, 2, 1 => only input 2 selected", async () => {
// Arrange
const { wrapper } = setupBaseInputButtonWrapper({
mockData: {
isMultiSelect: true,
},
});
// Act
const buttonWrappers = wrapper.findAllComponents({
name: "baseInputButtonWrapper",
});
const inputButtonOne = buttonWrappers.at(0);
const inputButtonTwo = buttonWrappers.at(1);
expect(inputButtonOne.vm.isMultiSelect).toBe(true);
expect(inputButtonTwo.vm.isMultiSelect).toBe(true);
expect(wrapper.vm.value).toEqual([]);
await inputButtonOne.find("input").trigger("click");
await inputButtonTwo.find("input").trigger("click");
await inputButtonOne.find("input").trigger("click");
// Assert
expect(wrapper.vm.value).toEqual(["value2"]);
});
const defaultCheckedCases = [
[["value2"], false, true],
[["value1", "value2"], true, true],
[["value1"], true, false],
[[], false, false]
]
test.each(defaultCheckedCases)("initial value is %s => correct input buttons are selected", async (modelValue, isInputButtonOneChecked, isInputButtonTwoChecked) => {
// Arrange
const { wrapper } = setupBaseInputButtonWrapper({
mockData: {
isMultiSelect: true,
initialValue: modelValue
},
});
// Act
const buttonWrappers = wrapper.findAllComponents({
name: "baseInputButtonWrapper",
});
const inputs = wrapper.findAll("input");
const inputButtonOne = buttonWrappers.at(0);
const inputButtonTwo = buttonWrappers.at(1);
// Assert
expect(inputButtonOne.vm.isMultiSelect).toBe(true);
expect(inputButtonTwo.vm.isMultiSelect).toBe(true);
expect(inputs[0].element.checked).toBe(isInputButtonOneChecked);
expect(inputs[1].element.checked).toBe(isInputButtonTwoChecked);
expect(wrapper.vm.value).toEqual(modelValue);
});
});
describe("radio", () => {
test("click on both => last clicked is selected", async () => {
// Arrange
const { wrapper } = setupBaseInputButtonWrapper({
mockData: {
isMultiSelect: false,
},
});
// Act
const buttonWrappers = wrapper.findAllComponents({
name: "baseInputButtonWrapper",
});
const inputButtonOne = buttonWrappers.at(0);
const inputButtonTwo = buttonWrappers.at(1);
expect(inputButtonOne.vm.isMultiSelect).toBe(false);
expect(inputButtonTwo.vm.isMultiSelect).toBe(false);
expect(wrapper.vm.value).toEqual("");
await inputButtonOne.find("input").trigger("click");
await inputButtonTwo.find("input").trigger("click");
// Assert
expect(wrapper.vm.value).toEqual("value2");
});
test("click input 1, 2, 1 => input 1 is selected", async () => {
// Arrange
const { wrapper } = setupBaseInputButtonWrapper({
mockData: {
isMultiSelect: false,
},
});
// Act
const buttonWrappers = wrapper.findAllComponents({
name: "baseInputButtonWrapper",
});
const inputButtonOne = buttonWrappers.at(0);
const inputButtonTwo = buttonWrappers.at(1);
expect(inputButtonOne.vm.isMultiSelect).toBe(false);
expect(inputButtonTwo.vm.isMultiSelect).toBe(false);
expect(wrapper.vm.value).toEqual("");
await inputButtonOne.find("input").trigger("click");
await inputButtonTwo.find("input").trigger("click");
await inputButtonOne.find("input").trigger("click");
// Assert
expect(wrapper.vm.value).toEqual("value1");
});
const defaultCheckedCases = [
["", false, false],
["value1", true, false],
["value2", false, true],
[[], false, false]
]
test.each(defaultCheckedCases)("initial value is %s => correct input buttons are selected", async (modelValue, isInputButtonOneChecked, isInputButtonTwoChecked) => {
// Arrange
const { wrapper } = setupBaseInputButtonWrapper({
mockData: {
isMultiSelect: false,
initialValue: modelValue
},
});
// Act
const buttonWrappers = wrapper.findAllComponents({
name: "baseInputButtonWrapper",
});
const inputs = wrapper.findAll("input");
const inputButtonOne = buttonWrappers.at(0);
const inputButtonTwo = buttonWrappers.at(1);
// Assert
expect(inputButtonOne.vm.isMultiSelect).toBe(false);
expect(inputButtonTwo.vm.isMultiSelect).toBe(false);
expect(inputs[0].element.checked).toBe(isInputButtonOneChecked);
expect(inputs[1].element.checked).toBe(isInputButtonTwoChecked);
expect(wrapper.vm.value).toEqual(modelValue);
});
})
});
});
function setupMocks({ mockData = {}, shouldShallowMount = true }) {
const baseInputButtonWrapper = {
components: { baseInputButton },
template: '<baseInputButton v-model="myValue" />',
data() {
return {
myValue: "",
};
},
};
const mountMockData = {
...mockData,
propsData: {
// to get rid of some annoying warnings
groupName: "groupName",
modelValue: mockData.propsData?.isMultiSelect ? [] : "",
value: "5",
setLastValuePushedToGa: () => {},
// should override the above if they exist
...mockData.propsData,
},
};
const wrapper = shouldShallowMount
? shallowMount(baseInputButton, mountMockData)
: mount(baseInputButton, mountMockData);
wrapper.vm.pushEventToGA = jest.fn();
wrapper.vm.$route = {
query: {},
};
wrapper.vm.GaActions = {};
return { wrapper };
}
function setupBaseInputButtonWrapper({ mockData = {} }) {
const originalData = baseInputButton.data();
baseInputButton.data = () => {
return {
...originalData,
// $route: {
// query: {
// fmgPage: "myPage",
// },
// },
};
};
// console.log({
// test: baseInputButton.vm.$route
// })
baseInputButton.methods.pushClickEventToGA = jest.fn();
const baseInputButtonWrapper = {
name: "baseInputButtonWrapper",
components: { baseInputButton },
template:
'<div><baseInputButton v-bind="$props" v-model="selectedValue" /></div>',
mixins: [inputButtonWrapperMixin],
};
let parentComponentTemplate = `<div>`;
parentComponentTemplate += `<baseInputButtonWrapper v-model="value" groupName="myGroupName" :isMultiSelect="${mockData.isMultiSelect}" value="value1" />`;
parentComponentTemplate += `<baseInputButtonWrapper v-model="value" groupName="myGroupName" :isMultiSelect="${mockData.isMultiSelect}" value="value2" />`;
parentComponentTemplate += `</div>`;
const wrapper = mount(
{
data() {
return {
value:
mockData.initialValue ??
(mockData.isMultiSelect ? [] : ""),
$route: {
query: {
fmgPage: "myPage",
},
},
};
},
template: parentComponentTemplate,
components: { baseInputButtonWrapper },
},
{}
);
return { wrapper };
}

View file

@ -2,8 +2,9 @@
<label
:class="[buttonWrapperClasses, { 'has-error': errors.length > 0 }]"
:for="buttonId"
@mousedown.left="handleEventAction('click', $event)"
@keyup="handleKeyboardNavigation">
@focusin="handleFocus"
@focusout="handleBlur"
@mousedown.left="handleEventAction('click', $event)">
<input
:type="inputType"
:id="buttonId"
@ -13,8 +14,8 @@
:aria-required="isRequired"
:value="value"
:checked="isChecked"
@blur="handleBlur"
@keypress.enter="handleEventAction('keypressSubmit', $event)"
@keypress.space="handleEventAction('space', $event)"
@keypress.enter="handleEventAction('enter', $event)"
@change="handleEventAction('change', $event)" />
<slot></slot>
@ -22,43 +23,18 @@
</template>
<script>
import { queryStrings } from "@/constants/query-strings";
import { useField } from "vee-validate";
import { toRef } from "vue";
import { queryStrings } from "@/constants/query-strings";
import { handleButtonComponentFocus, handleInputComponentBlur } from "@/helpers/button-question-focus-helper";
import { inputButtonProps } from "@/common-components/base-input-button/button-functionality-props";
export default {
name: "base-input-button",
emits: ["change"],
model: {
prop: "modelValue",
event: "change",
},
props: {
value: {
type: [String, Number],
required: true,
},
modelValue: {
type: [Array, String, Number],
required: true,
},
isMultiSelect: Boolean,
groupName: {
type: String,
required: true,
},
validationRules: {
type: String,
default: "",
},
...inputButtonProps,
buttonWrapperClasses: [String, Array, Object],
inputClasses: [String, Array, Object],
valueToLogType: String,
selectOnKeypress: {
type: Boolean,
default: true,
},
isRequired: Boolean,
},
data() {
return {
@ -72,32 +48,34 @@ export default {
},
methods: {
handleEventAction(eventType, e) {
const eventTypes = {
CHANGE: "change",
KEYPRESS_SUBMIT: "keypressSubmit",
CLICK: "click",
};
// console.log({
// eventType,
// e
// })
if (this.isMultiSelect) {
switch (eventType) {
case eventTypes.KEYPRESS_SUBMIT:
case eventTypes.CHANGE:
case this.eventTypes.ENTER:
case this.eventTypes.CHANGE:
this.handleClick(e);
window.lastFocusedInputGroup = this.groupName;
this.pushClickEventToGA();
this.handlePushClickEventToGACheck(
this.eventTypes.CLICK
);
break;
}
} else {
switch (eventType) {
case eventTypes.CLICK:
case eventTypes.KEYPRESS_SUBMIT:
case this.eventTypes.CLICK:
case this.eventTypes.ENTER:
case this.eventTypes.SPACE:
this.handleClick(e);
this.test();
this.handlePushClickEventToGACheck(
this.eventTypes.CLICK
);
break;
case eventTypes.CHANGE:
this.selectOnKeypress
? this.handleClick(e)
: this.handleSelectionChange(e);
case this.eventTypes.CHANGE:
this.selectingInitiatesLoad
? this.handleSelectionChange(e)
: this.handleClick(e);
break;
}
}
@ -119,21 +97,40 @@ export default {
this.valueToEmit = this.value;
}
window.currentlySelectedValues =
window.currentlySelectedValues ?? {};
window.currentlySelectedValues[this.groupName] = this.value;
this.handleChange(this.valueToEmit);
},
handleClick(e) {
this.handleSelectionChange(e);
this.$emit("buttonClicked", this.valueToEmit);
this.$emit("update:modelValue", this.valueToEmit);
},
handleFocus() {
handleButtonComponentFocus({
groupName: this.groupName,
});
},
handleBlur() {
handleInputComponentBlur({
groupName: this.groupName,
onButtonQuestionLostFocusCallback: this.handlePushClickEventToGACheck,
});
},
handlePushClickEventToGACheck(source) {
if (source === this.eventTypes.CLICK) {
this.pushClickEventToGA();
} else {
if (
this.valueToEmit !== null &&
!this.isValueSelectedOnClick &&
this.isChecked &&
this.lastValuePushedToGa != this.value
) {
this.pushClickEventToGA();
}
}
},
pushClickEventToGA(value) {
window.firedGaClickEventValues =
window.firedGaClickEventValues ?? {};
window.firedGaClickEventValues[window.lastFocusedInputGroup] =
value ?? this.value;
console.log("PUSHHH")
console.log(this.$route)
this.pushEventToGA(
this.$route.query[queryStrings.FMG_PAGE],
this.GaActions.CLICKED,
@ -141,88 +138,38 @@ export default {
true,
this.valueToLogType
);
},
handleBlur() {
window.lastFocusedInputGroup = this.groupName;
window.wasLastFocusedInputMultiselect = this.isMultiSelect;
},
handleKeyboardNavigation(key) {
this.test(key);
},
test(event) {
window.firedGaClickEventValues =
window.firedGaClickEventValues ?? {};
window.currentlySelectedValues =
window.currentlySelectedValues ?? {};
const keyCode = event?.code;
// Keyboard navigation
if (keyCode) {
if (
window.currentlySelectedValues &&
window.firedGaClickEventValues &&
window.lastFocusedInputGroup &&
!window.wasLastFocusedInputMultiselect &&
window.currentlySelectedValues[
window.lastFocusedInputGroup
] &&
window.firedGaClickEventValues[
window.lastFocusedInputGroup
] !=
window.currentlySelectedValues[
window.lastFocusedInputGroup
]
) {
if (keyCode === "Tab") {
this.pushClickEventToGA(
window.currentlySelectedValues[
window.lastFocusedInputGroup
]
);
} else if (keyCode?.includes("Arrow")) {
if (window.lastFocusedInputGroup != this.groupName) {
this.pushClickEventToGA(
window.currentlySelectedValues[
window.lastFocusedInputGroup
]
);
}
}
}
} else {
// Radio click
window.lastFocusedInputGroup = this.groupName;
if (
window.firedGaClickEventValues[
window.lastFocusedInputGroup
] !=
window.currentlySelectedValues[window.lastFocusedInputGroup]
) {
this.pushClickEventToGA(
window.currentlySelectedValues[
window.lastFocusedInputGroup
]
);
}
}
this.setLastValuePushedToGa(this.value);
},
},
computed: {
isChecked() {
if (this.isMultiSelect && this.modelValue instanceof Array) {
return this.modelValue.includes(this.value);
} else if (!this.isMultiSelect) {
return this.modelValue == this.value;
} else {
return false;
}
return this.modelValue == this.value;
},
inputType() {
return this.isMultiSelect ? "checkbox" : "radio";
},
buttonId() {
return `${this.groupName}-${JSON.stringify(this.value)?.replace(
" ",
"-"
)}`;
return `${this.groupName?.replace(" ", "-")}-${this.value
?.toString()
?.replace(" ", "-")}`;
},
isValueSelectedOnClick() {
return this.isMultiSelect || this.selectingInitiatesLoad;
},
eventTypes() {
return {
CHANGE: "change",
ENTER: "enter",
SPACE: "space",
CLICK: "click",
};
},
},
setup(props) {

View file

@ -0,0 +1,30 @@
export const inputButtonProps = {
value: {
type: [String, Number],
required: true,
},
modelValue: {
type: [Array, String, Number],
required: true,
},
isMultiSelect: Boolean,
groupName: {
type: String,
required: true,
},
validationRules: {
type: String,
default: "",
},
valueToLogType: String,
isRequired: {
type: Boolean,
default: true,
},
lastValuePushedToGa: [String, Number],
setLastValuePushedToGa: Function,
selectingInitiatesLoad: {
type: Boolean,
default: false,
},
}

View file

@ -2,189 +2,171 @@ 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 });
jest.mock(
"@/store",
() => {
return {};
},
{ virtual: true }
);
// TODO KO
describe.skip("buttonQuestion.vue", () => {
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"
}
groupName: "group-name",
},
});
// Assert
const fieldSet = wrapper.find('fieldset');
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 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 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");
// });
// });
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 };
// // 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");
});
});
// 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",
};
// 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("");
});
});
// 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" };
// 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"
);
});
});
// // Assert
// expect(buttonQuestion.methods.getValue.call(localThis, answer)).toBe('testText');
// });
// });
describe("buttonQuestion.vue", () => {
it("Should return answer.Name if prop useTextForValue is false and there's not buttonLabel and answer.Name exists", async () => {
// Act
const localThis = { useTextForValue: false };
const answer = { Name: "testName", Text: "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"
);
});
});
// // Assert
// expect(buttonQuestion.methods.getValue.call(localThis, answer)).toBe('testName');
// });
// });
// TODO KO
describe("buttonQuestion.vue", () => {
describe.skip("handleAnswerChange", () => {
test("is radio => should emit captured value", async () => {
// Act
const wrapper = shallowMount(
buttonQuestion,
setupMocks({ propsData: { groupName: "group-name" } })
);
await wrapper.setProps({
answers: ["2022", "2021", "2020"],
isMultiSelect: false,
modelValue: "",
});
const val = "2021";
wrapper.vm.handleAnswerChange(val);
// 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"]]);
// });
// });
// 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"]]);
// });
// });
test("is checkbox => should emit captured value", async () => {
const wrapper = shallowMount(
buttonQuestion,
setupMocks({ propsData: { groupName: "group-name" } })
);
await wrapper.setProps({
answers: ["2022", "2021", "2020"],
isMultiSelect: false,
modelValue: "",
});
const val = ["2022", "2021", , "2019", "2020"];
wrapper.vm.handleAnswerChange(val);
// 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"]);
// });
// });
// Assert
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([
["2022", "2021", , "2019", "2020"],
]);
});
});
});
function setupMocks(mountOptionsMockData = {}) {
const defaultMountOptions = { route: { query: { fmgPage: 'page-name' } } };
const baseMountOptions = getMountOptions(Object.assign(defaultMountOptions, mountOptionsMockData));
const defaultMountOptions = { route: { query: { fmgPage: "page-name" } } };
const baseMountOptions = getMountOptions(
Object.assign(defaultMountOptions, mountOptionsMockData)
);
const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions);
return allMountOptions;

View file

@ -22,6 +22,7 @@
<legend
class="sr-only"
:data-focus-target="formatString(groupName)"
tabindex="-1"
:id="formatString(groupName)">
{{ questionText }}
{{
@ -44,22 +45,21 @@
:groupName="answer.groupName"
:isMultiSelect="isMultiSelect"
:value="answer.value"
:modelValue="modelValue"
:selectingInitiatesLoad="selectingInitiatesLoad"
:isWide="isWide"
:validationRules="validationRules"
:textPosition="textPosition"
:selectOnKeypress="selectOnKeypress"
@blur="handleBlur"
@focus="handleFocus"
@buttonClicked="handleAnswerChange"
:additionalData="additionalData" />
:lastValuePushedToGa="lastValuePushedToGa"
:setLastValuePushedToGa="setLastValuePushedToGa"
v-model="selectedValues"
/>
<!-- For nested questions -->
<transition name="fade" mode="out-in">
<div
v-if="
typeof primaryValue == 'string' &&
primaryValue == answer.value
typeof selectedValues == 'string' &&
selectedValues == answer.value
">
<slot></slot>
</div>
@ -80,9 +80,9 @@
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";
import servicePackageRadio from "@/layouts/quote/service-package-question/service-package-radio/service-package-radio";
import { ErrorMessage } from "vee-validate";
export default {
name: "buttonQuestion",
@ -118,16 +118,11 @@ export default {
suppressError: Boolean,
useTextForValue: Boolean,
valueToLogType: String,
selectOnKeypress: {
type: Boolean,
default: true,
},
additionalData: null
},
data() {
return {
primaryValue: "",
lastFocusedInputGroup: "",
lastValuePushedToGa: null,
};
},
computed: {
@ -185,7 +180,6 @@ export default {
}
},
buttonsInfo() {
// TODO KO temporary. It should always just be an array
return (Array.isArray(this.answers) ? this.answers : [])?.map(
(answer) => ({
buttonLabel: answer.buttonLabel ?? answer.Text ?? answer,
@ -204,6 +198,14 @@ export default {
})
);
},
selectedValues: {
get() {
return this.modelValue;
},
set(selectedAnswers) {
this.$emit("update:modelValue", selectedAnswers);
}
}
},
methods: {
formatString(str) {
@ -227,21 +229,8 @@ export default {
: this.formatString(answer.toString());
}
},
handleAnswerChange(primaryAnswerValue) {
this.primaryValue = primaryAnswerValue;
this.$emit("buttonQuestionChange", primaryAnswerValue);
this.$emit("update:modelValue", primaryAnswerValue);
},
handleBlur() {
// this.lastFocusedInputGroup = this.groupName;
// window.lastFocusedInputGroup = this.groupName
// console.log("blur: ", this.groupName);
},
handleFocus() {
// console.log("focused: ", {
// groupName: this.groupName,
// lastFocusedInputGroup: this.lastFocusedInputGroup,
// });
setLastValuePushedToGa(lastValuePushedToGa) {
this.lastValuePushedToGa = lastValuePushedToGa;
},
},
components: {

View file

@ -1,164 +1,199 @@
<template>
<div id="app">
<fieldset>
<legend class="d-flex justify-content-center">Date Picker</legend>
<div class="grid-container">
<legend class=" sr-only">Date Picker</legend>
<div class="calendar-grid-container">
<div class="month-year body-small d-flex align-items-center ps-3 small">{{currentMonth}} {{currentYear}}</div>
<div class="legend caption d-flex align-items-center justify-content-end"><span class="legend-circle me-1"></span> &equals; Available</div>
<div class="nav-back ps-3"><button></button></div>
<div class="nav-forward pe-3"><button></button></div>
<!-- Do the days of the weeek need to be read? -->
<div class="grid-item"><span class="sr-only">Sunday</span>S</div>
<div class="grid-item"><span class="sr-only">Monday</span>M</div>
<div class="grid-item"><span class="sr-only">Tuesday</span>T</div>
<div class="grid-item"><span class="sr-only">Wednesday</span>W</div>
<div class="grid-item"><span class="sr-only">Thursday</span>T</div>
<div class="grid-item"><span class="sr-only">Friday</span>F</div>
<div class="grid-item"><span class="sr-only">Saturday</span>S</div>
<div class="grid-item radio-wrapper past-day">
<input type="radio" name="day-of-month" id="thirtypast" />
<label for="thirtypast"><span>30</span></label>
<div class="grid-item caption"><span class="sr-only">Sunday</span>S</div>
<div class="grid-item caption"><span class="sr-only">Monday</span>M</div>
<div class="grid-item caption"><span class="sr-only">Tuesday</span>T</div>
<div class="grid-item caption"><span class="sr-only">Wednesday</span>W</div>
<div class="grid-item caption"><span class="sr-only">Thursday</span>T</div>
<div class="grid-item caption"><span class="sr-only">Friday</span>F</div>
<div class="grid-item caption"><span class="sr-only">Saturday</span>S</div>
<div class="grid-item radio-wrapper">
<input disabled type="radio" name="day-of-month" id="sep-25" />
<label class="past-day" for="sep-25"><span>25</span></label>
</div>
<div class="grid-item radio-wrapper past-day">
<input type="radio" name="day-of-month" id="thirtyonepast" />
<label for="thirtyonepast"><span>31</span></label>
<div class="grid-item radio-wrapper">
<input disabled type="radio" name="day-of-month" id="sep-26" />
<label class="past-day" for="sep-26"><span>26</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day first-of-month">
<div v-if="isFirstDayOfMonth" class="current-month">OCT</div>
<input type="radio" name="day-of-month" id="one" />
<label for="one"><span class="sr-only">{{dayOfWeek}} {{pastMonth}} {{dayOfMonth}}</span><span>1</span></label>
<div class="grid-item radio-wrapper">
<input disabled type="radio" name="day-of-month" id="sep-27" />
<label class="past-day" for="sep-27"><span>27</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="two" />
<label for="two"><span>2</span></label>
<div class="grid-item radio-wrapper">
<input disabled type="radio" name="day-of-month" id="sep-28" />
<label class="past-day" for="sep-28"><span>28</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="three" />
<label for="three"><span>3</span></label>
<div class="grid-item radio-wrapper">
<input disabled type="radio" name="day-of-month" id="sep-29" />
<label class="past-day" for="sep-29"><span>29</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day" :class="[isCurrentDay ? 'current-day' : '']">
<div v-if="isCurrentDayDot" class="current-day-dot"></div>
<input type="radio" name="day-of-month" id="four" />
<label for="four"><span>4</span></label>
<div class="grid-item radio-wrapper">
<input disabled type="radio" name="day-of-month" id="sep-30" />
<label class="past-day" for="sep-30"><span>30</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="five" />
<label for="five"><span>5</span></label>
<!-- Use this as the base calendar item -->
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-1" />
<label class="highlighted-day selectable-day" :class="[isCurrentDay ? 'current-day' : '']" for="oct-1"><span>1</span>
<span class="sr-only">October 1st</span>
<div v-if="isFirstDayOfMonth" class="current-month first-day">OCT</div>
</label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="six" />
<label for="six"><span>6</span></label>
<!-- END -->
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-2" />
<label class="selectable-day" for="oct-2"><span>2</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="seven" />
<label for="seven"><span>7</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-3" />
<label class="selectable-day" for="oct-3"><span>3</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="eight" />
<label for="eight"><span>8</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-4" />
<label class="selectable-day" for="oct-4"><span>4</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="nine" />
<label for="nine"><span>9</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-5" />
<label class="selectable-day" for="oct-5"><span>5</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="ten" />
<label for="ten"><span>10</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-6" />
<label class="selectable-day" for="oct-6"><span>6</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="eleven" />
<label for="eleven"><span>11</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-7" />
<label class="selectable-day" for="oct-7"><span>7</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="twelve" />
<label for="twelve"><span>12</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-8" />
<label class="selectable-day" for="oct-8"><span>8</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="thirteen" />
<label for="thirteen"><span>13</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-9" />
<label class="selectable-day" for="oct-9"><span>9</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="fourteen" />
<label for="fourteen"><span>17</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-10" />
<label class="selectable-day" for="oct-10"><span>10</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="fifteen" />
<label for="fifteen"><span>15</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-11" />
<label class="selectable-day" for="oct-11"><span>11</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="sixteen" />
<label for="sixteen"><span>16</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-12" />
<label class="selectable-day" for="oct-12"><span>12</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="seventeen" />
<label for="seventeen"><span>17</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-13" />
<label class="selectable-day" for="oct-13"><span>13</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="eighteen" />
<label for="eighteen"><span>18</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-14" />
<label class="selectable-day" for="oct-14"><span>14</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="nineteen" />
<label for="nineteen"><span>19</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-15" />
<label class="selectable-day" for="oct-15"><span>15</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="twenty" />
<label for="twenty"><span>20</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-16" />
<label class="selectable-day" for="oct-16"><span>16</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="twentyone" />
<label for="twentyone"><span>21</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-17" />
<label class="selectable-day" for="oct-17"><span>17</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="twentytwo" />
<label for="twentytwo"><span>22</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-18" />
<label class="selectable-day" for="oct-18"><span>18</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="twentythree" />
<label for="twentythree"><span>23</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-19" />
<label class="selectable-day" for="oct-19"><span>19</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="twentyfour" />
<label for="twentyfour"><span>24</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-20" />
<label class="selectable-day" for="oct-20"><span>20</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="twentyfive" />
<label for="twentyfive"><span>25</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-21" />
<label class="selectable-day" for="oct-21"><span>21</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="twentysix" />
<label for="twentysix"><span>26</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-22" />
<label class="selectable-day" for="oct-22"><span>22</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="twentyseven" />
<label for="twentyseven"><span>27</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-23" />
<label class="selectable-day" for="oct-23"><span>23</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="twentyeight" />
<label for="twentyeight"><span>28</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-24" />
<label class="selectable-day" for="oct-24"><span>24</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="twentynine" />
<label for="twentynine"><span>29</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-25" />
<label class="selectable-day" for="oct-25"><span>25</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="thirty" />
<label for="thirty"><span>30</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-26" />
<label class="selectable-day" for="oct-26"><span>26</span></label>
</div>
<div class="grid-item radio-wrapper highlighted-day">
<input type="radio" name="day-of-month" id="thirtyone" />
<label for="thirtyone"><span>31</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-27" />
<label class="selectable-day" for="oct-27"><span>27</span></label>
</div>
<div class="grid-item radio-wrapper future-day">
<input type="radio" name="day-of-month" id="onefuture" />
<label for="onefuture"><span>1</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-28" />
<label class="selectable-day" for="oct-28"><span>28</span></label>
</div>
<div class="grid-item radio-wrapper future-day">
<input type="radio" name="day-of-month" id="twofuture" />
<label for="twofuture"><span>2</span></label>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-29" />
<label class="selectable-day" for="oct-29"><span>29</span></label>
</div>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="nov-30" />
<label class="selectable-day" for="nov-30"><span>30</span></label>
</div>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="oct-31" />
<label class="selectable-day" for="oct-31"><span>31</span></label>
</div>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="nov-1" />
<label class="future-day selectable-day" for="nov-1"><span>1</span>
<div v-if="isFirstDayOfMonth" class="current-month first-day">OCT</div>
</label>
</div>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="nov-2" />
<label class="future-day selectable-day" for="nov-2"><span>2</span></label>
</div>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="nov-3" />
<label class="future-day selectable-day" for="nov-3"><span>3</span></label>
</div>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="nov-4" />
<label class="future-day selectable-day" for="nov-4"><span>4</span></label>
</div>
<div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="nov-5" />
<label class="future-day selectable-day" for="nov-5"><span>5</span></label>
</div>
</div>
</fieldset>
</div>
<form class="w-100 d-flex justify-content-center mt-5">
<input type="date">
</form>
</template>
<script>
@ -166,41 +201,94 @@ export default {
name: "datePicker",
data() {
return {
dayOfWeek: "Tuesday",
dayOfMonth: "1st",
pastMonth: "October",
currentMonth: "October",
currentYear: "2022",
isFirstDayOfMonth: true,
isCurrentDay: true,
isCurrentDayDot: true,
};
},
};
</script>
<style lang="scss">
.container {
//max-width: 341px;
label {
display: initial; //Override Bootstrap default
}
#availableDates label:nth-child(14n):after {
content: "\a";
white-space: pre;
}
}
.grid-container {
.calendar-grid-container {
margin: 0 auto;
max-width: 341px;
max-width: 414px;
display: grid;
grid-gap: 4px;
grid-template-columns: auto auto auto auto auto auto auto;
grid-template-columns: repeat(7, 1fr);
justify-content: center;
align-items: center;
padding: 4px;
.grid-item {
text-align: center;
padding: 4px;
margin: 10%;
}
.month-year {
grid-area: 1 / 1 / 2 / 4;
text-transform: uppercase;
font-weight: 300;
letter-spacing: .75px;
}
.legend {
grid-area: 1 / 4 / 2 / 6;
.legend-circle {
border-radius: 50%;
width: 16px;
height: 16px;
background-color: $blue-100;
border: 1px solid $blue;
}
}
.nav-forward {
grid-area: 1 / 7 / 2 / 8;
display: flex;
justify-content: flex-end;
button {
&:after {
content: "";
position: absolute;
width: 7px;
height: 12px;
background-image: url("data:image/svg+xml,%3Csvg width='7' height='12' viewBox='0 0 7 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M6.66831 5.99879C6.66955 6.17554 6.60074 6.34558 6.47695 6.47168L1.15501 11.8017C1.02812 11.9287 0.85603 12 0.676587 12C0.497145 12 0.325053 11.9287 0.198168 11.8017C0.0712831 11.6748 7.268e-09 11.5026 8.07183e-09 11.3231C8.87567e-09 11.1436 0.0712831 10.9714 0.198168 10.8445L5.05126 5.99879L0.198168 1.14736C0.0725521 1.02042 0.00248585 0.848755 0.00338306 0.670131C0.00428028 0.491506 0.0760674 0.320554 0.202952 0.194881C0.329837 0.0692091 0.501426 -0.000888818 0.679971 9.54485e-06C0.858515 0.000906955 1.02939 0.0727263 1.15501 0.199668L6.47312 5.52399C6.59843 5.65017 6.66862 5.82092 6.66831 5.99879Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
}
}
}
.nav-back {
grid-area: 1 / 6 / 2 / 7;
display: flex;
justify-content: flex-end;
button {
&:after {
content: "";
position: absolute;
width: 7px;
height: 12px;
background-image: url("data:image/svg+xml,%3Csvg width='7' height='12' viewBox='0 0 7 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0.331685 6.00121C0.330445 5.82446 0.399256 5.65442 0.523053 5.52832L5.84499 0.198256C5.97188 0.0713149 6.14397 -8.63821e-08 6.32341 -6.72174e-08C6.50285 -4.80527e-08 6.67495 0.071315 6.80183 0.198256C6.92872 0.325198 7 0.497368 7 0.67689C7 0.856412 6.92872 1.02858 6.80183 1.15552L1.94874 6.00121L6.80183 10.8526C6.92745 10.9796 6.99751 11.1512 6.99662 11.3299C6.99572 11.5085 6.92393 11.6794 6.79705 11.8051C6.67016 11.9308 6.49857 12.0009 6.32003 12C6.14148 11.9991 5.97061 11.9273 5.84499 11.8003L0.526881 6.47601C0.401568 6.34983 0.331375 6.17908 0.331685 6.00121Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
}
}
}
.nav-back,
.nav-forward {
button {
position: relative;
border-radius: 50%;
width: 28px;
height: 28px;
background-color: $blue-100;
border: 1px solid $blue;
display: flex;
justify-content: center;
align-items: center;
&:focus,
&:focus-visible {
border: 2.5px solid $blue;
box-shadow: none;
background-color: $blue-100;
color: $white;
}
}
}
.radio-wrapper {
@ -208,252 +296,136 @@ export default {
display: flex;
justify-content: center;
align-items: center;
margin: 0;
width: 46px;
height: 46px;
outline: none;
.current-day-dot {
position: absolute;
input[type="radio"] {
position: absolute; //override bootstrap
height: 0;
opacity: 0;
}
&.first-of-month {
.current-month {
position: absolute;
font-size: 9px;
top: 4px;
color: #1574a1;
&:focus-visible + label {
box-shadow: 0 0 0 2.5px $blue;
}
}
&.highlighted-day {
input[type="radio"] {
position: absolute;
// opacity: 0;
height: 0.1px;
width: 0.1px;
+ label {
position: relative;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
border-radius: 50%;
color: #1574a1;
span {
z-index: 2;
}
&::before {
content: "";
position: absolute;
border-radius: 50%;
border: 1px solid #1574a1;
background-color: #e4f1f7;
color: #1574a1;
width: 40px;
height: 40px;
z-index: -1;
}
&::after {
content: "";
position: absolute;
border-radius: 50%;
width: 40px;
height: 40px;
}
&:focus + label,
&:checked:focus + label {
box-shadow: 0 0 0 3px #fff, 0 0 0 5.5px #1574a1;
background-color: $blue;
color: $white;
&.past-day {
box-shadow: none;
background-color: transparent;
color: $gray-500;
font-weight: normal;
}
&:checked {
+ label::after {
background: #1574a1;
box-shadow: 0 0 0 3px #fff, 0 0 0 5.5px #1574a1;
color: #fff;
}
+ label span {
color: #fff;
}
}
&:focus {
+ label::before {
background-color: #1574a1;
}
+ label span {
color: #fff;
}
}
}
}
&.past-day {
input[type="radio"] {
position: absolute;
opacity: 0;
height: 0.1px;
width: 0.1px;
+ label {
position: relative;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
border-radius: 50%;
color: #8E9292;
span {
z-index: 2;
}
&::before {
content: "";
position: absolute;
border-radius: 50%;
border: 1px solid transparent;
background-color: transparent;
color: #1574a1;
width: 40px;
height: 40px;
z-index: -1;
}
&::after {
content: "";
position: absolute;
border-radius: 50%;
width: 40px;
height: 40px;
}
}
&:checked {
+ label::after {
background: #1574a1;
box-shadow: 0 0 0 3px #fff, 0 0 0 5.5px #1574a1;
color: #fff;
}
+ label span {
color: #fff;
}
}
&:focus {
+ label::before {
background-color: transparent;
}
+ label span {
color: #1574a1;
}
}
&:active {
+ label::before {
background-color: #1574a1;
}
+ label span {
color: #fff;
}
}
}
}
&.future-day {
input[type="radio"] {
position: absolute;
opacity: 0;
height: 0.1px;
width: 0.1px;
+ label {
position: relative;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
border-radius: 50%;
color: #4D5151;
span {
z-index: 2;
}
&::before {
content: "";
position: absolute;
border-radius: 50%;
border: 1px solid transparent;
background-color: transparent;
color: #1574a1;
width: 40px;
height: 40px;
z-index: -1;
}
&::after {
content: "";
position: absolute;
border-radius: 50%;
width: 40px;
height: 40px;
}
}
}
}
&.current-day {
input[type="radio"] {
position: absolute;
// opacity: 0;
height: 0.1px;
width: 0.1px;
&:after {
content: "\A";
width:4px;
height:4px;
border-radius:50%;
background: #1574a1;
position: absolute;
top: 8px;
left: -1px;
z-index: 1;
}
&:checked {
+ label::after {
background: #1574a1;
box-shadow: 0 0 0 3px #fff, 0 0 0 5.5px #1574a1;
color: #fff;
}
+ label span {
color: #fff;
}
&.current-day {
&:after {
background: #fff;
background-color: $white;
}
.first-day {
color: $white;
}
}
}
&:checked + label {
color: $white;
background: $blue;
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $blue;
&:after {
background-color: $white;
}
.first-day {
color: $white;
}
}
}
.first-day {
position: absolute;
font-size: 9px;
font-weight: 500;
color: $gray-600;
top: 0;
z-index: 1;
}
label {
position: relative;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
width: 40px;
height: 40px;
min-width: 40px;
border-radius: 50%;
&.past-day {
color: $gray-500;
background-color: transparent;
border: 1px solid transparent;
pointer-events: none;
}
&.selectable-day {
color: $blue;
background-color: $blue-100;
border: 1px solid $blue;
}
&.current-day {
&:after {
content: '';
width: .25rem;
height: .25rem;
border-radius: 50%;
background-color: $blue;
position: absolute;
top: 28px;
}
.first-day {
color: $blue;
}
}
&.future-day {
color: $gray-600;
background-color: transparent;
border: 1px solid transparent;
}
span {
position: relative;
top: -.25rem;
&.small {
font-size: .75rem;
color: $gray-550;
}
}
&:hover,
&:checked {
@include media-breakpoint-up(sm) {
box-shadow: 0 0 0 4px transparent;
background-color: $blue;
color: $white;
&:after {
background-color: $white;
}
}
cursor: pointer;
&.current-day {
+ .first-day {
color: $white;
}
}
.first-day {
color: $white;
}
}
+ p {
display: none;
}
}
}
}
</style>

View file

@ -1,17 +1,6 @@
<template>
<div v-for="(q, i) in questions" :key="i">
<transition appear name="fade" mode="out-in">
<!-- <buttonQuestion
v-if="q.answerSelected || (q.questionSequence === currentQuestionNum)"
class="radioQuestion"
:class="(q.questionSequence === currentQuestionNum) && 'current-question'"
:questionText="q.questionText"
:answers="q.answers"
:groupName="`question-${glassIndex}-${q.questionSequence}`"
v-model="q.answerSelected"
isRequired
:validationRules="validationRules"
/> -->
<buttonQuestion
v-if="q.answerSelected || (q.questionSequence === currentQuestionNum)"
class="radioQuestion"

View file

@ -18,11 +18,11 @@ const storeActions = {
LOOKUP_VEHICLE_BY_VIN: "lookupVehicleByVin",
LOOKUP_VIN_BY_PLATE: "lookupVinByPlate",
LOOKUP_VIN_BY_ADDRESS: "lookupVinByAddress",
GET_PARTS_OR_QUESTIONS: "getPartsOrQuestions",
GET_PARTS: "getParts",
GET_CAPABILITY_QUESTIONS: "getCapabilityQuestions",
GET_PART_FROM_CAPABILITY_QUESTION_ANSWER: "getPartFromCapabilityQuestionAnswer",
GET_PART_FROM_CAPABILITY_QUESTION_ANSWER:
"getPartFromCapabilityQuestionAnswer",
GET_MOLDING_QUESTIONS: "getMoldingQuestions",
SAVE_SESSION: "saveSession",
LOAD_SESSION: "loadSession",
@ -45,22 +45,23 @@ const storeActions = {
RESET_STATE: "resetState",
// SAVE COMPONENT STATE
SAVE_VEHICLE_YEAR: "saveVehicleYear",
SAVE_VEHICLE_MAKE:"saveVehicleMake",
SAVE_VEHICLE_MODEL:"saveVehicleModel",
SAVE_VEHICLE_YEAR: "saveVehicleYear",
SAVE_VEHICLE_MAKE: "saveVehicleMake",
SAVE_VEHICLE_MODEL: "saveVehicleModel",
SAVE_VEHICLE_STYLE: "saveVehicleStyle",
SAVE_VEHICLE_DAMAGE: "saveVehicleDamage",
SAVE_VIN_LOOKUP: "saveVinLookup",
SAVE_SERVICE_LOCATION: "saveServiceLocation",
SAVE_EMAIL: "saveEmail",
SAVE_REGISTRATION_LICENSE_PLATE_LOOKUP: "saveRegistrationLicensePlateLookup",
SAVE_VIN: "saveVin",
SAVE_VEHICLE_DAMAGE: "saveVehicleDamage",
SAVE_VIN_LOOKUP: "saveVinLookup",
SAVE_SERVICE_LOCATION: "saveServiceLocation",
SAVE_EMAIL: "saveEmail",
SAVE_REGISTRATION_LICENSE_PLATE_LOOKUP: "saveRegistrationLicensePlateLookup",
SAVE_VIN: "saveVin",
SAVE_REGISTRATION_ADDRESS_LOOKUP: "saveRegistrationAddressLookup",
SAVE_GLASS_PARTS: "saveGlassParts",
SAVE_PART_QUESTION_ANSWERS: "savePartQuestionAnswers",
RESET_MOLDING_AND_CAPABILITY_QUESTIONS_IF_NEEDED: "resetMoldingAndCapabilityQuestionAnswersIfNeeded",
RESET_MOLDING_AND_CAPABILITY_QUESTIONS_IF_NEEDED:
"resetMoldingAndCapabilityQuestionAnswersIfNeeded",
SAVE_MOLDING_QUESTION_ANSWERS: "saveMoldingQuestionAnswers",
SAVE_CAPABILITY_QUESTION_ANSWERS: "saveCapabilityQuestionAnswers"
SAVE_CAPABILITY_QUESTION_ANSWERS: "saveCapabilityQuestionAnswers",
};
export { storeActions };

View file

@ -1,5 +1,4 @@
const storeMutations = {
// VEHICLE MUTATIONS
UPDATE_YEAR: "updateYear",
UPDATE_MAKE: "updateMake",
@ -22,7 +21,7 @@ const storeMutations = {
UPDATE_GLASS_PARTS: "updateGlassParts",
UPDATE_OTHER_PARTS: "updateOtherParts",
UPDATE_REGISTRATION_LICENSE_PLATE : "updateRegistrationLicensePlate",
UPDATE_REGISTRATION_LICENSE_PLATE: "updateRegistrationLicensePlate",
UPDATE_REGISTRATION_ADDRESS: "updateRegistrationAddress",
UPDATE_REGISTRATION_CITY: "updateRegistrationCity",
UPDATE_REGISTRATION_STATE: "updateRegistrationState",
@ -69,4 +68,4 @@ const storeMutations = {
UPDATE_TRIGGERED_SITE_ENTRY: "updateTriggeredSiteEntry",
};
export { storeMutations };
export { storeMutations };

View file

@ -0,0 +1,42 @@
/**
* Helper for GA click event. When the user mouse clicks on a `base-input-button`, we
* push the click event. When the user tabs through a list of radio buttons via a
* keyboard, we only want to push the GA click event iff the select was deliberate
* (space/enter key) or if there is a selection and the user tabs off of the radio group.
*/
let lastFocusedInputGroupName = "";
let onButtonQuestionLostFocusCallback = null;
// TODO KO add tests
const handleAnyComponentFocus = (e) => {
const targetType = e.target.type;
if (targetType !== "radio" && targetType !== "checkbox") {
invokeButtonQuestionLostFocusCallback();
}
};
const handleButtonComponentFocus = (e) => {
if (e && lastFocusedInputGroupName !== e.groupName) {
invokeButtonQuestionLostFocusCallback();
}
};
const handleInputComponentBlur = (e) => {
if (e) {
lastFocusedInputGroupName = e.groupName;
onButtonQuestionLostFocusCallback = e.onButtonQuestionLostFocusCallback;
}
};
const invokeButtonQuestionLostFocusCallback = () => {
if (onButtonQuestionLostFocusCallback) {
onButtonQuestionLostFocusCallback();
}
};
export {
handleAnyComponentFocus,
handleButtonComponentFocus,
handleInputComponentBlur,
};

View file

@ -18,7 +18,6 @@ export async function loadSessionIfPresent() {
return null;
}
// TODO KO isOrderDifferent and isOrderSubmitted
// Reset state if cookie says to.
if (funnelCookie.ShouldResetState) {
baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE);

View file

@ -34,14 +34,14 @@ describe("addressVehiclesQuestion.vue", () => {
const wrapper = shallowMount(addressVehiclesQuestion, {
mixins: [mockMixin],
propsData: {
vehicles: ["1", "2"],
modelValue: ["1", "2"],
vehicles: ["1", "2", "newValue"],
modelValue: "2",
}
});
// Act
const localThis = { $emit: jest.fn() }
addressVehiclesQuestion.computed.selectedVehicleVinAsArray.set.call(localThis, ['newValue']);
addressVehiclesQuestion.computed.selectedVehicleVin.set.call(localThis, 'newValue');
// Assert
expect(localThis.$emit).toBeCalledWith("update:modelValue", "newValue");

View file

@ -5,7 +5,7 @@
groupName="ChooseAddressVehicle"
:questionText="questionText"
:answers="vehicles"
v-model="selectedVehicleVinAsArray"
v-model="selectedVehicleVin"
isRequired
:validation-rules="validationRules"
:valueToLogType="ValueToLogTypes.LAST_5"
@ -63,18 +63,16 @@ export default {
questionText() {
return this.getCmsContent("VehicleConfirmationQuestion", "QuestionText");
},
selectedVehicleVinAsArray: {
selectedVehicleVin: {
get: function() {
const modelValueAsArray = this.modelValue ? [this.modelValue] : [];
return modelValueAsArray;
return this.modelValue;
},
set: function(newValue) {
const newValueAsScalar = newValue && newValue.length > 0 ? newValue[newValue.length-1] : null;
this.$emit("update:modelValue", newValueAsScalar);
this.$emit("update:modelValue", newValue);
}
},
selectedVehicle() { // this computed is only needed for the computed differentVehicleAlertBody text above
return this.vehicles.find( ({ vin }) => vin === this.selectedVehicleVinAsArray[this.selectedVehicleVinAsArray.length-1] );
return this.vehicles.find( ({ vin }) => vin === this.selectedVehicleVin[this.selectedVehicleVin.length-1] );
},
},
components: {

View file

@ -195,7 +195,7 @@ export default {
selectedVehicleVin: {
handler() {
// does this vehicle match the previously selected carId?
this.isCarIdDifferent = this.selectedVehicle.vehicle.carId !== store.getters.vehicle.carId;
this.isCarIdDifferent = this.selectedVehicle?.vehicle.carId !== store.getters.vehicle.carId;
if (this.isCarIdDifferent) {
this.$refs.funnelFooter.updateButtonText(`Continue with ${this.selectedVehicle.vehicle.year} ${this.selectedVehicle.vehicle.make} ${this.selectedVehicle.vehicle.model}`);
} else {

View file

@ -6,7 +6,8 @@
:answers="answersToDisplay"
:groupName="groupName"
buttonType="listCard"
v-model="selectedDamageLocations"
isRequired
v-model="selectedValues"
validationRules="damage-location-required"
/>
</div>
@ -26,12 +27,11 @@ export default ({
name: "damageLocationQuestion",
data(){
return {
damageOptions: {},
selectedDamageLocations: this.modelValue
damageOptions: Object,
}
},
props: {
modelValue: [Array, String, Number],
props: {
modelValue: Array,
groupName: String,
cmsWidgetName: String,
},
@ -47,6 +47,14 @@ export default ({
answersFromCms(){
return this.getCmsContent(this.cmsWidgetName, 'Answers');
},
selectedValues: {
get: function() {
return this.modelValue;
},
set: function(newValue) {
this.$emit("update:modelValue", newValue);
}
},
damageOptionsMap(){
return {
Windshield: true,
@ -69,11 +77,6 @@ export default ({
});
},
},
watch: {
selectedDamageLocations(selectedDamageLocations) {
this.$emit("update:modelValue", selectedDamageLocations);
}
},
components: {
buttonQuestion,
},

View file

@ -36,8 +36,7 @@ describe("replace-options-question.vue", () => {
});
describe("replace-options-question.vue", () => {
// TODO KO
test.skip("when updateSelectedValues method is called with a single answerToDisplay it will call to update this.selectedValues", async () => {
test("when updateSelectedValues method is called with a single answerToDisplay it will call to update this.selectedReplaceOptions", async () => {
//Arrange
const { wrapper, cmsContent, replaceOptions

View file

@ -8,7 +8,7 @@
:answers="answersToDisplay"
:groupName="groupName"
buttonType="listCard"
v-model="selectedReplaceOptions"
v-model="selectedValues"
:validationRules="validationRules"
:suppressError="suppressError"
:isRequired="isRequired"
@ -20,22 +20,19 @@
<script>
import buttonQuestion from "@/common-components/button-question/button-question";
import store from "@/store";
// import buttonQuestionWrapperMixin from "@/mixins/button-question-wrapper-mixin";
export default ({
name: "replaceOptionsQuestion",
// mixins: [buttonQuestionWrapperMixin],
data() {
data(){
return {
replaceOptions: [],
selectedReplaceOptions: this.modelValue
replaceOptions: this.isMultiSelect ? [] : "",
}
},
props: {
modelValue: [Array, String, Number],
isAvailable: Boolean,
filterByVehicleCategory: Boolean,
groupName: String,
modelValue: [Array, String, Number],
isMultiSelect: Boolean,
validationRules: String,
suppressError: Boolean,
@ -43,15 +40,13 @@ export default ({
isRequired: Boolean,
},
methods: {
initializeComponent(replaceOptions) {
initializeComponent(replaceOptions){
this.replaceOptions = replaceOptions;
},
updateSelectedValues() {
// UPDATE SELECTEDVALUES IF ONLY ONE ANSWER
// ex: BackGlass stationary
if (Array.isArray(this.answersToDisplay) && this.answersToDisplay.length === 1) {
const selectedAnswer = this.answersToDisplay[0].Name;
this.selectedReplaceOptions = this.isMultiSelect ? [selectedAnswer] : selectedAnswer;
if(Array.isArray(this.answersToDisplay) && this.answersToDisplay.length === 1) {
this.selectedValues = this.isMultiSelect ? [this.answersToDisplay[0].Name] : this.answersToDisplay[0].Name;
}
},
},
@ -62,6 +57,14 @@ export default ({
answersFromCms(){
return this.getCmsContent(this.cmsWidgetName, 'Answers');
},
selectedValues: {
get: function() {
return this.modelValue;
},
set: function(newValue) {
this.$emit("update:modelValue", newValue);
}
},
answersToDisplay(){
const filteredAnswers = Array.isArray(this.answersFromCms)
? this.answersFromCms.filter(ans =>
@ -88,11 +91,8 @@ export default ({
},
shouldDisplayReplaceOptionsQuestion(shouldDisplayReplaceOptionsQuestion) {
if (!shouldDisplayReplaceOptionsQuestion) {
this.selectedReplaceOptions = [];
this.selectedValues = this.isMultiSelect ? [] : "";
}
},
selectedReplaceOptions(selectedReplaceOptions) {
this.$emit("update:modelValue", selectedReplaceOptions);
}
},
components: {

View file

@ -57,7 +57,7 @@ export default ({
name: "sideDoorOptions",
props: {
groupName: String,
modelValue: Array,
modelValue: Object,
selectedDamageLocations: Array,
cmsWidgetName: String,
},

View file

@ -55,8 +55,7 @@ jest.mock("@/store", () => ({
},
}));
// TODO KO
describe.skip("vehicle-damage.vue", () => {
describe("vehicle-damage.vue", () => {
describe("navigation", () => {
test("BackButtonAction triggers a router.navigateWithoutSaving change", async () => {
@ -130,20 +129,20 @@ describe.skip("vehicle-damage.vue", () => {
},
});
wrapper.vm.selectedDamageLocations = ["Windshield", "SideDoor", "RearWindow"];
wrapper.vm.selectedWindshieldOptions = {
selectedWindshieldChipCount: null,
selectedWindshieldReplaceOptions: ["Single"],
selectedWindshieldDamageType: "Replace"
};
wrapper.vm.sideDoorOptionsData = {
selectedDoorSides: ["DriverSide", "PassengerSide"],
selectedDriverSideReplaceOptions: ["Back"],
selectedPassengerSideReplaceOptions: ["Quarter"]
};
wrapper.vm.selectedRearReplaceOptions = ["Stationary"];
wrapper.setData({
selectedDamageLocations: ["Windshield", "SideDoor", "RearWindow"],
selectedWindshieldOptions: {
selectedWindshieldChipCount: null,
selectedWindshieldReplaceOptions: ["Single"],
selectedWindshieldDamageType: "Replace"
},
sideDoorOptionsData: {
selectedDoorSides: ["DriverSide", "PassengerSide"],
selectedDriverSideReplaceOptions: ["Back"],
selectedPassengerSideReplaceOptions: ["Quarter"]
},
selectedRearReplaceOptions: "Stationary",
})
const expectedGlassToReplace = [{ glassLocation: "Windshield", glassName: "Single" }, { glassLocation: "Driver", glassName: "Back" },
{ glassLocation: "Passenger", glassName: "Quarter" }, { glassLocation: "Rear", glassName: "Stationary" }];
@ -520,19 +519,19 @@ describe.skip("vehicle-damage.vue", () => {
const storeWindshieldOptions = [[1, false, "Windshield", "Single", {
selectedWindshieldDamageType: damageLocationsSelected.REPLACE,
selectedWindshieldChipCount: [], selectedWindshieldReplaceOptions: [damageLocationsSelected.SINGLE]
selectedWindshieldChipCount: null, selectedWindshieldReplaceOptions: [damageLocationsSelected.SINGLE]
}],
[2, false, "Windshield", "Driver", {
selectedWindshieldDamageType: damageLocationsSelected.REPLACE,
selectedWindshieldChipCount: [], selectedWindshieldReplaceOptions: [damageLocationsSelected.DRIVER]
selectedWindshieldChipCount: null, selectedWindshieldReplaceOptions: [damageLocationsSelected.DRIVER]
}],
[3, false, "Windshield", "Passenger", {
selectedWindshieldDamageType: damageLocationsSelected.REPLACE,
selectedWindshieldChipCount: [], selectedWindshieldReplaceOptions: [damageLocationsSelected.PASSENGER]
selectedWindshieldChipCount: null, selectedWindshieldReplaceOptions: [damageLocationsSelected.PASSENGER]
}],
[4, true, "", "", {
selectedWindshieldDamageType: damageLocationsSelected.REPAIR,
selectedWindshieldChipCount: [2], selectedWindshieldReplaceOptions: []
selectedWindshieldChipCount: 2, selectedWindshieldReplaceOptions: []
}]
];
test.each(storeWindshieldOptions)("getWindshieldOptionsFromStore test #%s", async (testNum, isRepair, damageLocation, damageName, expectedWindshieldOptions) => {
@ -619,8 +618,8 @@ describe.skip("vehicle-damage.vue", () => {
expect(glassSelections).toEqual(expectedGlass);
});
const rearReplaceOptions = [["Rear", "Stationary", [damageLocationsSelected.STATIONARY]],
["Rear", "Slider", [damageLocationsSelected.SLIDER]]
const rearReplaceOptions = [["Rear", "Stationary", damageLocationsSelected.STATIONARY],
["Rear", "Slider", damageLocationsSelected.SLIDER]
];
test.each(rearReplaceOptions)("getRearReplaceOptionsFromStore for %s-%s returns expected %s", async (damageLocation, damageName, expectedGlass) => {

View file

@ -17,7 +17,7 @@ describe("windshield-chip-count-question.vue", () => {
const { wrapper } = setupMocks({ modelValueProp: 1 });
//Act
await wrapper.setData({ numberOfChips: 2 });
wrapper.vm.selectedValue = "2";
//Assert
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([2]);

View file

@ -7,7 +7,7 @@
:groupName="groupName"
buttonType="listButtonHorizontal"
useTextForValue
v-model="numberOfChips"
v-model="selectedValue"
:validationRules="validationRules"
isRequired
/>
@ -17,16 +17,9 @@
<script>
import buttonQuestion from "@/common-components/button-question/button-question";
// import buttonQuestionWrapperMixin from "@/mixins/button-question-wrapper-mixin";
export default ({
name: "windshieldChipCountQuestion",
// mixins: [buttonQuestionWrapperMixin],
data() {
return {
numberOfChips: this.modelValue
}
},
name: "windshieldOptions",
props: {
modelValue: [String, Number],
groupName: String,
@ -41,14 +34,18 @@ export default ({
answersFromCms(){
return this.getCmsContent(this.cmsWidgetName, 'Answers');
},
selectedValue: {
get: function() {
return this.modelValue;
},
set: function(newValue) {
const numberValue = Number(newValue);
this.$emit("update:modelValue", numberValue);
}
},
},
components: {
buttonQuestion,
},
watch: {
numberOfChips(numberOfChips) {
this.$emit("update:modelValue", numberOfChips);
}
}
})
</script>

View file

@ -15,7 +15,7 @@ describe("windshield-damage-type-question.vue", () => {
await wrapper.vm.$nextTick();
//Assert
expect(wrapper.vm.selectedWindshieldDamageType).toEqual("Repair");
expect(wrapper.vm.selectedValues).toEqual("Repair");
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([{ modelValue: "Replace" }]);
});
});

View file

@ -1,34 +1,25 @@
<template>
<transition name="fade" mode="out-in">
<div
class="windshield-damage-type-question"
v-if="isAvailable"
aria-live="polite">
<div class="windshield-damage-type-question" v-if="isAvailable" aria-live="polite">
<buttonQuestion
:questionText="questionText"
:answers="answersFromCms"
:groupName="groupName"
buttonType="listCard"
v-model="selectedWindshieldDamageType"
v-model="selectedValues"
:suppressError="suppressError"
:validationRules="validationRules"
isRequired />
isRequired
/>
</div>
</transition>
</template>
<script>
import buttonQuestion from "@/common-components/button-question/button-question";
// import buttonQuestionWrapperMixin from "@/mixins/button-question-wrapper-mixin";
export default {
export default ({
name: "windshieldDamageTypeQuestion",
// mixins: [buttonQuestionWrapperMixin],
data() {
return {
selectedWindshieldDamageType: this.modelValue,
};
},
props: {
modelValue: String,
groupName: String,
@ -38,20 +29,23 @@ export default {
cmsWidgetName: String,
},
computed: {
questionText() {
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
questionText(){
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
answersFromCms() {
return this.getCmsContent(this.cmsWidgetName, "Answers");
answersFromCms(){
return this.getCmsContent(this.cmsWidgetName, 'Answers');
},
},
watch: {
selectedWindshieldDamageType(selectedWindshieldDamageType) {
this.$emit("update:modelValue", selectedWindshieldDamageType);
selectedValues: {
get: function() {
return this.modelValue;
},
set: function(newValue) {
this.$emit("update:modelValue", newValue);
}
},
},
components: {
buttonQuestion,
},
};
</script>
}
})
</script>

View file

@ -57,8 +57,8 @@ defineRule("windshield-replace-options-required", required(errorMessages.WINSHIE
defineRule("check-for-repair-and-replace", (selectedWindshieldDamageType, selectedDamageLocations) => {
return selectedWindshieldDamageType.toString() != damageLocationsSelected.REPAIR ||
!selectedDamageLocations.includes(damageLocationsSelected.WINDSHIELD) ||
selectedDamageLocations.length === 1;
(!selectedDamageLocations.includes(damageLocationsSelected.WINDSHIELD) && !selectedDamageLocations[0]?.includes(damageLocationsSelected.WINDSHIELD)) ||
(selectedDamageLocations[0].length === 1);
});
defineRule("repair-only", (value) => {
return value.toString() === damageLocationsSelected.REPAIR;
@ -83,7 +83,7 @@ export default ({
},
props: {
modelValue: String,
modelValue: Object,
selectedDamageLocations: Array,
hasRepairReplaceConflict: Boolean,
hasSplitSingleConflict: Boolean,

View file

@ -1,15 +1,15 @@
<template>
<buttonQuestion
class="radioQuestion"
isOverflowScrollable
selectingInitiatesLoad
:questionText="questionText"
:answers="makes"
groupName="ChooseVehicleMake"
textPosition="text-start"
v-model="selectedMake"
:selectOnKeypress="false"
isRequired />
<buttonQuestion
class="radioQuestion"
isOverflowScrollable
selectingInitiatesLoad
:questionText="questionText"
:answers="makes"
groupName="ChooseVehicleMake"
textPosition="text-start"
v-model="selectedValue"
isRequired
/>
</template>
<script>
@ -18,44 +18,44 @@ import buttonQuestion from "@/common-components/button-question/button-question"
import store from "@/store";
import { storeActions } from "@/constants/store-actions.js";
import baseMixin from "@/mixins/base-mixin.js";
// import buttonQuestionWrapperMixin from "../../../mixins/button-question-wrapper-mixin";
export default {
name: "make-question",
// mixins: [buttonQuestionWrapperMixin],
data() {
return {
makes: [],
selectedMake: ""
};
name: "make-question",
data() {
return {
makes: [],
};
},
props: {
modelValue: String,
cmsWidgetName: String,
},
computed: {
questionText(){
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
props: {
modelValue: String,
cmsWidgetName: String,
selectedValue: {
get: function() {
return this.modelValue
},
set: function(newValue) {
this.$emit("update:modelValue", newValue);
}
}
},
components: {
buttonQuestion,
},
methods: {
loadInitialData() {
return baseMixin.methods.dispatchStoreAction(
storeActions.GET_VEHICLE_MAKES,
{ year: store.getters.vehicle.year }
);
},
computed: {
questionText() {
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
},
},
components: {
buttonQuestion,
},
methods: {
loadInitialData() {
return baseMixin.methods.dispatchStoreAction(
storeActions.GET_VEHICLE_MAKES,
{ year: store.getters.vehicle.year }
);
},
initializeComponent(initialData) {
this.makes = initialData;
},
},
watch: {
selectedMake(selectedMake) {
this.$emit("update:modelValue", selectedMake);
},
initializeComponent(initialData) {
this.makes = initialData;
},
},
};
</script>

View file

@ -7,8 +7,7 @@
:answers="models"
groupName="ChooseVehicleModel"
textPosition="text-start"
v-model="selectedModel"
:selectOnKeypress="false"
v-model="selectedValue"
isRequired
/>
</template>
@ -19,15 +18,12 @@ import buttonQuestion from "@/common-components/button-question/button-question"
import store from "@/store";
import { storeActions } from "@/constants/store-actions.js";
import baseMixin from "@/mixins/base-mixin.js";
import buttonQuestionWrapperMixin from "@/mixins/button-question-wrapper-mixin";
export default {
name: "model-question",
mixins: [buttonQuestionWrapperMixin],
data() {
return {
models: [],
selectedModel: ""
};
},
props: {
@ -38,6 +34,14 @@ export default {
questionText(){
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
selectedValue: {
get: function() {
return this.modelValue
},
set: function(newValue) {
this.$emit("update:modelValue", newValue);
}
}
},
components: {
buttonQuestion,
@ -53,10 +57,5 @@ export default {
this.models = initialData;
},
},
watch: {
selectedModel(selectedModel) {
this.$emit("update:modelValue", selectedModel);
}
}
};
</script>

View file

@ -18,9 +18,7 @@ const featureListData = {
modelValueProp: {}
}
// TODO KO
describe.skip("glass-part-question.vue", () => {
describe("glass-part-question.vue", () => {
test("Part data passed in, should map data for ButtonQuestion (radio type)", async () => {
//Arrange

View file

@ -13,8 +13,7 @@
altText=""
isRequired
:groupName="`${glassLocation}-${glassName}`"
:validationRules="tintValidationRules"
>
:validationRules="tintValidationRules">
<div class="row my-2" aria-live="polite">
<div class="col">
<buttonQuestion
@ -28,7 +27,7 @@
isRequired
:groupName="`${glassLocation}-${glassName}-${selectedTint}`"
:validationRules="partValidationRules"
/>
/>
</div>
</div>
</buttonQuestion>
@ -63,7 +62,10 @@ export default {
glassLocation: String,
colorAnswers: Array,
modelValue: Object,
alreadyPopulatedPartsData: Array
alreadyPopulatedPartsData: {
type: Array,
default: () => [],
},
},
mounted() {
this.LoadPreselectedValues();
@ -74,12 +76,18 @@ export default {
computed: {
tintValidationRules() {
const validationRuleName = `${this.glassLocation}-${this.glassName}-tint-required`;
defineRule(validationRuleName, required(errorMessages.OPTION_REQUIRED));
defineRule(
validationRuleName,
required(errorMessages.OPTION_REQUIRED)
);
return validationRuleName;
},
partValidationRules() {
const validationRuleName = `${this.glassLocation}-${this.glassName}-part-required`;
defineRule(validationRuleName, required(errorMessages.OPTION_REQUIRED));
defineRule(
validationRuleName,
required(errorMessages.OPTION_REQUIRED)
);
return validationRuleName;
},
colorQuestionText() {
@ -110,16 +118,29 @@ export default {
return this.modelValue?.partNumber;
},
set(newValue) {
this.$emit("update:modelValue", this.partsForSelectedTint.filter(part => part.partNumber == newValue)[0]);
this.$emit(
"update:modelValue",
this.partsForSelectedTint.filter(
(part) => part.partNumber == newValue
)[0]
);
},
},
partsForSelectedTint() {
const matchingGlass = this.PartDataFromApi.partsOrQuestions?.filter(dataForGlassLocationAndName =>
dataForGlassLocationAndName.glassName == this.glassName &&
dataForGlassLocationAndName.glassLocation == this.glassLocation);
const matchingGlassParts = matchingGlass?.length == 1 ? matchingGlass[0].parts : [];
return matchingGlassParts.filter(part => part.color == this.selectedTint) ?? [];
const matchingGlass = this.PartDataFromApi.partsOrQuestions?.filter(
(dataForGlassLocationAndName) =>
dataForGlassLocationAndName.glassName == this.glassName &&
dataForGlassLocationAndName.glassLocation ==
this.glassLocation
);
const matchingGlassParts =
matchingGlass?.length == 1 ? matchingGlass[0].parts : [];
return (
matchingGlassParts.filter(
(part) => part.color == this.selectedTint
) ?? []
);
},
// Creates a map of the feature list data in the correct Name/Value
@ -151,7 +172,9 @@ export default {
},
PartDataFromApi() {
return this.$store.getters.pageData(this.$route.query.fmgPage) ?? {};
return (
this.$store.getters.pageData(this.$route.query.fmgPage) ?? {}
);
},
},
methods: {
@ -188,7 +211,8 @@ export default {
// Check if only a single part is present for the tint and set the v-model if it is.
AutoSelectIfSinglePart() {
if (this.partsForSelectedTint?.length == 1) {
this.selectedPartNumber = this.partsForSelectedTint[0].partNumber;
this.selectedPartNumber =
this.partsForSelectedTint[0].partNumber;
}
},
@ -197,7 +221,7 @@ export default {
this.$nextTick(() => {
if (this.modelValue !== undefined) {
// Populate button-question model-value if parts data already exists in VueX
this.selectedTint = this.alreadyPopulatedPartsData.filter(part => part.partNumber === this.selectedPartNumber)[0]?.color;
this.selectedTint = this.modelValue?.color
}
});
},
@ -205,8 +229,8 @@ export default {
watch: {
selectedTint() {
this.AutoSelectIfSinglePart();
}
}
},
},
};
</script>

View file

@ -148,7 +148,7 @@ describe("vehicle-parts.vue", () => {
expect(arePagePrerequisitesValid).toBe(true);
});
test("Initial data, should populate this.selectedGlassParts", async () => {
test.only("Initial data, should populate this.selectedGlassParts", async () => {
//Arrange
store.getters.pageData.mockReturnValue(basePartResponse);
@ -183,7 +183,16 @@ describe("vehicle-parts.vue", () => {
await nextTick();
//Assert
expect(wrapper.vm.selectedGlassParts).toEqual({ "Rear-Stationary": { "Rear": ['DB12209YPYNOEM'] } });
expect(wrapper.vm.selectedGlassParts).toEqual({
"Rear-Stationary": {
partNumber: "DB12209YPYNOEM",
description: "heated glass, solar, 1 hole",
color: "Gray Tint Privacy",
requiresRecalibration: false,
requiresCapabilityQuestions: false,
childParts: null
}
});
});
test("User had part questions > BackButtonAction triggers a router.navigateWithoutSaving change with correct scenario", async () => {
@ -509,6 +518,8 @@ function setupMocks({ pageHeaderWidgetHeaderText = {}, mountOptionsMockData = {}
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
wrapper.vm.$refs.loadingModal.showModal = jest.fn();
// wrapper.vm.$refs.onSubmit = jest.fn();
// wrapper.vm.$refs.onInvalidSubmit = jest.fn();
return { wrapper, apiPromise };
}

View file

@ -204,9 +204,7 @@ export default {
const partNumber = this.alreadyPopulatedPartsData[key].partNumber;
g.parts.forEach((p) => {
if (p.partNumber === partNumber) {
this.selectedGlassParts[g.glassLocation + "-" + g.glassName] = {
[g.glassLocation]: [partNumber],
};
this.selectedGlassParts[g.glassLocation + "-" + g.glassName] = p;
}
});
});

View file

@ -7,8 +7,7 @@
:answers="styles"
groupName="ChooseVehicleStyle"
textPosition="text-start"
v-model="selectedStyle"
:selectOnKeypress="false"
v-model="selectedValue"
isRequired
/>
</template>
@ -19,24 +18,30 @@ import buttonQuestion from "@/common-components/button-question/button-question"
import store from "@/store";
import { storeActions } from "@/constants/store-actions.js";
import baseMixin from "@/mixins/base-mixin.js";
// import buttonQuestionWrapperMixin from "@/mixins/button-question-wrapper-mixin";
export default {
name: "style-question",
// mixins: [buttonQuestionWrapperMixin],
data() {
return {
styles: [],
selectedStyle: ""
};
},
props: {
modelValue: String,
cmsWidgetName: String,
},
computed: {
questionText(){
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
selectedValue: {
get: function() {
return this.modelValue
},
set: function(newValue) {
this.$emit("update:modelValue", newValue);
}
}
},
components: {
buttonQuestion,
@ -56,10 +61,5 @@ export default {
this.styles = initialData;
},
},
watch: {
selectedStyle(selectedStyle) {
this.$emit("update:modelValue", selectedStyle);
}
}
};
</script>

View file

@ -1,18 +1,15 @@
<template>
<div>
<buttonQuestion
class="radioQuestion"
isOverflowScrollable
selectingInitiatesLoad
:questionText="questionText"
:answers="years"
groupName="ChooseVehicleYear"
textPosition="text-start"
v-model="selectedYear"
:selectOnKeypress="false"
isRequired
/>
</div>
<buttonQuestion
class="radioQuestion"
isOverflowScrollable
selectingInitiatesLoad
:questionText="questionText"
:answers="years"
groupName="ChooseVehicleYear"
textPosition="text-start"
v-model="selectedValue"
isRequired
/>
</template>
<script>
@ -20,27 +17,32 @@ import buttonQuestion from "@/common-components/button-question/button-question"
// Supporting files
import { storeActions } from "@/constants/store-actions.js";
import baseMixin from "@/mixins/base-mixin.js";
// import buttonQuestionWrapperMixin from "@/mixins/button-question-wrapper-mixin";
export default {
name: "year-question",
// mixins: [buttonQuestionWrapperMixin],
data() {
return {
years: [],
selectedYear: ""
};
},
props: {
modelValue: String,
cmsWidgetName: String,
},
components: {
buttonQuestion
buttonQuestion,
},
computed: {
questionText() {
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
questionText(){
return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
},
selectedValue: {
get: function() {
return this.modelValue
},
set: function(newValue) {
this.$emit("update:modelValue", newValue);
}
}
},
methods: {
loadInitialData() {
@ -53,10 +55,5 @@ export default {
this.years = initialData;
},
},
watch: {
selectedYear(selectedYear) {
this.$emit("update:modelValue", selectedYear);
}
}
};
</script>

View file

@ -29,8 +29,6 @@ export default {
logCustomEvent(category, action, label, value) {
const currentPageName = getPageNameByQueryString();
console.log("PUSHING: ", label)
var payload = {
userId: getDeviceIdValue(),
sessionKey: getSessionKeyValue(),
@ -50,6 +48,7 @@ export default {
pushEventToGA(category, action, label, pushToLogApp = false, valueToLogType = null) {
const currentPageName = getPageNameByQueryString();
const labelToLog = getValueToLog(label, valueToLogType);
const eventToBePushed = {
'event': GaEvents.GENERIC_EVENT,
'category': category,
@ -142,7 +141,7 @@ export default {
noSession() {
return getSessionKeyValue() === 0 || getSessionIdValue() === '00000000-0000-0000-0000-000000000000';
}
},
},
computed: {
analyticsPageEvents() {

View file

@ -6,12 +6,11 @@ import { vehicleCategories } from "@/constants/vehicle-categories.js";
import { routerParams } from "@/router/router-constants/router-params";
import { queryStrings } from "@/constants/query-strings";
import { dynamicStrings } from "@/constants/dynamic-strings";
import { settleAllPromises } from "@/helpers/layout-helper";
export default {
data() {
return {
cmsContentByWidget: {}
cmsContentByWidget: {},
};
},
methods: {
@ -19,7 +18,9 @@ export default {
this.$root.cmsContentByWidget = cmsContent;
},
getCmsContent(widgetName, fieldName) {
return this.$root.cmsContentByWidget?.[widgetName]?.[fieldName] ? this.$root.cmsContentByWidget[widgetName][fieldName] : '';
return this.$root.cmsContentByWidget?.[widgetName]?.[fieldName]
? this.$root.cmsContentByWidget[widgetName][fieldName]
: "";
},
dispatchStoreAction(type, payload, encodePayload = true) {
// Encode the payload if required
@ -32,7 +33,7 @@ export default {
savePageDataToStore(page, data) {
store.commit(storeMutations.UPDATE_PAGE_DATA, { page: page, data: data });
},
onSubmit() { }, // DO NOT REMOVE; needed to prevent default form submit behavior. Cannot use .prevent modifier for vee-validate Form
onSubmit() {}, // DO NOT REMOVE; needed to prevent default form submit behavior. Cannot use .prevent modifier for vee-validate Form
onInvalidSubmit({ values, errors, results }) {
// identify the first error field and put focus on it
// get error names array
@ -49,12 +50,15 @@ export default {
return footerInfoBox ? footerInfoBox.offsetHeight : 0;
},
async getZipCodeData(zipCode) {
const serviceZipValidationResponse = await this.dispatchStoreAction(storeActions.VALIDATE_ZIP, { zip: zipCode });
return {
isValid: serviceZipValidationResponse.data.isValid,
const serviceZipValidationResponse = await this.dispatchStoreAction(
storeActions.VALIDATE_ZIP,
{ zip: zipCode }
);
return {
isValid: serviceZipValidationResponse.data.isValid,
isServiceable: serviceZipValidationResponse.data.isServiceable,
state: serviceZipValidationResponse.data.state
state: serviceZipValidationResponse.data.state,
};
},
getEconomyPackagePrice(lineItems) {
@ -82,13 +86,13 @@ export default {
routerParams() {
return routerParams;
},
queryStrings(){
queryStrings() {
return queryStrings;
},
dynamicStrings(){
dynamicStrings() {
return dynamicStrings;
},
cssClassNameForCmsWidget(){
cssClassNameForCmsWidget() {
return "widget-name-" + this.cmsWidgetName;
},
},

View file

@ -1,35 +0,0 @@
import { ref, isRef } from "vue";
export default {
props: {
modelValue: [Array, String, Number],
// modelValueName: {
// type: String,
// required: true,
// },
},
data() {
return {
selectedValue: null,
};
},
created() {
this.selectedValue = this.modelValue;
console.log("Created: ", {
selectedValue: this.selectedValue,
// modelValue: this.modelValue,
// modelValueName: this.modelValueName
})
if (this.modelValueName) {
this[this.modelValueName] = this.selectedValue;
// this.$on("update:modelValue", (dynamicModelValue) => {
// console.log("ON HIT", {
// dynamicModelValue: dynamicModelValue
// })
// this.selectedValue = dynamicModelValue;
// this.$emit("update:modelValue", dynamicModelValue)
// })
}
},
};

View file

@ -1,43 +1,36 @@
import { inputButtonProps } from "@/common-components/base-input-button/button-functionality-props";
export default {
model: {
prop: "modelValue",
event: "change",
},
props: {
modelValue: [Array, String, Number],
value: [String, Number],
isMultiSelect: Boolean,
groupName: String,
...inputButtonProps,
buttonLabel: [Number, String],
buttonLabelSubCopy: String,
buttonImage: String,
altText: {
type: String,
default: ""
default: "",
},
textPosition: String,
screenReaderOnlyText: String,
valueToLogType: String,
validationRules: String,
isWide: Boolean,
isRequired: Boolean,
additionalData: null,
},
data() {
return {
selectedValue: null,
};
},
mounted() {
this.selectedValue = this.modelValue;
},
methods: {
handleAnswerChange(e) {
if (this.preHandleAnswerChange) {
this.preHandleAnswerChange(e)
}
this.$emit("change", e);
computed: {
selectedValue: {
get() {
return this.modelValue;
},
set(e) {
if (this.preHandleAnswerChange) {
this.preHandleAnswerChange(e);
}
this.$emit("update:modelValue", e);
},
},
},
watch: {
selectedValue(selectedValue) {
this.$emit("update:modelValue", selectedValue);
},
},
};
};

View file

@ -0,0 +1,81 @@
describe("input-button-wrapper-mixin", () => {
describe("mouse clicks", () => {
describe("checkbox", () => {
test.todo("clicking once checks the baseInputButton");
test.todo("clicking twice unchecks the baseInputButton");
test.todo(
"is initially checked => checking unchecks the baseInputButton"
);
test.todo("clicked => correct event and value are emitted");
});
describe("radio", () => {
test.todo("clicking once selects the baseInputButton");
test.todo("clicking twice keeps the baseInputButton selected");
test.todo(
"is initially selected => click keeps the baseInputButton selected"
);
test.todo("clicked => correct event and value are emitted");
});
});
describe("keyboard navigation and", () => {
describe("checkbox", () => {
test.todo(
"focus on a checkbox => inputButtonClicked is not emitted"
);
test.todo(
"blur from a checkbox => inputButtonClicked is not emitted"
);
test.todo(
"focus and click space on a checkbox => inputButtonClicked is emitted with correct value"
);
test.todo(
"focus and click space on a checkbox that is already checked => inputButtonClicked is emitted with correct value"
);
test.todo(
"focus and click enter on a checkbox => inputButtonClicked is emitted with correct value"
);
test.todo(
"focus and click enter on a checkbox that is already checked => inputButtonClicked is emitted with correct value"
);
});
describe("radio", () => {
test.todo(
"focus on a radio button => inputButtonClicked is not emitted"
);
test.todo(
"blur from a radio button => inputButtonClicked is not emitted"
);
test.todo(
"focus and click space on a radio button => inputButtonClicked is emitted with correct value"
);
test.todo(
"focus and click space on a radio button that is already selected => inputButtonClicked is emitted with correct value"
);
test.todo(
"focus and click enter on a radio button => inputButtonClicked is emitted with correct value"
);
test.todo(
"focus and click enter on a radio button that is already selected => inputButtonClicked is emitted with correct value"
);
});
});
});

View file

@ -136,9 +136,6 @@ export default {
const hasChildPartQuestions = this.hasChildPartQuestions(partsOrQuestions);
const hasCapabilityQuestions = this.hasCapabilityQuestions(partsOrQuestions);
console.log({
hasGlassLocationWithMultipleParts: hasGlassLocationWithMultipleParts
})
if (hasPartQuestions && this.currentPageComesBeforePage(currentPage, fmgPageValues.PART_QUESTIONS)) {
self.$router.navigateWithSaving(self.navigationScenarios.HAS_PART_QUESTIONS, self.$route, {}, {}, { partsOrQuestions: partsOrQuestions });
}

View file

@ -239,8 +239,6 @@ function navigateToUrl(url, optionalQuery = {}) {
externalUrl.searchParams.append(queryKey, optionalQuery[queryKey]);
}
externalUrl.searchParams.append("experiments", "ConceptFunnel=ConceptFunnel_V1=ConceptFunnel_TEST=true");
window.location.assign(externalUrl);
}

View file

@ -1,4 +1,4 @@
import { createStore } from "vuex";
import { createStore, Store } from "vuex";
import { endpoints } from "@/constants/endpoints.js";
import { storeMutations } from "@/constants/store-mutations";
import { getDateForSavedSessionTimeout } from "@/helpers/heritage-integration/session-helper";
@ -53,19 +53,19 @@ const getDefaultState = () => {
capabilityQuestionAnswers: null,
},
lineItems: {
glassParts: null
glassParts: null,
},
payment: {
isInsurance: null,
insuranceCoverage: {
isVerified: null
}
isVerified: null,
},
},
referralNumber: null,
referralDate: null,
referralCorrelationId: null,
accountNumber: 0,
eon: null
eon: null,
},
applicationUser: {
eventBus: [],
@ -76,9 +76,15 @@ const getDefaultState = () => {
crmCustomerId: null,
lastPageVisited: null,
experiments: [],
triggeredSiteEntry: false
triggeredSiteEntry: false,
},
}
// gaClickInformation: {
// currentlySelectedValues: {},
// firedGaClickEventValues: {},
// lastFocusedInputGroup: "",
// wasLastFocusedInputMultiselect: undefined,
// },
};
};
export const state = getDefaultState();
@ -195,7 +201,6 @@ export const mutations = {
state.order.customer.emailAddress = customerEmailAddress;
},
updateVehicle(state, vehicleInfo) {
state.order.vehicle.year = vehicleInfo.year;
state.order.vehicle.make = vehicleInfo.make;
state.order.vehicle.model = vehicleInfo.model;
@ -209,7 +214,8 @@ export const mutations = {
state.order.vehicle.imageColor = vehicleInfo.imageVifColor;
},
updateRegistration(state, registrationInfo) {
state.order.vehicle.registration.licensePlate = registrationInfo?.licensePlate;
state.order.vehicle.registration.licensePlate =
registrationInfo?.licensePlate;
state.order.vehicle.registration.address = registrationInfo?.address;
state.order.vehicle.registration.city = registrationInfo?.city;
state.order.vehicle.registration.state = registrationInfo?.state;
@ -235,7 +241,7 @@ export const mutations = {
state.applicationUser.crmCustomerId = crmCustomerId;
},
updateLastPageVisited(state, lastPageVisited) {
state.applicationUser.lastPageVisited = lastPageVisited
state.applicationUser.lastPageVisited = lastPageVisited;
},
// EVENT BUS MUTATIONS
addEventToBus(state, event) {
@ -244,8 +250,7 @@ export const mutations = {
removeEventFromBus(state, eventData) {
const matchedEvent = state.applicationUser.eventBus.find(
({ category, subCategory }) =>
category === eventData.category &&
subCategory === eventData.subCategory
category === eventData.category && subCategory === eventData.subCategory
);
const itemIndex = state.applicationUser.eventBus.indexOf(matchedEvent);
@ -331,7 +336,7 @@ export const mutations = {
state: orderInformation.vehicle.registration.state,
zipCode: orderInformation.vehicle.registration.zipCode,
licensePlate: orderInformation.vehicle.registration.licensePlateNumber,
}
},
});
state.order.damage.glassToReplace = orderInformation.damage.glassToReplace;
@ -340,13 +345,18 @@ export const mutations = {
state.order.lineItems.glassParts = orderInformation.parts;
state.order.accountNumber = orderInformation.accountNumber;
state.order.serviceLocation.address = orderInformation.serviceLocation.streetAddress,
state.order.serviceLocation.city = orderInformation.serviceLocation.city,
state.order.serviceLocation.state = orderInformation.serviceLocation.state,
state.order.serviceLocation.zipCode = orderInformation.serviceLocation.zipCode;
(state.order.serviceLocation.address =
orderInformation.serviceLocation.streetAddress),
(state.order.serviceLocation.city =
orderInformation.serviceLocation.city),
(state.order.serviceLocation.state =
orderInformation.serviceLocation.state),
(state.order.serviceLocation.zipCode =
orderInformation.serviceLocation.zipCode);
state.order.payment.isInsurance = orderInformation.IsInsuranceOrder;
state.order.payment.insuranceCoverage.isVerified = orderInformation?.insuranceInfo.coverageVerified;
state.order.payment.insuranceCoverage.isVerified =
orderInformation?.insuranceInfo.coverageVerified;
state.order.customer.emailAddress = orderInformation.customer.emailAddress;
state.applicationUser.experiments = orderInformation.experiments;
@ -356,8 +366,23 @@ export const mutations = {
},
updateTriggeredSiteEntry(state, wasSiteEntryTriggered) {
state.applicationUser.triggeredSiteEntry = wasSiteEntryTriggered;
}
}
},
// START GA click event mutations
// updateCurrentlySelectedValues(state, groupName, value) {
// state.gaClickInformation.currentlySelectedValues[groupName] = value;
// },
// updateFiredGaClickEventValues(state, groupName, value) {
// state.gaClickInformation.firedGaClickEventValues[groupName] = value;
// },
// updateLastFocusedInputGroup(state, groupName) {
// state.gaClickInformation.lastFocusedInputGroup = groupName;
// },
// updateWasLastFocusedInputMultiselect(state, wasLastFocusedInputMultiselect) {
// state.gaClickInformation.wasLastFocusedInputMultiselect =
// wasLastFocusedInputMultiselect;
// },
// END GA click event mutations
};
// Export Getters
export const getters = {
@ -377,7 +402,9 @@ export const getters = {
return !!nonWindshieldItems.length;
},
lineItems: (state) => state.order.lineItems,
pageData: (state) => (page) => { return state.applicationUser.pageData[page]; },
pageData: (state) => (page) => {
return state.applicationUser.pageData[page];
},
applicationUser: (state) => state.applicationUser,
order: (state) => state.order,
payment: (state) => state.order.payment,
@ -394,7 +421,8 @@ export const getters = {
funnelServiceState: state.order.serviceLocation.state,
funnelServiceZipCode: state.order.serviceLocation.zipCode,
funnelParentAccountNumber: state.order.accountNumber,
funnelIsCoverageVerified: state.order.payment.insuranceCoverage.isVerified,
funnelIsCoverageVerified:
state.order.payment.insuranceCoverage.isVerified,
funnelHasRecalibrationPart: getHasRecalibrationPart(state),
funnelSelectedMultiGlass: state.order.damage.glassToReplace?.length > 1,
funnelSelectedWindshieldGlass: getNonFalseValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, "glassLocation").includes(damageLocationsSelected.WINDSHIELD),
@ -407,8 +435,12 @@ export const getters = {
funnelOrderPartTypes: [...getNonFalseValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, "recalibrationType"), ...getNonFalseValuesOfPropertyInArrayOfObjects(state.order.lineItems.otherParts, "recalibrationType")],
}
},
experimentSettings: (state) => state.applicationUser.experiments.map(x => x.settings).reduce((r, c) => Object.assign(r, c), {}) ?? {}
}
experimentSettings: (state) =>
state.applicationUser.experiments
.map((x) => x.settings)
.reduce((r, c) => Object.assign(r, c), {}) ?? {},
// gaClickInformation: (state) => state.gaClickInformation,
};
function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
return (array ?? []).map(x => x[propertyName]).filter(x => x);
@ -416,7 +448,6 @@ function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
// Export Actions
export const actions = {
// Vehicle API Actions
getVehicleYears(context) {
return globalMethods.callHttpClient({
@ -447,11 +478,14 @@ export const actions = {
endpoint: endpoints.LookupVinByPlate.url,
payload: {
licensePlate: licensePlate,
licenseState: licenseState
licenseState: licenseState,
},
});
},
lookupVinByAddress(context, { licenseLastName, licenseStreetAddress, licenseZip, licenseState }) {
lookupVinByAddress(
context,
{ licenseLastName, licenseStreetAddress, licenseZip, licenseState }
) {
return globalMethods.callHttpClient({
method: endpoints.LookupVinByAddress.method,
endpoint: endpoints.LookupVinByAddress.url,
@ -459,7 +493,7 @@ export const actions = {
licenseLastName: licenseLastName,
licenseStreetAddress: licenseStreetAddress,
licenseZip: licenseZip,
licenseState: licenseState
licenseState: licenseState,
},
});
},
@ -493,10 +527,22 @@ export const actions = {
})
.then((response) => {
context.commit(storeMutations.UPDATE_CAR_ID, response.data.carId);
context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, response.data.category);
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, response.data.imageUrl);
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, response.data.imageVifNumber);
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, response.data.imageVifColor);
context.commit(
storeMutations.UPDATE_VEHICLE_CATEGORY,
response.data.category
);
context.commit(
storeMutations.UPDATE_VEHICLE_IMAGE_URL,
response.data.imageUrl
);
context.commit(
storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER,
response.data.imageVifNumber
);
context.commit(
storeMutations.UPDATE_VEHICLE_IMAGE_COLOR,
response.data.imageVifColor
);
return response;
});
},
@ -510,8 +556,8 @@ export const actions = {
validateZip(context, { zip }) {
return globalMethods.callHttpClient({
methods: endpoints.ValidateZip.method,
endpoint: `${endpoints.ValidateZip.url}/${zip}`
})
endpoint: `${endpoints.ValidateZip.url}/${zip}`,
});
},
// Dependency Actions
@ -526,7 +572,7 @@ export const actions = {
},
resetRegistrationAndDependencies(context) {
context.commit(storeMutations.RESET_REGISTRATION_STATE);
context.commit(storeMutations.RESET_GLASS_PARTS_STATE)
context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
},
resetPartsAndDependencies(context) {
context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
@ -582,8 +628,8 @@ export const actions = {
assignmentId: experiment.assignmentId,
sessionKey: sessionKey,
pageName: pageName,
}
}
},
},
});
},
@ -591,13 +637,28 @@ export const actions = {
updateStoreWithSaveSessionResponse(context, { referralNumber, referralDate, referralCorrelationId, eon, accountNumber, savedSessionId, crmCustomerId }) {
context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
context.commit(storeMutations.UPDATE_REFERRAL_DATE, referralDate);
context.commit(storeMutations.UPDATE_REFERRAL_CORRELATION_ID, referralCorrelationId);
context.commit(
storeMutations.UPDATE_REFERRAL_CORRELATION_ID,
referralCorrelationId
);
context.commit(storeMutations.UPDATE_EON, eon);
context.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, accountNumber);
context.commit(storeMutations.UPDATE_SAVED_SESSION_ID, savedSessionId);
context.commit(storeMutations.UPDATE_CRM_CUSTOMER_ID, crmCustomerId);
},
logPageView(context, { userId, sessionKey, pageName, sessionId, action, event, shouldUseSessionId, experimentsForUser }) {
logPageView(
context,
{
userId,
sessionKey,
pageName,
sessionId,
action,
event,
shouldUseSessionId,
experimentsForUser,
}
) {
var payload = {
userId: userId,
sessionKey: sessionKey,
@ -607,17 +668,31 @@ export const actions = {
action: action,
event: event,
shouldUseSessionId: shouldUseSessionId,
experimentsForUser: experimentsForUser
experimentsForUser: experimentsForUser,
};
return globalMethods.callHttpClient({
method: endpoints.LogPageView.method,
endpoint: endpoints.LogPageView.url,
payload: payload,
logApiCall: false
logApiCall: false,
});
},
logCustomEvent(context, { userId, sessionKey, pageName, sessionId, category, action, label, value, shouldUseSessionId, experimentsForUser }) {
logCustomEvent(
context,
{
userId,
sessionKey,
pageName,
sessionId,
category,
action,
label,
value,
shouldUseSessionId,
experimentsForUser,
}
) {
var payload = {
userId: userId,
sessionKey: sessionKey,
@ -629,14 +704,14 @@ export const actions = {
label: label,
value: value,
shouldUseSessionId: shouldUseSessionId,
experimentsForUser: experimentsForUser
experimentsForUser: experimentsForUser,
};
return globalMethods.callHttpClient({
method: endpoints.LogCustomEvent.method,
endpoint: endpoints.LogCustomEvent.url,
payload: payload,
logApiCall: false
logApiCall: false,
});
},
initializeSession(context, { userId, sessionId, userAgent, referrer }) {
@ -648,22 +723,28 @@ export const actions = {
userAgent: userAgent,
operatorId: "WEB",
userName: "SafeliteConceptFunnel",
referrer: referrer
referrer: referrer,
};
return globalMethods.callHttpClient({
method: endpoints.InitializeSession.method,
endpoint: endpoints.InitializeSession.url,
payload: payload,
logApiCall: false
logApiCall: false,
});
},
// Misc Actions
setReferralInformation(context, { referralNumber, referralDate, referralCorrelationId, eon }) {
setReferralInformation(
context,
{ referralNumber, referralDate, referralCorrelationId, eon }
) {
context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
context.commit(storeMutations.UPDATE_REFERRAL_DATE, referralDate);
context.commit(storeMutations.UPDATE_REFERRAL_CORRELATION_ID, referralCorrelationId);
context.commit(
storeMutations.UPDATE_REFERRAL_CORRELATION_ID,
referralCorrelationId
);
context.commit(storeMutations.UPDATE_EON, eon);
},
@ -671,11 +752,14 @@ export const actions = {
return globalMethods.callHttpClient({
method: endpoints.GetExperimentsByUser.method,
endpoint: `${endpoints.GetExperimentsByUser.url}/${userId}`,
payload: {}
payload: {},
});
},
async runExperimentsForTrigger(context, { userId, triggerEvent, triggerValue }) {
async runExperimentsForTrigger(
context,
{ userId, triggerEvent, triggerValue }
) {
if (triggerEvent == experimentTriggers.SITE_ENTRY) {
context.commit(storeMutations.UPDATE_TRIGGERED_SITE_ENTRY, true);
}
@ -685,7 +769,7 @@ export const actions = {
userId: userId,
triggerEvent: triggerEvent,
triggerValue: triggerValue,
experimentOrder: context.getters.experimentOrder
experimentOrder: context.getters.experimentOrder,
};
const response = await globalMethods.callHttpClient({
@ -694,7 +778,10 @@ export const actions = {
payload: payload,
});
context.commit(storeMutations.UPDATE_EXPERIMENTS, response.data.experiments);
context.commit(
storeMutations.UPDATE_EXPERIMENTS,
response.data.experiments
);
},
getEvoxImage(context, { relativeUrl }) {
@ -723,7 +810,7 @@ export const actions = {
carId: carId,
glass: glassArray ?? [],
zip: zipCode,
vin: vin
vin: vin,
},
});
},
@ -748,7 +835,7 @@ export const actions = {
glass: glassArray,
answerResults: resultsArray,
zip: zipCode,
vin: vin
vin: vin,
},
});
},
@ -757,24 +844,31 @@ export const actions = {
return globalMethods.callHttpClient({
method: endpoints.GetCapabilityQuestions.method,
endpoint: `${endpoints.GetCapabilityQuestions.url}/${carId}/${partNumber}`,
})
});
},
getPartFromCapabilityQuestionAnswer(context, glassLocation) {
const pageData = context.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS);
const pageData = context.getters.pageData(
fmgPageValues.CAPABILITY_QUESTIONS
);
const part = pageData.partsOrQuestions.find(x => x.glassLocation === glassLocation).parts[0];
const capabilityQuestionAnswers = context.getters.damage.capabilityQuestionAnswers;
const capabilityQuestionAnswersForPart = capabilityQuestionAnswers.find(x => x.glassLocation === glassLocation);
const part = pageData.partsOrQuestions.find(
(x) => x.glassLocation === glassLocation
).parts[0];
const capabilityQuestionAnswers =
context.getters.damage.capabilityQuestionAnswers;
const capabilityQuestionAnswersForPart = capabilityQuestionAnswers.find(
(x) => x.glassLocation === glassLocation
);
return globalMethods.callHttpClient({
method: endpoints.GetPartFromCapabilityAnswer.method,
endpoint: endpoints.GetPartFromCapabilityAnswer.url,
payload: {
part,
capabilityAnswerResults: capabilityQuestionAnswersForPart
}
})
capabilityAnswerResults: capabilityQuestionAnswersForPart,
},
});
},
// Session API Actions
@ -809,21 +903,21 @@ export const actions = {
damage: {
numberOfChips: damage.numberOfChips,
glassToReplace: damage.glassToReplace,
isRepair: damage.isRepair
isRepair: damage.isRepair,
},
customer: {
emailAddress: order.customer.emailAddress,
},
lineItems: {
glassParts: lineItems.glassParts
glassParts: lineItems.glassParts,
},
serviceLocation: {
streetAddress: order.serviceLocation.address,
city: order.serviceLocation.city,
state: order.serviceLocation.state,
zipCode: order.serviceLocation.zipCode
zipCode: order.serviceLocation.zipCode,
},
referralNumber: order.referralNumber?.toString(), // TODO It'd be nice to save these as strings in the first place
referralNumber: order.referralNumber?.toString(), // TODO It'd be nice to save these as strings in the first place
referralDate: order.referralDate,
accountNumber: order.accountNumber?.toString(),
existingPromoCode: null,
@ -858,8 +952,7 @@ export const actions = {
// Vehicle
saveVehicleYear(context, year) {
//Reset dependent state when changing
//Reset dependent state when changing
if (context.state.order.vehicle.year !== year) {
context.commit(storeMutations.UPDATE_MAKE, null);
context.commit(storeMutations.UPDATE_MODEL, null);
@ -880,7 +973,6 @@ export const actions = {
}
},
saveVehicleMake(context, make) {
//Reset dependent state when changing
if (context.state.order.vehicle.make !== make) {
context.commit(storeMutations.UPDATE_MODEL, null);
@ -901,8 +993,7 @@ export const actions = {
}
},
saveVehicleModel(context, model) {
//Reset dependent state when changing
//Reset dependent state when changing
if (context.state.order.vehicle.model !== model) {
context.commit(storeMutations.UPDATE_STYLE, null);
context.commit(storeMutations.UPDATE_CAR_ID, null);
@ -921,7 +1012,7 @@ export const actions = {
}
},
saveVehicleStyle(context, style) {
//Reset dependent state when changing
//Reset dependent state when changing
if (context.state.order.vehicle.style !== style) {
context.commit(storeMutations.UPDATE_CAR_ID, null);
context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, null);
@ -938,20 +1029,32 @@ export const actions = {
context.commit(storeMutations.UPDATE_STYLE, style);
}
},
saveVehicleDamage(context, { isWindshieldRepair, selectedGlassToReplace, selectedWindshieldChipCount }) {
saveVehicleDamage(
context,
{ isWindshieldRepair, selectedGlassToReplace, selectedWindshieldChipCount }
) {
const selectedGlassPassedInSorted = selectedGlassToReplace.slice().sort();
const isGlassToReplaceTheSame = (context.state.order.damage.glassToReplace?.length === selectedGlassToReplace.length)
&& context.state.order.damage.glassToReplace
const isGlassToReplaceTheSame =
context.state.order.damage.glassToReplace?.length ===
selectedGlassToReplace.length &&
context.state.order.damage.glassToReplace
.slice()
.sort()
.every((obj, index) => obj.glassLocation === selectedGlassPassedInSorted[index].glassLocation && obj.glassName === selectedGlassPassedInSorted[index].glassName);
const isWindshieldRepairTheSame = isWindshieldRepair === context.state.order.damage.isRepair;
const isChipCountTheSame = Array.isArray(selectedWindshieldChipCount) //TODO: fix the underlying components so this is never an array
? selectedWindshieldChipCount[0] === context.state.order.damage.numberOfChips
: selectedWindshieldChipCount === context.state.order.damage.numberOfChips;
.every(
(obj, index) =>
obj.glassLocation ===
selectedGlassPassedInSorted[index].glassLocation &&
obj.glassName === selectedGlassPassedInSorted[index].glassName
);
const isWindshieldRepairTheSame =
isWindshieldRepair === context.state.order.damage.isRepair;
const isDamageChanging = !isGlassToReplaceTheSame || !isWindshieldRepairTheSame || (isWindshieldRepair && !isChipCountTheSame);
const isChipCountTheSame = selectedWindshieldChipCount === context.state.order.damage.numberOfChips;
const isDamageChanging =
!isGlassToReplaceTheSame ||
!isWindshieldRepairTheSame ||
(isWindshieldRepair && !isChipCountTheSame);
if (isDamageChanging) {
//Reset dependent state when changing
@ -959,14 +1062,23 @@ export const actions = {
// Save new values
context.commit(storeMutations.UPDATE_IS_REPAIR, isWindshieldRepair);
context.commit(storeMutations.UPDATE_NUMBER_OF_CHIPS, isWindshieldRepair ? parseInt(selectedWindshieldChipCount) : null);
context.commit(storeMutations.UPDATE_GLASS_TO_REPLACE, selectedGlassToReplace);
context.commit(
storeMutations.UPDATE_NUMBER_OF_CHIPS,
isWindshieldRepair ? parseInt(selectedWindshieldChipCount) : null
);
context.commit(
storeMutations.UPDATE_GLASS_TO_REPLACE,
selectedGlassToReplace
);
}
},
// Vin lookup
saveVinLookup(context, { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }) {
//Reset dependent state when changing
saveVinLookup(
context,
{ isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }
) {
//Reset dependent state when changing
if (vehicleInfo.vin !== context.state.order.vehicle.vin) {
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
@ -980,10 +1092,15 @@ export const actions = {
context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo);
}
},
saveRegistrationLicensePlateLookup(context, { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }) {
//Reset dependent state when changing
if (registrationInfo?.licensePlate !== context.state.order.vehicle.registration?.licensePlate) {
saveRegistrationLicensePlateLookup(
context,
{ isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }
) {
//Reset dependent state when changing
if (
registrationInfo?.licensePlate !==
context.state.order.vehicle.registration?.licensePlate
) {
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
if (!isSelectedGlassAvailableForVehicle) {
@ -996,10 +1113,25 @@ export const actions = {
context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo);
}
},
saveRegistrationAddressLookup(context, { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }) {
//Reset dependent state when changing
if (registrationInfo?.address !== context.state.order.vehicle.registration?.address || registrationInfo?.city !== context.state.order.vehicle.registration?.city || registrationInfo?.state !== context.state.order.vehicle.registration?.state || registrationInfo?.zipCode !== context.state.order.vehicle.registration?.zipCode || registrationInfo?.firstName !== context.state.order.vehicle.registration?.firstName || registrationInfo?.lastName !== context.state.order.vehicle.registration?.lastName) {
saveRegistrationAddressLookup(
context,
{ isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }
) {
//Reset dependent state when changing
if (
registrationInfo?.address !==
context.state.order.vehicle.registration?.address ||
registrationInfo?.city !==
context.state.order.vehicle.registration?.city ||
registrationInfo?.state !==
context.state.order.vehicle.registration?.state ||
registrationInfo?.zipCode !==
context.state.order.vehicle.registration?.zipCode ||
registrationInfo?.firstName !==
context.state.order.vehicle.registration?.firstName ||
registrationInfo?.lastName !==
context.state.order.vehicle.registration?.lastName
) {
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
if (!isSelectedGlassAvailableForVehicle) {
@ -1014,72 +1146,141 @@ export const actions = {
},
savePartQuestionAnswers(context, partQuestionAnswersArray) {
// if part question answers have changed, reset subsequent question answers
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(context.getters.damage.partQuestionAnswers, "result");
const sortedPartQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(partQuestionAnswersArray, "result")
const havePartQuestionAnswersChanged = sortedPreviousResultsArray?.length !== sortedPartQuestionAnswersArray.length ||
!sortedPreviousResultsArray?.every((x, i) => x.result === sortedPartQuestionAnswersArray[i].result);
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(
context.getters.damage.partQuestionAnswers,
"result"
);
const sortedPartQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(
partQuestionAnswersArray,
"result"
);
const havePartQuestionAnswersChanged =
sortedPreviousResultsArray?.length !==
sortedPartQuestionAnswersArray.length ||
!sortedPreviousResultsArray?.every(
(x, i) => x.result === sortedPartQuestionAnswersArray[i].result
);
if (havePartQuestionAnswersChanged) {
context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null);
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.VEHICLE_PARTS, data: null });
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.MOLDING_QUESTIONS, data: null });
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.CAPABILITY_QUESTIONS, data: null });
context.commit(storeMutations.UPDATE_PAGE_DATA, {
page: fmgPageValues.VEHICLE_PARTS,
data: null,
});
context.commit(storeMutations.UPDATE_PAGE_DATA, {
page: fmgPageValues.MOLDING_QUESTIONS,
data: null,
});
context.commit(storeMutations.UPDATE_PAGE_DATA, {
page: fmgPageValues.CAPABILITY_QUESTIONS,
data: null,
});
}
//Save new values
context.commit(storeMutations.UPDATE_PART_QUESTION_ANSWERS, partQuestionAnswersArray);
context.commit(
storeMutations.UPDATE_PART_QUESTION_ANSWERS,
partQuestionAnswersArray
);
},
resetMoldingAndCapabilityQuestionAnswersIfNeeded(context, matchedParts) {
const partsOrQuestionsDataToCompareWith = context.getters.pageData(fmgPageValues.MOLDING_QUESTIONS)?.partsOrQuestions ?? context.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS)?.partsOrQuestions ?? [];
const partsOrQuestionsDataToCompareWith =
context.getters.pageData(fmgPageValues.MOLDING_QUESTIONS)
?.partsOrQuestions ??
context.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS)
?.partsOrQuestions ??
[];
function getAllPartNumbers(partsOrQuestions) {
return partsOrQuestions[0]?.parts
? [...partsOrQuestions].map(glass => glass.parts).flat().map(part => part.partNumber).filter(partNumber => !partNumber.toUpperCase().includes("FEE")).sort().join(",")
: []
? [...partsOrQuestions]
.map((glass) => glass.parts)
.flat()
.map((part) => part.partNumber)
.filter((partNumber) => !partNumber.toUpperCase().includes("FEE"))
.sort()
.join(",")
: [];
}
const previouslySelectedPartNumbers = getAllPartNumbers(partsOrQuestionsDataToCompareWith);
const previouslySelectedPartNumbers = getAllPartNumbers(
partsOrQuestionsDataToCompareWith
);
const currentlySelectedPartNumbers = getAllPartNumbers(matchedParts);
const haveSelectedVehiclePartsChanged = previouslySelectedPartNumbers !== currentlySelectedPartNumbers;
const haveSelectedVehiclePartsChanged =
previouslySelectedPartNumbers !== currentlySelectedPartNumbers;
if (haveSelectedVehiclePartsChanged) {
context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null);
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.MOLDING_QUESTIONS, data: null });
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.CAPABILITY_QUESTIONS, data: null });
context.commit(storeMutations.UPDATE_PAGE_DATA, {
page: fmgPageValues.MOLDING_QUESTIONS,
data: null,
});
context.commit(storeMutations.UPDATE_PAGE_DATA, {
page: fmgPageValues.CAPABILITY_QUESTIONS,
data: null,
});
}
},
saveMoldingQuestionAnswers(context, moldingQuestionAnswers) {
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(context.getters.damage.moldingQuestionAnswers, "partNum");
const sortedMoldingQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(moldingQuestionAnswers, "partNum");
const haveMoldingQuestionAnswersChanged = sortedPreviousResultsArray?.length !== sortedMoldingQuestionAnswersArray.length ||
!sortedPreviousResultsArray?.every((x, i) => x.partNum === sortedMoldingQuestionAnswersArray[i].partNum);
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(
context.getters.damage.moldingQuestionAnswers,
"partNum"
);
const sortedMoldingQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(
moldingQuestionAnswers,
"partNum"
);
const haveMoldingQuestionAnswersChanged =
sortedPreviousResultsArray?.length !==
sortedMoldingQuestionAnswersArray.length ||
!sortedPreviousResultsArray?.every(
(x, i) => x.partNum === sortedMoldingQuestionAnswersArray[i].partNum
);
if (haveMoldingQuestionAnswersChanged) {
context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.CAPABILITY_QUESTIONS, data: null });
context.commit(storeMutations.UPDATE_PAGE_DATA, {
page: fmgPageValues.CAPABILITY_QUESTIONS,
data: null,
});
}
//Save new values
context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, moldingQuestionAnswers);
context.commit(
storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS,
moldingQuestionAnswers
);
},
saveCapabilityQuestionAnswers(context, capabilityQuestionAnswers) {
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(context.getters.damage.capabilityQuestionAnswers, "result");
const sortedCapabilityQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(capabilityQuestionAnswers, "result");
const haveCapabilityQuestionAnswersChanged = sortedPreviousResultsArray?.length !== sortedCapabilityQuestionAnswersArray.length ||
!sortedPreviousResultsArray?.every((x, i) => x.result === sortedCapabilityQuestionAnswersArray[i].result);
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(
context.getters.damage.capabilityQuestionAnswers,
"result"
);
const sortedCapabilityQuestionAnswersArray =
sortArrayOfObjectsByPropertyValue(capabilityQuestionAnswers, "result");
const haveCapabilityQuestionAnswersChanged =
sortedPreviousResultsArray?.length !==
sortedCapabilityQuestionAnswersArray.length ||
!sortedPreviousResultsArray?.every(
(x, i) => x.result === sortedCapabilityQuestionAnswersArray[i].result
);
if (haveCapabilityQuestionAnswersChanged) {
context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
}
//Save new values
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, capabilityQuestionAnswers);
context.commit(
storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS,
capabilityQuestionAnswers
);
},
// Misc order actions
saveServiceLocation(context, serviceLocationInfo) {
@ -1091,7 +1292,6 @@ export const actions = {
saveVin(context, { isSelectedGlassAvailableForVehicle, vehicleInfo }) {
//Reset dependent state when changing
if (vehicleInfo.vin !== context.state.order.vehicle.vin) {
if (!isSelectedGlassAvailableForVehicle) {
context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
@ -1106,12 +1306,11 @@ export const actions = {
},
clearVin(context) {
context.commit(storeMutations.UPDATE_VEHICLE_VIN, null);
}
}
},
};
export default createStore({
plugins: [createPersistedState()],
// IMPORTANT: Be VERY careful when modifying these fields for at least a few reasons:
// * The CMS can reference the fields by name
// * Return users may have a previous "version" of the model, and we don't want
@ -1134,7 +1333,8 @@ function getHasRecalibrationPart(state) {
} else { // Has 'requiresRecalibration' but no 'recalibrationType' at all
return true;
}
} else { // Does not have 'requiresRecalibration'
} else {
// Does not have 'requiresRecalibration'
return false;
}
}
@ -1143,11 +1343,8 @@ function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {
if (!arrayOfObjects) return null;
return arrayOfObjects.sort((a, b) => {
if (a[propertyName] < b[propertyName])
return -1;
else if (a[propertyName] > b[propertyName])
return 1;
else
return 0;
})
}
if (a[propertyName] < b[propertyName]) return -1;
else if (a[propertyName] > b[propertyName]) return 1;
else return 0;
});
}

View file

@ -3,7 +3,6 @@ html {
&.list-button,
&.list-card,
&.list-card.list-button {
// border: none;
color: $red;
input[type=checkbox]:focus + label,
input[type=radio]:focus + label {
@ -39,6 +38,17 @@ html {
box-shadow: 0 0 1px $red;
}
}
&.grid-item {
input[type="radio"] {
+ label {
border: 1px solid $red;
&:hover {
background-color: $blue-100;
box-shadow: 0px 0px 0px 4px $red-200;
}
}
}
}
&.ui-radio,
&.ui-checkbox {
input[type=checkbox],

View file

@ -2,15 +2,17 @@
<button
:aria-disabled="isDisabled"
class="btn d-flex align-items-center py-3 px-4 delay"
:class="[isPrimary ? 'btn-primary' : 'btn-secondary',isFloat ? 'float-end' : '', isLoaderDisplayed ? 'has-loader' : '']"
@click="clicked"
>
:class="[
isPrimary ? 'btn-primary' : 'btn-secondary',
isFloat ? 'float-end' : '',
isLoaderDisplayed ? 'has-loader' : '',
]"
@click="clicked">
<span class="m-0">{{ this.buttonText }}</span>
<loader
class="ms-2"
v-if="isLoaderDisplayed"
v-bind:class="[this.loaderColor, this.loaderPosition]"
/>
v-bind:class="[this.loaderColor, this.loaderPosition]" />
</button>
</template>
@ -33,11 +35,16 @@ export default {
};
},
methods: {
removeLoader(){
removeLoader() {
this.isLoaderDisplayed = false;
},
clicked() {
this.pushEventToGA(this.$route.query[this.queryStrings.FMG_PAGE], this.GaActions.CLICKED, this.buttonText, true);
this.pushEventToGA(
this.$route.query[this.queryStrings.FMG_PAGE],
this.GaActions.CLICKED,
this.buttonText,
true
);
if (!this.isDisabled) {
this.isLoaderDisplayed = true;
this.$emit("click-event");
@ -98,7 +105,8 @@ export default {
background: $blue-700;
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
}
&.delay {// fixes flicker while transitioning between states
&.delay {
// fixes flicker while transitioning between states
transition: background 0s 0s ease-in-out;
}
}
@ -137,7 +145,8 @@ export default {
color: $white;
@include blue-gradient;
}
&.delay {// fixes flicker while transitioning between states
&.delay {
// fixes flicker while transitioning between states
transition: background 0s 0s ease-in-out;
}
}

View file

@ -1,272 +1,202 @@
import { shallowMount } from "@vue/test-utils";
import { shallowMount, mount } from "@vue/test-utils";
import listButtonHorizontal from "./list-button-horizontal";
import { nextTick } from "vue";
import { GaActions } from "@/constants/analytics";
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
// TODO KO
describe.skip("list-button-horizontal.vue", () => {
it("Should return input type checkbox if isMultiSelect is true", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
isMultiSelect: true,
},
describe("list-button-horizontal.vue", () => {
// TODO KO Have this moved out to somewhere shared
describe("Shared baseInputButton checks", () => {
it("Should return input type checkbox if isMultiSelect is true", async () => {
// Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: true,
},
},
});
// Assert
const input = wrapper.find("input");
expect(input.attributes().type).toEqual("checkbox");
});
// Assert
const input = wrapper.find("input");
it("Should return input type radio if isMultiSelect is false or not specified", async () => {
// Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: false,
},
},
});
expect(input.attributes().type).toEqual("checkbox");
});
it("Should return input type radio if isMultiSelect is false or not specified", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
isMultiSelect: false,
},
// Assert
const input = wrapper.find("input");
expect(input.attributes().type).toEqual("radio");
});
// Assert
const input = wrapper.find("input");
// TODO KO
it.skip("Should emit button value on click", async () => {
// Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isRadioHorizontal: true,
buttonLabel: "Windshield",
value: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
isRequired: true,
isWide: false,
modelValue: ["List Card Checkbox"],
buttonID: "list-card-id",
},
},
});
expect(input.attributes().type).toEqual("radio");
// Act
wrapper.vm.handleAnswerChange("test");
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.emitted()["change"][0]).toEqual(["test"]);
});
});
it("Should return primary label text (buttonID)", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
buttonID: "List Card Checkbox",
},
describe("styling/UI", () => {
it("Should return screen reader text", async () => {
// Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
screenReaderOnlyText: "Screen Reader Only Text",
},
},
});
// Assert
const paragraph = wrapper.find("span.sr-only");
expect(paragraph.text()).toEqual("Screen Reader Only Text");
});
// Assert
const label = wrapper.find("label");
it("Should return text alignment class", async () => {
// Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
textPosition: "text-center",
},
},
});
expect(label.attributes().for).toEqual("List Card Checkbox");
});
// Assert
const paragraph = wrapper.find("span.m-0");
it("Should return screen reader text", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
screenReaderOnlyText: "Screen Reader Only Text",
},
expect(paragraph.attributes("class")).toContain("text-center");
});
// Assert
const paragraph = wrapper.find("span.sr-only");
it("Should return aria-required state", async () => {
// Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isRequired: true,
},
},
});
expect(paragraph.text()).toEqual("Screen Reader Only Text");
});
// Assert
const input = wrapper.find("input");
it("Should return text alignment class", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
textPosition: "text-center",
},
expect(input.attributes()["aria-required"]).toEqual("true");
});
// Assert
const paragraph = wrapper.find("span.m-0");
it("is cash or insurance button => has 'radio-fancy' class", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isCashOrInsurance: true,
},
},
});
expect(paragraph.attributes("class")).toContain("text-center");
});
// Assert
const label = wrapper.find("label");
it("Should return aria-required state", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
isRequired: true,
},
expect(label.classes()).toContain("radio-fancy");
});
// Assert
const input = wrapper.find("input");
test("has buttonLabel => displays buttonLabel", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
buttonLabel: "Surprise!",
},
},
});
expect(input.attributes()["aria-required"]).toEqual("true");
});
it("Should return loader enabled true", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
global: {
mocks: {
'$route': { query: { fmgPage: 'page-name' } },
GaActions: GaActions,
pushEventToGA: jest.fn(),
}
},
propsData: {
selectingInitiatesLoad: true,
},
// Assert
const content = wrapper.find(".list-button-horizontal-content");
expect(content.isVisible()).toBe(true);
expect(content.text()).toContain("Surprise!");
});
// Assert
test("has buttonLabelSubCopy => displays buttonLabelSubCopy", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
buttonLabel: "Surprise!",
buttonLabelSubCopy: "Super duper surprise :)",
},
},
});
const label = wrapper.find("label");
wrapper.vm.handleCheckChange = jest.fn();
wrapper.vm.triggerButton();
await nextTick();
const loader = wrapper.find("loader-stub");
expect(loader.exists()).toBe(true);
});
it("Should return loader color", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
global: {
mocks: {
'$route': { query: { fmgPage: 'page-name' } },
GaActions: GaActions,
pushEventToGA: jest.fn(),
}
},
propsData: {
loaderColor: "blue",
selectingInitiatesLoad: true,
},
// Assert
const content = wrapper.find(".list-button-horizontal-content");
expect(content.isVisible()).toBe(true);
expect(content.text()).toContain("Super duper surprise :)");
});
// Assert
test("has screenReaderOnlyText => displays screenReaderOnlyText", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
buttonLabel: "Surprise!",
buttonLabelSubCopy: "Super duper surprise :)",
screenReaderOnlyText: "Tests are fun!"
},
},
});
const label = wrapper.find("label");
wrapper.vm.handleCheckChange = jest.fn();
wrapper.vm.triggerButton();
await nextTick();
const loader = wrapper.find("loader-stub");
expect(loader.attributes("class")).toContain("blue");
});
it("Should return loader position", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
global: {
mocks: {
'$route': { query: { fmgPage: 'page-name' } },
GaActions: GaActions,
pushEventToGA: jest.fn(),
}
},
propsData: {
loaderPosition: "right",
selectingInitiatesLoad: true,
},
// Assert
const content = wrapper.find(".list-button-horizontal-content");
const screenReaderOnlyText = wrapper.find(".sr-only");
expect(content.isVisible()).toBe(true);
expect(screenReaderOnlyText.exists()).toBe(true);
expect(screenReaderOnlyText.text()).toContain("Tests are fun!");
});
// Assert
const label = wrapper.find("label");
wrapper.vm.handleCheckChange = jest.fn();
wrapper.vm.triggerButton();
await nextTick();
const loader = wrapper.find("loader-stub");
expect(loader.attributes("class")).toContain("right");
});
it("Should emit button value on click", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
isRadioHorizontal: true,
buttonLabel: "Windshield",
value: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
isRequired: true,
isWide: false,
modelValue: ["List Card Checkbox"],
},
});
wrapper.vm.handleCheckChange();
// Assert
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: Boolean, buttonId: undefined, checkValue: false}]);
});
it("Should set checkValue data if selectedButtonIDs has value(s)", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
isRadioHorizontal: true,
buttonLabel: "Windshield",
buttonID: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
isRequired: true,
isWide: false,
modelValue: ["List Card Checkbox"],
isMultiSelect: false,
value: "Car-Front",
selectedValues: ["Car-Front"]
},
});
// Assert
expect(wrapper.vm.checkValue).toEqual(true);
});
it("Should run handleCheckChange if selectingInitiatesLoad is false and handleInputChange is triggered", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
selectingInitiatesLoad: false,
},
});
// Assert
wrapper.vm.handleInputChange();
await nextTick();
expect(wrapper.vm.handleCheckChange).toBeCalled;
});
it("Should do nothing if isMultiSelect is true and handleKeyupArrow is triggered", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
isMultiSelect: true,
},
});
// Assert
wrapper.vm.handleKeyupArrow();
await nextTick();
expect(wrapper.vm.handleKeyupArrow).toHaveReturned;
});
it("Should run handleCheckChange if selectingInitiatesLoad is false and handleKeyupArrow is triggered", async () => {
// Act
const wrapper = shallowMount(listButtonHorizontal, {
propsData: {
selectingInitiatesLoad: false,
isMultiSelect: false,
},
});
// Assert
wrapper.vm.handleKeyupArrow();
await nextTick();
expect(wrapper.vm.handleCheckChange).toBeCalled;
});
});
function setupMocks({ mockData }) {
const wrapper = mount(listButtonHorizontal, {
...mockData,
propsData: {
...mockData.propsData,
groupName: "my-group",
modelValue: mockData.propsData?.isMultiSelect ? ["5"] : "5",
value: mockData.propsData?.isMultiSelect ? ["4"] : "4"
},
mixins: [inputButtonWrapperMixin],
});
return { wrapper };
}

View file

@ -5,7 +5,7 @@
'list-group list-button-horizontal d-flex flex-column w-100',
{ 'radio-fancy': additionalData?.isCashOrInsurance },
]"
@buttonClicked="handleAnswerChange">
v-model="selectedValue">
<div
class="list-button-horizontal-content d-flex flex-column justify-content-center p-3">
<span class="m-0" :class="textPosition">

View file

@ -1,264 +1,262 @@
import { shallowMount } from "@vue/test-utils";
import { shallowMount, mount } from "@vue/test-utils";
import listButton from "./list-button";
import { nextTick } from "vue";
import { GaActions } from "@/constants/analytics";
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
// TODO KO
describe.skip("list-button.vue", () => {
it("Should return input type checkbox if isMultiSelect is true", async () => {
// Act
const wrapper = shallowMount(listButton, {
propsData: {
isMultiSelect: true,
},
describe("loader", () => {
it("selectingInitiatesLoad is true and answer is changed => show the loader", async () => {
// Act
const { wrapper } = setupMocks({
mockData: {
global: {
mocks: {
$route: { query: { fmgPage: "page-name" } },
GaActions: GaActions,
pushEventToGA: jest.fn(),
},
},
propsData: {
selectingInitiatesLoad: true,
},
},
});
// Act
wrapper.vm.handleAnswerChange("something");
await wrapper.vm.$nextTick();
// Assert
const loader = wrapper.findComponent({ name: "loader" });
expect(loader.exists()).toBe(true);
});
// Assert
const input = wrapper.find("input");
it("Should return loader color", async () => {
// Act
const { wrapper } = setupMocks({
mockData: {
global: {
mocks: {
$route: { query: { fmgPage: "page-name" } },
GaActions: GaActions,
pushEventToGA: jest.fn(),
},
},
propsData: {
loaderColor: "blue",
selectingInitiatesLoad: true,
},
},
});
expect(input.attributes().type).toEqual("checkbox");
});
// Act
wrapper.vm.handleAnswerChange("something");
await nextTick();
it("Should return input type radio if isMultiSelect is false or not specified", async () => {
// Act
const wrapper = shallowMount(listButton, {
propsData: {
isMultiSelect: false,
},
// Assert
const loader = wrapper.findComponent({ name: "loader" });
expect(loader.attributes("class")).toContain("blue");
});
// Assert
const input = wrapper.find("input");
it("Should return loader position", async () => {
// Act
const { wrapper } = setupMocks({
mockData: {
global: {
mocks: {
$route: { query: { fmgPage: "page-name" } },
GaActions: GaActions,
pushEventToGA: jest.fn(),
},
},
propsData: {
loaderPosition: "right",
selectingInitiatesLoad: true,
},
},
});
expect(input.attributes().type).toEqual("radio");
// Act
wrapper.vm.handleAnswerChange("test");
await nextTick();
// Assert
const loader = wrapper.findComponent({ name: "loader" });
expect(loader.attributes("class")).toContain("right");
});
});
it("Should return primary label text (buttonID)", async () => {
// Act
const wrapper = shallowMount(listButton, {
propsData: {
buttonID: "List Card Checkbox",
},
describe("baseInputButton checks", () => {
it("Should return input type checkbox if isMultiSelect is true", async () => {
// Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: true,
},
},
});
// Assert
const input = wrapper.find("input");
expect(input.attributes().type).toEqual("checkbox");
});
// Assert
const label = wrapper.find("label");
it("Should return input type radio if isMultiSelect is false or not specified", async () => {
// Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isMultiSelect: false,
},
},
});
expect(label.attributes().for).toEqual("List Card Checkbox");
});
it("Should return screen reader text", async () => {
// Act
const wrapper = shallowMount(listButton, {
propsData: {
screenReaderOnlyText: "Screen Reader Only Text",
},
// Assert
const input = wrapper.find("input");
expect(input.attributes().type).toEqual("radio");
});
// Assert
const paragraph = wrapper.find("span.sr-only");
it("Should emit button value on click", async () => {
// Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isRadioHorizontal: true,
buttonLabel: "Windshield",
value: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
isRequired: true,
isWide: false,
modelValue: ["List Card Checkbox"],
buttonID: "list-card-id",
},
},
});
expect(paragraph.text()).toEqual("Screen Reader Only Text");
// Act
wrapper.vm.handleAnswerChange("test");
await wrapper.vm.$nextTick();
// Assert
expect(wrapper.emitted()["change"][0]).toEqual(["test"]);
});
});
it("Should return text alignment class", async () => {
// Act
const wrapper = shallowMount(listButton, {
propsData: {
textPosition: "text-center",
},
describe("styling/UI", () => {
test("has buttonLabel => displays buttonLabel", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
buttonLabel: "Surprise!",
},
},
});
// Assert
const content = wrapper.find(".list-button-content");
expect(content.isVisible()).toBe(true);
expect(content.text()).toContain("Surprise!");
});
// Assert
const paragraph = wrapper.find("span.m-0");
test("has buttonLabelSubCopy => displays buttonLabelSubCopy", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
buttonLabel: "Surprise!",
buttonLabelSubCopy: "Super duper surprise :)",
},
},
});
expect(paragraph.attributes("class")).toContain("text-center");
});
it("Should return aria-required state", async () => {
// Act
const wrapper = shallowMount(listButton, {
propsData: {
isRequired: true,
},
// Assert
const content = wrapper.find(".list-button-content");
expect(content.isVisible()).toBe(true);
expect(content.text()).toContain("Super duper surprise :)");
});
// Assert
const input = wrapper.find("input");
test("has screenReaderOnlyText => displays screenReaderOnlyText", () => {
// Arrange/Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
buttonLabel: "Surprise!",
buttonLabelSubCopy: "Super duper surprise :)",
screenReaderOnlyText: "Tests are fun!",
},
},
});
expect(input.attributes()["aria-required"]).toEqual("true");
});
it("Should return loader enabled true", async () => {
// Act
const wrapper = shallowMount(listButton, {
global: {
mocks: {
'$route': { query: { fmgPage: 'page-name' } },
GaActions: GaActions,
pushEventToGA: jest.fn(),
}
},
propsData: {
selectingInitiatesLoad: true,
},
// Assert
const content = wrapper.find(".list-button-content");
const screenReaderOnlyText = wrapper.find(".sr-only");
expect(content.isVisible()).toBe(true);
expect(screenReaderOnlyText.exists()).toBe(true);
expect(screenReaderOnlyText.text()).toContain("Tests are fun!");
});
// Assert
wrapper.vm.handleCheckChange = jest.fn();
wrapper.vm.triggerButton();
it("Should return screen reader text", async () => {
// Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
screenReaderOnlyText: "Screen Reader Only Text",
},
},
});
await nextTick();
// Assert
const paragraph = wrapper.find("span.sr-only");
const loader = wrapper.find("loader-stub");
expect(loader.exists()).toBe(true);
});
it("Should return loader color", async () => {
// Act
const wrapper = shallowMount(listButton, {
global: {
mocks: {
'$route': { query: { fmgPage: 'page-name' } },
GaActions: GaActions,
pushEventToGA: jest.fn(),
}
},
propsData: {
loaderColor: "blue",
selectingInitiatesLoad: true,
},
expect(paragraph.text()).toEqual("Screen Reader Only Text");
});
// Assert
wrapper.vm.handleCheckChange = jest.fn();
wrapper.vm.triggerButton();
await nextTick();
it("Should return text alignment class", async () => {
// Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
textPosition: "text-center",
},
},
});
const loader = wrapper.find("loader-stub");
// Assert
const paragraph = wrapper.find("span.m-0");
expect(loader.attributes("class")).toContain("blue");
});
it("Should return loader position", async () => {
// Act
const wrapper = shallowMount(listButton, {
global: {
mocks: {
'$route': { query: { fmgPage: 'page-name' } },
GaActions: GaActions,
pushEventToGA: jest.fn(),
}
},
propsData: {
loaderPosition: "right",
selectingInitiatesLoad: true,
},
expect(paragraph.attributes("class")).toContain("text-center");
});
// Assert
wrapper.vm.handleCheckChange = jest.fn();
wrapper.vm.triggerButton();
it("Should return aria-required state", async () => {
// Act
const { wrapper } = setupMocks({
mockData: {
propsData: {
isRequired: true,
},
},
});
await nextTick();
// Assert
const input = wrapper.find("input");
const loader = wrapper.find("loader-stub");
expect(loader.attributes("class")).toContain("right");
});
it("Should emit button value on click", async () => {
// Act
const wrapper = shallowMount(listButton, {
propsData: {
isRadioHorizontal: true,
buttonLabel: "Windshield",
value: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
isRequired: true,
isWide: false,
modelValue: ["List Card Checkbox"],
buttonID: 'list-card-id'
},
expect(input.attributes()["aria-required"]).toEqual("true");
});
wrapper.vm.handleCheckChange();
// Assert
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: false, buttonId: 'list-card-id'}]);
});
it("Should set checkValue data if selectedButtonIDs has value(s)", async () => {
// Act
const wrapper = shallowMount(listButton, {
propsData: {
isRadioHorizontal: true,
buttonLabel: "Windshield",
buttonID: "List Card Checkbox",
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
isRequired: true,
isWide: false,
modelValue: ["List Card Checkbox"],
selectedValues: "Car-Front"
},
});
// Assert
expect(wrapper.componentVM.checkValue).toEqual(false);
});
it("Should run handleCheckChange if selectingInitiatesLoad is false and handleInputChange is triggered", async () => {
// Act
const wrapper = shallowMount(listButton, {
propsData: {
selectingInitiatesLoad: false,
},
});
// Assert
wrapper.vm.handleInputChange();
await nextTick();
expect(wrapper.vm.handleCheckChange).toBeCalled;
});
it("Should do nothing if isMultiSelect is true and handleKeyupArrow is triggered", async () => {
// Act
const wrapper = shallowMount(listButton, {
propsData: {
isMultiSelect: true,
},
});
// Assert
wrapper.vm.handleKeyupArrow();
await nextTick();
expect(wrapper.vm.handleKeyupArrow).toHaveReturned;
});
it("Should run handleCheckChange if selectingInitiatesLoad is false and handleKeyupArrow is triggered", async () => {
// Act
const wrapper = shallowMount(listButton, {
propsData: {
selectingInitiatesLoad: false,
isMultiSelect: false,
},
});
// Assert
wrapper.vm.handleKeyupArrow();
await nextTick();
expect(wrapper.vm.handleCheckChange).toBeCalled;
});
});
function setupMocks({ mockData }) {
const wrapper = mount(listButton, {
...mockData,
mixins: [inputButtonWrapperMixin],
});
return { wrapper };
}

View file

@ -2,7 +2,7 @@
<baseInputButton
v-bind="$props"
buttonWrapperClasses="list-group list-button rounded-3 d-flex flex-column w-100 mb-2"
@buttonClicked="handleAnswerChange">
v-model="selectedValue">
<div
:aria-label="buttonLabel"
class="list-button-content d-flex flex-column justify-content-center py-3 px-4">

View file

@ -14,6 +14,7 @@ describe("list-card.vue", () => {
groupID: "checkbox-demo-1",
groupName: "Checkbox 1",
buttonImage: "windshield-damage.svg",
value: "test value",
},
});
@ -32,6 +33,7 @@ describe("list-card.vue", () => {
groupID: "radio-demo-1",
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
value: "test value",
},
});
@ -51,6 +53,7 @@ describe("list-card.vue", () => {
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
buttonLabelSubCopy: "Test",
value: "test value",
},
});
@ -70,6 +73,7 @@ describe("list-card.vue", () => {
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
buttonLabelSubCopy: "Test",
value: "test value",
},
});
@ -89,6 +93,7 @@ describe("list-card.vue", () => {
groupName: "radio 1",
buttonImage: "windshield-damage.svg",
isRequired: true,
value: "test value",
},
});
@ -110,6 +115,7 @@ describe("list-card.vue", () => {
isRequired: true,
isWide: true,
buttonLabelSubCopy: "",
value: "test value",
},
});
@ -134,6 +140,7 @@ describe("list-card.vue", () => {
isRequired: true,
isWide: true,
buttonLabelSubCopy: "Button Subcopy",
value: "test value",
},
});
@ -159,6 +166,7 @@ describe("list-card.vue", () => {
buttonImage: "windshield-damage.svg",
isRequired: true,
isWide: false,
value: "test value",
},
});

View file

@ -5,7 +5,7 @@
'list-card w-100 rounded-3 d-flex align-items-center h-100',
{ horizontal: isWide },
]"
@buttonClicked="handleAnswerChange">
v-model="selectedValue">
<div
class="d-flex w-100 align-items-center px-2 h-100 list-card-content"
:class="labelClasses">

View file

@ -6,12 +6,16 @@ import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
describe("radio.vue", () => {
it("Should have correct group name", async () => {
// Arrange
let { wrapper } = setupMocks({});
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: {
groupName: "radio-button-test",
value: "test value",
},
},
});
// Act
await wrapper.setProps({
groupName: "radio-button-test",
});
const input = wrapper.find("input");
// Assert
@ -20,12 +24,16 @@ describe("radio.vue", () => {
it("Should have correct label text", async () => {
// Act
let { wrapper } = setupMocks({});
let { wrapper } = setupMocks({
mountOptionsMockData: {
propsData: {
buttonLabel: "label text",
value: "test value",
},
},
});
// Arrange
await wrapper.setProps({
buttonLabel: "label text",
});
const paragraph = wrapper.find("p");
// Assert
@ -39,6 +47,7 @@ describe("radio.vue", () => {
// Arrange
await wrapper.setProps({
screenReaderOnlyText: "screenreader text",
value: "test value",
});
const paragraph = wrapper.find(".sr-only");
@ -48,13 +57,10 @@ describe("radio.vue", () => {
});
function setupMocks({ mountOptionsMockData = {} }) {
const wrapper = mount(
radio,
getMountOptions({
...mountOptionsMockData,
mixins: [inputButtonWrapperMixin],
})
);
const wrapper = mount(radio, {
...mountOptionsMockData,
mixins: [inputButtonWrapperMixin],
});
return { wrapper };
}

View file

@ -3,7 +3,7 @@
v-bind="$props"
buttonWrapperClasses="ui-radio form-check"
inputClasses="form-check-input"
@buttonClicked="handleAnswerChange">
v-model="selectedValue">
<div class="d-flex align-items-start form-check-label">
<p v-if="buttonLabel" class="m-0">{{ buttonLabel }}</p>
<span v-if="screenReaderOnlyText" class="sr-only">{{