CSR-762 Add tests

This commit is contained in:
Katie 2022-10-05 14:22:25 -04:00
parent f634d8ced1
commit fe9602200c
9 changed files with 656 additions and 32 deletions

View file

@ -0,0 +1,523 @@
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", () => {
describe("checkbox", () => {
test.todo("modelValue is null => inputButtonClicked is emitted with correct")
})
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",
// should override the above if they exist
...mockData.propsData,
},
};
const wrapper = shouldShallowMount
? shallowMount(baseInputButton, mountMockData)
: mount(baseInputButton, mountMockData);
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 };
}

View file

@ -21,14 +21,14 @@
</template> </template>
<script> <script>
// import { queryStrings } from "@/constants/query-strings";
import { useField } from "vee-validate"; import { useField } from "vee-validate";
import { toRef } from "vue"; import { toRef } from "vue";
// import { gaStoreActions } from "../../constants/store-actions"; import { queryStrings } from "@/constants/query-strings";
import eventBus from "@/helpers/event-bus/event-bus.js";
export default { export default {
name: "base-input-button", name: "base-input-button",
emits: ["change"], emits: ["change", "inputButtonClicked"],
model: { model: {
prop: "modelValue", prop: "modelValue",
event: "change", event: "change",
@ -79,6 +79,8 @@ export default {
CLICK: "click", CLICK: "click",
}; };
console.log("Handling: ", eventType)
if (this.isMultiSelect) { if (this.isMultiSelect) {
switch (eventType) { switch (eventType) {
case eventTypes.ENTER: case eventTypes.ENTER:
@ -89,7 +91,7 @@ export default {
// this.groupName, // this.groupName,
// false // false
// ); // );
// this.pushClickEventToGA(); // this.pushClickEventToGA();
break; break;
} }
@ -138,12 +140,20 @@ export default {
}, },
handleClick(e) { handleClick(e) {
this.handleSelectionChange(e); this.handleSelectionChange(e);
this.$emit("buttonClicked", this.valueToEmit); // console.log("addEventToBus: ", this.value?.toString())
// eventBus.addEventToBus("GA", "click", {
// category: this.$route.query[queryStrings.FMG_PAGE],
// action: this.GaActions.CLICKED,
// label: this.value?.toString(),
// pushToLogApp: true,
// valueToLogType: this.valueToLogType,
// });
this.$emit("inputButtonClicked", this.valueToEmit);
}, },
// pushClickEventToGA(value) { // pushClickEventToGA(value) {
// const lastFocusedInputGroup = // const lastFocusedInputGroup =
// this.$store.getters.gaClickInformation.lastFocusedInputGroup; // this.$store.getters.gaClickInformation.lastFocusedInputGroup;
// // this.dispatchStoreAction( // // this.disA MKJ `,,.patchStoreAction(
// // gaStoreActions.UPDATE_FIRED_GA_CLICK_EVENT_VALUES, // // gaStoreActions.UPDATE_FIRED_GA_CLICK_EVENT_VALUES,
// // { // // {
// // groupName: lastFocusedInputGroup, // // groupName: lastFocusedInputGroup,
@ -213,7 +223,7 @@ export default {
// // this.groupName, // // this.groupName,
// // false // // false
// // ); // // );
// if ( // if (
// firedGaClickEventValues[this.groupName] != // firedGaClickEventValues[this.groupName] !=
// currentlySelectedValues[this.groupName] // currentlySelectedValues[this.groupName]
@ -221,7 +231,7 @@ export default {
// this.pushClickEventToGA( // this.pushClickEventToGA(
// currentlySelectedValues[this.groupName] // currentlySelectedValues[this.groupName]
// ); // );
// } // }
// } // }
// }, // },
}, },
@ -229,18 +239,19 @@ export default {
isChecked() { isChecked() {
if (this.isMultiSelect && this.modelValue instanceof Array) { if (this.isMultiSelect && this.modelValue instanceof Array) {
return this.modelValue.includes(this.value); return this.modelValue.includes(this.value);
} else if (!this.isMultiSelect) {
return this.modelValue == this.value;
} else {
return false;
} }
return this.modelValue == this.value;
}, },
inputType() { inputType() {
return this.isMultiSelect ? "checkbox" : "radio"; return this.isMultiSelect ? "checkbox" : "radio";
}, },
buttonId() { buttonId() {
return `${this.groupName}-${JSON.stringify(this.value)?.replace( return `${this.groupName?.replace(" ", "-")}-${this.value
" ", .toString()
"-" ?.replace(" ", "-")}`;
)}`;
}, },
}, },
setup(props) { setup(props) {

View file

@ -54,7 +54,7 @@
:validationRules="validationRules" :validationRules="validationRules"
:textPosition="textPosition" :textPosition="textPosition"
:selectOnKeypress="selectOnKeypress" :selectOnKeypress="selectOnKeypress"
@buttonClicked="handleAnswerChange" /> @inputButtonClicked="handleAnswerChange" />
<!-- For nested questions --> <!-- For nested questions -->
<transition name="fade" mode="out-in"> <transition name="fade" mode="out-in">
<div <div
@ -84,6 +84,7 @@ import listCard from "@/ux-components/list-card/list-card";
import radio from "@/ux-components/radio/radio"; import radio from "@/ux-components/radio/radio";
import { ErrorMessage } from "vee-validate"; import { ErrorMessage } from "vee-validate";
import { queryStrings } from "@/constants/query-strings"; import { queryStrings } from "@/constants/query-strings";
import eventBus from "../../helpers/event-bus/event-bus";
export default { export default {
name: "buttonQuestion", name: "buttonQuestion",
@ -229,26 +230,29 @@ export default {
handleAnswerChange(primaryAnswerValue) { handleAnswerChange(primaryAnswerValue) {
this.primaryValue = primaryAnswerValue; this.primaryValue = primaryAnswerValue;
this.pushClickEventToGA(null, primaryAnswerValue); this.pushClickEventToGA(primaryAnswerValue);
this.$emit("buttonQuestionChange", primaryAnswerValue); this.$emit("buttonQuestionChange", primaryAnswerValue);
this.$emit("update:modelValue", primaryAnswerValue); this.$emit("update:modelValue", primaryAnswerValue);
}, },
pushClickEventToGA(e, value) { pushClickEventToGA() {
const valueToPushToGa = // const valueToPushToGa =
value?.toString() ?? this.primaryValue?.toString(); // value?.toString() ?? this.primaryValue?.toString();
const event = eventBus.readAndPopEventFromBus("GA", "click")
const valueToPushToGa = event.label;
console.log({ console.log({
valueToPushToGa, valueToPushToGa,
lastPushedGaEventValue: this.lastPushedGaEventValue, lastPushedGaEventValue: this.lastPushedGaEventValue,
}); });
if (valueToPushToGa !== this.lastPushedGaEventValue) { if (valueToPushToGa && valueToPushToGa !== this.lastPushedGaEventValue) {
this.lastPushedGaEventValue = valueToPushToGa; this.lastPushedGaEventValue = valueToPushToGa;
this.pushEventToGA( this.pushEventToGA(
this.$route.query[queryStrings.FMG_PAGE], event.category,
this.GaActions.CLICKED, event.action,
valueToPushToGa, valueToPushToGa,
true, event.pushToLogType,
this.valueToLogType event.valueToLogType
); );
} }
}, },

View file

@ -38,9 +38,14 @@ export default {
this.$emit("change", e); this.$emit("change", e);
}, },
}, },
watch: { computed: {
selectedValue(selectedValue) { selectedValue: {
this.$emit("update:modelValue", selectedValue); get: () => {
}, return this.modelValue;
},
set: function(newValue) {
this.$emit("update:modelValue", newValue);
}
}
}, },
}; };

View file

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

View file

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

View file

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

View file

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

View file

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