added modal vue and started test page

This commit is contained in:
Jason Wheeler 2023-03-31 10:51:30 -04:00
parent b1bc933665
commit b1ac5130e8
12 changed files with 1043 additions and 90 deletions

View file

@ -1,68 +1,255 @@
jest.mock("vee-validate", () => ({
useForm: jest.fn(),
}));
const mockValidate = (returnValue) => jest.fn(async () => Promise.resolve({ valid: returnValue }));
const mockMeta = (returnValue) => jest.fn(async () => Promise.resolve(returnValue));
import { shallowMount } from "@vue/test-utils";
import Modal from "./modal";
import modal from "./modal";
import crypto from "crypto";
import { useForm } from "vee-validate";
import { Modal } from "bootstrap";
const footerButtonText = "Sample footer text here.";
const headerText = "Sample header text here.";
global.crypto = crypto;
describe("modal.vue", () => {
it("Should display header text when HeaderText is defined in the CMS", async () => {
// Act
const wrapper = shallowMount(Modal, {
mixins: [mockMixin],
it("Should display modal header text when headerText is defined", async () => {
// Arrange / Act
const fakeMeta = {
touched: true,
dirty: true,
valid: true,
validated: true,
};
useForm.mockReturnValue({
meta: mockMeta(fakeMeta),
validate: mockValidate(true),
});
const wrapper = shallowMount(modal, {
props: {
cmsWidgetName: "test",
headerText: headerText,
footerButtonText: footerButtonText,
},
attachTo: document.body,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["HeaderText"]));
// Assert
expect(wrapper.html()).toEqual(expect.stringContaining(headerText));
});
it("Should display subheader text when SubheaderText is defined in the CMS", async () => {
// Act
const wrapper = shallowMount(Modal, {
mixins: [mockMixin],
it("Should display footer button text when footerButtonText is defined", async () => {
// Arrange / Act
const fakeMeta = {
touched: true,
dirty: true,
valid: true,
validated: true,
};
useForm.mockReturnValue({
meta: mockMeta(fakeMeta),
validate: mockValidate(true),
});
const wrapper = shallowMount(modal, {
props: {
cmsWidgetName: "test",
footerButtonText: footerButtonText,
},
attachTo: document.body,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["SubheaderText"]));
// Assert
expect(wrapper.html()).toEqual(expect.stringContaining(footerButtonText));
});
it("Should insert image url when Image is defined in the CMS", async () => {
// Act
const wrapper = shallowMount(Modal, {
mixins: [mockMixin],
it("Should emit 'footer-button-event' if the form is valid", async () => {
// Arrange
const fakeMeta = {
touched: true,
dirty: true,
valid: true,
validated: true,
};
useForm.mockReturnValue({
meta: mockMeta(fakeMeta),
validate: mockValidate(true),
});
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
cmsWidgetName: "test",
footerButtonText: footerButtonText,
headerText: headerText,
},
attachTo: document.body,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["Image"]));
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Act
const buttonMain = wrapper.findComponent({ ref: "modalButtonMain" });
await buttonMain.trigger("click-event");
// Assert
expect(wrapper.emitted("footer-button-event")).toBeTruthy();
});
it("Should display body text when BodyText is defined in the CMS", async () => {
// Act
const wrapper = shallowMount(Modal, {
mixins: [mockMixin],
it("Should not emit 'footer-button-event' if the form is invalid", async () => {
// Arrange
const fakeMeta = {
touched: true,
dirty: true,
valid: false,
validated: true,
};
useForm.mockReturnValue({
meta: mockMeta(fakeMeta),
validate: mockValidate(false),
});
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
cmsWidgetName: "test",
footerButtonText: footerButtonText,
headerText: headerText,
},
attachTo: document.body,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["BodyText"]));
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Act
const buttonMain = wrapper.findComponent({ ref: "modalButtonMain" });
await buttonMain.trigger("click-event");
// Assert
expect(wrapper.emitted("footer-button-event")).toBeFalsy();
});
it("Should have a disabled footer button when the form has not been touched", async () => {
// Arrange / Act
const fakeMeta = {
touched: true,
dirty: true,
valid: true,
validated: true,
};
useForm.mockReturnValue({
meta: mockMeta(fakeMeta),
validate: mockValidate(true),
});
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
footerButtonText: footerButtonText,
headerText: headerText,
},
attachTo: document.body,
});
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Assert
expect(wrapper.vm.isFooterButtonDisabled).toBe(true);
});
it("Should have a disabled footer button when the form is invalid", async () => {
// Arrange / Act
const fakeMeta = {
touched: true,
dirty: true,
valid: true,
validated: true,
};
useForm.mockReturnValue({
meta: mockMeta(fakeMeta),
validate: mockValidate(true),
});
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
footerButtonText: footerButtonText,
headerText: headerText,
},
attachTo: document.body,
});
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Assert
expect(wrapper.vm.isFooterButtonDisabled).toBe(true);
});
it("Should call bootstrap Modal method 'show' when calling 'openModal'", async () => {
// Arrange
const fakeMeta = {
touched: true,
dirty: true,
valid: true,
validated: true,
};
useForm.mockReturnValue({
meta: mockMeta(fakeMeta),
validate: mockValidate(true),
});
const showMock = jest.spyOn(Modal.prototype, "show");
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
footerButtonText: footerButtonText,
headerText: headerText,
},
attachTo: document.body,
});
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Act
wrapper.vm.openModal();
// Assert
expect(showMock).toHaveBeenCalled();
});
it("Should call bootstrap Modal method 'hide' when calling 'closeModal'", async () => {
// Arrange
const fakeMeta = {
touched: true,
dirty: true,
valid: true,
validated: true,
};
useForm.mockReturnValue({
meta: mockMeta(fakeMeta),
validate: mockValidate(true),
});
const hideMock = jest.spyOn(Modal.prototype, "hide");
const resetButtonStyle = jest.fn();
const wrapper = shallowMount(modal, {
props: {
footerButtonText: footerButtonText,
headerText: headerText,
},
attachTo: document.body,
});
wrapper.vm.resetButtonStyle = resetButtonStyle;
// Act
wrapper.vm.openModal();
wrapper.vm.closeModal();
// Assert
expect(hideMock).toHaveBeenCalled();
});
});
const mockMixin = {
methods: {
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
return mockCmsContent[cmsFieldName];
}),
},
};
const mockCmsContent = {
HeaderText: "Sample header text here.",
SubheaderText: "Sample subheader text here.",
Image: "https://www.sampleImage.sample",
BodyText: "Sample body text here.",
FooterText: "Sample footer text here.",
};

