Skip to content
This repository has been archived by the owner on Oct 31, 2024. It is now read-only.

[Draft]feat(theme): add bottom nav, refactor use theme composables #292

Merged
merged 9 commits into from
Jan 22, 2020
56 changes: 8 additions & 48 deletions packages/composables/__tests__/useCartSidebar.spec.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,24 @@
import Vue from "vue";
import VueCompositionApi, {
computed,
ref,
Ref,
reactive
} from "@vue/composition-api";
import VueCompositionApi from "@vue/composition-api";
Vue.use(VueCompositionApi);

import { useCartSidebar, setStore } from "@shopware-pwa/composables";
import { useCartSidebar } from "@shopware-pwa/composables";

describe("Composables - useCartSidebar", () => {
const stateCartSidebarOpen: Ref<boolean> = ref(false);
beforeEach(() => {
// mock vuex store
jest.resetAllMocks();
stateCartSidebarOpen.value = false;
setStore({
getters: reactive({
getIsCartSidebarOpen: computed(() => stateCartSidebarOpen.value)
}),
commit: (name: string, value: any) => {
stateCartSidebarOpen.value = value;
}
});
});
describe("computed", () => {
describe("isOpen", () => {
describe("isSidebarOpen", () => {
it("should be false when not set", () => {
const { isOpen } = useCartSidebar();
expect(isOpen.value).toBeFalsy();
});

it("should be true if is in store", () => {
stateCartSidebarOpen.value = true;
const { isOpen } = useCartSidebar();
expect(isOpen.value).toBeTruthy();
const { isSidebarOpen } = useCartSidebar();
expect(isSidebarOpen.value).toBeFalsy();
});
});
});

describe("methods", () => {
describe("toggle", () => {
it("should status change to true after first toggle", () => {
const { isOpen, toggle } = useCartSidebar();
toggle();
expect(isOpen.value).toBeTruthy();
});

it("should cart sidebar state toggle from false to true", () => {
stateCartSidebarOpen.value = false;
const { isOpen, toggle } = useCartSidebar();
toggle();
expect(isOpen.value).toBeTruthy();
});

it("should cart sidebar state toggle from true to false", () => {
stateCartSidebarOpen.value = true;
const { isOpen, toggle } = useCartSidebar();
toggle();
expect(isOpen.value).toBeFalsy();
const { isSidebarOpen, toggleSidebar } = useCartSidebar();
toggleSidebar();
expect(isSidebarOpen.value).toBeTruthy();
});
});
});
Expand Down
53 changes: 53 additions & 0 deletions packages/composables/__tests__/useNavigation.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import Vue from "vue";
import VueCompositionApi from "@vue/composition-api";
Vue.use(VueCompositionApi);
import { useNavigation } from "@shopware-pwa/composables";
import * as shopwareClient from "@shopware-pwa/shopware-6-client";
jest.mock("@shopware-pwa/shopware-6-client");
const mockedGetPage = shopwareClient as jest.Mocked<typeof shopwareClient>;

describe("Composables - useNavigation", () => {
describe("computed", () => {
describe("routeNames", () => {
it("should get null when routeNames are not fetched", () => {
const { routeNames } = useNavigation();
expect(routeNames.value).toBe(null);
});
});
});

describe("methods", () => {
describe("fetchRouteNames", () => {
it("should routeNames set to null when navigation data are not fetched", async () => {
mockedGetPage.getNavigation.mockResolvedValueOnce({} as any);
const { routeNames, fetchRouteNames } = useNavigation();
await fetchRouteNames();
expect(routeNames.value).toBe(null);
});
it("should fetch routeNames correcly", async () => {
mockedGetPage.getNavigation.mockResolvedValueOnce({
count: 3,
children: [{ name: "test1" }, { name: "test2" }, { name: "test3" }]
} as any);
const { routeNames, fetchRouteNames } = useNavigation();
await fetchRouteNames();
expect(routeNames.value).toEqual(["test1", "test2", "test3"]);
});
});

describe("convertToSlug", () => {
it("should return empty string when nothing provided in prams", () => {
const { convertToSlug } = useNavigation();
expect(convertToSlug()).toEqual("");
});
it("should convert string without space to slug correcly", () => {
const { convertToSlug } = useNavigation();
expect(convertToSlug("test")).toEqual("/test/");
});
it("should convert string with spaces to slug correcly", () => {
const { convertToSlug } = useNavigation();
expect(convertToSlug("test test")).toEqual("/test-test/");
});
});
});
});
25 changes: 25 additions & 0 deletions packages/composables/__tests__/useUserLoginModal.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import Vue from "vue";
import VueCompositionApi from "@vue/composition-api";
Vue.use(VueCompositionApi);
import { useUserLoginModal } from "@shopware-pwa/composables";

describe("Composables - useUserLoginModal", () => {
describe("computed", () => {
describe("isModalOpen", () => {
it("should be false when not set", () => {
const { isModalOpen } = useUserLoginModal();
expect(isModalOpen.value).toBeFalsy();
});
});
});

describe("methods", () => {
describe("toggle", () => {
it("should status change to true after first toggle", () => {
const { isModalOpen, toggleModal } = useUserLoginModal();
toggleModal();
expect(isModalOpen.value).toBeTruthy();
});
});
});
});
33 changes: 33 additions & 0 deletions packages/composables/src/hooks/useNavigation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Vue from "vue";
import { reactive, computed } from "@vue/composition-api";
import { getNavigation } from "@shopware-pwa/shopware-6-client";

const sharedNavigation = Vue.observable({
routeNames: null
} as any);
export const useNavigation = (): any => {
const localNavigation = reactive(sharedNavigation);
const routeNames = computed(() => localNavigation.routeNames);

const fetchRouteNames = async (params?: any): Promise<void> => {
const navigation = await getNavigation(params);
if (typeof navigation.children === "undefined") return;
sharedNavigation.routeNames = navigation.children.map(
(element: { name: string }) => element.name
);
};

const convertToSlug = (name: string): string => {
if (typeof name !== "string") {
return "";
}
const slug = name.replace(" ", "-");
return `\/${slug.toLowerCase()}\/`;
};

return {
routeNames,
fetchRouteNames,
convertToSlug
};
};
2 changes: 2 additions & 0 deletions packages/composables/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ export * from "./hooks/useProduct";
export * from "./hooks/useCart";
export * from "./logic/useAddToCart";
export * from "./hooks/useCategoryFilters";
export * from "./hooks/useNavigation";
export * from "./hooks/useUser";
export * from "./hooks/useProductListing";
export * from "./theme/cart/useCartSidebar";
export * from "./theme/user/useUserLoginModal";

/**
* Workaround for current reactivity problems with SSR for Nuxt.
Expand Down
20 changes: 12 additions & 8 deletions packages/composables/src/theme/cart/useCartSidebar.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { computed } from "@vue/composition-api";
import { getStore } from "../..";
import Vue from "vue";
import { computed, reactive } from "@vue/composition-api";

const sharedCartSidebarState = Vue.observable({
open: false
} as any);

/**
* @alpha
*/
export const useCartSidebar = (): any => {
let vuexStore = getStore();
const isOpen = computed(() => vuexStore.getters.getIsCartSidebarOpen);
const localCartSidebarState = reactive(sharedCartSidebarState);
const isSidebarOpen = computed(() => localCartSidebarState.open);

function toggle() {
vuexStore.commit("SET_CART_SIDEBAR_IS_OPEN", !isOpen.value);
function toggleSidebar() {
sharedCartSidebarState.open = !sharedCartSidebarState.open;
}

return {
isOpen,
toggle
isSidebarOpen,
toggleSidebar
};
};
20 changes: 20 additions & 0 deletions packages/composables/src/theme/user/useUserLoginModal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import Vue from "vue";
import { computed, reactive } from "@vue/composition-api";

const sharedUserLoginModalState = Vue.observable({
open: false
} as any);

export const useUserLoginModal = (): any => {
const localUserLoginModal = reactive(sharedUserLoginModalState);
const isModalOpen = computed(() => localUserLoginModal.open);

const toggleModal = () => {
localUserLoginModal.open = !localUserLoginModal.open;
};

return {
isModalOpen,
toggleModal
};
};
77 changes: 77 additions & 0 deletions packages/default-theme/components/SwBottomNavigation.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<template>
<div class="sw-bottom-navigation">
<SfBottomNavigation :style="{'z-index': isSidebarOpen ? 0: 1}">
<nuxt-link to="/">
<SfBottomNavigationItem>
<SfIcon icon="home" size="20px" />
</SfBottomNavigationItem>
</nuxt-link>
<SfBottomNavigationItem class="menu-button">
<SfIcon icon="menu" size="20px" style="width: 25px" />
<SfSelect class="menu-button__select">
<SfSelectOption v-for="routeName in routeNames" :key="routeName">
<SfProductOption @click="changeRoute(convertToSlug(routeName))">{{routeName}}</SfProductOption>
</SfSelectOption>
</SfSelect>
</SfBottomNavigationItem>
<SfBottomNavigationItem>
<SfIcon icon="profile" @click="toggleModal" size="20px" />
</SfBottomNavigationItem>
<SfBottomNavigationItem>
<SfCircleIcon
class="sf-bottom-navigation__floating-icon"
@click="toggleSidebar"
>
<SfIcon icon="add_to_cart" size="20px" color="white" />
</SfCircleIcon>
</SfBottomNavigationItem>
</SfBottomNavigation>

</div>
</template>

<script>
import { SfBottomNavigation, SfCircleIcon, SfIcon, SfSelect } from '@storefront-ui/vue'
import { useCartSidebar, useUserLoginModal } from '@shopware-pwa/composables'
import { useNavigation } from '@shopware-pwa/composables'
export default {
name: 'SwBottomNavigation',
components: { SfBottomNavigation, SfIcon, SfCircleIcon, SfSelect },
data() {
return {
navigationElements: []
}
},
setup() {
const { toggleSidebar, isSidebarOpen } = useCartSidebar()
const { routeNames, convertToSlug } = useNavigation()
const { toggleModal } = useUserLoginModal()
return {
routeNames,
isSidebarOpen,
convertToSlug,
toggleSidebar,
toggleModal
}
},
methods: {
changeRoute(name) {
this.$router.push(name)
},
}
}
</script>
<style lang="scss" scoped>
.menu-button {
position: relative;
&__select {
position: absolute !important;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
}
}
</style>
Loading