CASH-399 - Convert Technician Notes to Dropdown/Accordion
This commit is contained in:
parent
001a3e0e86
commit
6a7ffac6d8
5 changed files with 251 additions and 65 deletions
|
|
@ -1,11 +1,6 @@
|
|||
<template>
|
||||
<div class="textarea-question">
|
||||
<div class="label-wrapper d-flex flex-column mb-1" :aria-label="questionText">
|
||||
<!-- Wrap label and span because v-html prevents v-if from displaying if v-if <span> is inside <label>-->
|
||||
<div class="d-flex mb-1">
|
||||
<label :for="textAreaLabelCopy" class="fw-bold" v-html="questionText"></label>
|
||||
<span v-if="!isRequired" class="fw-normal ms-1">(Optional)</span>
|
||||
</div>
|
||||
<div class="label-wrapper d-flex flex-column mb-1 mt-1" :aria-label="questionText">
|
||||
<textarea
|
||||
:id="textAreaLabelCopy"
|
||||
ref="textarea"
|
||||
|
|
@ -44,8 +39,8 @@ export default {
|
|||
default: "text area",
|
||||
},
|
||||
},
|
||||
// TODO: At some point in the future we should probably add the tie in to validation here in case the field must be populated for some other use cases
|
||||
setup() {},
|
||||
// TODO: At some point in the future we should probably add the tie in to validation here in case the field must be populated for some other use cases
|
||||
setup() {},
|
||||
computed: {
|
||||
questionText() {
|
||||
return this.getCmsContent(this.cmsWidgetName, "QuestionText");
|
||||
|
|
|
|||
|
|
@ -1,53 +1,82 @@
|
|||
// Components
|
||||
import customerDetails from "@/layouts/customer-details/customer-details.vue";
|
||||
import { shallowMount, mount } from "@vue/test-utils";
|
||||
import CustomerDetails from "./customer-details.vue";
|
||||
import TechNotes from "./tech-notes/tech-notes.vue";
|
||||
import TextboxQuestion from "@/digital-components/textbox-question/textbox-question.vue";
|
||||
import PhoneNumberQuestion from "@/digital-components/phone-number-question/phone-number-question.vue";
|
||||
import CheckboxQuestion from "@/digital-components/checkbox-question/checkbox-question.vue";
|
||||
import TextBlock from "@/digital-components/text-block/text-block.vue";
|
||||
import FunnelHeader from "@/fmg-components/funnel-header/funnel-header.vue";
|
||||
import FunnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header.vue";
|
||||
import Navbar from "@/fmg-components/nav-bar/nav-bar.vue";
|
||||
|
||||
// Supporting Files
|
||||
import { shallowMount } from "@vue/test-utils";
|
||||
import { getMountOptions } from "@/helpers/unit-test-helper.js";
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent() {
|
||||
return "Mocked CMS Content";
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Mock our module for promises.
|
||||
jest.mock("@/helpers/layout-helper.js", () => ({
|
||||
settleAllPromises: jest.fn(),
|
||||
}));
|
||||
const mockStoreActions = {
|
||||
SAVE_CUSTOMER_DETAILS: "SAVE_CUSTOMER_DETAILS",
|
||||
SAVE_SERVICE_LOCATION_TECH_NOTES: "SAVE_SERVICE_LOCATION_TECH_NOTES",
|
||||
SAVE_PAYMENT_METHOD_CHOICE: "SAVE_PAYMENT_METHOD_CHOICE",
|
||||
}
|
||||
|
||||
describe("customer-details.vue", () => {
|
||||
describe("navigation", () => {
|
||||
test("if the back button is clicked, navigate back", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks();
|
||||
describe("CustomerDetails.vue", () => {
|
||||
let wrapper;
|
||||
|
||||
// Act
|
||||
await wrapper.vm.backButtonAction();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.$router.navigateWithoutSaving).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
test("if the continue button is clicked, navigate forward", async () => {
|
||||
// Arrange
|
||||
const { wrapper } = setupMocks();
|
||||
|
||||
// Act
|
||||
await wrapper.vm.forwardButtonAction();
|
||||
|
||||
// Assert
|
||||
expect(wrapper.vm.$router.navigateWithSaving).toHaveBeenCalled();
|
||||
beforeEach(() => {
|
||||
wrapper = mount(CustomerDetails, {
|
||||
global: {
|
||||
components: {
|
||||
TechNotes,
|
||||
TextboxQuestion,
|
||||
PhoneNumberQuestion,
|
||||
CheckboxQuestion,
|
||||
TextBlock,
|
||||
FunnelHeader,
|
||||
FunnelSubHeader,
|
||||
Navbar,
|
||||
},
|
||||
mixins: [mockMixin],
|
||||
mocks: {
|
||||
storeActions: mockStoreActions,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
firstName: "John",
|
||||
lastName: "Doe",
|
||||
emailAddress: "john.doe@example.com",
|
||||
phoneNumber: "1234567890",
|
||||
isSmsOptIn: true,
|
||||
techNotes: "Initial Tech Notes",
|
||||
};
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("Should render the CustomerDetails component", () => {
|
||||
expect(wrapper.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("Should render all child components", () => {
|
||||
expect(wrapper.findComponent(TechNotes).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(TextboxQuestion).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(PhoneNumberQuestion).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(CheckboxQuestion).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(TextBlock).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(FunnelHeader).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(FunnelSubHeader).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(Navbar).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("Should pass the correct props to TechNotes", () => {
|
||||
const techNotes = wrapper.findComponent(TechNotes);
|
||||
expect(techNotes.props("modelValue")).toBe("Initial Tech Notes");
|
||||
expect(techNotes.props("textAreaLabelCopy")).toBe("Mocked CMS Content");
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
function setupMocks() {
|
||||
const wrapper = shallowMount(
|
||||
customerDetails,
|
||||
getMountOptions({
|
||||
router: {
|
||||
navigate: jest.fn(),
|
||||
navigate: jest.fn(),
|
||||
navigateWithSaving: jest.fn(),
|
||||
navigateWithoutSaving: jest.fn(),
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
return { wrapper };
|
||||
}
|
||||
|
|
|
|||
|
|
@ -47,13 +47,7 @@
|
|||
cmsWidgetName="TextMeQuestionWidget"
|
||||
v-model="isSmsOptIn" />
|
||||
|
||||
<textareaQuestion
|
||||
class="mb-4"
|
||||
v-model="techNotes"
|
||||
cmsWidgetName="TextAreaContentWidget"
|
||||
textAreaLabelCopy="Notes for your technician"
|
||||
maxLength="150" />
|
||||
|
||||
<techNotes v-model="techNotes" :textAreaLabelCopy="textAreaLabelCopy" />
|
||||
<textBlock cmsWidgetName="DisclaimerCopyWidget" typeStyle="caption" />
|
||||
|
||||
<navbar
|
||||
|
|
@ -72,20 +66,18 @@
|
|||
import funnelHeader from "@/fmg-components/funnel-header/funnel-header";
|
||||
import navbar from "@/fmg-components/nav-bar/nav-bar";
|
||||
import funnelSubHeader from "@/fmg-components/funnel-sub-header/funnel-sub-header";
|
||||
import textareaQuestion from "@/digital-components/textarea-question/textarea-question";
|
||||
import textboxQuestion from "@/digital-components/textbox-question/textbox-question";
|
||||
import phoneNumberQuestion from "@/digital-components/phone-number-question/phone-number-question";
|
||||
import textBlock from "@/digital-components/text-block/text-block";
|
||||
import checkboxQuestion from "@/digital-components/checkbox-question/checkbox-question";
|
||||
import techNotes from "@/layouts/customer-details/tech-notes/tech-notes";
|
||||
//Supporting files
|
||||
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
|
||||
import { settleAllPromises } from "@/helpers/layout-helper";
|
||||
import { storeActions } from "@/constants/store-actions";
|
||||
import { errorMessages } from "@/constants/error-messages";
|
||||
import { routerParams } from "@/router/router-constants/router-params";
|
||||
import { required, regex } from "@/helpers/validation-rules";
|
||||
import { Form, defineRule } from "vee-validate";
|
||||
import { useField, validate } from "vee-validate";
|
||||
import store from "@/store";
|
||||
import { paymentMethods } from "@/constants/payment-method-constants";
|
||||
|
||||
|
|
@ -199,13 +191,13 @@ export default {
|
|||
components: {
|
||||
funnelHeader,
|
||||
funnelSubHeader,
|
||||
textareaQuestion,
|
||||
textboxQuestion,
|
||||
navbar,
|
||||
Form,
|
||||
textBlock,
|
||||
phoneNumberQuestion,
|
||||
checkboxQuestion,
|
||||
techNotes,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
76
src/layouts/customer-details/tech-notes/tech-notes.spec.js
Normal file
76
src/layouts/customer-details/tech-notes/tech-notes.spec.js
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
import { shallowMount, mount } from "@vue/test-utils";
|
||||
import TechNotes from "./tech-notes.vue";
|
||||
import TextareaQuestion from "@/digital-components/textarea-question/textarea-question.vue";
|
||||
|
||||
const mockMixin = {
|
||||
methods: {
|
||||
getCmsContent() {
|
||||
return "Mocked CMS Content";
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe("TechNotes.vue", () => {
|
||||
it("Should render TechNotes", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(TechNotes, {
|
||||
props: {
|
||||
modelValue: "Initial Tech Notes",
|
||||
textAreaLabelCopy: "Notes for your technician",
|
||||
},
|
||||
global: {
|
||||
mixins: [mockMixin],
|
||||
},
|
||||
});
|
||||
|
||||
// Act
|
||||
const techNotes = wrapper.findComponent({ ref: "techNotes" });
|
||||
|
||||
// Assert
|
||||
expect(techNotes.exists()).toBe(true);
|
||||
});
|
||||
|
||||
it("Should populate the techNotes prop and render it correctly", async () => {
|
||||
// Arrange
|
||||
const wrapper = mount(TechNotes, {
|
||||
props: {
|
||||
modelValue: "Populated Tech Notes",
|
||||
textAreaLabelCopy: "Notes for your technician",
|
||||
},
|
||||
global: {
|
||||
components: {
|
||||
TextareaQuestion,
|
||||
},
|
||||
mixins: [mockMixin],
|
||||
},
|
||||
});
|
||||
|
||||
// Act
|
||||
const textarea = wrapper.find("textarea");
|
||||
|
||||
// Assert
|
||||
expect(textarea.element.value).toBe("Populated Tech Notes");
|
||||
});
|
||||
|
||||
it("Should toggle the active class when clicked", async () => {
|
||||
// Arrange
|
||||
const wrapper = shallowMount(TechNotes, {
|
||||
props: {
|
||||
modelValue: "Initial Tech Notes",
|
||||
textAreaLabelCopy: "Notes for your technician",
|
||||
},
|
||||
global: {
|
||||
mixins: [mockMixin],
|
||||
},
|
||||
});
|
||||
|
||||
const toggleElement = wrapper.find(".tech-notes-toggle");
|
||||
|
||||
// Act & Assert
|
||||
expect(wrapper.vm.isActive).toBe(false); // inactive
|
||||
await toggleElement.trigger("click");
|
||||
expect(wrapper.vm.isActive).toBe(true); // Active after first click
|
||||
await toggleElement.trigger("click");
|
||||
expect(wrapper.vm.isActive).toBe(false); // Inactive after second click
|
||||
});
|
||||
});
|
||||
94
src/layouts/customer-details/tech-notes/tech-notes.vue
Normal file
94
src/layouts/customer-details/tech-notes/tech-notes.vue
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
<template>
|
||||
<div class="tech-notes">
|
||||
<div class="tech-notes-toggle" :class="{ active: isActive }" @click="toggleClass">
|
||||
<textLink linkType="text" href="#!" :text="textAreaLabelCopy" />
|
||||
</div>
|
||||
<div class="tech-notes-content">
|
||||
<textareaQuestion
|
||||
ref="techNotes"
|
||||
class="mb-4"
|
||||
v-model="localTechNotes"
|
||||
cmsWidgetName="TextAreaContentWidget"
|
||||
:textAreaLabelCopy="textAreaLabelCopy"
|
||||
maxLength="150" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import textLink from "@/ux-components/text-link/text-link";
|
||||
import textareaQuestion from "@/digital-components/textarea-question/textarea-question";
|
||||
|
||||
export default {
|
||||
name: "techNotes",
|
||||
components: {
|
||||
textLink,
|
||||
textareaQuestion,
|
||||
},
|
||||
props: {
|
||||
modelValue: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
textAreaLabelCopy: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
localTechNotes: {
|
||||
get() {
|
||||
return this.modelValue;
|
||||
},
|
||||
set(value) {
|
||||
this.$emit("update:modelValue", value);
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isActive: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toggleClass() {
|
||||
this.isActive = !this.isActive;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.tech-notes {
|
||||
.tech-notes-toggle {
|
||||
cursor: pointer;
|
||||
&:after {
|
||||
content: "";
|
||||
transition: all 0.5s ease;
|
||||
background-image: url("data:image/svg+xml;charset=UTF-8,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 8.9' xml:space='preserve'%3e%3cpath d='M8 8.9c-.2 0-.5-.1-.6-.3L.3 1.5C.1 1.4 0 1.1 0 .9 0 .7.1.4.3.3.4.1.7 0 .9 0c.2 0 .5.1.6.3L8 6.7 14.5.2c.1-.1.4-.2.6-.2.2 0 .5.1.6.3s.3.4.3.6c0 .2-.1.5-.3.6L8.6 8.6c-.1.2-.4.3-.6.3z' fill='%231474a2'/%3e%3c/svg%3e");
|
||||
background-repeat: no-repeat;
|
||||
background-position: right center;
|
||||
margin-left: 0.5rem;
|
||||
width: 16px;
|
||||
height: 9px;
|
||||
display: inline-flex;
|
||||
}
|
||||
&.active:after {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
&.active + .tech-notes-content {
|
||||
max-height: 500px;
|
||||
transition: all 250ms ease-in;
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
}
|
||||
.tech-notes-content {
|
||||
max-height: 0;
|
||||
transition: all 250ms ease-out;
|
||||
overflow: hidden;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in a new issue