View file

@ -2,39 +2,38 @@
<!-- Modal -->
<div
class="modal fade modal-component"
v-on="{ 'hidden.bs.modal': resetButtonStyle }"
:id="cmsWidgetName"
v-on="{ 'hidden.bs.modal': onModalClosed, 'shown.bs.modal': onModalOpened }"
:id="modalId"
tabindex="-1"
aria-labelledby="ModalComponentLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<div class="modal-header mb-2 mt-2">
<label
v-if="headerText"
class="modal-title d-flex justify-content-center pb-0 w-100">
{{ headerText }}
</label>
<button
ref="closeButton"
type="button"
class="btn-close"
data-bs-dismiss="modal"
@mousedown="closeModal"
aria-label="Close"></button>
</div>
<div class="modal-body ps-4 pe-4 pt-5 pb-4">
<img :src="ModalImage" class="mw-100 d-flex mx-auto mb-4" alt="" />
<h5 class="mb-4" v-html="ModalHeadline"></h5>
<p class="fw-bold mb-2 subheader-text" v-html="ModalSubheadertext"></p>
<p class="mb-0" v-html="ModalBodyText"></p>
<p
class="my-4 caption modal-sub-body"
v-if="ModalSubBodyText"
v-html="ModalSubBodyText"></p>
<div class="modal-body ps-5 pe-5 pb-4 pt-0">
<slot></slot>
</div>
<div class="modal-footer px-5 py-4">
<buttonMain
<modalButtonMain
isPrimary
class="w-100"
ref="buttonMain"
suppressLoader
:buttonText="ModalCloseButtonText"
@click-event="buttonClick"
data-bs-dismiss="modal" />
ref="modalButtonMain"
loaderColor="white"
:buttonText="footerButtonText"
@click-event="validateAndEmit"
:class="isFooterButtonDisabled && 'form-test-invalid'" />
</div>
</div>
</div>
@ -42,40 +41,72 @@
</template>
<script>
import buttonMain from "@/ux-components/button-main/button-main";
import modalButtonMain from "@/ux-components/modal-button-main/modal-button-main";
import { Modal } from "bootstrap";
import { useForm } from "vee-validate";
export default {
name: "modal",
props: {
cmsWidgetName: String,
headerText: String,
footerButtonText: String,
onModalOpenedCallback: {
type: Function,
},
onModalClosedCallback: {
type: Function,
},
},
computed: {
ModalHeadline() {
return this.getCmsContent(this.cmsWidgetName, "HeaderText");
},
ModalSubheadertext() {
return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
},
ModalBodyText() {
return this.getCmsContent(this.cmsWidgetName, "BodyText");
},
ModalSubBodyText() {
return this.getCmsContent(this.cmsWidgetName, "BodyText2");
},
ModalImage() {
return this.getCmsContent(this.cmsWidgetName, "Image");
},
ModalCloseButtonText() {
return this.getCmsContent(this.cmsWidgetName, "FooterText");
},
setup() {
const modalId = `modal-${crypto.randomUUID()}`;
const { meta, validate, resetForm } = useForm();
return {
modalId,
meta,
validate,
resetForm,
};
},
methods: {
async validateAndEmit() {
const validationResult = await this.validate();
if (validationResult.valid) {
this.$emit("footer-button-event");
} else {
this.resetButtonStyle();
}
},
resetButtonStyle() {
this.$refs.buttonMain.resetButtonStyle();
this.$refs.modalButtonMain.resetButtonStyle();
},
onModalOpened() {
this.onModalOpenedCallback?.();
},
onModalClosed() {
this.resetButtonStyle();
this.onModalClosedCallback?.();
},
openModal() {
const modal = Modal.getOrCreateInstance(document.getElementById(this.modalId));
modal.show();
},
closeModal() {
const modal = Modal.getInstance(document.getElementById(this.modalId));
modal.hide();
},
},
computed: {
isFooterButtonDisabled() {
if (!this.meta.touched) {
return !this.meta.valid;
}
return !this.meta.dirty || !this.meta.valid;
},
},
components: {
buttonMain,
modalButtonMain,
},
};
</script>
@ -92,9 +123,17 @@ export default {
.modal-header {
border-bottom: none;
.btn-close {
position: absolute;
right: 1rem;
top: 1rem;
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.6874 1.82179L9.50889 8L15.6874 14.1782C16.1042 14.595 16.1042 15.2707 15.6874 15.6875C15.4843 15.8907 15.2146 16 14.9328 16C14.6514 16 14.3818 15.8906 14.1787 15.6875L8.00017 9.50934L1.82171 15.6875C1.6182 15.8907 1.34876 16 1.06708 16C0.785733 16 0.516056 15.8906 0.312965 15.6875C-0.10429 15.2706 -0.10429 14.5952 0.312797 14.1784L6.03276 8.45867L6.49146 8L0.312945 1.82177C-0.10429 1.40483 -0.10429 0.729418 0.312797 0.31262C0.728936 -0.104145 1.40489 -0.104051 1.82185 0.31262L7.54152 6.03202L8.00017 6.49065L14.1786 0.312472C14.5955 -0.104107 15.2707 -0.104201 15.6874 0.312452C16.1042 0.729289 16.1042 1.40496 15.6874 1.82179Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
opacity: 1;
}
.modal-title {
font-weight: 500;
color: rgb(0, 0, 0);
}
}
&.modal-component {
.modal-dialog {
@ -141,4 +180,4 @@ body {
}
}
}
</style>
</style>

View file

@ -0,0 +1,144 @@
<template>
<!-- Modal -->
<div
class="modal fade modal-component"
v-on="{ 'hidden.bs.modal': resetButtonStyle }"
:id="cmsWidgetName"
tabindex="-1"
aria-labelledby="ModalComponentLabel"
aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<button
type="button"
class="btn-close"
data-bs-dismiss="modal"
aria-label="Close"></button>
</div>
<div class="modal-body ps-4 pe-4 pt-5 pb-4">
<img :src="ModalImage" class="mw-100 d-flex mx-auto mb-4" alt="" />
<h5 class="mb-4" v-html="ModalHeadline"></h5>
<p class="fw-bold mb-2 subheader-text" v-html="ModalSubheadertext"></p>
<p class="mb-0" v-html="ModalBodyText"></p>
<p
class="my-4 caption modal-sub-body"
v-if="ModalSubBodyText"
v-html="ModalSubBodyText"></p>
</div>
<div class="modal-footer px-5 py-4">
<buttonMain
isPrimary
class="w-100"
ref="buttonMain"
suppressLoader
:buttonText="ModalCloseButtonText"
@click-event="buttonClick"
data-bs-dismiss="modal" />
</div>
</div>
</div>
</div>
</template>
<script>
import buttonMain from "@/ux-components/button-main/button-main";
export default {
name: "modal",
props: {
cmsWidgetName: String,
},
computed: {
ModalHeadline() {
return this.getCmsContent(this.cmsWidgetName, "HeaderText");
},
ModalSubheadertext() {
return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
},
ModalBodyText() {
return this.getCmsContent(this.cmsWidgetName, "BodyText");
},
ModalSubBodyText() {
return this.getCmsContent(this.cmsWidgetName, "BodyText2");
},
ModalImage() {
return this.getCmsContent(this.cmsWidgetName, "Image");
},
ModalCloseButtonText() {
return this.getCmsContent(this.cmsWidgetName, "FooterText");
},
},
methods: {
resetButtonStyle() {
this.$refs.buttonMain.resetButtonStyle();
},
},
components: {
buttonMain,
},
};
</script>
<style lang="scss">
.modal {
top: auto;
bottom: 0;
h5,
strong,
.subheader-text {
color: $black;
}
.modal-header {
border-bottom: none;
.btn-close {
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M15.6874 1.82179L9.50889 8L15.6874 14.1782C16.1042 14.595 16.1042 15.2707 15.6874 15.6875C15.4843 15.8907 15.2146 16 14.9328 16C14.6514 16 14.3818 15.8906 14.1787 15.6875L8.00017 9.50934L1.82171 15.6875C1.6182 15.8907 1.34876 16 1.06708 16C0.785733 16 0.516056 15.8906 0.312965 15.6875C-0.10429 15.2706 -0.10429 14.5952 0.312797 14.1784L6.03276 8.45867L6.49146 8L0.312945 1.82177C-0.10429 1.40483 -0.10429 0.729418 0.312797 0.31262C0.728936 -0.104145 1.40489 -0.104051 1.82185 0.31262L7.54152 6.03202L8.00017 6.49065L14.1786 0.312472C14.5955 -0.104107 15.2707 -0.104201 15.6874 0.312452C16.1042 0.729289 16.1042 1.40496 15.6874 1.82179Z' fill='%231574A1'/%3E%3C/svg%3E%0A");
opacity: 1;
}
}
&.modal-component {
.modal-dialog {
max-width: 576px;
margin: 0 auto;
.modal-content {
margin: 0 auto;
box-shadow: 0px 16px 48px -16px rgba(0, 0, 0, 0.25);
border-radius: 1.5rem 1.5rem 0 0;
.modal-body {
.modal-sub-body {
color: $gray-600;
}
ul {
margin-bottom: 0;
}
p {
&:last-child {
margin-bottom: 0;
}
}
}
}
&.modal-dialog-centered {
align-items: flex-end;
min-height: 100%;
}
}
.modal-footer {
border-top: none;
background-color: $gray-100;
box-shadow: 0px -1px 0px rgba(179, 180, 181, 0.3);
button {
margin: 0;
}
}
}
}
body {
.modal-backdrop {
height: 100%;
&.show {
opacity: 0.4;
}
}
}
</style>

View file

@ -48,6 +48,8 @@ export function getMountOptions(mockData) {
mocks.queryStrings = queryStrings;
mocks.$router = mockData?.router;
mocks.$route = mockData?.route;
mocks.GaActions = GaActions;
mocks.pushEventToGA = jest.fn();
mocks.$loadScript = mockData?.loadScript;
mocks.prependActionToMethod = jest.fn();

View file

@ -0,0 +1,67 @@
import { mount } from "@vue/test-utils";
import contentGroupModal from "./content-group-modal";
import crypto from "crypto";
global.crypto = crypto;
describe("content-group-modal.vue", () => {
it("Should display header text when HeaderText is defined in the CMS", async () => {
const wrapper = mount(contentGroupModal, {
mixins: [mockMixin],
props: {
cmsWidgetName: "test",
},
attachTo: document.body,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["HeaderText"]));
});
it("Should display subheader text when SubheaderText is defined in the CMS", async () => {
const wrapper = mount(contentGroupModal, {
mixins: [mockMixin],
props: {
cmsWidgetName: "test",
},
attachTo: document.body,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["SubheaderText"]));
});
it("Should insert image url when Image is defined in the CMS", async () => {
const wrapper = mount(contentGroupModal, {
mixins: [mockMixin],
props: {
cmsWidgetName: "test",
},
attachTo: document.body,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["Image"]));
});
it("Should display body text when BodyText is defined in the CMS", async () => {
const wrapper = mount(contentGroupModal, {
mixins: [mockMixin],
props: {
cmsWidgetName: "test",
},
attachTo: document.body,
});
expect(wrapper.html()).toEqual(expect.stringContaining(mockCmsContent["BodyText"]));
});
});
const mockMixin = {
methods: {
getCmsContent: jest.fn((widgetName, cmsFieldName) => {
return mockCmsContent[cmsFieldName];
}),
},
};
const mockCmsContent = {
HeaderText: "Sample header text here.",
SubheaderText: "Sample subheader text here.",
Image: "https://www.sampleImage.sample",
BodyText: "Sample body text here.",
FooterText: "Sample footer text here.",
};

View file

@ -0,0 +1,86 @@
<template>
<modal
:ref="ModalName"
:modalId="ModalName"
:footerButtonText="ModalCloseButtonText"
@footer-button-event="footerButtonClick">
<img :src="ModalImage" class="mw-100 d-flex mx-auto mb-4" alt="" />
<h5 class="mb-4" v-html="ModalHeadline"></h5>
<p class="fw-bold mb-2 subheader-text" v-html="ModalSubheadertext"></p>
<p class="mb-0" v-html="ModalBodyText"></p>
<p
class="my-4 caption modal-sub-body"
v-if="ModalSubBodyText"
v-html="ModalSubBodyText"></p>
</modal>
</template>
<script>
import modal from "@/digital-components/modal/modal";
export default {
name: "content-group-modal",
props: {
cmsWidgetName: String,
},
computed: {
ModalName() {
return this.cmsWidgetName;
},
ModalHeadline() {
return this.getCmsContent(this.cmsWidgetName, "HeaderText");
},
ModalSubheadertext() {
return this.getCmsContent(this.cmsWidgetName, "SubheaderText");
},
ModalBodyText() {
return this.getCmsContent(this.cmsWidgetName, "BodyText");
},
ModalSubBodyText() {
return this.getCmsContent(this.cmsWidgetName, "BodyText2");
},
ModalImage() {
return this.getCmsContent(this.cmsWidgetName, "Image");
},
ModalCloseButtonText() {
return this.getCmsContent(this.cmsWidgetName, "FooterText");
},
},
methods: {
openModal() {
this.$refs[this.ModalName].openModal();
},
footerButtonClick() {
this.$refs[this.ModalName].closeModal();
},
},
components: {
modal,
},
};
</script>
<style lang="scss">
.modal {
&.modal-component {
.modal-dialog {
.modal-content {
.modal-body {
.modal-sub-body {
color: $gray-600;
}
ul {
margin-bottom: 0;
}
p {
&:last-child {
margin-bottom: 0;
}
}
}
}
}
}
}
</style>

View file

@ -146,11 +146,11 @@
break;
case "returnurl":
this.mainStore.iisConfig.returnURL = value;
this.mainStore.issConfig.returnURL = value;
break;
case "returnurl2":
this.mainStore.iisConfig.returnURL2 = value;
this.mainStore.issConfig.returnURL2 = value;
break;
// Not stored

View file

@ -350,7 +350,7 @@ function setupMocks({
],
route: {
query: {
fmgPage: "part-questions",
issPage: "part-questions",
},
},
data() {

View file

@ -0,0 +1,87 @@
<template>
<Form @submit="onSubmit" @invalid-submit="onInvalidSubmit" ref="theForm" v-slot="{ meta }" >
<div class="page-container-grouped-styles welcome">
<siteHeader cmsWidgetName="SiteHeaderWidget"/>
<div class="fade-on-route-transition overflow-scroll container">
<div class="row mt-4 position-sticky top-100" id="welcomeFooter">
<div class="col">
<p @click="() => this.openModal('TestModal')" class="small">
here
</p>
<contentGroupModal cmsWidgetName="TestModal"
:ref="TestModal"
/>
<siteFooter
cmsWidgetName="SiteFooterWidget"
:isForwardActionDisabled="!meta.valid"
@back-clicked="backButtonAction"
@ForwardClicked="forwardButtonAction" />
</div>
</div>
</div>
</div>
</Form>
</template>
<script>
import siteHeader from '@/iss-components/site-header/site-header';
import siteFooter from "@/iss-components/site-footer/site-footer";
import contentGroupModal from "@/iss-components/content-group-modal/content-group-modal"
import { fetchCmsContentForPage } from "@/helpers/cms-content-helper";
import { settleAllPromises } from "@/helpers/layout-helper";
import { Form, defineRule } from "vee-validate";
import { required, regex } from "@/helpers/validation-rules";
import { errorMessages } from "@/constants/error-messages";
import BaseFormMixin from '@/mixins/base-form-mixin.js';
import { useMainStore } from '@/store';
export default {
name: "z-test-page-jg",
mixins: [BaseFormMixin],
method: {
openModal(modalName) {
console.log("here")
this.$refs[modalName].openModal();
},
},
computed: {
bodyText() {
return this.getCmsContent("ModalLink", "BodyText");
},
},
async beforeRouteEnter(to, from, next)
{
// Call APIs
const cmsContentPromise = fetchCmsContentForPage(to.query.issPage);
// Settle promises and get results
const promiseResultMap = [
{
resultKey: "cmsContent",
promise: cmsContentPromise,
},
];
//use resultMap to populate layout content.
let resultMap = await settleAllPromises(promiseResultMap);
next((vm) => {
vm.setCmsContent(resultMap.cmsContent);
});
},
components: {
siteHeader,
siteFooter,
Form,
contentGroupModal
},
}
</script>

View file

@ -11,7 +11,7 @@ describe("list-button.vue", () => {
mockData: {
global: {
mocks: {
$route: { query: { fmgPage: "page-name" } },
$route: { query: { issPage: "page-name" } },
GaActions: GaActions,
pushEventToGA: jest.fn(),
},
@ -37,7 +37,7 @@ describe("list-button.vue", () => {
mockData: {
global: {
mocks: {
$route: { query: { fmgPage: "page-name" } },
$route: { query: { issPage: "page-name" } },
GaActions: GaActions,
pushEventToGA: jest.fn(),
},
@ -64,7 +64,7 @@ describe("list-button.vue", () => {
mockData: {
global: {
mocks: {
$route: { query: { fmgPage: "page-name" } },
$route: { query: { issPage: "page-name" } },
GaActions: GaActions,
pushEventToGA: jest.fn(),
},

View file

@ -0,0 +1,188 @@
import { shallowMount } from "@vue/test-utils";
import modalButtonMain from "./modal-button-main";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { nextTick } from "vue";
describe("modal-button-main.vue", () => {
it("Should return btn-primary class", () => {
// Arrange/Act
const wrapper = shallowMount(
modalButtonMain,
setupMocks({
propsData: {
isPrimary: true,
},
})
);
const button = wrapper.find("button");
// Assert
expect(button.attributes("class")).toContain("btn-primary");
});
it("Should return aria-disabled state", () => {
// Arrange/Act
const wrapper = shallowMount(
modalButtonMain,
setupMocks({
propsData: {
isDisabled: true,
},
})
);
const button = wrapper.find("button");
// Assert
expect(button.attributes()["aria-disabled"]).toEqual("true");
});
it("Should return loader color", async () => {
// Arrange
const wrapper = shallowMount(
modalButtonMain,
setupMocks({
propsData: {
loaderColor: "blue",
loaderEnabled: true,
},
})
);
// Act
wrapper.vm.clicked();
await nextTick();
// Assert
const loader = wrapper.find("loader-stub");
expect(loader.attributes("class")).toContain("blue");
});
it("Should return loader position", async () => {
// Arrange
const wrapper = shallowMount(
modalButtonMain,
setupMocks({
propsData: {
loaderPosition: "right",
loaderEnabled: true,
},
})
);
// Act
wrapper.vm.clicked();
await nextTick();
// Assert
const loader = wrapper.find("loader-stub");
expect(loader.attributes("class")).toContain("right");
});
it("Should set 'isLoaderDisplayed' to false when calling 'removeLoader'", async () => {
// Arrange
const wrapper = shallowMount(
modalButtonMain,
setupMocks({
propsData: {
loaderPosition: "right",
loaderEnabled: true,
},
})
);
wrapper.setData({
isLoaderDisplayed: true,
});
// Act
wrapper.vm.removeLoader();
await nextTick();
// Assert
const loader = wrapper.find("loader-stub");
expect(wrapper.vm.isLoaderDisplayed).toBe(false);
});
it("Should set 'isLoaderDisplayed' to false when calling 'resetButtonStyle'", async () => {
// Arrange
const wrapper = shallowMount(
modalButtonMain,
setupMocks({
propsData: {
loaderPosition: "right",
loaderEnabled: true,
},
})
);
wrapper.setData({
isLoaderDisplayed: true,
});
// Act
wrapper.vm.resetButtonStyle();
await nextTick();
// Assert
expect(wrapper.vm.isLoaderDisplayed).toBe(false);
});
it("Should emit 'click-event' event when clicking if the button is enabled", async () => {
// Arrange
const wrapper = shallowMount(
modalButtonMain,
setupMocks({
propsData: {
loaderPosition: "right",
loaderEnabled: true,
isDisabled: false,
},
})
);
const buttonElement = wrapper.find("button");
// Act
buttonElement.trigger("click");
await nextTick();
// Assert
expect(wrapper.emitted("click-event")).toBeTruthy();
});
it("Should not emit 'click-event' event when clicking if the button is disabled", async () => {
// Arrange
const wrapper = shallowMount(
modalButtonMain,
setupMocks({
propsData: {
loaderPosition: "right",
loaderEnabled: true,
isDisabled: true,
},
})
);
const buttonElement = wrapper.find("button");
// Act
buttonElement.trigger("click");
await nextTick();
// Assert
expect(wrapper.emitted("click-event")).toBeFalsy();
});
});
function setupMocks(mountOptionsMockData = {}) {
const defaultMountOptions = { route: { query: { issPage: "page-name" } } };
const baseMountOptions = getMountOptions(
Object.assign(defaultMountOptions, mountOptionsMockData)
);
const allMountOptions = Object.assign(defaultMountOptions, baseMountOptions);
return allMountOptions;
}

View file

@ -0,0 +1,153 @@
<template>
<button
type="button"
:aria-disabled="isDisabled"
class="btn d-flex align-items-center justify-content-center py-3 px-4 delay"
: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 && !suppressLoader"
v-bind:class="[this.loaderColor, this.loaderPosition]" />
</button>
</template>
<script>
import loader from "@/ux-components/loader/loader";
export default {
name: "modalButtonMain",
props: {
isPrimary: Boolean,
buttonText: String,
isDisabled: Boolean,
loaderColor: String,
loaderPosition: String,
isFloat: Boolean,
suppressLoader: Boolean,
},
data() {
return {
isLoaderDisplayed: false,
};
},
methods: {
removeLoader() {
this.isLoaderDisplayed = false;
},
clicked() {
this.pushEventToGA(
this.$route.query[this.queryStrings.ISS_PAGE],
this.GaActions.CLICKED,
this.buttonText,
true
);
if (!this.isDisabled) {
this.isLoaderDisplayed = true;
this.$emit("click-event");
}
},
resetButtonStyle() {
this.isLoaderDisplayed = false;
},
},
components: {
loader,
},
};
</script>
<style lang="scss">
.btn {
&.btn-primary {
position: relative;
background: linear-gradient(270deg, $blue 0%, $blue-800 100%);
border: none;
border-radius: $border-radius-lg;
color: $white;
justify-content: center;
font-weight: 500;
@media (hover: hover) {
background: linear-gradient(270deg, $blue 0%, $blue-800 100%);
}
&:focus {
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
}
&:focus, // Mouse, touch, stylus focus
&:focus-visible {
// Keyboard focus for accessibility
outline: none;
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
color: $white;
background: linear-gradient(270deg, rgba(6, 87, 124, 1) 0%, rgba(6, 87, 124, 1) 100%);
}
&:disabled {
background: $gray-200 !important;
background: linear-gradient(270deg, $gray-200 0%, $gray-200 100%) !important;
color: $gray-600 !important;
font-weight: 400;
height: 48px;
border: none;
border-radius: $border-radius-lg;
cursor: pointer;
pointer-events: all;
}
&.has-loader {
color: $white;
background: $blue-700;
box-shadow: 0 0 0 3px, 0 0 0 5.5px $blue-700;
pointer-events: none;
}
&.delay {
// fixes flicker while transitioning between states
transition: background 0s 0s ease-in-out;
}
}
&.btn-secondary {
position: relative;
background: transparent;
border: 1px solid $blue;
border-radius: $border-radius-lg;
color: $blue;
font-weight: 500;
transition: all 150ms linear;
height: 3rem;
&:hover {
color: $white;
@include blue-gradient;
}
&:focus, // Mouse, touch, stylus focus
&:focus-visible {
// Keyboard focus for accessibility
outline: none;
box-shadow: 0 0 0 3px $white, 0 0 0 5.5px $blue-700;
color: $white;
@include blue-gradient;
}
&:disabled {
background: transparent;
color: $gray-550 !important;
font-weight: 400;
height: 48px;
border: 1px solid $gray-550;
border-radius: $border-radius-lg;
cursor: pointer;
pointer-events: all;
}
&.has-loader {
color: $white;
@include blue-gradient;
pointer-events: none;
}
&.delay {
// fixes flicker while transitioning between states
transition: background 0s 0s ease-in-out;
}
}
}
</style>