Merge branch 'develop' into feature/Digital/SSR-117

This commit is contained in:
Jason Wheeler 2022-11-09 12:16:06 -05:00
commit 51ef43177c
6 changed files with 559 additions and 0 deletions

View file

@ -0,0 +1,42 @@
import { mount } from "@vue/test-utils";
import siteFooter from "./site-footer";
describe("site-footer.vue", () => {
it("Should emit ForwardClicked on button click", async () => {
// Act
const wrapper = mount(siteFooter, {
mixins: [mockMixin],
});
wrapper.vm.buttonClick();
// Assert
expect(wrapper.emitted()["ForwardClicked"][0]).toHaveBeenCalled;
});
it("Should emit BackClicked on link click", async () => {
// Act
const wrapper = mount(siteFooter, {
mixins: [mockMixin],
});
wrapper.vm.linkClick();
// Assert
expect(wrapper.emitted()["BackClicked"][0]).toHaveBeenCalled;
});
it("Should change button text when update button text is called", async () => {
// Act
const wrapper = mount(siteFooter, {
mixins: [mockMixin],
});
wrapper.vm.updateButtonText("newText");
// Assert
expect(wrapper.componentVM.customButtontext).toBe("newText");
});
});
const mockMixin = {
methods: {
getCmsContent: jest.fn(),
getFooterInfoBoxHeight: jest.fn(() => 80),
},
};

View file

@ -0,0 +1,132 @@
<template>
<div class="row" :style="`padding-bottom: ${paddingHeight}px`"></div>
<footer class="footer container-fluid fixed-bottom g-5 bg-light py-4" id="infoBox">
<div class="row d-flex flex-row-reverse align-items-center vw-100">
<div class="col button-col d-flex" id="stacked">
<buttonMain
ref="buttonMain"
isPrimary
:buttonText="buttonText"
loaderColor="white"
:class="isForwardActionDisabled && 'form-test-invalid'"
:aria-disabled="isForwardActionDisabled"
:isDisabled="isForwardActionDisabled"
@click-event="buttonClick"
data-bs-target="#footerModal"
data-bs-dismiss="modal" />
</div>
<div v-if="!isBackButtonHidden" class="col-auto link-col py-1 text-break">
<textLink
linkType="navigation"
:text="backLink"
@click-event="linkClick"
href="javascript:void(0)"
data-bs-target="#footerModal"
data-bs-dismiss="modal" />
</div>
</div>
</footer>
</template>
<script>
import textLink from "@/ux-components/text-link/text-link";
import buttonMain from "@/ux-components/button-main/button-main";
export default {
name: "funnelFooter",
props: {
isForwardActionDisabled: Boolean,
isBackButtonHidden: { type: Boolean, default: false },
cmsWidgetName: String,
},
components: {
textLink,
buttonMain,
},
data() {
return {
paddingHeight: 0,
customButtontext: "",
};
},
mounted() {
this.paddingHeight = this.getFooterInfoBoxHeight() + 24;
this.$nextTick(() => {
window.addEventListener("resize", this.onResize);
});
},
beforeUnmount() {
window.removeEventListener("resize", this.onResize);
},
unmounted() {
document.onkeydown = null;
},
computed: {
backLink() {
return this.getCmsContent(this.cmsWidgetName, "BackButtonText");
},
buttonText() {
return this.customButtontext
? this.customButtontext
: this.getCmsContent(this.cmsWidgetName, "ForwardButtonText");
},
},
methods: {
onResize() {
this.paddingHeight = this.getFooterInfoBoxHeight();
},
updateButtonText(newText) {
this.customButtontext = newText;
},
removeLoader() {
this.$refs.buttonMain.removeLoader();
document.onkeydown = function (e) {
return true;
};
},
buttonClick() {
//prevent keyboard input after button click
document.onkeydown = function (e) {
return false;
};
this.$emit("ForwardClicked");
},
linkClick() {
this.$emit("BackClicked");
},
},
};
</script>
<style lang="scss" scoped>
.footer {
display: flex;
a {
display: flex;
justify-content: center;
}
.col,
.col-auto,
.col button {
width: 100%;
}
@media only screen and (min-width: 340px) {
.col,
.col-auto,
.col button {
width: auto;
justify-content: flex-end;
}
.btn-primary {
width: auto;
}
a {
display: flex;
justify-content: flex-start;
}
}
& > .container-fluid {
overflow-x: visible; // needed to fix hidden footer on some iphones
}
}
</style>

View file

