Merge pull request #763 from Safelite/feature/CSR-762-GA
DO NOT MERGE - For viewing purposes
This commit is contained in:
commit
39d922fd4e
31 changed files with 1803 additions and 1103 deletions
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"tabWidth": 2,
|
||||
"tabWidth": 4,
|
||||
"bracketSameLine": true
|
||||
}
|
||||
|
|
@ -28,8 +28,7 @@ module.exports = {
|
|||
testMatch: ["**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"],
|
||||
coverageThreshold: {
|
||||
global: {
|
||||
// TODO KO
|
||||
statements: 55,
|
||||
statements: 85,
|
||||
// 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
|
||||
},
|
||||
},
|
||||
|
|
|
|||
71
src/App.vue
71
src/App.vue
|
|
@ -1,16 +1,65 @@
|
|||
<template>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition :duration="{ enter: 200, leave: 200 }" name="route-fade" mode="out-in">
|
||||
<!-- The above durations should be kept in sync with the global css class "fade-on-route-transition" -->
|
||||
<component :is="Component" />
|
||||
</transition>
|
||||
</router-view>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition
|
||||
:duration="{ enter: 200, leave: 200 }"
|
||||
name="route-fade"
|
||||
mode="out-in">
|
||||
<!-- The above durations should be kept in sync with the global css class "fade-on-route-transition" -->
|
||||
<component :is="Component" @focusin="handleChildFocus" />
|
||||
</transition>
|
||||
</router-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// TODO KO glass-part-question has nested button questions
|
||||
// TODO KO selectedValues - make consistent for checkbox and radio if possible
|
||||
export default {
|
||||
name: "app",
|
||||
data() {
|
||||
return {
|
||||
lastFocusedInputGroupName: "",
|
||||
onFocusCallback: null,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
handleChildFocus(e) {
|
||||
const targetType = e.target.type;
|
||||
if (targetType !== "radio" && targetType !== "checkbox") {
|
||||
this.handleInputFocus({
|
||||
groupName: null,
|
||||
});
|
||||
}
|
||||
},
|
||||
handleInputFocus(e) {
|
||||
if (
|
||||
e &&
|
||||
this.lastFocusedInputGroupName !== e.groupName &&
|
||||
this.onFocusCallback
|
||||
) {
|
||||
this.onFocusCallback();
|
||||
}
|
||||
},
|
||||
handleInputBlur(e) {
|
||||
if (e) {
|
||||
this.lastFocusedInputGroupName = e.groupName;
|
||||
this.onFocusCallback = e.onFocusCallback;
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route: {
|
||||
handler() {
|
||||
this.lastFocusedInputGroup = null;
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "./node_modules/bootstrap/scss/bootstrap";
|
||||
@import "@/styles/common-styles.scss";
|
||||
@import "@/styles/common-typography-styles.scss";
|
||||
@import "@/styles/common-error-styles.scss";
|
||||
@import "@/styles/common-animations.scss";
|
||||
@import "./node_modules/bootstrap/scss/bootstrap";
|
||||
@import "@/styles/common-styles.scss";
|
||||
@import "@/styles/common-typography-styles.scss";
|
||||
@import "@/styles/common-error-styles.scss";
|
||||
@import "@/styles/common-animations.scss";
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,539 @@
|
|||
import { shallowMount, mount } from "@vue/test-utils";
|
||||
import baseInputButton from "./base-input-button";
|
||||
|
||||
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()["inputButtonClicked"][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()["inputButtonClicked"][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()["inputButtonClicked"][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(
|
||||
"inputButtonClicked"
|
||||
);
|
||||
});
|
||||
|
||||
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()["inputButtonClicked"][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("inputButtonClicked");
|
||||
expect(wrapper.emitted()["inputButtonClicked"][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("inputButtonClicked");
|
||||
expect(wrapper.emitted()["inputButtonClicked"][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");
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// TODO KO look at how I tested groups of these in SFA (making sure selecting one radio changes the value, etc, that this acts like a regular input aside from a different emitted event)
|
||||
|
||||
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 baseInputButtonWrapper = {
|
||||
components: { baseInputButton },
|
||||
template:
|
||||
'<div><baseInputButton v-model="myValue" groupName="myGroupName" value="X" :isMultiSelect="isMultiSelect" /></div>',
|
||||
data() {
|
||||
return {
|
||||
myValue: "",
|
||||
isMultiSelect: mockData.isMultiSelect,
|
||||
};
|
||||
},
|
||||
|
||||
// const parentComponent = mount({
|
||||
// data() {
|
||||
// return {
|
||||
// value: "value1",
|
||||
// }
|
||||
// },
|
||||
// template: '<div><baseInputButton v-model="value" value="value1" /><baseInputButton v-model="value" value="value2" /></div>',
|
||||
// components: { baseInputButton }
|
||||
// })
|
||||
};
|
||||
|
||||
const wrapper = mount(baseInputButtonWrapper, {});
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
@ -2,8 +2,9 @@
|
|||
<label
|
||||
:class="[buttonWrapperClasses, { 'has-error': errors.length > 0 }]"
|
||||
:for="buttonId"
|
||||
@mousedown.left="handleEventAction('click', $event)"
|
||||
@keyup="handleKeyboardNavigation">
|
||||
@focusin="handleFocus"
|
||||
@focusout="handleBlur"
|
||||
@mousedown.left="handleEventAction('click', $event)">
|
||||
<input
|
||||
:type="inputType"
|
||||
:id="buttonId"
|
||||
|
|
@ -13,7 +14,6 @@
|
|||
:aria-required="isRequired"
|
||||
:value="value"
|
||||
:checked="isChecked"
|
||||
@blur="handleBlur"
|
||||
@keypress.space="handleEventAction('space', $event)"
|
||||
@keypress.enter="handleEventAction('enter', $event)"
|
||||
@change="handleEventAction('change', $event)" />
|
||||
|
|
@ -23,18 +23,20 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { queryStrings } from "@/constants/query-strings";
|
||||
import { useField } from "vee-validate";
|
||||
import { toRef } from "vue";
|
||||
import { queryStrings } from "@/constants/query-strings";
|
||||
import inputButtonWrapperMixin from "../../mixins/input-button-wrapper-mixin";
|
||||
|
||||
export default {
|
||||
name: "base-input-button",
|
||||
emits: ["change"],
|
||||
emits: ["change", "inputButtonClicked"],
|
||||
model: {
|
||||
prop: "modelValue",
|
||||
event: "change",
|
||||
},
|
||||
props: {
|
||||
// ...inputButtonWrapperMixin.props
|
||||
value: {
|
||||
type: [String, Number],
|
||||
required: true,
|
||||
|
|
@ -60,6 +62,9 @@ export default {
|
|||
default: true,
|
||||
},
|
||||
isRequired: Boolean,
|
||||
lastValuePushedToGa: [String, Number],
|
||||
setLastValuePushedToGa: Function,
|
||||
shouldPushClickEventToGAOnMount: Boolean,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
|
@ -68,36 +73,37 @@ export default {
|
|||
},
|
||||
mounted() {
|
||||
if (this.isChecked) {
|
||||
this.handleChange(this.modelValue);
|
||||
if (this.shouldPushClickEventToGAOnMount) {
|
||||
this.handleEventAction(this.eventTypes.MOUNT)
|
||||
} else {
|
||||
this.handleChange(this.modelValue);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleEventAction(eventType, e) {
|
||||
const eventTypes = {
|
||||
CHANGE: "change",
|
||||
ENTER: "enter",
|
||||
SPACE: "space",
|
||||
CLICK: "click",
|
||||
};
|
||||
|
||||
if (this.isMultiSelect) {
|
||||
switch (eventType) {
|
||||
case eventTypes.ENTER:
|
||||
case eventTypes.CHANGE:
|
||||
case this.eventTypes.ENTER:
|
||||
case this.eventTypes.CHANGE:
|
||||
this.handleClick(e);
|
||||
window.lastFocusedInputGroup = this.groupName;
|
||||
this.pushClickEventToGA();
|
||||
this.handlePushClickEventToGACheck(
|
||||
this.eventTypes.CLICK
|
||||
);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (eventType) {
|
||||
case eventTypes.CLICK:
|
||||
case eventTypes.ENTER:
|
||||
case eventTypes.SPACE:
|
||||
case this.eventTypes.CLICK:
|
||||
case this.eventTypes.ENTER:
|
||||
case this.eventTypes.SPACE:
|
||||
case this.eventTypes.MOUNT:
|
||||
this.handleClick(e);
|
||||
this.test();
|
||||
this.handlePushClickEventToGACheck(
|
||||
this.eventTypes.CLICK
|
||||
);
|
||||
break;
|
||||
case eventTypes.CHANGE:
|
||||
case this.eventTypes.CHANGE:
|
||||
this.selectOnKeypress
|
||||
? this.handleClick(e)
|
||||
: this.handleSelectionChange(e);
|
||||
|
|
@ -122,21 +128,37 @@ export default {
|
|||
this.valueToEmit = this.value;
|
||||
}
|
||||
|
||||
window.currentlySelectedValues =
|
||||
window.currentlySelectedValues ?? {};
|
||||
window.currentlySelectedValues[this.groupName] = this.value;
|
||||
this.handleChange(this.valueToEmit);
|
||||
},
|
||||
handleClick(e) {
|
||||
this.handleSelectionChange(e);
|
||||
this.$emit("buttonClicked", this.valueToEmit);
|
||||
this.$emit("inputButtonClicked", this.valueToEmit);
|
||||
},
|
||||
handleFocus() {
|
||||
this.$root.handleInputFocus({
|
||||
groupName: this.groupName,
|
||||
});
|
||||
},
|
||||
handleBlur() {
|
||||
this.$root.handleInputBlur({
|
||||
groupName: this.groupName,
|
||||
onFocusCallback: this.handlePushClickEventToGACheck,
|
||||
});
|
||||
},
|
||||
handlePushClickEventToGACheck(source) {
|
||||
if (source === this.eventTypes.CLICK) {
|
||||
this.pushClickEventToGA();
|
||||
} else {
|
||||
if (
|
||||
!this.isValueSelectedOnClick &&
|
||||
this.isChecked &&
|
||||
this.lastValuePushedToGa != this.value
|
||||
) {
|
||||
this.pushClickEventToGA();
|
||||
}
|
||||
}
|
||||
},
|
||||
pushClickEventToGA(value) {
|
||||
window.firedGaClickEventValues =
|
||||
window.firedGaClickEventValues ?? {};
|
||||
window.firedGaClickEventValues[window.lastFocusedInputGroup] =
|
||||
value ?? this.value;
|
||||
|
||||
this.pushEventToGA(
|
||||
this.$route.query[queryStrings.FMG_PAGE],
|
||||
this.GaActions.CLICKED,
|
||||
|
|
@ -144,88 +166,39 @@ export default {
|
|||
true,
|
||||
this.valueToLogType
|
||||
);
|
||||
},
|
||||
handleBlur() {
|
||||
window.lastFocusedInputGroup = this.groupName;
|
||||
window.wasLastFocusedInputMultiselect = this.isMultiSelect;
|
||||
},
|
||||
handleKeyboardNavigation(key) {
|
||||
this.test(key);
|
||||
},
|
||||
test(event) {
|
||||
window.firedGaClickEventValues =
|
||||
window.firedGaClickEventValues ?? {};
|
||||
window.currentlySelectedValues =
|
||||
window.currentlySelectedValues ?? {};
|
||||
|
||||
const keyCode = event?.code;
|
||||
// Keyboard navigation
|
||||
if (keyCode) {
|
||||
if (
|
||||
window.currentlySelectedValues &&
|
||||
window.firedGaClickEventValues &&
|
||||
window.lastFocusedInputGroup &&
|
||||
!window.wasLastFocusedInputMultiselect &&
|
||||
window.currentlySelectedValues[
|
||||
window.lastFocusedInputGroup
|
||||
] &&
|
||||
window.firedGaClickEventValues[
|
||||
window.lastFocusedInputGroup
|
||||
] !=
|
||||
window.currentlySelectedValues[
|
||||
window.lastFocusedInputGroup
|
||||
]
|
||||
) {
|
||||
if (keyCode === "Tab") {
|
||||
this.pushClickEventToGA(
|
||||
window.currentlySelectedValues[
|
||||
window.lastFocusedInputGroup
|
||||
]
|
||||
);
|
||||
} else if (keyCode?.includes("Arrow")) {
|
||||
if (window.lastFocusedInputGroup != this.groupName) {
|
||||
this.pushClickEventToGA(
|
||||
window.currentlySelectedValues[
|
||||
window.lastFocusedInputGroup
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Radio click
|
||||
window.lastFocusedInputGroup = this.groupName;
|
||||
if (
|
||||
window.firedGaClickEventValues[
|
||||
window.lastFocusedInputGroup
|
||||
] !=
|
||||
window.currentlySelectedValues[window.lastFocusedInputGroup]
|
||||
) {
|
||||
this.pushClickEventToGA(
|
||||
window.currentlySelectedValues[
|
||||
window.lastFocusedInputGroup
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
this.setLastValuePushedToGa(this.value);
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isChecked() {
|
||||
if (this.isMultiSelect && this.modelValue instanceof Array) {
|
||||
return this.modelValue.includes(this.value);
|
||||
} else if (!this.isMultiSelect) {
|
||||
return this.modelValue == this.value;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.modelValue == this.value;
|
||||
},
|
||||
inputType() {
|
||||
return this.isMultiSelect ? "checkbox" : "radio";
|
||||
},
|
||||
buttonId() {
|
||||
return `${this.groupName}-${JSON.stringify(this.value)?.replace(
|
||||
" ",
|
||||
"-"
|
||||
)}`;
|
||||
return `${this.groupName?.replace(" ", "-")}-${this.value
|
||||
?.toString()
|
||||
?.replace(" ", "-")}`;
|
||||
},
|
||||
isValueSelectedOnClick() {
|
||||
return this.isMultiSelect || this.selectingInitiatesLoad;
|
||||
},
|
||||
eventTypes() {
|
||||
return {
|
||||
CHANGE: "change",
|
||||
ENTER: "enter",
|
||||
SPACE: "space",
|
||||
CLICK: "click",
|
||||
MOUNT: "mount"
|
||||
};
|
||||
},
|
||||
},
|
||||
setup(props) {
|
||||
|
|
|
|||
|
|
@ -2,189 +2,170 @@ import { shallowMount } from "@vue/test-utils";
|
|||
import buttonQuestion from "@/common-components/button-question/button-question";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
|
||||
jest.mock("@/store", () => { return {}; }, { virtual: true });
|
||||
jest.mock(
|
||||
"@/store",
|
||||
() => {
|
||||
return {};
|
||||
},
|
||||
{ virtual: true }
|
||||
);
|
||||
|
||||
// TODO KO
|
||||
describe.skip("buttonQuestion.vue", () => {
|
||||
describe("buttonQuestion.vue", () => {
|
||||
it("Should show overflow classes on fieldset if isOverflowScrollable is true", () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(buttonQuestion, {
|
||||
propsData: {
|
||||
isOverflowScrollable: true,
|
||||
groupName: "group-name"
|
||||
}
|
||||
groupName: "group-name",
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const fieldSet = wrapper.find('fieldset');
|
||||
const fieldSet = wrapper.find("fieldset");
|
||||
expect(fieldSet.classes()).toContain("overflow-scroll");
|
||||
});
|
||||
});
|
||||
|
||||
// describe("buttonQuestion.vue", () => {
|
||||
// it("Fieldset classes should contain row if button type is listCard", () => {
|
||||
// // Act
|
||||
// const wrapper = shallowMount(buttonQuestion, {
|
||||
// propsData: {
|
||||
// buttonType: "listCard",
|
||||
// groupName: "group-name"
|
||||
// }
|
||||
// });
|
||||
// // Assert
|
||||
// const Div = wrapper.find('fieldset div');
|
||||
// expect(Div.classes()).toContain("row");
|
||||
// });
|
||||
// });
|
||||
describe("buttonQuestion.vue", () => {
|
||||
it("Fieldset classes should contain row if button type is listCard", () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(buttonQuestion, {
|
||||
propsData: {
|
||||
buttonType: "listCard",
|
||||
groupName: "group-name",
|
||||
},
|
||||
});
|
||||
// Assert
|
||||
const Div = wrapper.find("fieldset div");
|
||||
expect(Div.classes()).toContain("row");
|
||||
});
|
||||
});
|
||||
|
||||
// describe("buttonQuestion.vue", () => {
|
||||
// it("Fieldset classes should contain d-flex if button type is listButtonHorizontal", () => {
|
||||
// // Act
|
||||
// const wrapper = shallowMount(buttonQuestion, {
|
||||
// propsData: {
|
||||
// buttonType: "listButtonHorizontal",
|
||||
// groupName: "group-name"
|
||||
// }
|
||||
// });
|
||||
// // Assert
|
||||
// const Div = wrapper.find('fieldset div');
|
||||
// expect(Div.classes()).toContain("d-flex");
|
||||
// });
|
||||
// });
|
||||
describe("buttonQuestion.vue", () => {
|
||||
it("Fieldset classes should contain d-flex if button type is listButtonHorizontal", () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(buttonQuestion, {
|
||||
propsData: {
|
||||
buttonType: "listButtonHorizontal",
|
||||
groupName: "group-name",
|
||||
},
|
||||
});
|
||||
// Assert
|
||||
const Div = wrapper.find("fieldset div");
|
||||
expect(Div.classes()).toContain("d-flex");
|
||||
});
|
||||
});
|
||||
|
||||
// describe("buttonQuestion.vue", () => {
|
||||
// it("Fieldset classes should contain ui-radio if button type is radio", () => {
|
||||
// // Act
|
||||
// const wrapper = shallowMount(buttonQuestion, {
|
||||
// propsData: {
|
||||
// buttonType: "radio",
|
||||
// groupName: "group-name"
|
||||
// }
|
||||
// });
|
||||
// // Assert
|
||||
// const Div = wrapper.find('fieldset div');
|
||||
// expect(Div.classes()).toContain("ui-radio");
|
||||
// });
|
||||
// });
|
||||
describe("buttonQuestion.vue", () => {
|
||||
it("Fieldset classes should contain ui-radio if button type is radio", () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(buttonQuestion, {
|
||||
propsData: {
|
||||
buttonType: "radio",
|
||||
groupName: "group-name",
|
||||
},
|
||||
});
|
||||
// Assert
|
||||
const Div = wrapper.find("fieldset div");
|
||||
expect(Div.classes()).toContain("ui-radio");
|
||||
});
|
||||
});
|
||||
|
||||
// testing a computed property
|
||||
describe("buttonQuestion.vue", () => {
|
||||
it("getColLength should return '12' if prop isWide is set to true", () => {
|
||||
// Act
|
||||
const localThis = { isWide: true };
|
||||
|
||||
// // testing a computed property
|
||||
// describe("buttonQuestion.vue", () => {
|
||||
// it("getColLength should return '12' if prop isWide is set to true", () => {
|
||||
// // Act
|
||||
// const localThis = { isWide: true }
|
||||
expect(buttonQuestion.computed.getColLength.call(localThis)).toBe("12");
|
||||
});
|
||||
});
|
||||
|
||||
// expect(buttonQuestion.computed.getColLength.call(localThis)).toBe("12");
|
||||
// });
|
||||
// });
|
||||
describe("buttonQuestion.vue", () => {
|
||||
it("getColLength should return '' if prop isWide is set to false", () => {
|
||||
// Act
|
||||
const localThis = {
|
||||
isWide: false,
|
||||
answers: ["a", "b"],
|
||||
groupName: "group-name",
|
||||
};
|
||||
|
||||
// describe("buttonQuestion.vue", () => {
|
||||
// it("getColLength should return '' if prop isWide is set to false", () => {
|
||||
// // Act
|
||||
// const localThis = {
|
||||
// isWide: false,
|
||||
// answers: ['a', 'b'],
|
||||
// groupName: "group-name"
|
||||
// }
|
||||
expect(buttonQuestion.computed.getColLength.call(localThis)).toBe("");
|
||||
});
|
||||
});
|
||||
|
||||
// expect(buttonQuestion.computed.getColLength.call(localThis)).toBe("");
|
||||
// });
|
||||
// });
|
||||
describe("buttonQuestion.vue", () => {
|
||||
it("Should return answer.Text if prop useTextForValue is true", async () => {
|
||||
// Act
|
||||
const localThis = { useTextForValue: true };
|
||||
const answer = { Name: "testName", Text: "testText" };
|
||||
|
||||
// describe("buttonQuestion.vue", () => {
|
||||
// it("Should return answer.Text if prop useTextForValue is true", async () => {
|
||||
// // Act
|
||||
// const localThis = { useTextForValue: true };
|
||||
// const answer = { 'Name': 'testName', 'Text': 'testText' };
|
||||
// Assert
|
||||
expect(buttonQuestion.methods.getValue.call(localThis, answer)).toBe(
|
||||
"testText"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// // Assert
|
||||
// expect(buttonQuestion.methods.getValue.call(localThis, answer)).toBe('testText');
|
||||
// });
|
||||
// });
|
||||
describe("buttonQuestion.vue", () => {
|
||||
it("Should return answer.Name if prop useTextForValue is false and there's not buttonLabel and answer.Name exists", async () => {
|
||||
// Act
|
||||
const localThis = { useTextForValue: false };
|
||||
const answer = { Name: "testName", Text: "testText" };
|
||||
|
||||
// describe("buttonQuestion.vue", () => {
|
||||
// it("Should return answer.Name if prop useTextForValue is false and answer.Name exists", async () => {
|
||||
// // Act
|
||||
// const localThis = { useTextForValue: false };
|
||||
// const answer = { 'Name': 'testName', 'Text': 'testText' };
|
||||
// Assert
|
||||
expect(buttonQuestion.methods.getValue.call(localThis, answer)).toBe(
|
||||
"testName"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// // Assert
|
||||
// expect(buttonQuestion.methods.getValue.call(localThis, answer)).toBe('testName');
|
||||
// });
|
||||
// });
|
||||
describe("buttonQuestion.vue", () => {
|
||||
describe("handleAnswerChange", () => {
|
||||
test("is radio => should emit captured value", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({ propsData: { groupName: "group-name" } })
|
||||
);
|
||||
await wrapper.setProps({
|
||||
answers: ["2022", "2021", "2020"],
|
||||
isMultiSelect: false,
|
||||
modelValue: "",
|
||||
});
|
||||
const val = "2021";
|
||||
wrapper.vm.handleAnswerChange(val);
|
||||
|
||||
// describe("buttonQuestion.vue", () => {
|
||||
// it("Should trigger event modelValue change to new value on when radio button selected", async () => {
|
||||
// // Act
|
||||
// const wrapper = shallowMount(buttonQuestion, setupMocks({propsData: {groupName: "group-name"}}));
|
||||
// await wrapper.setProps({
|
||||
// answers: ["2022", "2021", "2020"],
|
||||
// isMultiSelect: false,
|
||||
// modelValue: []
|
||||
// });
|
||||
// const val = { checkValue: true, value: "2021", }
|
||||
// wrapper.vm.handleCheckedChanged(val);
|
||||
// // Assert
|
||||
// expect(wrapper.emitted()["update:modelValue"][0]).toEqual([["2021"]]);
|
||||
// });
|
||||
// });
|
||||
// Assert
|
||||
expect(wrapper.emitted()["update:modelValue"][0]).toEqual(["2021"]);
|
||||
});
|
||||
|
||||
// describe("buttonQuestion.vue", () => {
|
||||
// it("Should add values to array on checkbox click", () => {
|
||||
// // Act
|
||||
// const wrapper = shallowMount(buttonQuestion, setupMocks({
|
||||
// propsData: {
|
||||
// modelValue: ["2022", "2021", "2020"],
|
||||
// isMultiSelect: true,
|
||||
// groupName: "group-name"
|
||||
// }
|
||||
// }));
|
||||
// const val = { checkValue: true, value: "2019", }
|
||||
// wrapper.vm.handleCheckedChanged(val);
|
||||
// // Assert
|
||||
// expect(wrapper.emitted()["update:modelValue"][0]).toEqual([["2022", "2021", "2020", "2019"]]);
|
||||
// });
|
||||
// });
|
||||
test("is checkbox => should emit captured value", async () => {
|
||||
const wrapper = shallowMount(
|
||||
buttonQuestion,
|
||||
setupMocks({ propsData: { groupName: "group-name" } })
|
||||
);
|
||||
await wrapper.setProps({
|
||||
answers: ["2022", "2021", "2020"],
|
||||
isMultiSelect: false,
|
||||
modelValue: "",
|
||||
});
|
||||
const val = ["2022", "2021", , "2019", "2020"];
|
||||
wrapper.vm.handleAnswerChange(val);
|
||||
|
||||
// describe("buttonQuestion.vue", () => {
|
||||
// it("Should add a value to this.selectedValues if prop isMultiSelect is true, checkValue is true and this.selectedValues already exists", () => {
|
||||
// // Act
|
||||
// const wrapper = shallowMount(buttonQuestion, setupMocks({
|
||||
// propsData: {
|
||||
// isMultiSelect: true,
|
||||
// modelValue: ['a', 'b'],
|
||||
// groupName: "group-name"
|
||||
// }
|
||||
// }));
|
||||
// const val = { checkValue: true, value: "2021", }
|
||||
// wrapper.vm.handleCheckedChanged(val);
|
||||
|
||||
// // Assert
|
||||
// expect(wrapper.vm.selectedValues).toEqual(["a", "b", "2021"]);
|
||||
// });
|
||||
// });
|
||||
|
||||
// describe("buttonQuestion.vue", () => {
|
||||
// it("Should remove a value to this.selectedValues if prop isMultiSelect is true, checkValue is false and this.selectedValues already exists", () => {
|
||||
// // Act
|
||||
// const wrapper = shallowMount(buttonQuestion, setupMocks({
|
||||
// propsData: {
|
||||
// isMultiSelect: true,
|
||||
// modelValue: ['a', 'b'],
|
||||
// groupName: "group-name"
|
||||
// }
|
||||
// }));
|
||||
|
||||
// const val = { checkValue: false, value: "a", }
|
||||
// wrapper.vm.handleCheckedChanged(val);
|
||||
|
||||
// // Assert
|
||||
// expect(wrapper.vm.selectedValues).toEqual(["b"]);
|
||||
// });
|
||||
// });
|
||||
// Assert
|
||||
expect(wrapper.emitted()["update:modelValue"][0]).toEqual([
|
||||
["2022", "2021", , "2019", "2020"],
|
||||
]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function setupMocks(mountOptionsMockData = {}) {
|
||||
const defaultMountOptions = { route: { query: { fmgPage: 'page-name' } } };
|
||||
const baseMountOptions = getMountOptions(Object.assign(defaultMountOptions, mountOptionsMockData));
|
||||
const defaultMountOptions = { route: { query: { fmgPage: "page-name" } } };
|
||||
const baseMountOptions = getMountOptions(
|
||||
Object.assign(defaultMountOptions, mountOptionsMockData)
|
||||
);
|
||||
const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions);
|
||||
|
||||
return allMountOptions;
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
</div>
|
||||
|
||||
<div class="w-100 d-flex justify-content-center">
|
||||
|
||||
<fieldset
|
||||
class="w-100"
|
||||
:aria-required="isRequired"
|
||||
|
|
@ -52,13 +51,16 @@
|
|||
:validationRules="validationRules"
|
||||
:textPosition="textPosition"
|
||||
:selectOnKeypress="selectOnKeypress"
|
||||
@buttonClicked="handleAnswerChange" />
|
||||
:lastValuePushedToGa="lastValuePushedToGa"
|
||||
:setLastValuePushedToGa="setLastValuePushedToGa"
|
||||
:shouldPushClickEventToGAOnMount="shouldPushClickEventToGAOnMount"
|
||||
@inputButtonClicked="handleAnswerChange" />
|
||||
<!-- For nested questions -->
|
||||
<transition name="fade" mode="out-in">
|
||||
<div
|
||||
v-if="
|
||||
typeof primaryValue == 'string' &&
|
||||
primaryValue == answer.value
|
||||
typeof selectedValues == 'string' &&
|
||||
selectedValues == answer.value
|
||||
">
|
||||
<slot></slot>
|
||||
</div>
|
||||
|
|
@ -79,8 +81,8 @@
|
|||
import listButton from "@/ux-components/list-button/list-button";
|
||||
import listButtonHorizontal from "@/ux-components/list-button-horizontal/list-button-horizontal";
|
||||
import listCard from "@/ux-components/list-card/list-card";
|
||||
import { ErrorMessage } from "vee-validate";
|
||||
import radio from "@/ux-components/radio/radio";
|
||||
import { ErrorMessage } from "vee-validate";
|
||||
|
||||
export default {
|
||||
name: "buttonQuestion",
|
||||
|
|
@ -120,11 +122,11 @@ export default {
|
|||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
shouldPushClickEventToGAOnMount: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
primaryValue: "",
|
||||
lastFocusedInputGroup: "",
|
||||
lastValuePushedToGa: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -196,6 +198,14 @@ export default {
|
|||
})
|
||||
);
|
||||
},
|
||||
selectedValues: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(selectedAnswers) {
|
||||
this.$emit("update:modelValue", selectedAnswers);
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatString(str) {
|
||||
|
|
@ -219,10 +229,13 @@ export default {
|
|||
: this.formatString(answer.toString());
|
||||
}
|
||||
},
|
||||
handleAnswerChange(primaryAnswerValue) {
|
||||
this.primaryValue = primaryAnswerValue;
|
||||
this.$emit("buttonQuestionChange", primaryAnswerValue);
|
||||
this.$emit("update:modelValue", primaryAnswerValue);
|
||||
handleAnswerChange(selectedAnswers) {
|
||||
this.selectedValues = selectedAnswers;
|
||||
|
||||
this.$emit("buttonQuestionChange", selectedAnswers);
|
||||
},
|
||||
setLastValuePushedToGa(lastValuePushedToGa) {
|
||||
this.lastValuePushedToGa = lastValuePushedToGa;
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ const storeActions = {
|
|||
RUN_EXPERIMENTS_FOR_TRIGGER: "runExperimentsForTrigger",
|
||||
CLEAR_VIN: "clearVin",
|
||||
RESET_SAVE_SESSION_PROMISE: "resetSaveSessionPromise",
|
||||
|
||||
// DEPENDENCY MUTATIONS
|
||||
RESET_VEHICLE_STATE_AND_DEPENDENCIES: "resetVehicleAndDependencies",
|
||||
RESET_DAMAGE_STATE_AND_DEPENDENCIES: "resetDamageAndDependencies",
|
||||
|
|
@ -63,12 +64,4 @@ const storeActions = {
|
|||
SAVE_CAPABILITY_QUESTION_ANSWERS: "saveCapabilityQuestionAnswers",
|
||||
};
|
||||
|
||||
const gaStoreActions = {
|
||||
UPDATE_CURRENTLY_SELECTED_VALUES: "updateCurrentlySelectedValues",
|
||||
UPDATE_FIRED_GA_CLICK_EVENT_VALUES: "updateFiredGaClickEventValues",
|
||||
UPDATE_LAST_FOCUSED_INPUT_GROUP: "updateLastFocusedInputGroup",
|
||||
UPDATE_WAS_LAST_FOCUSED_INPUT_MULTISELECT:
|
||||
"updateWasLastFocusedInputMultiselect",
|
||||
};
|
||||
|
||||
export { storeActions, gaStoreActions };
|
||||
export { storeActions };
|
||||
|
|
|
|||
|
|
@ -68,12 +68,4 @@ const storeMutations = {
|
|||
UPDATE_TRIGGERED_SITE_ENTRY: "updateTriggeredSiteEntry",
|
||||
};
|
||||
|
||||
const gaStoreMutations = {
|
||||
UPDATE_CURRENTLY_SELECTED_VALUES: "updateCurrentlySelectedValues",
|
||||
UPDATE_FIRED_GA_CLICK_EVENT_VALUES: "updateFiredGaClickEventValues",
|
||||
UPDATE_LAST_FOCUSED_INPUT_GROUP: "updateLastFocusedInputGroup",
|
||||
UPDATE_WAS_LAST_FOCUSED_INPUT_MULTISELECT:
|
||||
"updateWasLastFocusedInputMultiselect",
|
||||
};
|
||||
|
||||
export { storeMutations, gaStoreMutations };
|
||||
export { storeMutations };
|
||||
|
|
@ -36,8 +36,7 @@ describe("replace-options-question.vue", () => {
|
|||
});
|
||||
|
||||
describe("replace-options-question.vue", () => {
|
||||
// TODO KO
|
||||
test.skip("when updateSelectedValues method is called with a single answerToDisplay it will call to update this.selectedValues", async () => {
|
||||
test("when updateSelectedValues method is called with a single answerToDisplay it will call to update this.selectedReplaceOptions", async () => {
|
||||
|
||||
//Arrange
|
||||
const { wrapper, cmsContent, replaceOptions
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ export default ({
|
|||
name: "sideDoorOptions",
|
||||
props: {
|
||||
groupName: String,
|
||||
modelValue: Array,
|
||||
modelValue: [Array, Object], // TODO Does this take an array?
|
||||
selectedDamageLocations: Array,
|
||||
cmsWidgetName: String,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -55,8 +55,7 @@ jest.mock("@/store", () => ({
|
|||
},
|
||||
}));
|
||||
|
||||
// TODO KO
|
||||
describe.skip("vehicle-damage.vue", () => {
|
||||
describe("vehicle-damage.vue", () => {
|
||||
describe("navigation", () => {
|
||||
test("BackButtonAction triggers a router.navigateWithoutSaving change", async () => {
|
||||
|
||||
|
|
@ -130,20 +129,20 @@ describe.skip("vehicle-damage.vue", () => {
|
|||
},
|
||||
});
|
||||
|
||||
wrapper.vm.selectedDamageLocations = ["Windshield", "SideDoor", "RearWindow"];
|
||||
wrapper.vm.selectedWindshieldOptions = {
|
||||
selectedWindshieldChipCount: null,
|
||||
selectedWindshieldReplaceOptions: ["Single"],
|
||||
selectedWindshieldDamageType: "Replace"
|
||||
};
|
||||
|
||||
wrapper.vm.sideDoorOptionsData = {
|
||||
selectedDoorSides: ["DriverSide", "PassengerSide"],
|
||||
selectedDriverSideReplaceOptions: ["Back"],
|
||||
selectedPassengerSideReplaceOptions: ["Quarter"]
|
||||
};
|
||||
|
||||
wrapper.vm.selectedRearReplaceOptions = ["Stationary"];
|
||||
wrapper.setData({
|
||||
selectedDamageLocations: ["Windshield", "SideDoor", "RearWindow"],
|
||||
selectedWindshieldOptions: {
|
||||
selectedWindshieldChipCount: null,
|
||||
selectedWindshieldReplaceOptions: ["Single"],
|
||||
selectedWindshieldDamageType: "Replace"
|
||||
},
|
||||
sideDoorOptionsData: {
|
||||
selectedDoorSides: ["DriverSide", "PassengerSide"],
|
||||
selectedDriverSideReplaceOptions: ["Back"],
|
||||
selectedPassengerSideReplaceOptions: ["Quarter"]
|
||||
},
|
||||
selectedRearReplaceOptions: "Stationary",
|
||||
})
|
||||
|
||||
const expectedGlassToReplace = [{ glassLocation: "Windshield", glassName: "Single" }, { glassLocation: "Driver", glassName: "Back" },
|
||||
{ glassLocation: "Passenger", glassName: "Quarter" }, { glassLocation: "Rear", glassName: "Stationary" }];
|
||||
|
|
@ -520,19 +519,19 @@ describe.skip("vehicle-damage.vue", () => {
|
|||
|
||||
const storeWindshieldOptions = [[1, false, "Windshield", "Single", {
|
||||
selectedWindshieldDamageType: damageLocationsSelected.REPLACE,
|
||||
selectedWindshieldChipCount: [], selectedWindshieldReplaceOptions: [damageLocationsSelected.SINGLE]
|
||||
selectedWindshieldChipCount: null, selectedWindshieldReplaceOptions: [damageLocationsSelected.SINGLE]
|
||||
}],
|
||||
[2, false, "Windshield", "Driver", {
|
||||
selectedWindshieldDamageType: damageLocationsSelected.REPLACE,
|
||||
selectedWindshieldChipCount: [], selectedWindshieldReplaceOptions: [damageLocationsSelected.DRIVER]
|
||||
selectedWindshieldChipCount: null, selectedWindshieldReplaceOptions: [damageLocationsSelected.DRIVER]
|
||||
}],
|
||||
[3, false, "Windshield", "Passenger", {
|
||||
selectedWindshieldDamageType: damageLocationsSelected.REPLACE,
|
||||
selectedWindshieldChipCount: [], selectedWindshieldReplaceOptions: [damageLocationsSelected.PASSENGER]
|
||||
selectedWindshieldChipCount: null, selectedWindshieldReplaceOptions: [damageLocationsSelected.PASSENGER]
|
||||
}],
|
||||
[4, true, "", "", {
|
||||
selectedWindshieldDamageType: damageLocationsSelected.REPAIR,
|
||||
selectedWindshieldChipCount: [2], selectedWindshieldReplaceOptions: []
|
||||
selectedWindshieldChipCount: 2, selectedWindshieldReplaceOptions: []
|
||||
}]
|
||||
];
|
||||
test.each(storeWindshieldOptions)("getWindshieldOptionsFromStore test #%s", async (testNum, isRepair, damageLocation, damageName, expectedWindshieldOptions) => {
|
||||
|
|
@ -619,8 +618,8 @@ describe.skip("vehicle-damage.vue", () => {
|
|||
expect(glassSelections).toEqual(expectedGlass);
|
||||
});
|
||||
|
||||
const rearReplaceOptions = [["Rear", "Stationary", [damageLocationsSelected.STATIONARY]],
|
||||
["Rear", "Slider", [damageLocationsSelected.SLIDER]]
|
||||
const rearReplaceOptions = [["Rear", "Stationary", damageLocationsSelected.STATIONARY],
|
||||
["Rear", "Slider", damageLocationsSelected.SLIDER]
|
||||
];
|
||||
test.each(rearReplaceOptions)("getRearReplaceOptionsFromStore for %s-%s returns expected %s", async (damageLocation, damageName, expectedGlass) => {
|
||||
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ export default ({
|
|||
},
|
||||
|
||||
props: {
|
||||
modelValue: String,
|
||||
modelValue: [Object, String], // TODO Does this take a string?
|
||||
selectedDamageLocations: Array,
|
||||
hasRepairReplaceConflict: Boolean,
|
||||
hasSplitSingleConflict: Boolean,
|
||||
|
|
|
|||
|
|
@ -18,9 +18,7 @@ const featureListData = {
|
|||
modelValueProp: {}
|
||||
}
|
||||
|
||||
// TODO KO
|
||||
describe.skip("glass-part-question.vue", () => {
|
||||
|
||||
describe("glass-part-question.vue", () => {
|
||||
test("Part data passed in, should map data for ButtonQuestion (radio type)", async () => {
|
||||
|
||||
//Arrange
|
||||
|
|
|
|||
|
|
@ -13,8 +13,7 @@
|
|||
altText=""
|
||||
isRequired
|
||||
:groupName="`${glassLocation}-${glassName}`"
|
||||
:validationRules="tintValidationRules"
|
||||
>
|
||||
:validationRules="tintValidationRules">
|
||||
<div class="row my-2" aria-live="polite">
|
||||
<div class="col">
|
||||
<buttonQuestion
|
||||
|
|
@ -28,7 +27,9 @@
|
|||
isRequired
|
||||
:groupName="`${glassLocation}-${glassName}-${selectedTint}`"
|
||||
:validationRules="partValidationRules"
|
||||
/>
|
||||
:shouldPushClickEventToGAOnMount="
|
||||
shouldPushClickEventToGAOnMount
|
||||
" />
|
||||
</div>
|
||||
</div>
|
||||
</buttonQuestion>
|
||||
|
|
@ -56,6 +57,7 @@ export default {
|
|||
glassColorQuestion: "",
|
||||
glassFeatureQuestion: "",
|
||||
selectedTint: "",
|
||||
shouldPushClickEventToGAOnMount: true,
|
||||
};
|
||||
},
|
||||
props: {
|
||||
|
|
@ -63,7 +65,10 @@ export default {
|
|||
glassLocation: String,
|
||||
colorAnswers: Array,
|
||||
modelValue: Object,
|
||||
alreadyPopulatedPartsData: Array
|
||||
alreadyPopulatedPartsData: {
|
||||
type: Array,
|
||||
default: () => [],
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.LoadPreselectedValues();
|
||||
|
|
@ -74,12 +79,18 @@ export default {
|
|||
computed: {
|
||||
tintValidationRules() {
|
||||
const validationRuleName = `${this.glassLocation}-${this.glassName}-tint-required`;
|
||||
defineRule(validationRuleName, required(errorMessages.OPTION_REQUIRED));
|
||||
defineRule(
|
||||
validationRuleName,
|
||||
required(errorMessages.OPTION_REQUIRED)
|
||||
);
|
||||
return validationRuleName;
|
||||
},
|
||||
partValidationRules() {
|
||||
const validationRuleName = `${this.glassLocation}-${this.glassName}-part-required`;
|
||||
defineRule(validationRuleName, required(errorMessages.OPTION_REQUIRED));
|
||||
defineRule(
|
||||
validationRuleName,
|
||||
required(errorMessages.OPTION_REQUIRED)
|
||||
);
|
||||
return validationRuleName;
|
||||
},
|
||||
colorQuestionText() {
|
||||
|
|
@ -110,16 +121,29 @@ export default {
|
|||
return this.modelValue?.partNumber;
|
||||
},
|
||||
set(newValue) {
|
||||
this.$emit("update:modelValue", this.partsForSelectedTint.filter(part => part.partNumber == newValue)[0]);
|
||||
this.$emit(
|
||||
"update:modelValue",
|
||||
this.partsForSelectedTint.filter(
|
||||
(part) => part.partNumber == newValue
|
||||
)[0]
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
partsForSelectedTint() {
|
||||
const matchingGlass = this.PartDataFromApi.partsOrQuestions?.filter(dataForGlassLocationAndName =>
|
||||
dataForGlassLocationAndName.glassName == this.glassName &&
|
||||
dataForGlassLocationAndName.glassLocation == this.glassLocation);
|
||||
const matchingGlassParts = matchingGlass?.length == 1 ? matchingGlass[0].parts : [];
|
||||
return matchingGlassParts.filter(part => part.color == this.selectedTint) ?? [];
|
||||
const matchingGlass = this.PartDataFromApi.partsOrQuestions?.filter(
|
||||
(dataForGlassLocationAndName) =>
|
||||
dataForGlassLocationAndName.glassName == this.glassName &&
|
||||
dataForGlassLocationAndName.glassLocation ==
|
||||
this.glassLocation
|
||||
);
|
||||
const matchingGlassParts =
|
||||
matchingGlass?.length == 1 ? matchingGlass[0].parts : [];
|
||||
return (
|
||||
matchingGlassParts.filter(
|
||||
(part) => part.color == this.selectedTint
|
||||
) ?? []
|
||||
);
|
||||
},
|
||||
|
||||
// Creates a map of the feature list data in the correct Name/Value
|
||||
|
|
@ -151,7 +175,9 @@ export default {
|
|||
},
|
||||
|
||||
PartDataFromApi() {
|
||||
return this.$store.getters.pageData(this.$route.query.fmgPage) ?? {};
|
||||
return (
|
||||
this.$store.getters.pageData(this.$route.query.fmgPage) ?? {}
|
||||
);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -188,7 +214,8 @@ export default {
|
|||
// Check if only a single part is present for the tint and set the v-model if it is.
|
||||
AutoSelectIfSinglePart() {
|
||||
if (this.partsForSelectedTint?.length == 1) {
|
||||
this.selectedPartNumber = this.partsForSelectedTint[0].partNumber;
|
||||
this.selectedPartNumber =
|
||||
this.partsForSelectedTint[0].partNumber;
|
||||
}
|
||||
},
|
||||
|
||||
|
|
@ -197,7 +224,8 @@ export default {
|
|||
this.$nextTick(() => {
|
||||
if (this.modelValue !== undefined) {
|
||||
// Populate button-question model-value if parts data already exists in VueX
|
||||
this.selectedTint = this.alreadyPopulatedPartsData.filter(part => part.partNumber === this.selectedPartNumber)[0]?.color;
|
||||
this.shouldPushClickEventToGAOnMount = false;
|
||||
this.selectedTint = this.modelValue?.color
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
@ -205,8 +233,8 @@ export default {
|
|||
watch: {
|
||||
selectedTint() {
|
||||
this.AutoSelectIfSinglePart();
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -183,7 +183,16 @@ describe("vehicle-parts.vue", () => {
|
|||
await nextTick();
|
||||
|
||||
//Assert
|
||||
expect(wrapper.vm.glassParts).toEqual({ "Rear-Stationary": { "Rear": ['DB12209YPYNOEM'] } });
|
||||
expect(wrapper.vm.glassParts).toEqual({
|
||||
"Rear-Stationary": {
|
||||
partNumber: "DB12209YPYNOEM",
|
||||
description: "heated glass, solar, 1 hole",
|
||||
color: "Gray Tint Privacy",
|
||||
requiresRecalibration: false,
|
||||
requiresCapabilityQuestions: false,
|
||||
childParts: null
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("User had part questions > BackButtonAction triggers a router.navigateWithoutSaving change with correct scenario", async () => {
|
||||
|
|
@ -509,6 +518,8 @@ function setupMocks({ pageHeaderWidgetHeaderText = {}, mountOptionsMockData = {}
|
|||
wrapper.vm.setCmsContent = baseMixin.methods.setCmsContent;
|
||||
wrapper.vm.$refs.funnelFooter.removeLoader = jest.fn();
|
||||
wrapper.vm.$refs.loadingModal.showModal = jest.fn();
|
||||
// wrapper.vm.$refs.onSubmit = jest.fn();
|
||||
// wrapper.vm.$refs.onInvalidSubmit = jest.fn();
|
||||
|
||||
return { wrapper, apiPromise };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,9 +204,7 @@ export default {
|
|||
const partNumber = this.alreadyPopulatedPartsData[key].partNumber;
|
||||
g.parts.forEach((p) => {
|
||||
if (p.partNumber === partNumber) {
|
||||
this.glassParts[g.glassLocation + "-" + g.glassName] = {
|
||||
[g.glassLocation]: [partNumber],
|
||||
};
|
||||
this.glassParts[g.glassLocation + "-" + g.glassName] = p;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -6,12 +6,11 @@ import { vehicleCategories } from "@/constants/vehicle-categories.js";
|
|||
import { routerParams } from "@/router/router-constants/router-params";
|
||||
import { queryStrings } from "@/constants/query-strings";
|
||||
import { dynamicStrings } from "@/constants/dynamic-strings";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
cmsContentByWidget: {}
|
||||
cmsContentByWidget: {},
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -19,7 +18,9 @@ export default {
|
|||
this.$root.cmsContentByWidget = cmsContent;
|
||||
},
|
||||
getCmsContent(widgetName, fieldName) {
|
||||
return this.$root.cmsContentByWidget?.[widgetName]?.[fieldName] ? this.$root.cmsContentByWidget[widgetName][fieldName] : '';
|
||||
return this.$root.cmsContentByWidget?.[widgetName]?.[fieldName]
|
||||
? this.$root.cmsContentByWidget[widgetName][fieldName]
|
||||
: "";
|
||||
},
|
||||
dispatchStoreAction(type, payload, encodePayload = true) {
|
||||
// Encode the payload if required
|
||||
|
|
@ -32,7 +33,7 @@ export default {
|
|||
savePageDataToStore(page, data) {
|
||||
store.commit(storeMutations.UPDATE_PAGE_DATA, { page: page, data: data });
|
||||
},
|
||||
onSubmit() { }, // DO NOT REMOVE; needed to prevent default form submit behavior. Cannot use .prevent modifier for vee-validate Form
|
||||
onSubmit() {}, // DO NOT REMOVE; needed to prevent default form submit behavior. Cannot use .prevent modifier for vee-validate Form
|
||||
onInvalidSubmit({ values, errors, results }) {
|
||||
// identify the first error field and put focus on it
|
||||
// get error names array
|
||||
|
|
@ -49,14 +50,17 @@ export default {
|
|||
return footerInfoBox ? footerInfoBox.offsetHeight : 0;
|
||||
},
|
||||
async getZipCodeData(zipCode) {
|
||||
const serviceZipValidationResponse = await this.dispatchStoreAction(storeActions.VALIDATE_ZIP, { zip: zipCode });
|
||||
|
||||
return {
|
||||
isValid: serviceZipValidationResponse.data.isValid,
|
||||
const serviceZipValidationResponse = await this.dispatchStoreAction(
|
||||
storeActions.VALIDATE_ZIP,
|
||||
{ zip: zipCode }
|
||||
);
|
||||
|
||||
return {
|
||||
isValid: serviceZipValidationResponse.data.isValid,
|
||||
isServiceable: serviceZipValidationResponse.data.isServiceable,
|
||||
state: serviceZipValidationResponse.data.state
|
||||
state: serviceZipValidationResponse.data.state,
|
||||
};
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
storeActions() {
|
||||
|
|
@ -74,13 +78,13 @@ export default {
|
|||
routerParams() {
|
||||
return routerParams;
|
||||
},
|
||||
queryStrings(){
|
||||
queryStrings() {
|
||||
return queryStrings;
|
||||
},
|
||||
dynamicStrings(){
|
||||
dynamicStrings() {
|
||||
return dynamicStrings;
|
||||
},
|
||||
cssClassNameForCmsWidget(){
|
||||
cssClassNameForCmsWidget() {
|
||||
return "widget-name-" + this.cmsWidgetName;
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export default {
|
|||
buttonImage: String,
|
||||
altText: {
|
||||
type: String,
|
||||
default: ""
|
||||
default: "",
|
||||
},
|
||||
textPosition: String,
|
||||
screenReaderOnlyText: String,
|
||||
|
|
@ -18,29 +18,19 @@ export default {
|
|||
isWide: Boolean,
|
||||
isRequired: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedValue: null,
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.selectedValue = this.modelValue;
|
||||
default: true,
|
||||
},
|
||||
lastValuePushedToGa: [String, Number],
|
||||
setLastValuePushedToGa: Function,
|
||||
shouldPushClickEventToGAOnMount: Boolean
|
||||
},
|
||||
methods: {
|
||||
handleAnswerChange(e) {
|
||||
if (this.preHandleAnswerChange) {
|
||||
this.preHandleAnswerChange(e)
|
||||
this.preHandleAnswerChange(e);
|
||||
}
|
||||
|
||||
|
||||
this.$emit("change", e);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
selectedValue(selectedValue) {
|
||||
this.$emit("update:modelValue", selectedValue);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
81
src/mixins/input-button-wrapper-mixin.spec.js
Normal file
81
src/mixins/input-button-wrapper-mixin.spec.js
Normal 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"
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -1,90 +0,0 @@
|
|||
import { createStore } from "vuex";
|
||||
import { endpoints } from "@/constants/endpoints.js";
|
||||
import { gaStoreMutations } from "@/constants/store-mutations";
|
||||
import { getDateForSavedSessionTimeout } from "@/helpers/heritage-integration/session-helper";
|
||||
import createPersistedState from "vuex-persistedstate";
|
||||
import globalMethods from "@/global-methods";
|
||||
import { gaStoreActions } from "@/constants/store-actions";
|
||||
import { applicationConfig } from "@/constants/application-config";
|
||||
import { experimentTriggers } from "@/constants/experiments";
|
||||
import { damageLocationsSelected } from "@/constants/damage-locations-selected";
|
||||
import { fmgPageValues } from "@/router/router-constants/fmgPage-values";
|
||||
|
||||
// Export State
|
||||
const getDefaultState = () => {
|
||||
return {
|
||||
gaClickInformation: {
|
||||
currentlySelectedValues: {},
|
||||
firedGaClickEventValues: {},
|
||||
lastFocusedInputGroup: "",
|
||||
wasLastFocusedInputMultiselect: undefined,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const gaState = getDefaultState();
|
||||
|
||||
// Export Mutations
|
||||
export const gaMutations = {
|
||||
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;
|
||||
},
|
||||
};
|
||||
|
||||
// Export Getters
|
||||
export const getters = {
|
||||
gaClickInformation: (state) => state.gaClickInformation,
|
||||
};
|
||||
|
||||
// Export Actions
|
||||
export const gaActions = {
|
||||
updateCurrentlySelectedValues(context, groupName, value) {
|
||||
context.commit(
|
||||
gaStoreMutations.UPDATE_CURRENTLY_SELECTED_VALUES,
|
||||
groupName,
|
||||
value
|
||||
);
|
||||
},
|
||||
updateFiredGaClickEventValues(context, groupName, value) {
|
||||
context.commit(
|
||||
gaStoreMutations.UPDATE_FIRED_GA_CLICK_EVENT_VALUES,
|
||||
groupName,
|
||||
value
|
||||
);
|
||||
},
|
||||
updateLastFocusedInputGroup(context, groupName) {
|
||||
context.commit(
|
||||
gaStoreMutations.UPDATE_LAST_FOCUSED_INPUT_GROUP,
|
||||
groupName
|
||||
);
|
||||
},
|
||||
updateWasLastFocusedInputMultiselect(
|
||||
context,
|
||||
wasLastFocusedInputMultiselect
|
||||
) {
|
||||
context.commit(
|
||||
gaStoreMutations.UPDATE_WAS_LAST_FOCUSED_INPUT_MULTISELECT,
|
||||
wasLastFocusedInputMultiselect
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
export default createStore({
|
||||
gaState,
|
||||
gaMutations,
|
||||
getters,
|
||||
gaActions,
|
||||
});
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { createStore } from "vuex";
|
||||
import { createStore, Store } from "vuex";
|
||||
import { endpoints } from "@/constants/endpoints.js";
|
||||
import { storeMutations } from "@/constants/store-mutations";
|
||||
import { getDateForSavedSessionTimeout } from "@/helpers/heritage-integration/session-helper";
|
||||
|
|
@ -50,22 +50,22 @@ const getDefaultState = () => {
|
|||
glassToReplace: null,
|
||||
partQuestionAnswers: null,
|
||||
moldingQuestionAnswers: null,
|
||||
capabilityQuestionAnswers: null
|
||||
capabilityQuestionAnswers: null,
|
||||
},
|
||||
lineItems: {
|
||||
glassParts: null
|
||||
glassParts: null,
|
||||
},
|
||||
payment: {
|
||||
isInsurance: null,
|
||||
insuranceCoverage: {
|
||||
isVerified: null
|
||||
}
|
||||
isVerified: null,
|
||||
},
|
||||
},
|
||||
referralNumber: null,
|
||||
referralDate: null,
|
||||
referralCorrelationId: null,
|
||||
accountNumber: 0,
|
||||
eon: null
|
||||
eon: null,
|
||||
},
|
||||
applicationUser: {
|
||||
eventBus: [],
|
||||
|
|
@ -76,14 +76,15 @@ const getDefaultState = () => {
|
|||
crmCustomerId: null,
|
||||
lastPageVisited: null,
|
||||
experiments: [],
|
||||
triggeredSiteEntry: false
|
||||
triggeredSiteEntry: false,
|
||||
},
|
||||
gaInformation: {
|
||||
currentlySelectedValues: {},
|
||||
firedGaClickEventValues: {},
|
||||
lastFocusedInputGroup: ""
|
||||
}
|
||||
}
|
||||
// gaClickInformation: {
|
||||
// currentlySelectedValues: {},
|
||||
// firedGaClickEventValues: {},
|
||||
// lastFocusedInputGroup: "",
|
||||
// wasLastFocusedInputMultiselect: undefined,
|
||||
// },
|
||||
};
|
||||
};
|
||||
|
||||
export const state = getDefaultState();
|
||||
|
|
@ -200,7 +201,6 @@ export const mutations = {
|
|||
state.order.customer.emailAddress = customerEmailAddress;
|
||||
},
|
||||
updateVehicle(state, vehicleInfo) {
|
||||
|
||||
state.order.vehicle.year = vehicleInfo.year;
|
||||
state.order.vehicle.make = vehicleInfo.make;
|
||||
state.order.vehicle.model = vehicleInfo.model;
|
||||
|
|
@ -214,7 +214,8 @@ export const mutations = {
|
|||
state.order.vehicle.imageColor = vehicleInfo.imageVifColor;
|
||||
},
|
||||
updateRegistration(state, registrationInfo) {
|
||||
state.order.vehicle.registration.licensePlate = registrationInfo?.licensePlate;
|
||||
state.order.vehicle.registration.licensePlate =
|
||||
registrationInfo?.licensePlate;
|
||||
state.order.vehicle.registration.address = registrationInfo?.address;
|
||||
state.order.vehicle.registration.city = registrationInfo?.city;
|
||||
state.order.vehicle.registration.state = registrationInfo?.state;
|
||||
|
|
@ -240,7 +241,7 @@ export const mutations = {
|
|||
state.applicationUser.crmCustomerId = crmCustomerId;
|
||||
},
|
||||
updateLastPageVisited(state, lastPageVisited) {
|
||||
state.applicationUser.lastPageVisited = lastPageVisited
|
||||
state.applicationUser.lastPageVisited = lastPageVisited;
|
||||
},
|
||||
// EVENT BUS MUTATIONS
|
||||
addEventToBus(state, event) {
|
||||
|
|
@ -249,8 +250,7 @@ export const mutations = {
|
|||
removeEventFromBus(state, eventData) {
|
||||
const matchedEvent = state.applicationUser.eventBus.find(
|
||||
({ category, subCategory }) =>
|
||||
category === eventData.category &&
|
||||
subCategory === eventData.subCategory
|
||||
category === eventData.category && subCategory === eventData.subCategory
|
||||
);
|
||||
const itemIndex = state.applicationUser.eventBus.indexOf(matchedEvent);
|
||||
|
||||
|
|
@ -336,7 +336,7 @@ export const mutations = {
|
|||
state: orderInformation.vehicle.registration.state,
|
||||
zipCode: orderInformation.vehicle.registration.zipCode,
|
||||
licensePlate: orderInformation.vehicle.registration.licensePlateNumber,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
state.order.damage.glassToReplace = orderInformation.damage.glassToReplace;
|
||||
|
|
@ -345,13 +345,18 @@ export const mutations = {
|
|||
|
||||
state.order.lineItems.glassParts = orderInformation.parts;
|
||||
state.order.accountNumber = orderInformation.accountNumber;
|
||||
state.order.serviceLocation.address = orderInformation.serviceLocation.streetAddress,
|
||||
state.order.serviceLocation.city = orderInformation.serviceLocation.city,
|
||||
state.order.serviceLocation.state = orderInformation.serviceLocation.state,
|
||||
state.order.serviceLocation.zipCode = orderInformation.serviceLocation.zipCode;
|
||||
(state.order.serviceLocation.address =
|
||||
orderInformation.serviceLocation.streetAddress),
|
||||
(state.order.serviceLocation.city =
|
||||
orderInformation.serviceLocation.city),
|
||||
(state.order.serviceLocation.state =
|
||||
orderInformation.serviceLocation.state),
|
||||
(state.order.serviceLocation.zipCode =
|
||||
orderInformation.serviceLocation.zipCode);
|
||||
|
||||
state.order.payment.isInsurance = orderInformation.IsInsuranceOrder;
|
||||
state.order.payment.insuranceCoverage.isVerified = orderInformation?.insuranceInfo.coverageVerified;
|
||||
state.order.payment.insuranceCoverage.isVerified =
|
||||
orderInformation?.insuranceInfo.coverageVerified;
|
||||
|
||||
state.order.customer.emailAddress = orderInformation.customer.emailAddress;
|
||||
state.applicationUser.experiments = orderInformation.experiments;
|
||||
|
|
@ -361,8 +366,23 @@ export const mutations = {
|
|||
},
|
||||
updateTriggeredSiteEntry(state, wasSiteEntryTriggered) {
|
||||
state.applicationUser.triggeredSiteEntry = wasSiteEntryTriggered;
|
||||
}
|
||||
}
|
||||
},
|
||||
// START GA click event mutations
|
||||
// updateCurrentlySelectedValues(state, groupName, value) {
|
||||
// state.gaClickInformation.currentlySelectedValues[groupName] = value;
|
||||
// },
|
||||
// updateFiredGaClickEventValues(state, groupName, value) {
|
||||
// state.gaClickInformation.firedGaClickEventValues[groupName] = value;
|
||||
// },
|
||||
// updateLastFocusedInputGroup(state, groupName) {
|
||||
// state.gaClickInformation.lastFocusedInputGroup = groupName;
|
||||
// },
|
||||
// updateWasLastFocusedInputMultiselect(state, wasLastFocusedInputMultiselect) {
|
||||
// state.gaClickInformation.wasLastFocusedInputMultiselect =
|
||||
// wasLastFocusedInputMultiselect;
|
||||
// },
|
||||
// END GA click event mutations
|
||||
};
|
||||
|
||||
// Export Getters
|
||||
export const getters = {
|
||||
|
|
@ -378,7 +398,9 @@ export const getters = {
|
|||
eventBus: (state) => state.applicationUser.eventBus,
|
||||
damage: (state) => state.order.damage,
|
||||
lineItems: (state) => state.order.lineItems,
|
||||
pageData: (state) => (page) => { return state.applicationUser.pageData[page]; },
|
||||
pageData: (state) => (page) => {
|
||||
return state.applicationUser.pageData[page];
|
||||
},
|
||||
applicationUser: (state) => state.applicationUser,
|
||||
order: (state) => state.order,
|
||||
payment: (state) => state.order.payment,
|
||||
|
|
@ -395,29 +417,33 @@ export const getters = {
|
|||
funnelServiceState: state.order.serviceLocation.state,
|
||||
funnelServiceZipCode: state.order.serviceLocation.zipCode,
|
||||
funnelParentAccountNumber: state.order.accountNumber,
|
||||
funnelIsCoverageVerified: state.order.payment.insuranceCoverage.isVerified,
|
||||
funnelIsCoverageVerified:
|
||||
state.order.payment.insuranceCoverage.isVerified,
|
||||
funnelHasRecalibrationPart: getHasRecalibrationPart(state),
|
||||
funnelSelectedMultiGlass: state.order.damage.glassToReplace?.length > 1,
|
||||
funnelSelectedWindshieldGlass: getAllValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, "glassLocation").includes(damageLocationsSelected.WINDSHIELD),
|
||||
funnelSelectedBackGlass: getAllValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, "glassLocation").includes(damageLocationsSelected.REAR),
|
||||
funnelSelectedDriverSideGlass: getAllValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, "glassLocation").includes(damageLocationsSelected.DRIVER),
|
||||
funnelSelectedPassengerSideGlass: getAllValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, "glassLocation").includes(damageLocationsSelected.PASSENGER),
|
||||
funnelSelectedWindshieldGlass: getNonFalseValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, "glassLocation").includes(damageLocationsSelected.WINDSHIELD),
|
||||
funnelSelectedBackGlass: getNonFalseValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, "glassLocation").includes(damageLocationsSelected.REAR),
|
||||
funnelSelectedDriverSideGlass: getNonFalseValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, "glassLocation").includes(damageLocationsSelected.DRIVER),
|
||||
funnelSelectedPassengerSideGlass: getNonFalseValuesOfPropertyInArrayOfObjects(state.order.damage.glassToReplace, "glassLocation").includes(damageLocationsSelected.PASSENGER),
|
||||
|
||||
funnelOrderPartNumbers: [...getAllValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, "partNumber"), ...getAllValuesOfPropertyInArrayOfObjects(state.order.lineItems.otherParts, "partNumber")],
|
||||
funnelOrderPartNumbers: [...getNonFalseValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, "partNumber"), ...getNonFalseValuesOfPropertyInArrayOfObjects(state.order.lineItems.otherParts, "partNumber")],
|
||||
|
||||
funnelOrderPartTypes: [...getAllValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, "recalibrationType"), ...getAllValuesOfPropertyInArrayOfObjects(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 getAllValuesOfPropertyInArrayOfObjects(array, propertyName) {
|
||||
function getNonFalseValuesOfPropertyInArrayOfObjects(array, propertyName) {
|
||||
return (array ?? []).map(x => x[propertyName]).filter(x => x);
|
||||
}
|
||||
|
||||
// Export Actions
|
||||
export const actions = {
|
||||
|
||||
// Vehicle API Actions
|
||||
getVehicleYears(context) {
|
||||
return globalMethods.callHttpClient({
|
||||
|
|
@ -448,11 +474,14 @@ export const actions = {
|
|||
endpoint: endpoints.LookupVinByPlate.url,
|
||||
payload: {
|
||||
licensePlate: licensePlate,
|
||||
licenseState: licenseState
|
||||
licenseState: licenseState,
|
||||
},
|
||||
});
|
||||
},
|
||||
lookupVinByAddress(context, { licenseLastName, licenseStreetAddress, licenseZip, licenseState }) {
|
||||
lookupVinByAddress(
|
||||
context,
|
||||
{ licenseLastName, licenseStreetAddress, licenseZip, licenseState }
|
||||
) {
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.LookupVinByAddress.method,
|
||||
endpoint: endpoints.LookupVinByAddress.url,
|
||||
|
|
@ -460,7 +489,7 @@ export const actions = {
|
|||
licenseLastName: licenseLastName,
|
||||
licenseStreetAddress: licenseStreetAddress,
|
||||
licenseZip: licenseZip,
|
||||
licenseState: licenseState
|
||||
licenseState: licenseState,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
@ -494,10 +523,22 @@ export const actions = {
|
|||
})
|
||||
.then((response) => {
|
||||
context.commit(storeMutations.UPDATE_CAR_ID, response.data.carId);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, response.data.category);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_URL, response.data.imageUrl);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER, response.data.imageVifNumber);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_IMAGE_COLOR, response.data.imageVifColor);
|
||||
context.commit(
|
||||
storeMutations.UPDATE_VEHICLE_CATEGORY,
|
||||
response.data.category
|
||||
);
|
||||
context.commit(
|
||||
storeMutations.UPDATE_VEHICLE_IMAGE_URL,
|
||||
response.data.imageUrl
|
||||
);
|
||||
context.commit(
|
||||
storeMutations.UPDATE_VEHICLE_IMAGE_VIF_NUMBER,
|
||||
response.data.imageVifNumber
|
||||
);
|
||||
context.commit(
|
||||
storeMutations.UPDATE_VEHICLE_IMAGE_COLOR,
|
||||
response.data.imageVifColor
|
||||
);
|
||||
return response;
|
||||
});
|
||||
},
|
||||
|
|
@ -511,8 +552,8 @@ export const actions = {
|
|||
validateZip(context, { zip }) {
|
||||
return globalMethods.callHttpClient({
|
||||
methods: endpoints.ValidateZip.method,
|
||||
endpoint: `${endpoints.ValidateZip.url}/${zip}`
|
||||
})
|
||||
endpoint: `${endpoints.ValidateZip.url}/${zip}`,
|
||||
});
|
||||
},
|
||||
|
||||
// Dependency Actions
|
||||
|
|
@ -527,7 +568,7 @@ export const actions = {
|
|||
},
|
||||
resetRegistrationAndDependencies(context) {
|
||||
context.commit(storeMutations.RESET_REGISTRATION_STATE);
|
||||
context.commit(storeMutations.RESET_GLASS_PARTS_STATE)
|
||||
context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
|
||||
},
|
||||
resetPartsAndDependencies(context) {
|
||||
context.commit(storeMutations.RESET_GLASS_PARTS_STATE);
|
||||
|
|
@ -583,8 +624,8 @@ export const actions = {
|
|||
assignmentId: experiment.assignmentId,
|
||||
sessionKey: sessionKey,
|
||||
pageName: pageName,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
|
|
@ -592,13 +633,28 @@ export const actions = {
|
|||
updateStoreWithSaveSessionResponse(context, { referralNumber, referralDate, referralCorrelationId, eon, accountNumber, savedSessionId, crmCustomerId }) {
|
||||
context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
|
||||
context.commit(storeMutations.UPDATE_REFERRAL_DATE, referralDate);
|
||||
context.commit(storeMutations.UPDATE_REFERRAL_CORRELATION_ID, referralCorrelationId);
|
||||
context.commit(
|
||||
storeMutations.UPDATE_REFERRAL_CORRELATION_ID,
|
||||
referralCorrelationId
|
||||
);
|
||||
context.commit(storeMutations.UPDATE_EON, eon);
|
||||
context.commit(storeMutations.UPDATE_PARENT_ACCT_NUMBER, accountNumber);
|
||||
context.commit(storeMutations.UPDATE_SAVED_SESSION_ID, savedSessionId);
|
||||
context.commit(storeMutations.UPDATE_CRM_CUSTOMER_ID, crmCustomerId);
|
||||
},
|
||||
logPageView(context, { userId, sessionKey, pageName, sessionId, action, event, shouldUseSessionId, experimentsForUser }) {
|
||||
logPageView(
|
||||
context,
|
||||
{
|
||||
userId,
|
||||
sessionKey,
|
||||
pageName,
|
||||
sessionId,
|
||||
action,
|
||||
event,
|
||||
shouldUseSessionId,
|
||||
experimentsForUser,
|
||||
}
|
||||
) {
|
||||
var payload = {
|
||||
userId: userId,
|
||||
sessionKey: sessionKey,
|
||||
|
|
@ -608,17 +664,31 @@ export const actions = {
|
|||
action: action,
|
||||
event: event,
|
||||
shouldUseSessionId: shouldUseSessionId,
|
||||
experimentsForUser: experimentsForUser
|
||||
experimentsForUser: experimentsForUser,
|
||||
};
|
||||
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.LogPageView.method,
|
||||
endpoint: endpoints.LogPageView.url,
|
||||
payload: payload,
|
||||
logApiCall: false
|
||||
logApiCall: false,
|
||||
});
|
||||
},
|
||||
logCustomEvent(context, { userId, sessionKey, pageName, sessionId, category, action, label, value, shouldUseSessionId, experimentsForUser }) {
|
||||
logCustomEvent(
|
||||
context,
|
||||
{
|
||||
userId,
|
||||
sessionKey,
|
||||
pageName,
|
||||
sessionId,
|
||||
category,
|
||||
action,
|
||||
label,
|
||||
value,
|
||||
shouldUseSessionId,
|
||||
experimentsForUser,
|
||||
}
|
||||
) {
|
||||
var payload = {
|
||||
userId: userId,
|
||||
sessionKey: sessionKey,
|
||||
|
|
@ -630,14 +700,14 @@ export const actions = {
|
|||
label: label,
|
||||
value: value,
|
||||
shouldUseSessionId: shouldUseSessionId,
|
||||
experimentsForUser: experimentsForUser
|
||||
experimentsForUser: experimentsForUser,
|
||||
};
|
||||
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.LogCustomEvent.method,
|
||||
endpoint: endpoints.LogCustomEvent.url,
|
||||
payload: payload,
|
||||
logApiCall: false
|
||||
logApiCall: false,
|
||||
});
|
||||
},
|
||||
initializeSession(context, { userId, sessionId, userAgent, referrer }) {
|
||||
|
|
@ -649,22 +719,28 @@ export const actions = {
|
|||
userAgent: userAgent,
|
||||
operatorId: "WEB",
|
||||
userName: "SafeliteConceptFunnel",
|
||||
referrer: referrer
|
||||
referrer: referrer,
|
||||
};
|
||||
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.InitializeSession.method,
|
||||
endpoint: endpoints.InitializeSession.url,
|
||||
payload: payload,
|
||||
logApiCall: false
|
||||
logApiCall: false,
|
||||
});
|
||||
},
|
||||
|
||||
// Misc Actions
|
||||
setReferralInformation(context, { referralNumber, referralDate, referralCorrelationId, eon }) {
|
||||
setReferralInformation(
|
||||
context,
|
||||
{ referralNumber, referralDate, referralCorrelationId, eon }
|
||||
) {
|
||||
context.commit(storeMutations.UPDATE_REFERRAL_NUMBER, referralNumber);
|
||||
context.commit(storeMutations.UPDATE_REFERRAL_DATE, referralDate);
|
||||
context.commit(storeMutations.UPDATE_REFERRAL_CORRELATION_ID, referralCorrelationId);
|
||||
context.commit(
|
||||
storeMutations.UPDATE_REFERRAL_CORRELATION_ID,
|
||||
referralCorrelationId
|
||||
);
|
||||
context.commit(storeMutations.UPDATE_EON, eon);
|
||||
},
|
||||
|
||||
|
|
@ -672,11 +748,14 @@ export const actions = {
|
|||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetExperimentsByUser.method,
|
||||
endpoint: `${endpoints.GetExperimentsByUser.url}/${userId}`,
|
||||
payload: {}
|
||||
payload: {},
|
||||
});
|
||||
},
|
||||
|
||||
async runExperimentsForTrigger(context, { userId, triggerEvent, triggerValue }) {
|
||||
async runExperimentsForTrigger(
|
||||
context,
|
||||
{ userId, triggerEvent, triggerValue }
|
||||
) {
|
||||
if (triggerEvent == experimentTriggers.SITE_ENTRY) {
|
||||
context.commit(storeMutations.UPDATE_TRIGGERED_SITE_ENTRY, true);
|
||||
}
|
||||
|
|
@ -686,7 +765,7 @@ export const actions = {
|
|||
userId: userId,
|
||||
triggerEvent: triggerEvent,
|
||||
triggerValue: triggerValue,
|
||||
experimentOrder: context.getters.experimentOrder
|
||||
experimentOrder: context.getters.experimentOrder,
|
||||
};
|
||||
|
||||
const response = await globalMethods.callHttpClient({
|
||||
|
|
@ -695,7 +774,10 @@ export const actions = {
|
|||
payload: payload,
|
||||
});
|
||||
|
||||
context.commit(storeMutations.UPDATE_EXPERIMENTS, response.data.experiments);
|
||||
context.commit(
|
||||
storeMutations.UPDATE_EXPERIMENTS,
|
||||
response.data.experiments
|
||||
);
|
||||
},
|
||||
|
||||
getEvoxImage(context, { relativeUrl }) {
|
||||
|
|
@ -724,7 +806,7 @@ export const actions = {
|
|||
carId: carId,
|
||||
glass: glassArray ?? [],
|
||||
zip: zipCode,
|
||||
vin: vin
|
||||
vin: vin,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
@ -749,7 +831,7 @@ export const actions = {
|
|||
glass: glassArray,
|
||||
answerResults: resultsArray,
|
||||
zip: zipCode,
|
||||
vin: vin
|
||||
vin: vin,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
@ -758,24 +840,31 @@ export const actions = {
|
|||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetCapabilityQuestions.method,
|
||||
endpoint: `${endpoints.GetCapabilityQuestions.url}/${carId}/${partNumber}`,
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
getPartFromCapabilityQuestionAnswer(context, glassLocation) {
|
||||
const pageData = context.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS);
|
||||
const pageData = context.getters.pageData(
|
||||
fmgPageValues.CAPABILITY_QUESTIONS
|
||||
);
|
||||
|
||||
const part = pageData.partsOrQuestions.find(x => x.glassLocation === glassLocation).parts[0];
|
||||
const capabilityQuestionAnswers = context.getters.damage.capabilityQuestionAnswers;
|
||||
const capabilityQuestionAnswersForPart = capabilityQuestionAnswers.find(x => x.glassLocation === glassLocation);
|
||||
const part = pageData.partsOrQuestions.find(
|
||||
(x) => x.glassLocation === glassLocation
|
||||
).parts[0];
|
||||
const capabilityQuestionAnswers =
|
||||
context.getters.damage.capabilityQuestionAnswers;
|
||||
const capabilityQuestionAnswersForPart = capabilityQuestionAnswers.find(
|
||||
(x) => x.glassLocation === glassLocation
|
||||
);
|
||||
|
||||
return globalMethods.callHttpClient({
|
||||
method: endpoints.GetPartFromCapabilityAnswer.method,
|
||||
endpoint: endpoints.GetPartFromCapabilityAnswer.url,
|
||||
payload: {
|
||||
part,
|
||||
capabilityAnswerResults: capabilityQuestionAnswersForPart
|
||||
}
|
||||
})
|
||||
capabilityAnswerResults: capabilityQuestionAnswersForPart,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
// Session API Actions
|
||||
|
|
@ -810,21 +899,21 @@ export const actions = {
|
|||
damage: {
|
||||
numberOfChips: damage.numberOfChips,
|
||||
glassToReplace: damage.glassToReplace,
|
||||
isRepair: damage.isRepair
|
||||
isRepair: damage.isRepair,
|
||||
},
|
||||
customer: {
|
||||
emailAddress: order.customer.emailAddress,
|
||||
},
|
||||
lineItems: {
|
||||
glassParts: lineItems.glassParts
|
||||
glassParts: lineItems.glassParts,
|
||||
},
|
||||
serviceLocation: {
|
||||
streetAddress: order.serviceLocation.address,
|
||||
city: order.serviceLocation.city,
|
||||
state: order.serviceLocation.state,
|
||||
zipCode: order.serviceLocation.zipCode
|
||||
zipCode: order.serviceLocation.zipCode,
|
||||
},
|
||||
referralNumber: order.referralNumber?.toString(), // TODO It'd be nice to save these as strings in the first place
|
||||
referralNumber: order.referralNumber?.toString(), // TODO It'd be nice to save these as strings in the first place
|
||||
referralDate: order.referralDate,
|
||||
accountNumber: order.accountNumber?.toString(),
|
||||
existingPromoCode: null,
|
||||
|
|
@ -859,8 +948,7 @@ export const actions = {
|
|||
|
||||
// Vehicle
|
||||
saveVehicleYear(context, year) {
|
||||
|
||||
//Reset dependent state when changing
|
||||
//Reset dependent state when changing
|
||||
if (context.state.order.vehicle.year !== year) {
|
||||
context.commit(storeMutations.UPDATE_MAKE, null);
|
||||
context.commit(storeMutations.UPDATE_MODEL, null);
|
||||
|
|
@ -881,7 +969,6 @@ export const actions = {
|
|||
}
|
||||
},
|
||||
saveVehicleMake(context, make) {
|
||||
|
||||
//Reset dependent state when changing
|
||||
if (context.state.order.vehicle.make !== make) {
|
||||
context.commit(storeMutations.UPDATE_MODEL, null);
|
||||
|
|
@ -902,8 +989,7 @@ export const actions = {
|
|||
}
|
||||
},
|
||||
saveVehicleModel(context, model) {
|
||||
|
||||
//Reset dependent state when changing
|
||||
//Reset dependent state when changing
|
||||
if (context.state.order.vehicle.model !== model) {
|
||||
context.commit(storeMutations.UPDATE_STYLE, null);
|
||||
context.commit(storeMutations.UPDATE_CAR_ID, null);
|
||||
|
|
@ -922,7 +1008,7 @@ export const actions = {
|
|||
}
|
||||
},
|
||||
saveVehicleStyle(context, style) {
|
||||
//Reset dependent state when changing
|
||||
//Reset dependent state when changing
|
||||
if (context.state.order.vehicle.style !== style) {
|
||||
context.commit(storeMutations.UPDATE_CAR_ID, null);
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_CATEGORY, null);
|
||||
|
|
@ -939,20 +1025,35 @@ export const actions = {
|
|||
context.commit(storeMutations.UPDATE_STYLE, style);
|
||||
}
|
||||
},
|
||||
saveVehicleDamage(context, { isWindshieldRepair, selectedGlassToReplace, selectedWindshieldChipCount }) {
|
||||
|
||||
saveVehicleDamage(
|
||||
context,
|
||||
{ isWindshieldRepair, selectedGlassToReplace, selectedWindshieldChipCount }
|
||||
) {
|
||||
const selectedGlassPassedInSorted = selectedGlassToReplace.slice().sort();
|
||||
const isGlassToReplaceTheSame = (context.state.order.damage.glassToReplace?.length === selectedGlassToReplace.length)
|
||||
&& context.state.order.damage.glassToReplace
|
||||
const isGlassToReplaceTheSame =
|
||||
context.state.order.damage.glassToReplace?.length ===
|
||||
selectedGlassToReplace.length &&
|
||||
context.state.order.damage.glassToReplace
|
||||
.slice()
|
||||
.sort()
|
||||
.every((obj, index) => obj.glassLocation === selectedGlassPassedInSorted[index].glassLocation && obj.glassName === selectedGlassPassedInSorted[index].glassName);
|
||||
const isWindshieldRepairTheSame = isWindshieldRepair === context.state.order.damage.isRepair;
|
||||
.every(
|
||||
(obj, index) =>
|
||||
obj.glassLocation ===
|
||||
selectedGlassPassedInSorted[index].glassLocation &&
|
||||
obj.glassName === selectedGlassPassedInSorted[index].glassName
|
||||
);
|
||||
const isWindshieldRepairTheSame =
|
||||
isWindshieldRepair === context.state.order.damage.isRepair;
|
||||
const isChipCountTheSame = Array.isArray(selectedWindshieldChipCount) //TODO: fix the underlying components so this is never an array
|
||||
? selectedWindshieldChipCount[0] === context.state.order.damage.numberOfChips
|
||||
: selectedWindshieldChipCount === context.state.order.damage.numberOfChips;
|
||||
? selectedWindshieldChipCount[0] ===
|
||||
context.state.order.damage.numberOfChips
|
||||
: selectedWindshieldChipCount ===
|
||||
context.state.order.damage.numberOfChips;
|
||||
|
||||
const isDamageChanging = !isGlassToReplaceTheSame || !isWindshieldRepairTheSame || (isWindshieldRepair && !isChipCountTheSame);
|
||||
const isDamageChanging =
|
||||
!isGlassToReplaceTheSame ||
|
||||
!isWindshieldRepairTheSame ||
|
||||
(isWindshieldRepair && !isChipCountTheSame);
|
||||
|
||||
if (isDamageChanging) {
|
||||
//Reset dependent state when changing
|
||||
|
|
@ -960,14 +1061,23 @@ export const actions = {
|
|||
|
||||
// Save new values
|
||||
context.commit(storeMutations.UPDATE_IS_REPAIR, isWindshieldRepair);
|
||||
context.commit(storeMutations.UPDATE_NUMBER_OF_CHIPS, isWindshieldRepair ? parseInt(selectedWindshieldChipCount) : null);
|
||||
context.commit(storeMutations.UPDATE_GLASS_TO_REPLACE, selectedGlassToReplace);
|
||||
context.commit(
|
||||
storeMutations.UPDATE_NUMBER_OF_CHIPS,
|
||||
isWindshieldRepair ? parseInt(selectedWindshieldChipCount) : null
|
||||
);
|
||||
context.commit(
|
||||
storeMutations.UPDATE_GLASS_TO_REPLACE,
|
||||
selectedGlassToReplace
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
// Vin lookup
|
||||
saveVinLookup(context, { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }) {
|
||||
//Reset dependent state when changing
|
||||
saveVinLookup(
|
||||
context,
|
||||
{ isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }
|
||||
) {
|
||||
//Reset dependent state when changing
|
||||
if (vehicleInfo.vin !== context.state.order.vehicle.vin) {
|
||||
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
||||
context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
|
||||
|
|
@ -981,10 +1091,15 @@ export const actions = {
|
|||
context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo);
|
||||
}
|
||||
},
|
||||
saveRegistrationLicensePlateLookup(context, { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }) {
|
||||
//Reset dependent state when changing
|
||||
if (registrationInfo?.licensePlate !== context.state.order.vehicle.registration?.licensePlate) {
|
||||
|
||||
saveRegistrationLicensePlateLookup(
|
||||
context,
|
||||
{ isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }
|
||||
) {
|
||||
//Reset dependent state when changing
|
||||
if (
|
||||
registrationInfo?.licensePlate !==
|
||||
context.state.order.vehicle.registration?.licensePlate
|
||||
) {
|
||||
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
||||
|
||||
if (!isSelectedGlassAvailableForVehicle) {
|
||||
|
|
@ -997,10 +1112,25 @@ export const actions = {
|
|||
context.commit(storeMutations.UPDATE_REGISTRATION, registrationInfo);
|
||||
}
|
||||
},
|
||||
saveRegistrationAddressLookup(context, { isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }) {
|
||||
//Reset dependent state when changing
|
||||
if (registrationInfo?.address !== context.state.order.vehicle.registration?.address || registrationInfo?.city !== context.state.order.vehicle.registration?.city || registrationInfo?.state !== context.state.order.vehicle.registration?.state || registrationInfo?.zipCode !== context.state.order.vehicle.registration?.zipCode || registrationInfo?.firstName !== context.state.order.vehicle.registration?.firstName || registrationInfo?.lastName !== context.state.order.vehicle.registration?.lastName) {
|
||||
|
||||
saveRegistrationAddressLookup(
|
||||
context,
|
||||
{ isSelectedGlassAvailableForVehicle, vehicleInfo, registrationInfo }
|
||||
) {
|
||||
//Reset dependent state when changing
|
||||
if (
|
||||
registrationInfo?.address !==
|
||||
context.state.order.vehicle.registration?.address ||
|
||||
registrationInfo?.city !==
|
||||
context.state.order.vehicle.registration?.city ||
|
||||
registrationInfo?.state !==
|
||||
context.state.order.vehicle.registration?.state ||
|
||||
registrationInfo?.zipCode !==
|
||||
context.state.order.vehicle.registration?.zipCode ||
|
||||
registrationInfo?.firstName !==
|
||||
context.state.order.vehicle.registration?.firstName ||
|
||||
registrationInfo?.lastName !==
|
||||
context.state.order.vehicle.registration?.lastName
|
||||
) {
|
||||
context.dispatch(storeActions.RESET_REGISTRATION_STATE_AND_DEPENDENCIES);
|
||||
|
||||
if (!isSelectedGlassAvailableForVehicle) {
|
||||
|
|
@ -1015,72 +1145,141 @@ export const actions = {
|
|||
},
|
||||
savePartQuestionAnswers(context, partQuestionAnswersArray) {
|
||||
// if part question answers have changed, reset subsequent question answers
|
||||
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(context.getters.damage.partQuestionAnswers, "result");
|
||||
const sortedPartQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(partQuestionAnswersArray, "result")
|
||||
const havePartQuestionAnswersChanged = sortedPreviousResultsArray?.length !== sortedPartQuestionAnswersArray.length ||
|
||||
!sortedPreviousResultsArray?.every((x, i) => x.result === sortedPartQuestionAnswersArray[i].result);
|
||||
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(
|
||||
context.getters.damage.partQuestionAnswers,
|
||||
"result"
|
||||
);
|
||||
const sortedPartQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(
|
||||
partQuestionAnswersArray,
|
||||
"result"
|
||||
);
|
||||
const havePartQuestionAnswersChanged =
|
||||
sortedPreviousResultsArray?.length !==
|
||||
sortedPartQuestionAnswersArray.length ||
|
||||
!sortedPreviousResultsArray?.every(
|
||||
(x, i) => x.result === sortedPartQuestionAnswersArray[i].result
|
||||
);
|
||||
|
||||
if (havePartQuestionAnswersChanged) {
|
||||
context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
|
||||
context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null);
|
||||
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
|
||||
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.VEHICLE_PARTS, data: null });
|
||||
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.MOLDING_QUESTIONS, data: null });
|
||||
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.CAPABILITY_QUESTIONS, data: null });
|
||||
context.commit(storeMutations.UPDATE_PAGE_DATA, {
|
||||
page: fmgPageValues.VEHICLE_PARTS,
|
||||
data: null,
|
||||
});
|
||||
context.commit(storeMutations.UPDATE_PAGE_DATA, {
|
||||
page: fmgPageValues.MOLDING_QUESTIONS,
|
||||
data: null,
|
||||
});
|
||||
context.commit(storeMutations.UPDATE_PAGE_DATA, {
|
||||
page: fmgPageValues.CAPABILITY_QUESTIONS,
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
//Save new values
|
||||
context.commit(storeMutations.UPDATE_PART_QUESTION_ANSWERS, partQuestionAnswersArray);
|
||||
context.commit(
|
||||
storeMutations.UPDATE_PART_QUESTION_ANSWERS,
|
||||
partQuestionAnswersArray
|
||||
);
|
||||
},
|
||||
resetMoldingAndCapabilityQuestionAnswersIfNeeded(context, matchedParts) {
|
||||
const partsOrQuestionsDataToCompareWith = context.getters.pageData(fmgPageValues.MOLDING_QUESTIONS)?.partsOrQuestions ?? context.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS)?.partsOrQuestions ?? [];
|
||||
const partsOrQuestionsDataToCompareWith =
|
||||
context.getters.pageData(fmgPageValues.MOLDING_QUESTIONS)
|
||||
?.partsOrQuestions ??
|
||||
context.getters.pageData(fmgPageValues.CAPABILITY_QUESTIONS)
|
||||
?.partsOrQuestions ??
|
||||
[];
|
||||
|
||||
function getAllPartNumbers(partsOrQuestions) {
|
||||
return partsOrQuestions[0]?.parts
|
||||
? [...partsOrQuestions].map(glass => glass.parts).flat().map(part => part.partNumber).filter(partNumber => !partNumber.toUpperCase().includes("FEE")).sort().join(",")
|
||||
: []
|
||||
? [...partsOrQuestions]
|
||||
.map((glass) => glass.parts)
|
||||
.flat()
|
||||
.map((part) => part.partNumber)
|
||||
.filter((partNumber) => !partNumber.toUpperCase().includes("FEE"))
|
||||
.sort()
|
||||
.join(",")
|
||||
: [];
|
||||
}
|
||||
|
||||
const previouslySelectedPartNumbers = getAllPartNumbers(partsOrQuestionsDataToCompareWith);
|
||||
const previouslySelectedPartNumbers = getAllPartNumbers(
|
||||
partsOrQuestionsDataToCompareWith
|
||||
);
|
||||
const currentlySelectedPartNumbers = getAllPartNumbers(matchedParts);
|
||||
|
||||
const haveSelectedVehiclePartsChanged = previouslySelectedPartNumbers !== currentlySelectedPartNumbers;
|
||||
const haveSelectedVehiclePartsChanged =
|
||||
previouslySelectedPartNumbers !== currentlySelectedPartNumbers;
|
||||
|
||||
if (haveSelectedVehiclePartsChanged) {
|
||||
context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
|
||||
context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, null);
|
||||
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
|
||||
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.MOLDING_QUESTIONS, data: null });
|
||||
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.CAPABILITY_QUESTIONS, data: null });
|
||||
context.commit(storeMutations.UPDATE_PAGE_DATA, {
|
||||
page: fmgPageValues.MOLDING_QUESTIONS,
|
||||
data: null,
|
||||
});
|
||||
context.commit(storeMutations.UPDATE_PAGE_DATA, {
|
||||
page: fmgPageValues.CAPABILITY_QUESTIONS,
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
},
|
||||
saveMoldingQuestionAnswers(context, moldingQuestionAnswers) {
|
||||
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(context.getters.damage.moldingQuestionAnswers, "partNum");
|
||||
const sortedMoldingQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(moldingQuestionAnswers, "partNum");
|
||||
const haveMoldingQuestionAnswersChanged = sortedPreviousResultsArray?.length !== sortedMoldingQuestionAnswersArray.length ||
|
||||
!sortedPreviousResultsArray?.every((x, i) => x.partNum === sortedMoldingQuestionAnswersArray[i].partNum);
|
||||
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(
|
||||
context.getters.damage.moldingQuestionAnswers,
|
||||
"partNum"
|
||||
);
|
||||
const sortedMoldingQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(
|
||||
moldingQuestionAnswers,
|
||||
"partNum"
|
||||
);
|
||||
const haveMoldingQuestionAnswersChanged =
|
||||
sortedPreviousResultsArray?.length !==
|
||||
sortedMoldingQuestionAnswersArray.length ||
|
||||
!sortedPreviousResultsArray?.every(
|
||||
(x, i) => x.partNum === sortedMoldingQuestionAnswersArray[i].partNum
|
||||
);
|
||||
|
||||
if (haveMoldingQuestionAnswersChanged) {
|
||||
context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
|
||||
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, null);
|
||||
context.commit(storeMutations.UPDATE_PAGE_DATA, { page: fmgPageValues.CAPABILITY_QUESTIONS, data: null });
|
||||
context.commit(storeMutations.UPDATE_PAGE_DATA, {
|
||||
page: fmgPageValues.CAPABILITY_QUESTIONS,
|
||||
data: null,
|
||||
});
|
||||
}
|
||||
|
||||
//Save new values
|
||||
context.commit(storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS, moldingQuestionAnswers);
|
||||
context.commit(
|
||||
storeMutations.UPDATE_MOLDING_QUESTION_ANSWERS,
|
||||
moldingQuestionAnswers
|
||||
);
|
||||
},
|
||||
saveCapabilityQuestionAnswers(context, capabilityQuestionAnswers) {
|
||||
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(context.getters.damage.capabilityQuestionAnswers, "result");
|
||||
const sortedCapabilityQuestionAnswersArray = sortArrayOfObjectsByPropertyValue(capabilityQuestionAnswers, "result");
|
||||
const haveCapabilityQuestionAnswersChanged = sortedPreviousResultsArray?.length !== sortedCapabilityQuestionAnswersArray.length ||
|
||||
!sortedPreviousResultsArray?.every((x, i) => x.result === sortedCapabilityQuestionAnswersArray[i].result);
|
||||
const sortedPreviousResultsArray = sortArrayOfObjectsByPropertyValue(
|
||||
context.getters.damage.capabilityQuestionAnswers,
|
||||
"result"
|
||||
);
|
||||
const sortedCapabilityQuestionAnswersArray =
|
||||
sortArrayOfObjectsByPropertyValue(capabilityQuestionAnswers, "result");
|
||||
const haveCapabilityQuestionAnswersChanged =
|
||||
sortedPreviousResultsArray?.length !==
|
||||
sortedCapabilityQuestionAnswersArray.length ||
|
||||
!sortedPreviousResultsArray?.every(
|
||||
(x, i) => x.result === sortedCapabilityQuestionAnswersArray[i].result
|
||||
);
|
||||
|
||||
if (haveCapabilityQuestionAnswersChanged) {
|
||||
context.commit(storeMutations.UPDATE_GLASS_PARTS, null);
|
||||
}
|
||||
|
||||
//Save new values
|
||||
context.commit(storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS, capabilityQuestionAnswers);
|
||||
context.commit(
|
||||
storeMutations.UPDATE_CAPABILITY_QUESTION_ANSWERS,
|
||||
capabilityQuestionAnswers
|
||||
);
|
||||
},
|
||||
// Misc order actions
|
||||
saveServiceLocation(context, serviceLocationInfo) {
|
||||
|
|
@ -1092,7 +1291,6 @@ export const actions = {
|
|||
saveVin(context, { isSelectedGlassAvailableForVehicle, vehicleInfo }) {
|
||||
//Reset dependent state when changing
|
||||
if (vehicleInfo.vin !== context.state.order.vehicle.vin) {
|
||||
|
||||
if (!isSelectedGlassAvailableForVehicle) {
|
||||
context.dispatch(storeActions.RESET_DAMAGE_STATE_AND_DEPENDENCIES);
|
||||
context.dispatch(storeActions.RESET_PARTS_STATE_AND_DEPENDENCIES);
|
||||
|
|
@ -1107,12 +1305,11 @@ export const actions = {
|
|||
},
|
||||
clearVin(context) {
|
||||
context.commit(storeMutations.UPDATE_VEHICLE_VIN, null);
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
export default createStore({
|
||||
plugins: [createPersistedState()],
|
||||
|
||||
// IMPORTANT: Be VERY careful when modifying these fields for at least a few reasons:
|
||||
// * The CMS can reference the fields by name
|
||||
// * Return users may have a previous "version" of the model, and we don't want
|
||||
|
|
@ -1126,30 +1323,27 @@ export default createStore({
|
|||
// Private Functions
|
||||
|
||||
function getHasRecalibrationPart(state) {
|
||||
var hasRequiresRecalibration = getAllValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, "requiresRecalibration")?.length > 0;
|
||||
var hasRecalibrationType = getAllValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, "recalibrationType")?.length > 0;
|
||||
var hasRequiresRecalibration = getNonFalseValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, "requiresRecalibration")?.length > 0;
|
||||
var hasRecalibrationType = getNonFalseValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, "recalibrationType")?.length > 0;
|
||||
|
||||
if (hasRequiresRecalibration) {
|
||||
if (hasRecalibrationType) { // Has both 'requiresRecalibration' and 'recalibrationType' and 'recalibrationType'
|
||||
return getAllValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, "recalibrationType")[0].toLowerCase() != "unknown";
|
||||
return getNonFalseValuesOfPropertyInArrayOfObjects(state.order.lineItems.glassParts, "recalibrationType")[0].toLowerCase() != "unknown";
|
||||
} else { // Has 'requiresRecalibration' but no 'recalibrationType' at all
|
||||
return true;
|
||||
}
|
||||
} else { // Does not have 'requiresRecalibration'
|
||||
} else {
|
||||
// Does not have 'requiresRecalibration'
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function sortArrayOfObjectsByPropertyValue(arrayOfObjects, propertyName) {
|
||||
if (!arrayOfObjects) return null;
|
||||
|
||||
return arrayOfObjects.sort((a, b) => {
|
||||
if (a[propertyName] < b[propertyName])
|
||||
return -1;
|
||||
else if (a[propertyName] > b[propertyName])
|
||||
return 1;
|
||||
else
|
||||
return 0;
|
||||
})
|
||||
}
|
||||
if (a[propertyName] < b[propertyName]) return -1;
|
||||
else if (a[propertyName] > b[propertyName]) return 1;
|
||||
else return 0;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,17 @@
|
|||
<button
|
||||
:aria-disabled="isDisabled"
|
||||
class="btn d-flex align-items-center py-3 px-4 delay"
|
||||
:class="[isPrimary ? 'btn-primary' : 'btn-secondary',isFloat ? 'float-end' : '', isLoaderDisplayed ? 'has-loader' : '']"
|
||||
@click="clicked"
|
||||
>
|
||||
:class="[
|
||||
isPrimary ? 'btn-primary' : 'btn-secondary',
|
||||
isFloat ? 'float-end' : '',
|
||||
isLoaderDisplayed ? 'has-loader' : '',
|
||||
]"
|
||||
@click="clicked">
|
||||
<span class="m-0">{{ this.buttonText }}</span>
|
||||
<loader
|
||||
class="ms-2"
|
||||
v-if="isLoaderDisplayed"
|
||||
v-bind:class="[this.loaderColor, this.loaderPosition]"
|
||||
/>
|
||||
v-bind:class="[this.loaderColor, this.loaderPosition]" />
|
||||
</button>
|
||||
</template>
|
||||
|
||||
|
|
@ -33,11 +35,16 @@ export default {
|
|||
};
|
||||
},
|
||||
methods: {
|
||||
removeLoader(){
|
||||
removeLoader() {
|
||||
this.isLoaderDisplayed = false;
|
||||
},
|
||||
clicked() {
|
||||
this.pushEventToGA(this.$route.query[this.queryStrings.FMG_PAGE], this.GaActions.CLICKED, this.buttonText, true);
|
||||
this.pushEventToGA(
|
||||
this.$route.query[this.queryStrings.FMG_PAGE],
|
||||
this.GaActions.CLICKED,
|
||||
this.buttonText,
|
||||
true
|
||||
);
|
||||
if (!this.isDisabled) {
|
||||
this.isLoaderDisplayed = true;
|
||||
this.$emit("click-event");
|
||||
|
|
@ -98,7 +105,8 @@ export default {
|
|||
background: $blue-700;
|
||||
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
|
||||
}
|
||||
&.delay {// fixes flicker while transitioning between states
|
||||
&.delay {
|
||||
// fixes flicker while transitioning between states
|
||||
transition: background 0s 0s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
|
@ -137,7 +145,8 @@ export default {
|
|||
color: $white;
|
||||
@include blue-gradient;
|
||||
}
|
||||
&.delay {// fixes flicker while transitioning between states
|
||||
&.delay {
|
||||
// fixes flicker while transitioning between states
|
||||
transition: background 0s 0s ease-in-out;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,272 +1,201 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import { shallowMount, mount } from "@vue/test-utils";
|
||||
import listButtonHorizontal from "./list-button-horizontal";
|
||||
import { nextTick } from "vue";
|
||||
import { GaActions } from "@/constants/analytics";
|
||||
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||
|
||||
// TODO KO
|
||||
describe.skip("list-button-horizontal.vue", () => {
|
||||
it("Should return input type checkbox if isMultiSelect is true", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButtonHorizontal, {
|
||||
propsData: {
|
||||
isMultiSelect: true,
|
||||
},
|
||||
describe("list-button-horizontal.vue", () => {
|
||||
// TODO KO Have this moved out to somewhere shared
|
||||
describe("Shared baseInputButton checks", () => {
|
||||
it("Should return input type checkbox if isMultiSelect is true", async () => {
|
||||
// Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
isMultiSelect: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
expect(input.attributes().type).toEqual("checkbox");
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
it("Should return input type radio if isMultiSelect is false or not specified", async () => {
|
||||
// Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
isMultiSelect: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(input.attributes().type).toEqual("checkbox");
|
||||
});
|
||||
|
||||
it("Should return input type radio if isMultiSelect is false or not specified", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButtonHorizontal, {
|
||||
propsData: {
|
||||
isMultiSelect: false,
|
||||
},
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
expect(input.attributes().type).toEqual("radio");
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
it("Should emit button value on click", async () => {
|
||||
// Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
isRadioHorizontal: true,
|
||||
buttonLabel: "Windshield",
|
||||
value: "List Card Checkbox",
|
||||
groupID: "radio-demo-1",
|
||||
groupName: "radio 1",
|
||||
buttonImage: "windshield-damage.svg",
|
||||
isRequired: true,
|
||||
isWide: false,
|
||||
modelValue: ["List Card Checkbox"],
|
||||
buttonID: "list-card-id",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(input.attributes().type).toEqual("radio");
|
||||
// Act
|
||||
wrapper.vm.handleAnswerChange("test");
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted()["change"][0]).toEqual(["test"]);
|
||||
});
|
||||
});
|
||||
|
||||
it("Should return primary label text (buttonID)", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButtonHorizontal, {
|
||||
propsData: {
|
||||
buttonID: "List Card Checkbox",
|
||||
},
|
||||
describe("styling/UI", () => {
|
||||
it("Should return screen reader text", async () => {
|
||||
// Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
screenReaderOnlyText: "Screen Reader Only Text",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const paragraph = wrapper.find("span.sr-only");
|
||||
|
||||
expect(paragraph.text()).toEqual("Screen Reader Only Text");
|
||||
});
|
||||
|
||||
// Assert
|
||||
const label = wrapper.find("label");
|
||||
it("Should return text alignment class", async () => {
|
||||
// Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
textPosition: "text-center",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(label.attributes().for).toEqual("List Card Checkbox");
|
||||
});
|
||||
// Assert
|
||||
const paragraph = wrapper.find("span.m-0");
|
||||
|
||||
it("Should return screen reader text", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButtonHorizontal, {
|
||||
propsData: {
|
||||
screenReaderOnlyText: "Screen Reader Only Text",
|
||||
},
|
||||
expect(paragraph.attributes("class")).toContain("text-center");
|
||||
});
|
||||
|
||||
// Assert
|
||||
const paragraph = wrapper.find("span.sr-only");
|
||||
it("Should return aria-required state", async () => {
|
||||
// Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
isRequired: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(paragraph.text()).toEqual("Screen Reader Only Text");
|
||||
});
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
|
||||
it("Should return text alignment class", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButtonHorizontal, {
|
||||
propsData: {
|
||||
textPosition: "text-center",
|
||||
},
|
||||
expect(input.attributes()["aria-required"]).toEqual("true");
|
||||
});
|
||||
|
||||
// Assert
|
||||
const paragraph = wrapper.find("span.m-0");
|
||||
it("is cash or insurance button => has 'radio-fancy' class", () => {
|
||||
// Arrange/Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
isCashOrInsurance: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(paragraph.attributes("class")).toContain("text-center");
|
||||
});
|
||||
// Assert
|
||||
const label = wrapper.find("label");
|
||||
|
||||
it("Should return aria-required state", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButtonHorizontal, {
|
||||
propsData: {
|
||||
isRequired: true,
|
||||
},
|
||||
expect(label.classes()).toContain("radio-fancy");
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
test("has buttonLabel => displays buttonLabel", () => {
|
||||
// Arrange/Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
buttonLabel: "Surprise!",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(input.attributes()["aria-required"]).toEqual("true");
|
||||
});
|
||||
|
||||
it("Should return loader enabled true", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButtonHorizontal, {
|
||||
global: {
|
||||
mocks: {
|
||||
'$route': { query: { fmgPage: 'page-name' } },
|
||||
GaActions: GaActions,
|
||||
pushEventToGA: jest.fn(),
|
||||
}
|
||||
},
|
||||
propsData: {
|
||||
selectingInitiatesLoad: true,
|
||||
},
|
||||
// Assert
|
||||
const content = wrapper.find(".list-button-horizontal-content");
|
||||
expect(content.isVisible()).toBe(true);
|
||||
expect(content.text()).toContain("Surprise!");
|
||||
});
|
||||
|
||||
// Assert
|
||||
test("has buttonLabelSubCopy => displays buttonLabelSubCopy", () => {
|
||||
// Arrange/Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
buttonLabel: "Surprise!",
|
||||
buttonLabelSubCopy: "Super duper surprise :)",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const label = wrapper.find("label");
|
||||
|
||||
wrapper.vm.handleCheckChange = jest.fn();
|
||||
wrapper.vm.triggerButton();
|
||||
|
||||
await nextTick();
|
||||
|
||||
const loader = wrapper.find("loader-stub");
|
||||
|
||||
expect(loader.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("Should return loader color", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButtonHorizontal, {
|
||||
global: {
|
||||
mocks: {
|
||||
'$route': { query: { fmgPage: 'page-name' } },
|
||||
GaActions: GaActions,
|
||||
pushEventToGA: jest.fn(),
|
||||
}
|
||||
},
|
||||
propsData: {
|
||||
loaderColor: "blue",
|
||||
selectingInitiatesLoad: true,
|
||||
},
|
||||
// Assert
|
||||
const content = wrapper.find(".list-button-horizontal-content");
|
||||
expect(content.isVisible()).toBe(true);
|
||||
expect(content.text()).toContain("Super duper surprise :)");
|
||||
});
|
||||
|
||||
// Assert
|
||||
test("has screenReaderOnlyText => displays screenReaderOnlyText", () => {
|
||||
// Arrange/Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
buttonLabel: "Surprise!",
|
||||
buttonLabelSubCopy: "Super duper surprise :)",
|
||||
screenReaderOnlyText: "Tests are fun!"
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const label = wrapper.find("label");
|
||||
|
||||
wrapper.vm.handleCheckChange = jest.fn();
|
||||
wrapper.vm.triggerButton();
|
||||
|
||||
await nextTick();
|
||||
|
||||
const loader = wrapper.find("loader-stub");
|
||||
|
||||
expect(loader.attributes("class")).toContain("blue");
|
||||
});
|
||||
|
||||
it("Should return loader position", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButtonHorizontal, {
|
||||
global: {
|
||||
mocks: {
|
||||
'$route': { query: { fmgPage: 'page-name' } },
|
||||
GaActions: GaActions,
|
||||
pushEventToGA: jest.fn(),
|
||||
}
|
||||
},
|
||||
propsData: {
|
||||
loaderPosition: "right",
|
||||
selectingInitiatesLoad: true,
|
||||
},
|
||||
// Assert
|
||||
const content = wrapper.find(".list-button-horizontal-content");
|
||||
const screenReaderOnlyText = wrapper.find(".sr-only");
|
||||
expect(content.isVisible()).toBe(true);
|
||||
expect(screenReaderOnlyText.exists()).toBe(true);
|
||||
expect(screenReaderOnlyText.text()).toContain("Tests are fun!");
|
||||
});
|
||||
|
||||
// Assert
|
||||
|
||||
const label = wrapper.find("label");
|
||||
|
||||
wrapper.vm.handleCheckChange = jest.fn();
|
||||
wrapper.vm.triggerButton();
|
||||
|
||||
await nextTick();
|
||||
|
||||
const loader = wrapper.find("loader-stub");
|
||||
|
||||
expect(loader.attributes("class")).toContain("right");
|
||||
});
|
||||
|
||||
it("Should emit button value on click", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButtonHorizontal, {
|
||||
propsData: {
|
||||
isRadioHorizontal: true,
|
||||
buttonLabel: "Windshield",
|
||||
value: "List Card Checkbox",
|
||||
groupID: "radio-demo-1",
|
||||
groupName: "radio 1",
|
||||
buttonImage: "windshield-damage.svg",
|
||||
isRequired: true,
|
||||
isWide: false,
|
||||
modelValue: ["List Card Checkbox"],
|
||||
},
|
||||
});
|
||||
wrapper.vm.handleCheckChange();
|
||||
// Assert
|
||||
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: Boolean, buttonId: undefined, checkValue: false}]);
|
||||
});
|
||||
|
||||
it("Should set checkValue data if selectedButtonIDs has value(s)", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButtonHorizontal, {
|
||||
propsData: {
|
||||
isRadioHorizontal: true,
|
||||
buttonLabel: "Windshield",
|
||||
buttonID: "List Card Checkbox",
|
||||
groupID: "radio-demo-1",
|
||||
groupName: "radio 1",
|
||||
buttonImage: "windshield-damage.svg",
|
||||
isRequired: true,
|
||||
isWide: false,
|
||||
modelValue: ["List Card Checkbox"],
|
||||
isMultiSelect: false,
|
||||
value: "Car-Front",
|
||||
selectedValues: ["Car-Front"]
|
||||
},
|
||||
});
|
||||
// Assert
|
||||
expect(wrapper.vm.checkValue).toEqual(true);
|
||||
});
|
||||
|
||||
it("Should run handleCheckChange if selectingInitiatesLoad is false and handleInputChange is triggered", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButtonHorizontal, {
|
||||
propsData: {
|
||||
selectingInitiatesLoad: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
wrapper.vm.handleInputChange();
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.handleCheckChange).toBeCalled;
|
||||
});
|
||||
|
||||
it("Should do nothing if isMultiSelect is true and handleKeyupArrow is triggered", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButtonHorizontal, {
|
||||
propsData: {
|
||||
isMultiSelect: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
wrapper.vm.handleKeyupArrow();
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.handleKeyupArrow).toHaveReturned;
|
||||
});
|
||||
|
||||
it("Should run handleCheckChange if selectingInitiatesLoad is false and handleKeyupArrow is triggered", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButtonHorizontal, {
|
||||
propsData: {
|
||||
selectingInitiatesLoad: false,
|
||||
isMultiSelect: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
wrapper.vm.handleKeyupArrow();
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.handleCheckChange).toBeCalled;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function setupMocks({ mockData }) {
|
||||
const wrapper = mount(listButtonHorizontal, {
|
||||
...mockData,
|
||||
propsData: {
|
||||
...mockData.propsData,
|
||||
groupName: "my-group",
|
||||
modelValue: mockData.propsData?.isMultiSelect ? ["5"] : "5",
|
||||
value: mockData.propsData?.isMultiSelect ? ["4"] : "4"
|
||||
},
|
||||
mixins: [inputButtonWrapperMixin],
|
||||
});
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
'list-group list-button-horizontal d-flex flex-column w-100',
|
||||
{ 'radio-fancy': isCashOrInsurance },
|
||||
]"
|
||||
@buttonClicked="handleAnswerChange">
|
||||
@inputButtonClicked="handleAnswerChange">
|
||||
<div
|
||||
class="list-button-horizontal-content d-flex flex-column justify-content-center p-3">
|
||||
<span class="m-0" :class="textPosition">
|
||||
|
|
|
|||
|
|
@ -1,264 +1,261 @@
|
|||
import { shallowMount } from "@vue/test-utils";
|
||||
import { shallowMount, mount } from "@vue/test-utils";
|
||||
import listButton from "./list-button";
|
||||
import { nextTick } from "vue";
|
||||
import { GaActions } from "@/constants/analytics";
|
||||
import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
||||
|
||||
// TODO KO
|
||||
describe.skip("list-button.vue", () => {
|
||||
it("Should return input type checkbox if isMultiSelect is true", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButton, {
|
||||
propsData: {
|
||||
isMultiSelect: true,
|
||||
},
|
||||
describe("list-button.vue", () => {
|
||||
describe("loader", () => {
|
||||
it("selectingInitiatesLoad is true and answer is changed => show the loader", async () => {
|
||||
// Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
global: {
|
||||
mocks: {
|
||||
$route: { query: { fmgPage: "page-name" } },
|
||||
GaActions: GaActions,
|
||||
pushEventToGA: jest.fn(),
|
||||
},
|
||||
},
|
||||
propsData: {
|
||||
selectingInitiatesLoad: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Act
|
||||
wrapper.vm.handleAnswerChange("something");
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Assert
|
||||
const loader = wrapper.findComponent({ name: "loader" });
|
||||
expect(loader.exists()).toBe(true);
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
it("Should return loader color", async () => {
|
||||
// Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
global: {
|
||||
mocks: {
|
||||
$route: { query: { fmgPage: "page-name" } },
|
||||
GaActions: GaActions,
|
||||
pushEventToGA: jest.fn(),
|
||||
},
|
||||
},
|
||||
propsData: {
|
||||
loaderColor: "blue",
|
||||
selectingInitiatesLoad: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(input.attributes().type).toEqual("checkbox");
|
||||
});
|
||||
// Act
|
||||
wrapper.vm.handleAnswerChange("something");
|
||||
await nextTick();
|
||||
|
||||
it("Should return input type radio if isMultiSelect is false or not specified", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButton, {
|
||||
propsData: {
|
||||
isMultiSelect: false,
|
||||
},
|
||||
// Assert
|
||||
const loader = wrapper.findComponent({ name: "loader" });
|
||||
expect(loader.attributes("class")).toContain("blue");
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
it("Should return loader position", async () => {
|
||||
// Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
global: {
|
||||
mocks: {
|
||||
$route: { query: { fmgPage: "page-name" } },
|
||||
GaActions: GaActions,
|
||||
pushEventToGA: jest.fn(),
|
||||
},
|
||||
},
|
||||
propsData: {
|
||||
loaderPosition: "right",
|
||||
selectingInitiatesLoad: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(input.attributes().type).toEqual("radio");
|
||||
// Act
|
||||
wrapper.vm.handleAnswerChange("test");
|
||||
await nextTick();
|
||||
|
||||
// Assert
|
||||
const loader = wrapper.findComponent({ name: "loader" });
|
||||
expect(loader.attributes("class")).toContain("right");
|
||||
});
|
||||
});
|
||||
|
||||
it("Should return primary label text (buttonID)", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButton, {
|
||||
propsData: {
|
||||
buttonID: "List Card Checkbox",
|
||||
},
|
||||
describe("baseInputButton checks", () => {
|
||||
it("Should return input type checkbox if isMultiSelect is true", async () => {
|
||||
// Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
isMultiSelect: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
|
||||
expect(input.attributes().type).toEqual("checkbox");
|
||||
});
|
||||
|
||||
// Assert
|
||||
const label = wrapper.find("label");
|
||||
it("Should return input type radio if isMultiSelect is false or not specified", async () => {
|
||||
// Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
isMultiSelect: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(label.attributes().for).toEqual("List Card Checkbox");
|
||||
});
|
||||
|
||||
it("Should return screen reader text", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButton, {
|
||||
propsData: {
|
||||
screenReaderOnlyText: "Screen Reader Only Text",
|
||||
},
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
expect(input.attributes().type).toEqual("radio");
|
||||
});
|
||||
|
||||
// Assert
|
||||
const paragraph = wrapper.find("span.sr-only");
|
||||
it("Should emit button value on click", async () => {
|
||||
// Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
isRadioHorizontal: true,
|
||||
buttonLabel: "Windshield",
|
||||
value: "List Card Checkbox",
|
||||
groupID: "radio-demo-1",
|
||||
groupName: "radio 1",
|
||||
buttonImage: "windshield-damage.svg",
|
||||
isRequired: true,
|
||||
isWide: false,
|
||||
modelValue: ["List Card Checkbox"],
|
||||
buttonID: "list-card-id",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(paragraph.text()).toEqual("Screen Reader Only Text");
|
||||
// Act
|
||||
wrapper.vm.handleAnswerChange("test");
|
||||
await wrapper.vm.$nextTick();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted()["change"][0]).toEqual(["test"]);
|
||||
});
|
||||
});
|
||||
|
||||
it("Should return text alignment class", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButton, {
|
||||
propsData: {
|
||||
textPosition: "text-center",
|
||||
},
|
||||
describe("styling/UI", () => {
|
||||
test("has buttonLabel => displays buttonLabel", () => {
|
||||
// Arrange/Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
buttonLabel: "Surprise!",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
const content = wrapper.find(".list-button-content");
|
||||
expect(content.isVisible()).toBe(true);
|
||||
expect(content.text()).toContain("Surprise!");
|
||||
});
|
||||
|
||||
// Assert
|
||||
const paragraph = wrapper.find("span.m-0");
|
||||
test("has buttonLabelSubCopy => displays buttonLabelSubCopy", () => {
|
||||
// Arrange/Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
buttonLabel: "Surprise!",
|
||||
buttonLabelSubCopy: "Super duper surprise :)",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(paragraph.attributes("class")).toContain("text-center");
|
||||
});
|
||||
|
||||
it("Should return aria-required state", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButton, {
|
||||
propsData: {
|
||||
isRequired: true,
|
||||
},
|
||||
// Assert
|
||||
const content = wrapper.find(".list-button-content");
|
||||
expect(content.isVisible()).toBe(true);
|
||||
expect(content.text()).toContain("Super duper surprise :)");
|
||||
});
|
||||
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
test("has screenReaderOnlyText => displays screenReaderOnlyText", () => {
|
||||
// Arrange/Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
buttonLabel: "Surprise!",
|
||||
buttonLabelSubCopy: "Super duper surprise :)",
|
||||
screenReaderOnlyText: "Tests are fun!",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(input.attributes()["aria-required"]).toEqual("true");
|
||||
});
|
||||
|
||||
it("Should return loader enabled true", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButton, {
|
||||
global: {
|
||||
mocks: {
|
||||
'$route': { query: { fmgPage: 'page-name' } },
|
||||
GaActions: GaActions,
|
||||
pushEventToGA: jest.fn(),
|
||||
}
|
||||
},
|
||||
propsData: {
|
||||
selectingInitiatesLoad: true,
|
||||
},
|
||||
// Assert
|
||||
const content = wrapper.find(".list-button-content");
|
||||
const screenReaderOnlyText = wrapper.find(".sr-only");
|
||||
expect(content.isVisible()).toBe(true);
|
||||
expect(screenReaderOnlyText.exists()).toBe(true);
|
||||
expect(screenReaderOnlyText.text()).toContain("Tests are fun!");
|
||||
});
|
||||
|
||||
// Assert
|
||||
wrapper.vm.handleCheckChange = jest.fn();
|
||||
wrapper.vm.triggerButton();
|
||||
it("Should return screen reader text", async () => {
|
||||
// Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
screenReaderOnlyText: "Screen Reader Only Text",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
// Assert
|
||||
const paragraph = wrapper.find("span.sr-only");
|
||||
|
||||
const loader = wrapper.find("loader-stub");
|
||||
|
||||
expect(loader.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("Should return loader color", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButton, {
|
||||
global: {
|
||||
mocks: {
|
||||
'$route': { query: { fmgPage: 'page-name' } },
|
||||
GaActions: GaActions,
|
||||
pushEventToGA: jest.fn(),
|
||||
}
|
||||
},
|
||||
propsData: {
|
||||
loaderColor: "blue",
|
||||
selectingInitiatesLoad: true,
|
||||
},
|
||||
expect(paragraph.text()).toEqual("Screen Reader Only Text");
|
||||
});
|
||||
|
||||
// Assert
|
||||
wrapper.vm.handleCheckChange = jest.fn();
|
||||
wrapper.vm.triggerButton();
|
||||
await nextTick();
|
||||
it("Should return text alignment class", async () => {
|
||||
// Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
textPosition: "text-center",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const loader = wrapper.find("loader-stub");
|
||||
// Assert
|
||||
const paragraph = wrapper.find("span.m-0");
|
||||
|
||||
expect(loader.attributes("class")).toContain("blue");
|
||||
});
|
||||
|
||||
it("Should return loader position", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButton, {
|
||||
global: {
|
||||
mocks: {
|
||||
'$route': { query: { fmgPage: 'page-name' } },
|
||||
GaActions: GaActions,
|
||||
pushEventToGA: jest.fn(),
|
||||
}
|
||||
},
|
||||
propsData: {
|
||||
loaderPosition: "right",
|
||||
selectingInitiatesLoad: true,
|
||||
},
|
||||
expect(paragraph.attributes("class")).toContain("text-center");
|
||||
});
|
||||
|
||||
// Assert
|
||||
wrapper.vm.handleCheckChange = jest.fn();
|
||||
wrapper.vm.triggerButton();
|
||||
it("Should return aria-required state", async () => {
|
||||
// Act
|
||||
const { wrapper } = setupMocks({
|
||||
mockData: {
|
||||
propsData: {
|
||||
isRequired: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await nextTick();
|
||||
// Assert
|
||||
const input = wrapper.find("input");
|
||||
|
||||
const loader = wrapper.find("loader-stub");
|
||||
|
||||
expect(loader.attributes("class")).toContain("right");
|
||||
});
|
||||
|
||||
it("Should emit button value on click", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButton, {
|
||||
propsData: {
|
||||
isRadioHorizontal: true,
|
||||
buttonLabel: "Windshield",
|
||||
value: "List Card Checkbox",
|
||||
groupID: "radio-demo-1",
|
||||
groupName: "radio 1",
|
||||
buttonImage: "windshield-damage.svg",
|
||||
isRequired: true,
|
||||
isWide: false,
|
||||
modelValue: ["List Card Checkbox"],
|
||||
buttonID: 'list-card-id'
|
||||
},
|
||||
expect(input.attributes()["aria-required"]).toEqual("true");
|
||||
});
|
||||
|
||||
wrapper.vm.handleCheckChange();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.emitted()["isCheckedChanged"][0]).toEqual([{value: "List Card Checkbox", checkValue: false, buttonId: 'list-card-id'}]);
|
||||
|
||||
});
|
||||
|
||||
it("Should set checkValue data if selectedButtonIDs has value(s)", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButton, {
|
||||
propsData: {
|
||||
isRadioHorizontal: true,
|
||||
buttonLabel: "Windshield",
|
||||
buttonID: "List Card Checkbox",
|
||||
groupID: "radio-demo-1",
|
||||
groupName: "radio 1",
|
||||
buttonImage: "windshield-damage.svg",
|
||||
isRequired: true,
|
||||
isWide: false,
|
||||
modelValue: ["List Card Checkbox"],
|
||||
selectedValues: "Car-Front"
|
||||
},
|
||||
});
|
||||
// Assert
|
||||
expect(wrapper.componentVM.checkValue).toEqual(false);
|
||||
});
|
||||
|
||||
it("Should run handleCheckChange if selectingInitiatesLoad is false and handleInputChange is triggered", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButton, {
|
||||
propsData: {
|
||||
selectingInitiatesLoad: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
wrapper.vm.handleInputChange();
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.handleCheckChange).toBeCalled;
|
||||
});
|
||||
|
||||
it("Should do nothing if isMultiSelect is true and handleKeyupArrow is triggered", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButton, {
|
||||
propsData: {
|
||||
isMultiSelect: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
wrapper.vm.handleKeyupArrow();
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.handleKeyupArrow).toHaveReturned;
|
||||
});
|
||||
|
||||
it("Should run handleCheckChange if selectingInitiatesLoad is false and handleKeyupArrow is triggered", async () => {
|
||||
// Act
|
||||
const wrapper = shallowMount(listButton, {
|
||||
propsData: {
|
||||
selectingInitiatesLoad: false,
|
||||
isMultiSelect: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Assert
|
||||
wrapper.vm.handleKeyupArrow();
|
||||
|
||||
await nextTick();
|
||||
|
||||
expect(wrapper.vm.handleCheckChange).toBeCalled;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function setupMocks({ mockData }) {
|
||||
const wrapper = mount(listButton, {
|
||||
...mockData,
|
||||
mixins: [inputButtonWrapperMixin],
|
||||
});
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
<baseInputButton
|
||||
v-bind="$props"
|
||||
buttonWrapperClasses="list-group list-button rounded-3 d-flex flex-column w-100 mb-2"
|
||||
@buttonClicked="handleAnswerChange">
|
||||
@inputButtonClicked="handleAnswerChange">
|
||||
<div
|
||||
:aria-label="buttonLabel"
|
||||
class="list-button-content d-flex flex-column justify-content-center py-3 px-4">
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ describe("list-card.vue", () => {
|
|||
groupID: "checkbox-demo-1",
|
||||
groupName: "Checkbox 1",
|
||||
buttonImage: "windshield-damage.svg",
|
||||
value: "test value",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -32,6 +33,7 @@ describe("list-card.vue", () => {
|
|||
groupID: "radio-demo-1",
|
||||
groupName: "radio 1",
|
||||
buttonImage: "windshield-damage.svg",
|
||||
value: "test value",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -51,6 +53,7 @@ describe("list-card.vue", () => {
|
|||
groupName: "radio 1",
|
||||
buttonImage: "windshield-damage.svg",
|
||||
buttonLabelSubCopy: "Test",
|
||||
value: "test value",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -70,6 +73,7 @@ describe("list-card.vue", () => {
|
|||
groupName: "radio 1",
|
||||
buttonImage: "windshield-damage.svg",
|
||||
buttonLabelSubCopy: "Test",
|
||||
value: "test value",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -89,6 +93,7 @@ describe("list-card.vue", () => {
|
|||
groupName: "radio 1",
|
||||
buttonImage: "windshield-damage.svg",
|
||||
isRequired: true,
|
||||
value: "test value",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -110,6 +115,7 @@ describe("list-card.vue", () => {
|
|||
isRequired: true,
|
||||
isWide: true,
|
||||
buttonLabelSubCopy: "",
|
||||
value: "test value",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -134,6 +140,7 @@ describe("list-card.vue", () => {
|
|||
isRequired: true,
|
||||
isWide: true,
|
||||
buttonLabelSubCopy: "Button Subcopy",
|
||||
value: "test value",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -159,6 +166,7 @@ describe("list-card.vue", () => {
|
|||
buttonImage: "windshield-damage.svg",
|
||||
isRequired: true,
|
||||
isWide: false,
|
||||
value: "test value",
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
'list-card w-100 rounded-3 d-flex align-items-center h-100',
|
||||
{ horizontal: isWide },
|
||||
]"
|
||||
@buttonClicked="handleAnswerChange">
|
||||
@inputButtonClicked="handleAnswerChange">
|
||||
<div
|
||||
class="d-flex w-100 align-items-center px-2 h-100 list-card-content"
|
||||
:class="labelClasses">
|
||||
|
|
|
|||
|
|
@ -6,12 +6,16 @@ import inputButtonWrapperMixin from "@/mixins/input-button-wrapper-mixin";
|
|||
describe("radio.vue", () => {
|
||||
it("Should have correct group name", async () => {
|
||||
// Arrange
|
||||
let { wrapper } = setupMocks({});
|
||||
let { wrapper } = setupMocks({
|
||||
mountOptionsMockData: {
|
||||
propsData: {
|
||||
groupName: "radio-button-test",
|
||||
value: "test value",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Act
|
||||
await wrapper.setProps({
|
||||
groupName: "radio-button-test",
|
||||
});
|
||||
const input = wrapper.find("input");
|
||||
|
||||
// Assert
|
||||
|
|
@ -20,12 +24,16 @@ describe("radio.vue", () => {
|
|||
|
||||
it("Should have correct label text", async () => {
|
||||
// Act
|
||||
let { wrapper } = setupMocks({});
|
||||
let { wrapper } = setupMocks({
|
||||
mountOptionsMockData: {
|
||||
propsData: {
|
||||
buttonLabel: "label text",
|
||||
value: "test value",
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// Arrange
|
||||
await wrapper.setProps({
|
||||
buttonLabel: "label text",
|
||||
});
|
||||
const paragraph = wrapper.find("p");
|
||||
|
||||
// Assert
|
||||
|
|
@ -39,6 +47,7 @@ describe("radio.vue", () => {
|
|||
// Arrange
|
||||
await wrapper.setProps({
|
||||
screenReaderOnlyText: "screenreader text",
|
||||
value: "test value",
|
||||
});
|
||||
const paragraph = wrapper.find(".sr-only");
|
||||
|
||||
|
|
@ -48,13 +57,10 @@ describe("radio.vue", () => {
|
|||
});
|
||||
|
||||
function setupMocks({ mountOptionsMockData = {} }) {
|
||||
const wrapper = mount(
|
||||
radio,
|
||||
getMountOptions({
|
||||
...mountOptionsMockData,
|
||||
mixins: [inputButtonWrapperMixin],
|
||||
})
|
||||
);
|
||||
const wrapper = mount(radio, {
|
||||
...mountOptionsMockData,
|
||||
mixins: [inputButtonWrapperMixin],
|
||||
});
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
v-bind="$props"
|
||||
buttonWrapperClasses="ui-radio form-check"
|
||||
inputClasses="form-check-input"
|
||||
@buttonClicked="handleAnswerChange">
|
||||
@inputButtonClicked="handleAnswerChange">
|
||||
<div class="d-flex align-items-start form-check-label">
|
||||
<p v-if="buttonLabel" class="m-0">{{ buttonLabel }}</p>
|
||||
<span v-if="screenReaderOnlyText" class="sr-only">{{
|
||||
|
|
|
|||
Loading…
Reference in a new issue