diff --git a/src/common-components/site-footer/site-footer.spec.js b/src/common-components/site-footer/site-footer.spec.js
new file mode 100644
index 00000000..8a4d8a69
--- /dev/null
+++ b/src/common-components/site-footer/site-footer.spec.js
@@ -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),
+ },
+};
diff --git a/src/common-components/site-footer/site-footer.vue b/src/common-components/site-footer/site-footer.vue
new file mode 100644
index 00000000..2a611968
--- /dev/null
+++ b/src/common-components/site-footer/site-footer.vue
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
diff --git a/src/ux-components/button-main/button-main.spec.js b/src/ux-components/button-main/button-main.spec.js
new file mode 100644
index 00000000..9fa8b10e
--- /dev/null
+++ b/src/ux-components/button-main/button-main.spec.js
@@ -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;
+}
diff --git a/src/ux-components/button-main/button-main.vue b/src/ux-components/button-main/button-main.vue
new file mode 100644
index 00000000..29234e1c
--- /dev/null
+++ b/src/ux-components/button-main/button-main.vue
@@ -0,0 +1,140 @@
+
+
+
+
+
+
+