@ -0,0 +1,102 @@
import { shallowMount } from "@vue/test-utils";
import buttonMain from "./button-main";
import { getMountOptions } from "@/helpers/unit-test-helper.js";
import { nextTick } from "vue";
describe("buttonMain.vue", () => {
it("Should return btn-primary class", async () => {
// Act
const wrapper = shallowMount(
buttonMain,
setupMocks({
propsData: {
isPrimary: true,
},
})
);
// Assert
const button = wrapper.find("button");
// Expect
expect(button.attributes("class")).toContain("btn-primary");
});
it("Should return aria-disabled state", async () => {
// Act
const wrapper = shallowMount(
buttonMain,
setupMocks({
propsData: {
isDisabled: true,
},
})
);
// Assert
const button = wrapper.find("button");
// Expect
expect(button.attributes()["aria-disabled"]).toEqual("true");
});
it("Should return loader color", async () => {
// Act
const wrapper = shallowMount(
buttonMain,
setupMocks({
propsData: {
loaderColor: "blue",
loaderEnabled: true,
},
})
);
// Assert
const label = wrapper.find("label");
wrapper.vm.clicked();
await nextTick();
const loader = wrapper.find("loader-stub");
expect(loader.attributes("class")).toContain("blue");
});
it("Should return loader position", async () => {
// Act
const wrapper = shallowMount(
buttonMain,
setupMocks({
propsData: {
loaderPosition: "right",
loaderEnabled: true,
},
})
);
// Assert
const label = wrapper.find("label");
wrapper.vm.clicked();
await nextTick();
const loader = wrapper.find("loader-stub");
expect(loader.attributes("class")).toContain("right");
});
});
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,140 @@
<template>
<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">
<span class="m-0">{{ this.buttonText }}</span>
<loader
class="ms-2"
v-if="isLoaderDisplayed"
v-bind:class="[this.loaderColor, this.loaderPosition]" />
</button>
</template>
<script>
import loader from "@/ux-components/loader/loader";
export default {
name: "buttonMain",
props: {
isPrimary: Boolean,
buttonText: String,
isDisabled: Boolean,
loaderColor: String,
loaderPosition: String,
isFloat: Boolean,
},
data() {
return {
isLoaderDisplayed: false,
};
},
methods: {
removeLoader() {
this.isLoaderDisplayed = false;
},
clicked() {
if (!this.isDisabled) {
this.isLoaderDisplayed = true;
this.$emit("click-event");
}
},
},
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;
}
&.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;
}
&.delay {
// fixes flicker while transitioning between states
transition: background 0s 0s ease-in-out;
}
}
}
</style>

View file

@ -0,0 +1,78 @@
import { shallowMount } from "@vue/test-utils";
import checkbox from "./checkbox";
import { nextTick } from "vue";
describe("checkbox.vue", () => {
it("Should return checkbox name", async () => {
// Act
const wrapper = shallowMount(checkbox, {
propsData: {
checkboxName: "Checkbox",
},
});
// Assert
const input = wrapper.find("input");
// Expect
expect(input.attributes().name).toEqual("Checkbox");
});
it("Should return checkbox id", async () => {
// Act
const wrapper = shallowMount(checkbox, {
propsData: {
buttonID: "Checkbox ID",
},
});
// Assert
const input = wrapper.find("input");
// Expect
expect(input.attributes().id).toEqual("Checkbox ID");
});
it("Should return tabindex value", async () => {
// Act
const wrapper = shallowMount(checkbox, {
propsData: {
tabIndex: "1",
},
});
// Assert
const input = wrapper.find("input");
// Expect
expect(input.attributes().tabindex).toEqual("1");
});
it("Should return label text", async () => {
// Act
const wrapper = shallowMount(checkbox, {
propsData: {
checkboxLabel: "label text",
},
});
// Assert
const paragraph = wrapper.find("p");
expect(paragraph.text()).toEqual("label text");
});
it("Should return label text", async () => {
// Act
const wrapper = shallowMount(checkbox, {
propsData: {
screenReaderOnlyText: "screenreader text",
},
});
// Assert
const paragraph = wrapper.find("span");
expect(paragraph.text()).toEqual("screenreader text");
});
});

View file

@ -0,0 +1,65 @@
<template>
<!-- Checkbox groups MUST be wrapped in a <fieldset> and <legend> tag -->
<div class="form-check ui-checkbox" :class="[hasError ? 'has-error' : '']">
<input
class="form-check-input"
type="checkbox"
aria-checked="false"
:name="checkboxName"
:id="buttonID"
:tabindex="tabIndex"
:aria-required="isRequired" />
<label class="d-flex align-items-start" :for="buttonID">
<p v-if="checkboxLabel" class="m-0">{{ checkboxLabel }}</p>
<span v-if="screenReaderOnlyText" class="sr-only">{{ screenReaderOnlyText }}</span>
</label>
</div>
</template>
<script>
export default {
name: "checkbox",
props: {
checkboxName: String,
buttonID: String,
tabIndex: Number,
checkboxLabel: String,
screenReaderOnlyText: String,
isRequired: Boolean,
hasError: Boolean,
},
};
</script>
<style lang="scss" scoped>
.form-check {
.form-check-input {
border: 1px solid $gray-500;
border-radius: 2px;
&:checked {
background-size: 125%;
border: 1px solid $blue;
+ label {
p {
font-weight: 500;
font-size: 0.875rem;
color: $black;
}
}
}
&:focus {
box-shadow: 0 0 0 2.5px $blue;
}
}
&:hover {
.form-check-input {
box-shadow: 0 0 0 4px $blue-300;
}
}
p {
font-weight: 400;
font-size: 0.875rem;
color: $gray-600;
}
}
</style>