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)"], testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
coverageThreshold: { coverageThreshold: {
global: { global: {
// TODO KO statements: 80,
statements: 55,
// 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 // 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> <template>
<router-view v-slot="{ Component }"> <router-view v-slot="{ Component }">
<transition :duration="{ enter: 200, leave: 200 }" name="route-fade" mode="out-in"> <transition
<!-- The above durations should be kept in sync with the global css class "fade-on-route-transition" --> :duration="{ enter: 200, leave: 200 }"
<component :is="Component" /> name="route-fade"
</transition> mode="out-in">
</router-view> <!-- The above durations should be kept in sync with the global css class "fade-on-route-transition" -->
<component :is="Component" @focusin="handleAnyComponentFocus" />
</transition>
</router-view>
</template> </template>
<script>
import { handleAnyComponentFocus } from "@/helpers/button-question-focus-helper"
export default {
name: "app",
methods: {
handleAnyComponentFocus: handleAnyComponentFocus
}
};
</script>
<style lang="scss"> <style lang="scss">
@import "./node_modules/bootstrap/scss/bootstrap"; @import "./node_modules/bootstrap/scss/bootstrap";
@import "@/styles/common-styles.scss"; @import "@/styles/common-styles.scss";
@import "@/styles/common-typography-styles.scss"; @import "@/styles/common-typography-styles.scss";
@import "@/styles/common-error-styles.scss"; @import "@/styles/common-error-styles.scss";
@import "@/styles/common-animations.scss"; @import "@/styles/common-animations.scss";
</style> </style>

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 <label
:class="[buttonWrapperClasses, { 'has-error': errors.length > 0 }]" :class="[buttonWrapperClasses, { 'has-error': errors.length > 0 }]"
:for="buttonId" :for="buttonId"
@mousedown.left="handleEventAction('click', $event)" @focusin="handleFocus"
@keyup="handleKeyboardNavigation"> @focusout="handleBlur"
@mousedown.left="handleEventAction('click', $event)">
<input <input
:type="inputType" :type="inputType"
:id="buttonId" :id="buttonId"
@ -13,8 +14,8 @@
:aria-required="isRequired" :aria-required="isRequired"
:value="value" :value="value"
:checked="isChecked" :checked="isChecked"
@blur="handleBlur" @keypress.space="handleEventAction('space', $event)"
@keypress.enter="handleEventAction('keypressSubmit', $event)" @keypress.enter="handleEventAction('enter', $event)"
@change="handleEventAction('change', $event)" /> @change="handleEventAction('change', $event)" />
<slot></slot> <slot></slot>
@ -22,43 +23,18 @@
</template> </template>
<script> <script>
import { queryStrings } from "@/constants/query-strings";
import { useField } from "vee-validate"; import { useField } from "vee-validate";
import { toRef } from "vue"; 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 { export default {
name: "base-input-button", name: "base-input-button",
emits: ["change"],
model: {
prop: "modelValue",
event: "change",
},
props: { props: {
value: { ...inputButtonProps,
type: [String, Number],
required: true,
},
modelValue: {
type: [Array, String, Number],
required: true,
},
isMultiSelect: Boolean,
groupName: {
type: String,
required: true,
},
validationRules: {
type: String,
default: "",
},
buttonWrapperClasses: [String, Array, Object], buttonWrapperClasses: [String, Array, Object],
inputClasses: [String, Array, Object], inputClasses: [String, Array, Object],
valueToLogType: String,
selectOnKeypress: {
type: Boolean,
default: true,
},
isRequired: Boolean,
}, },
data() { data() {
return { return {
@ -72,32 +48,34 @@ export default {
}, },
methods: { methods: {
handleEventAction(eventType, e) { handleEventAction(eventType, e) {
const eventTypes = { // console.log({
CHANGE: "change", // eventType,
KEYPRESS_SUBMIT: "keypressSubmit", // e
CLICK: "click", // })
};
if (this.isMultiSelect) { if (this.isMultiSelect) {
switch (eventType) { switch (eventType) {
case eventTypes.KEYPRESS_SUBMIT: case this.eventTypes.ENTER:
case eventTypes.CHANGE: case this.eventTypes.CHANGE:
this.handleClick(e); this.handleClick(e);
window.lastFocusedInputGroup = this.groupName; this.handlePushClickEventToGACheck(
this.pushClickEventToGA(); this.eventTypes.CLICK
);
break; break;
} }
} else { } else {
switch (eventType) { switch (eventType) {
case eventTypes.CLICK: case this.eventTypes.CLICK:
case eventTypes.KEYPRESS_SUBMIT: case this.eventTypes.ENTER:
case this.eventTypes.SPACE:
this.handleClick(e); this.handleClick(e);
this.test(); this.handlePushClickEventToGACheck(
this.eventTypes.CLICK
);
break; break;
case eventTypes.CHANGE: case this.eventTypes.CHANGE:
this.selectOnKeypress this.selectingInitiatesLoad
? this.handleClick(e) ? this.handleSelectionChange(e)
: this.handleSelectionChange(e); : this.handleClick(e);
break; break;
} }
} }
@ -119,21 +97,40 @@ export default {
this.valueToEmit = this.value; this.valueToEmit = this.value;
} }
window.currentlySelectedValues =
window.currentlySelectedValues ?? {};
window.currentlySelectedValues[this.groupName] = this.value;
this.handleChange(this.valueToEmit); this.handleChange(this.valueToEmit);
}, },
handleClick(e) { handleClick(e) {
this.handleSelectionChange(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) { pushClickEventToGA(value) {
window.firedGaClickEventValues = console.log("PUSHHH")
window.firedGaClickEventValues ?? {}; console.log(this.$route)
window.firedGaClickEventValues[window.lastFocusedInputGroup] =
value ?? this.value;
this.pushEventToGA( this.pushEventToGA(
this.$route.query[queryStrings.FMG_PAGE], this.$route.query[queryStrings.FMG_PAGE],
this.GaActions.CLICKED, this.GaActions.CLICKED,
@ -141,88 +138,38 @@ export default {
true, true,
this.valueToLogType 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; this.setLastValuePushedToGa(this.value);
// 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
]
);
}
}
}, },
}, },
computed: { computed: {
isChecked() { isChecked() {
if (this.isMultiSelect && this.modelValue instanceof Array) { if (this.isMultiSelect && this.modelValue instanceof Array) {
return this.modelValue.includes(this.value); return this.modelValue.includes(this.value);
} else if (!this.isMultiSelect) {
return this.modelValue == this.value;
} else {
return false;
} }
return this.modelValue == this.value;
}, },
inputType() { inputType() {
return this.isMultiSelect ? "checkbox" : "radio"; return this.isMultiSelect ? "checkbox" : "radio";
}, },
buttonId() { 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) { 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 buttonQuestion from "@/common-components/button-question/button-question";
import { getMountOptions } from "@/helpers/unit-test-helper.js"; import { getMountOptions } from "@/helpers/unit-test-helper.js";
jest.mock("@/store", () => { return {}; }, { virtual: true }); jest.mock(
"@/store",
() => {
return {};
},
{ virtual: true }
);
// TODO KO describe("buttonQuestion.vue", () => {
describe.skip("buttonQuestion.vue", () => {
it("Should show overflow classes on fieldset if isOverflowScrollable is true", () => { it("Should show overflow classes on fieldset if isOverflowScrollable is true", () => {
// Act // Act
const wrapper = shallowMount(buttonQuestion, { const wrapper = shallowMount(buttonQuestion, {
propsData: { propsData: {
isOverflowScrollable: true, isOverflowScrollable: true,
groupName: "group-name" groupName: "group-name",
} },
}); });
// Assert // Assert
const fieldSet = wrapper.find('fieldset'); const fieldSet = wrapper.find("fieldset");
expect(fieldSet.classes()).toContain("overflow-scroll"); expect(fieldSet.classes()).toContain("overflow-scroll");
}); });
}); });
// describe("buttonQuestion.vue", () => { describe("buttonQuestion.vue", () => {
// it("Fieldset classes should contain row if button type is listCard", () => { it("Fieldset classes should contain row if button type is listCard", () => {
// // Act // Act
// const wrapper = shallowMount(buttonQuestion, { const wrapper = shallowMount(buttonQuestion, {
// propsData: { propsData: {
// buttonType: "listCard", buttonType: "listCard",
// groupName: "group-name" groupName: "group-name",
// } },
// }); });
// // Assert // Assert
// const Div = wrapper.find('fieldset div'); const Div = wrapper.find("fieldset div");
// expect(Div.classes()).toContain("row"); expect(Div.classes()).toContain("row");
// }); });
// }); });
// describe("buttonQuestion.vue", () => { describe("buttonQuestion.vue", () => {
// it("Fieldset classes should contain d-flex if button type is listButtonHorizontal", () => { it("Fieldset classes should contain d-flex if button type is listButtonHorizontal", () => {
// // Act // Act
// const wrapper = shallowMount(buttonQuestion, { const wrapper = shallowMount(buttonQuestion, {
// propsData: { propsData: {
// buttonType: "listButtonHorizontal", buttonType: "listButtonHorizontal",
// groupName: "group-name" groupName: "group-name",
// } },
// }); });
// // Assert // Assert
// const Div = wrapper.find('fieldset div'); const Div = wrapper.find("fieldset div");
// expect(Div.classes()).toContain("d-flex"); expect(Div.classes()).toContain("d-flex");
// }); });
// }); });
// describe("buttonQuestion.vue", () => { describe("buttonQuestion.vue", () => {
// it("Fieldset classes should contain ui-radio if button type is radio", () => { it("Fieldset classes should contain ui-radio if button type is radio", () => {
// // Act // Act
// const wrapper = shallowMount(buttonQuestion, { const wrapper = shallowMount(buttonQuestion, {
// propsData: { propsData: {
// buttonType: "radio", buttonType: "radio",
// groupName: "group-name" groupName: "group-name",
// } },
// }); });
// // Assert // Assert
// const Div = wrapper.find('fieldset div'); const Div = wrapper.find("fieldset div");
// expect(Div.classes()).toContain("ui-radio"); 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 expect(buttonQuestion.computed.getColLength.call(localThis)).toBe("12");
// describe("buttonQuestion.vue", () => { });
// it("getColLength should return '12' if prop isWide is set to true", () => { });
// // Act
// const localThis = { isWide: true }
// expect(buttonQuestion.computed.getColLength.call(localThis)).toBe("12"); describe("buttonQuestion.vue", () => {
// }); it("getColLength should return '' if prop isWide is set to false", () => {
// }); // Act
const localThis = {
isWide: false,
answers: ["a", "b"],
groupName: "group-name",
};
// describe("buttonQuestion.vue", () => { expect(buttonQuestion.computed.getColLength.call(localThis)).toBe("");
// it("getColLength should return '' if prop isWide is set to false", () => { });
// // Act });
// const localThis = {
// isWide: false,
// answers: ['a', 'b'],
// groupName: "group-name"
// }
// expect(buttonQuestion.computed.getColLength.call(localThis)).toBe(""); describe("buttonQuestion.vue", () => {
// }); it("Should return answer.Text if prop useTextForValue is true", async () => {
// }); // Act
const localThis = { useTextForValue: true };
const answer = { Name: "testName", Text: "testText" };
// describe("buttonQuestion.vue", () => { // Assert
// it("Should return answer.Text if prop useTextForValue is true", async () => { expect(buttonQuestion.methods.getValue.call(localThis, answer)).toBe(
// // Act "testText"
// const localThis = { useTextForValue: true }; );
// const answer = { 'Name': 'testName', 'Text': 'testText' }; });
});
// // Assert describe("buttonQuestion.vue", () => {
// expect(buttonQuestion.methods.getValue.call(localThis, answer)).toBe('testText'); 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", () => { // Assert
// it("Should return answer.Name if prop useTextForValue is false and answer.Name exists", async () => { expect(buttonQuestion.methods.getValue.call(localThis, answer)).toBe(
// // Act "testName"
// const localThis = { useTextForValue: false }; );
// const answer = { 'Name': 'testName', 'Text': 'testText' }; });
});
// // Assert // TODO KO
// expect(buttonQuestion.methods.getValue.call(localThis, answer)).toBe('testName'); 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", () => { // Assert
// it("Should trigger event modelValue change to new value on when radio button selected", async () => { expect(wrapper.emitted()["update:modelValue"][0]).toEqual(["2021"]);
// // Act });
// const wrapper = shallowMount(buttonQuestion, setupMocks({propsData: {groupName: "group-name"}}));
// await wrapper.setProps({
// answers: ["2022", "2021", "2020"],
// isMultiSelect: false,
// modelValue: []
// });
// const val = { checkValue: true, value: "2021", }
// wrapper.vm.handleCheckedChanged(val);
// // Assert
// expect(wrapper.emitted()["update:modelValue"][0]).toEqual([["2021"]]);
// });
// });
// describe("buttonQuestion.vue", () => { test("is checkbox => should emit captured value", async () => {
// it("Should add values to array on checkbox click", () => { const wrapper = shallowMount(
// // Act buttonQuestion,
// const wrapper = shallowMount(buttonQuestion, setupMocks({ setupMocks({ propsData: { groupName: "group-name" } })
// propsData: { );
// modelValue: ["2022", "2021", "2020"], await wrapper.setProps({
// isMultiSelect: true, answers: ["2022", "2021", "2020"],
// groupName: "group-name" isMultiSelect: false,
// } modelValue: "",
// })); });
// const val = { checkValue: true, value: "2019", } const val = ["2022", "2021", , "2019", "2020"];
// wrapper.vm.handleCheckedChanged(val); wrapper.vm.handleAnswerChange(val);
// // Assert
// expect(wrapper.emitted()["update:modelValue"][0]).toEqual([["2022", "2021", "2020", "2019"]]);
// });
// });
// describe("buttonQuestion.vue", () => { // Assert
// it("Should add a value to this.selectedValues if prop isMultiSelect is true, checkValue is true and this.selectedValues already exists", () => { expect(wrapper.emitted()["update:modelValue"][0]).toEqual([
// // Act ["2022", "2021", , "2019", "2020"],
// const wrapper = shallowMount(buttonQuestion, setupMocks({ ]);
// propsData: { });
// isMultiSelect: true, });
// modelValue: ['a', 'b'], });
// groupName: "group-name"
// }
// }));
// const val = { checkValue: true, value: "2021", }
// wrapper.vm.handleCheckedChanged(val);
// // Assert
// expect(wrapper.vm.selectedValues).toEqual(["a", "b", "2021"]);
// });
// });
// describe("buttonQuestion.vue", () => {
// it("Should remove a value to this.selectedValues if prop isMultiSelect is true, checkValue is false and this.selectedValues already exists", () => {
// // Act
// const wrapper = shallowMount(buttonQuestion, setupMocks({
// propsData: {
// isMultiSelect: true,
// modelValue: ['a', 'b'],
// groupName: "group-name"
// }
// }));
// const val = { checkValue: false, value: "a", }
// wrapper.vm.handleCheckedChanged(val);
// // Assert
// expect(wrapper.vm.selectedValues).toEqual(["b"]);
// });
// });
function setupMocks(mountOptionsMockData = {}) { function setupMocks(mountOptionsMockData = {}) {
const defaultMountOptions = { route: { query: { fmgPage: 'page-name' } } }; const defaultMountOptions = { route: { query: { fmgPage: "page-name" } } };
const baseMountOptions = getMountOptions(Object.assign(defaultMountOptions, mountOptionsMockData)); const baseMountOptions = getMountOptions(
Object.assign(defaultMountOptions, mountOptionsMockData)
);
const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions); const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions);
return allMountOptions; return allMountOptions;

View file

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

View file

@ -1,164 +1,199 @@
<template> <template>
<div id="app"> <div id="app">
<fieldset> <fieldset>
<legend class="d-flex justify-content-center">Date Picker</legend> <legend class=" sr-only">Date Picker</legend>
<div class="grid-container"> <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? --> <!-- 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 caption"><span class="sr-only">Sunday</span>S</div>
<div class="grid-item"><span class="sr-only">Monday</span>M</div> <div class="grid-item caption"><span class="sr-only">Monday</span>M</div>
<div class="grid-item"><span class="sr-only">Tuesday</span>T</div> <div class="grid-item caption"><span class="sr-only">Tuesday</span>T</div>
<div class="grid-item"><span class="sr-only">Wednesday</span>W</div> <div class="grid-item caption"><span class="sr-only">Wednesday</span>W</div>
<div class="grid-item"><span class="sr-only">Thursday</span>T</div> <div class="grid-item caption"><span class="sr-only">Thursday</span>T</div>
<div class="grid-item"><span class="sr-only">Friday</span>F</div> <div class="grid-item caption"><span class="sr-only">Friday</span>F</div>
<div class="grid-item"><span class="sr-only">Saturday</span>S</div> <div class="grid-item caption"><span class="sr-only">Saturday</span>S</div>
<div class="grid-item radio-wrapper past-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="thirtypast" /> <input disabled type="radio" name="day-of-month" id="sep-25" />
<label for="thirtypast"><span>30</span></label> <label class="past-day" for="sep-25"><span>25</span></label>
</div> </div>
<div class="grid-item radio-wrapper past-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="thirtyonepast" /> <input disabled type="radio" name="day-of-month" id="sep-26" />
<label for="thirtyonepast"><span>31</span></label> <label class="past-day" for="sep-26"><span>26</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day first-of-month"> <div class="grid-item radio-wrapper">
<div v-if="isFirstDayOfMonth" class="current-month">OCT</div> <input disabled type="radio" name="day-of-month" id="sep-27" />
<input type="radio" name="day-of-month" id="one" /> <label class="past-day" for="sep-27"><span>27</span></label>
<label for="one"><span class="sr-only">{{dayOfWeek}} {{pastMonth}} {{dayOfMonth}}</span><span>1</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="two" /> <input disabled type="radio" name="day-of-month" id="sep-28" />
<label for="two"><span>2</span></label> <label class="past-day" for="sep-28"><span>28</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="three" /> <input disabled type="radio" name="day-of-month" id="sep-29" />
<label for="three"><span>3</span></label> <label class="past-day" for="sep-29"><span>29</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day" :class="[isCurrentDay ? 'current-day' : '']"> <div class="grid-item radio-wrapper">
<div v-if="isCurrentDayDot" class="current-day-dot"></div> <input disabled type="radio" name="day-of-month" id="sep-30" />
<input type="radio" name="day-of-month" id="four" /> <label class="past-day" for="sep-30"><span>30</span></label>
<label for="four"><span>4</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <!-- Use this as the base calendar item -->
<input type="radio" name="day-of-month" id="five" /> <div class="grid-item radio-wrapper">
<label for="five"><span>5</span></label> <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>
<div class="grid-item radio-wrapper highlighted-day"> <!-- END -->
<input type="radio" name="day-of-month" id="six" /> <div class="grid-item radio-wrapper">
<label for="six"><span>6</span></label> <input type="radio" name="day-of-month" id="oct-2" />
<label class="selectable-day" for="oct-2"><span>2</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="seven" /> <input type="radio" name="day-of-month" id="oct-3" />
<label for="seven"><span>7</span></label> <label class="selectable-day" for="oct-3"><span>3</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="eight" /> <input type="radio" name="day-of-month" id="oct-4" />
<label for="eight"><span>8</span></label> <label class="selectable-day" for="oct-4"><span>4</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="nine" /> <input type="radio" name="day-of-month" id="oct-5" />
<label for="nine"><span>9</span></label> <label class="selectable-day" for="oct-5"><span>5</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="ten" /> <input type="radio" name="day-of-month" id="oct-6" />
<label for="ten"><span>10</span></label> <label class="selectable-day" for="oct-6"><span>6</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="eleven" /> <input type="radio" name="day-of-month" id="oct-7" />
<label for="eleven"><span>11</span></label> <label class="selectable-day" for="oct-7"><span>7</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="twelve" /> <input type="radio" name="day-of-month" id="oct-8" />
<label for="twelve"><span>12</span></label> <label class="selectable-day" for="oct-8"><span>8</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="thirteen" /> <input type="radio" name="day-of-month" id="oct-9" />
<label for="thirteen"><span>13</span></label> <label class="selectable-day" for="oct-9"><span>9</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="fourteen" /> <input type="radio" name="day-of-month" id="oct-10" />
<label for="fourteen"><span>17</span></label> <label class="selectable-day" for="oct-10"><span>10</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="fifteen" /> <input type="radio" name="day-of-month" id="oct-11" />
<label for="fifteen"><span>15</span></label> <label class="selectable-day" for="oct-11"><span>11</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="sixteen" /> <input type="radio" name="day-of-month" id="oct-12" />
<label for="sixteen"><span>16</span></label> <label class="selectable-day" for="oct-12"><span>12</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="seventeen" /> <input type="radio" name="day-of-month" id="oct-13" />
<label for="seventeen"><span>17</span></label> <label class="selectable-day" for="oct-13"><span>13</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="eighteen" /> <input type="radio" name="day-of-month" id="oct-14" />
<label for="eighteen"><span>18</span></label> <label class="selectable-day" for="oct-14"><span>14</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="nineteen" /> <input type="radio" name="day-of-month" id="oct-15" />
<label for="nineteen"><span>19</span></label> <label class="selectable-day" for="oct-15"><span>15</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="twenty" /> <input type="radio" name="day-of-month" id="oct-16" />
<label for="twenty"><span>20</span></label> <label class="selectable-day" for="oct-16"><span>16</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="twentyone" /> <input type="radio" name="day-of-month" id="oct-17" />
<label for="twentyone"><span>21</span></label> <label class="selectable-day" for="oct-17"><span>17</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="twentytwo" /> <input type="radio" name="day-of-month" id="oct-18" />
<label for="twentytwo"><span>22</span></label> <label class="selectable-day" for="oct-18"><span>18</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="twentythree" /> <input type="radio" name="day-of-month" id="oct-19" />
<label for="twentythree"><span>23</span></label> <label class="selectable-day" for="oct-19"><span>19</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="twentyfour" /> <input type="radio" name="day-of-month" id="oct-20" />
<label for="twentyfour"><span>24</span></label> <label class="selectable-day" for="oct-20"><span>20</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="twentyfive" /> <input type="radio" name="day-of-month" id="oct-21" />
<label for="twentyfive"><span>25</span></label> <label class="selectable-day" for="oct-21"><span>21</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="twentysix" /> <input type="radio" name="day-of-month" id="oct-22" />
<label for="twentysix"><span>26</span></label> <label class="selectable-day" for="oct-22"><span>22</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="twentyseven" /> <input type="radio" name="day-of-month" id="oct-23" />
<label for="twentyseven"><span>27</span></label> <label class="selectable-day" for="oct-23"><span>23</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="twentyeight" /> <input type="radio" name="day-of-month" id="oct-24" />
<label for="twentyeight"><span>28</span></label> <label class="selectable-day" for="oct-24"><span>24</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="twentynine" /> <input type="radio" name="day-of-month" id="oct-25" />
<label for="twentynine"><span>29</span></label> <label class="selectable-day" for="oct-25"><span>25</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="thirty" /> <input type="radio" name="day-of-month" id="oct-26" />
<label for="thirty"><span>30</span></label> <label class="selectable-day" for="oct-26"><span>26</span></label>
</div> </div>
<div class="grid-item radio-wrapper highlighted-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="thirtyone" /> <input type="radio" name="day-of-month" id="oct-27" />
<label for="thirtyone"><span>31</span></label> <label class="selectable-day" for="oct-27"><span>27</span></label>
</div> </div>
<div class="grid-item radio-wrapper future-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="onefuture" /> <input type="radio" name="day-of-month" id="oct-28" />
<label for="onefuture"><span>1</span></label> <label class="selectable-day" for="oct-28"><span>28</span></label>
</div> </div>
<div class="grid-item radio-wrapper future-day"> <div class="grid-item radio-wrapper">
<input type="radio" name="day-of-month" id="twofuture" /> <input type="radio" name="day-of-month" id="oct-29" />
<label for="twofuture"><span>2</span></label> <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>
</div> </div>
</fieldset> </fieldset>
</div> </div>
<form class="w-100 d-flex justify-content-center mt-5">
<input type="date">
</form>
</template> </template>
<script> <script>
@ -166,41 +201,94 @@ export default {
name: "datePicker", name: "datePicker",
data() { data() {
return { return {
dayOfWeek: "Tuesday", currentMonth: "October",
dayOfMonth: "1st", currentYear: "2022",
pastMonth: "October",
isFirstDayOfMonth: true, isFirstDayOfMonth: true,
isCurrentDay: true, isCurrentDay: true,
isCurrentDayDot: true,
}; };
}, },
}; };
</script> </script>
<style lang="scss"> <style lang="scss">
.container { .calendar-grid-container {
//max-width: 341px;
label {
display: initial; //Override Bootstrap default
}
#availableDates label:nth-child(14n):after {
content: "\a";
white-space: pre;
}
}
.grid-container {
margin: 0 auto; margin: 0 auto;
max-width: 341px; max-width: 414px;
display: grid; display: grid;
grid-gap: 4px; grid-template-columns: repeat(7, 1fr);
grid-template-columns: auto auto auto auto auto auto auto;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
padding: 4px;
.grid-item { .grid-item {
text-align: center; 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 { .radio-wrapper {
@ -208,252 +296,136 @@ export default {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
margin: 0; outline: none;
width: 46px;
height: 46px;
.current-day-dot { input[type="radio"] {
position: absolute; position: absolute; //override bootstrap
height: 0;
opacity: 0;
} &:focus-visible + label {
box-shadow: 0 0 0 2.5px $blue;
&.first-of-month {
.current-month {
position: absolute;
font-size: 9px;
top: 4px;
color: #1574a1;
} }
}
&.highlighted-day { &:focus + label,
&:checked:focus + label {
input[type="radio"] { box-shadow: 0 0 0 3px #fff, 0 0 0 5.5px #1574a1;
position: absolute; background-color: $blue;
// opacity: 0; color: $white;
height: 0.1px; &.past-day {
width: 0.1px; box-shadow: none;
background-color: transparent;
+ label { color: $gray-500;
position: relative; font-weight: normal;
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;
}
} }
&.current-day {
&: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;
}
&:after { &: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 { span {
position: relative; &.small {
top: -.25rem; 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> </style>

View file

@ -1,17 +1,6 @@
<template> <template>
<div v-for="(q, i) in questions" :key="i"> <div v-for="(q, i) in questions" :key="i">
<transition appear name="fade" mode="out-in"> <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 <buttonQuestion
v-if="q.answerSelected || (q.questionSequence === currentQuestionNum)" v-if="q.answerSelected || (q.questionSequence === currentQuestionNum)"
class="radioQuestion" class="radioQuestion"

View file

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

View file

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

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; return null;
} }
// TODO KO isOrderDifferent and isOrderSubmitted
// Reset state if cookie says to. // Reset state if cookie says to.
if (funnelCookie.ShouldResetState) { if (funnelCookie.ShouldResetState) {
baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE); baseMixin.methods.dispatchStoreAction(storeActions.RESET_STATE);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,34 +1,25 @@
<template> <template>
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
<div <div class="windshield-damage-type-question" v-if="isAvailable" aria-live="polite">
class="windshield-damage-type-question"
v-if="isAvailable"
aria-live="polite">
<buttonQuestion <buttonQuestion
:questionText="questionText" :questionText="questionText"
:answers="answersFromCms" :answers="answersFromCms"
:groupName="groupName" :groupName="groupName"
buttonType="listCard" buttonType="listCard"
v-model="selectedWindshieldDamageType" v-model="selectedValues"
:suppressError="suppressError" :suppressError="suppressError"
:validationRules="validationRules" :validationRules="validationRules"
isRequired /> isRequired
/>
</div> </div>
</transition> </transition>
</template> </template>
<script> <script>
import buttonQuestion from "@/common-components/button-question/button-question"; import buttonQuestion from "@/common-components/button-question/button-question";
// import buttonQuestionWrapperMixin from "@/mixins/button-question-wrapper-mixin";
export default { export default ({
name: "windshieldDamageTypeQuestion", name: "windshieldDamageTypeQuestion",
// mixins: [buttonQuestionWrapperMixin],
data() {
return {
selectedWindshieldDamageType: this.modelValue,
};
},
props: { props: {
modelValue: String, modelValue: String,
groupName: String, groupName: String,
@ -38,20 +29,23 @@ export default {
cmsWidgetName: String, cmsWidgetName: String,
}, },
computed: { computed: {
questionText() { questionText(){
return this.getCmsContent(this.cmsWidgetName, "QuestionText"); return this.getCmsContent(this.cmsWidgetName, 'QuestionText');
}, },
answersFromCms() { answersFromCms(){
return this.getCmsContent(this.cmsWidgetName, "Answers"); return this.getCmsContent(this.cmsWidgetName, 'Answers');
}, },
}, selectedValues: {
watch: { get: function() {
selectedWindshieldDamageType(selectedWindshieldDamageType) { return this.modelValue;
this.$emit("update:modelValue", selectedWindshieldDamageType); },
set: function(newValue) {
this.$emit("update:modelValue", newValue);
}
}, },
}, },
components: { components: {
buttonQuestion, 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) => { defineRule("check-for-repair-and-replace", (selectedWindshieldDamageType, selectedDamageLocations) => {
return selectedWindshieldDamageType.toString() != damageLocationsSelected.REPAIR || return selectedWindshieldDamageType.toString() != damageLocationsSelected.REPAIR ||
!selectedDamageLocations.includes(damageLocationsSelected.WINDSHIELD) || (!selectedDamageLocations.includes(damageLocationsSelected.WINDSHIELD) && !selectedDamageLocations[0]?.includes(damageLocationsSelected.WINDSHIELD)) ||
selectedDamageLocations.length === 1; (selectedDamageLocations[0].length === 1);
}); });
defineRule("repair-only", (value) => { defineRule("repair-only", (value) => {
return value.toString() === damageLocationsSelected.REPAIR; return value.toString() === damageLocationsSelected.REPAIR;
@ -83,7 +83,7 @@ export default ({
}, },
props: { props: {
modelValue: String, modelValue: Object,
selectedDamageLocations: Array, selectedDamageLocations: Array,
hasRepairReplaceConflict: Boolean, hasRepairReplaceConflict: Boolean,
hasSplitSingleConflict: Boolean, hasSplitSingleConflict: Boolean,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

View file

@ -3,7 +3,6 @@ html {
&.list-button, &.list-button,
&.list-card, &.list-card,
&.list-card.list-button { &.list-card.list-button {
// border: none;
color: $red; color: $red;
input[type=checkbox]:focus + label, input[type=checkbox]:focus + label,
input[type=radio]:focus + label { input[type=radio]:focus + label {
@ -39,6 +38,17 @@ html {
box-shadow: 0 0 1px $red; 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-radio,
&.ui-checkbox { &.ui-checkbox {
input[type=checkbox], input[type=checkbox],

View file

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

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

View file

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

View file

@ -2,7 +2,7 @@
<baseInputButton <baseInputButton
v-bind="$props" v-bind="$props"
buttonWrapperClasses="list-group list-button rounded-3 d-flex flex-column w-100 mb-2" buttonWrapperClasses="list-group list-button rounded-3 d-flex flex-column w-100 mb-2"
@buttonClicked="handleAnswerChange"> v-model="selectedValue">
<div <div
:aria-label="buttonLabel" :aria-label="buttonLabel"
class="list-button-content d-flex flex-column justify-content-center py-3 px-4"> 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", groupID: "checkbox-demo-1",
groupName: "Checkbox 1", groupName: "Checkbox 1",
buttonImage: "windshield-damage.svg", buttonImage: "windshield-damage.svg",
value: "test value",
}, },
}); });
@ -32,6 +33,7 @@ describe("list-card.vue", () => {
groupID: "radio-demo-1", groupID: "radio-demo-1",
groupName: "radio 1", groupName: "radio 1",
buttonImage: "windshield-damage.svg", buttonImage: "windshield-damage.svg",
value: "test value",
}, },
}); });
@ -51,6 +53,7 @@ describe("list-card.vue", () => {
groupName: "radio 1", groupName: "radio 1",
buttonImage: "windshield-damage.svg", buttonImage: "windshield-damage.svg",
buttonLabelSubCopy: "Test", buttonLabelSubCopy: "Test",
value: "test value",
}, },
}); });
@ -70,6 +73,7 @@ describe("list-card.vue", () => {
groupName: "radio 1", groupName: "radio 1",
buttonImage: "windshield-damage.svg", buttonImage: "windshield-damage.svg",
buttonLabelSubCopy: "Test", buttonLabelSubCopy: "Test",
value: "test value",
}, },
}); });
@ -89,6 +93,7 @@ describe("list-card.vue", () => {
groupName: "radio 1", groupName: "radio 1",
buttonImage: "windshield-damage.svg", buttonImage: "windshield-damage.svg",
isRequired: true, isRequired: true,
value: "test value",
}, },
}); });
@ -110,6 +115,7 @@ describe("list-card.vue", () => {
isRequired: true, isRequired: true,
isWide: true, isWide: true,
buttonLabelSubCopy: "", buttonLabelSubCopy: "",
value: "test value",
}, },
}); });
@ -134,6 +140,7 @@ describe("list-card.vue", () => {
isRequired: true, isRequired: true,
isWide: true, isWide: true,
buttonLabelSubCopy: "Button Subcopy", buttonLabelSubCopy: "Button Subcopy",
value: "test value",
}, },
}); });
@ -159,6 +166,7 @@ describe("list-card.vue", () => {
buttonImage: "windshield-damage.svg", buttonImage: "windshield-damage.svg",
isRequired: true, isRequired: true,
isWide: false, isWide: false,
value: "test value",
}, },
}); });

View file

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

View file

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

View file

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