From 4d4cc78a1892a0ab7ffa3f35d5499588863feb07 Mon Sep 17 00:00:00 2001 From: mkucmus Date: Wed, 25 Nov 2020 13:47:28 +0100 Subject: [PATCH] feat(default-theme): show wish-list items qty in badge of header icon (#1253) --- api/composables.api.md | 2 + .../api/composables.iusewishlist.count.md | 14 +++ .../resources/api/composables.iusewishlist.md | 1 + .../composables/__tests__/useWishlist.spec.ts | 119 ++++++++++++++++++ packages/composables/src/logic/useWishlist.ts | 29 +++-- .../src/components/SwHeaderIcons.vue | 12 +- packages/default-theme/src/locales/en-GB.json | 7 +- 7 files changed, 169 insertions(+), 15 deletions(-) create mode 100644 docs/landing/resources/api/composables.iusewishlist.count.md create mode 100644 packages/composables/__tests__/useWishlist.spec.ts diff --git a/api/composables.api.md b/api/composables.api.md index c89a885f9..40c9c792c 100644 --- a/api/composables.api.md +++ b/api/composables.api.md @@ -395,6 +395,8 @@ export interface IUseWishlist { // (undocumented) clearWishlist: () => void; // (undocumented) + count: Ref; + // (undocumented) isInWishlist: Ref; // (undocumented) items: Ref; diff --git a/docs/landing/resources/api/composables.iusewishlist.count.md b/docs/landing/resources/api/composables.iusewishlist.count.md new file mode 100644 index 000000000..d0dca9768 --- /dev/null +++ b/docs/landing/resources/api/composables.iusewishlist.count.md @@ -0,0 +1,14 @@ + + +[Home](./index.md) > [@shopware-pwa/composables](./composables.md) > [IUseWishlist](./composables.iusewishlist.md) > [count](./composables.iusewishlist.count.md) + +## IUseWishlist.count property + +> This API is provided as a preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. +> + +Signature: + +```typescript +count: Ref; +``` diff --git a/docs/landing/resources/api/composables.iusewishlist.md b/docs/landing/resources/api/composables.iusewishlist.md index a66b954d2..cfeb2062a 100644 --- a/docs/landing/resources/api/composables.iusewishlist.md +++ b/docs/landing/resources/api/composables.iusewishlist.md @@ -21,6 +21,7 @@ export interface IUseWishlist | --- | --- | --- | | [addToWishlist](./composables.iusewishlist.addtowishlist.md) | () => void | (BETA) | | [clearWishlist](./composables.iusewishlist.clearwishlist.md) | () => void | (BETA) | +| [count](./composables.iusewishlist.count.md) | Ref<number> | (BETA) | | [isInWishlist](./composables.iusewishlist.isinwishlist.md) | Ref<boolean> | (BETA) | | [items](./composables.iusewishlist.items.md) | Ref<string\[\]> | (BETA) | | [removeFromWishlist](./composables.iusewishlist.removefromwishlist.md) | (id: string) => void | (BETA) | diff --git a/packages/composables/__tests__/useWishlist.spec.ts b/packages/composables/__tests__/useWishlist.spec.ts new file mode 100644 index 000000000..6d0243ecb --- /dev/null +++ b/packages/composables/__tests__/useWishlist.spec.ts @@ -0,0 +1,119 @@ +import Vue from "vue"; + +// Mock Vue Composition API onMounted method +import VueCompositionApi, * as vueComp from "@vue/composition-api"; +(vueComp.onMounted as any) = jest.fn(); +Vue.use(VueCompositionApi); +import { useWishlist } from "@shopware-pwa/composables"; + +describe("Composables - useWishlist", () => { + const rootContextMock: any = { + $shopwareApiInstance: jest.fn(), + }; + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe("computed", () => { + it("should return an empty array on frist useWishlist usage", () => { + const { items, count } = useWishlist(rootContextMock); + expect(items.value).toHaveLength(0); + expect(count.value).toBe(0); + }); + it("should return false if the provided product does not exist or isn't in wishlist yet", () => { + const { isInWishlist } = useWishlist(rootContextMock); + expect(isInWishlist.value).toBe(false); + }); + }); + describe("methods", () => { + const product = { + id: "some-id", + }; + + describe("addToWishlist", () => { + it("should add to wishlist current product if id exists", () => { + const { addToWishlist, items, isInWishlist } = useWishlist( + rootContextMock, + product as any + ); + addToWishlist(); + + expect(items.value[0]).toBe("some-id"); + expect(isInWishlist.value).toBe(true); + }); + it("should not add to wishlist current product if id does not exist or product is falsy", () => { + const { addToWishlist, isInWishlist } = useWishlist( + rootContextMock, + undefined as any + ); + addToWishlist(); + + expect(isInWishlist.value).toBe(false); + }); + }); + describe("removeFromWishlist", () => { + it("should remove an item if it's included", () => { + const { addToWishlist, isInWishlist, removeFromWishlist } = useWishlist( + rootContextMock, + product as any + ); + addToWishlist(); + + expect(isInWishlist.value).toBe(true); + removeFromWishlist(product.id); + expect(isInWishlist.value).toBe(false); + }); + it("should remove an item without providing its id directly", () => { + const { addToWishlist, isInWishlist, removeFromWishlist } = useWishlist( + rootContextMock, + product as any + ); + addToWishlist(); + + expect(isInWishlist.value).toBe(true); + removeFromWishlist(undefined as any); + expect(isInWishlist.value).toBe(false); + }); + it("should not do anything when there is no product id", () => { + const { addToWishlist, isInWishlist, removeFromWishlist } = useWishlist( + rootContextMock, + {} as any + ); + addToWishlist(); + + removeFromWishlist(undefined as any); + expect(isInWishlist.value).toBe(false); + }); + it("should remove an item without providing its id directly", () => { + const { addToWishlist, isInWishlist, removeFromWishlist } = useWishlist( + rootContextMock, + product as any + ); + addToWishlist(); + + expect(isInWishlist.value).toBe(true); + removeFromWishlist(undefined as any); + expect(isInWishlist.value).toBe(false); + }); + }); + describe("clearWishlist", () => { + it("should remove all items from wishlist", () => { + const { addToWishlist, items, clearWishlist } = useWishlist( + rootContextMock, + product as any + ); + addToWishlist(); + + expect(items.value).toHaveLength(1); + clearWishlist(); + expect(items.value).toHaveLength(0); + }); + }); + describe("onMounted", () => { + it("should invoke onMounted callback on composable init", () => { + useWishlist(rootContextMock); + expect(vueComp.onMounted).toBeCalled(); + }); + }); + }); +}); diff --git a/packages/composables/src/logic/useWishlist.ts b/packages/composables/src/logic/useWishlist.ts index bbb718ed2..f28d2592c 100644 --- a/packages/composables/src/logic/useWishlist.ts +++ b/packages/composables/src/logic/useWishlist.ts @@ -1,5 +1,5 @@ import Vue from "vue"; -import { ref, Ref, reactive, computed } from "@vue/composition-api"; +import { ref, Ref, reactive, computed, onMounted } from "@vue/composition-api"; import { Product } from "@shopware-pwa/commons/interfaces/models/content/product/Product"; import { ApplicationVueContext, getApplicationContext } from "../appContext"; @@ -14,6 +14,7 @@ export interface IUseWishlist { addToWishlist: () => void; isInWishlist: Ref; items: Ref; + count: Ref; } const sharedWishlist = Vue.observable({ @@ -39,23 +40,25 @@ export const useWishlist = ( JSON.stringify(sharedWishlist.items) ); }; - + /* istanbul ignore next */ const getFromStorage = () => { if (typeof window != "undefined" && localStorage) { - return JSON.parse(localStorage.getItem("sw-wishlist-items") ?? ""); + return JSON.parse(localStorage.getItem("sw-wishlist-items") ?? "[]"); } }; - - if (!sharedWishlist.items.length) { - try { - const currentWishlist = getFromStorage(); - if (currentWishlist) { - sharedWishlist.items = currentWishlist; + /* istanbul ignore next */ + onMounted(() => { + if (!sharedWishlist.items.length) { + try { + const currentWishlist = getFromStorage(); + if (Array.isArray(currentWishlist) && currentWishlist.length) { + sharedWishlist.items = currentWishlist; + } + } catch (error) { + console.error("useWishlist:getFromStorage", error); } - } catch (error) { - console.log(error); } - } + }); // removes item from the list const removeFromWishlist = (itemId: string): void => { @@ -94,6 +97,7 @@ export const useWishlist = ( }; const items = computed(() => localWishlist.items); + const count = computed(() => items.value.length); return { addToWishlist, @@ -101,5 +105,6 @@ export const useWishlist = ( isInWishlist, clearWishlist, items, + count, }; }; diff --git a/packages/default-theme/src/components/SwHeaderIcons.vue b/packages/default-theme/src/components/SwHeaderIcons.vue index a3d1df415..ee010653a 100644 --- a/packages/default-theme/src/components/SwHeaderIcons.vue +++ b/packages/default-theme/src/components/SwHeaderIcons.vue @@ -43,6 +43,8 @@ import { SfList, SfDropdown, SfIcon } from "@storefront-ui/vue" -import { useUser, useCart, useUIState } from "@shopware-pwa/composables" - +import { + useUser, + useCart, + useUIState, + useWishlist, +} from "@shopware-pwa/composables" import { PAGE_ACCOUNT, PAGE_WISHLIST } from "@/helpers/pages" import SwPluginSlot from "sw-plugins/SwPluginSlot" import SwButton from "@/components/atoms/SwButton" @@ -83,6 +89,7 @@ export default { setup(props, { root }) { const { isLoggedIn, logout } = useUser(root) const { count } = useCart(root) + const { count: wishlistCount } = useWishlist(root) const { switchState: toggleSidebar } = useUIState( root, "CART_SIDEBAR_STATE" @@ -98,6 +105,7 @@ export default { toggleSidebar, isLoggedIn, logout, + wishlistCount, } }, data() { diff --git a/packages/default-theme/src/locales/en-GB.json b/packages/default-theme/src/locales/en-GB.json index cc97f031a..bd068cc59 100644 --- a/packages/default-theme/src/locales/en-GB.json +++ b/packages/default-theme/src/locales/en-GB.json @@ -177,5 +177,10 @@ "Enter promo code": "Enter promo code", "Applied promo codes:": "Applied promo codes:", "Promotion code added successfully": "Promotion code added successfully", - "Promotion code does not exist": "Promotion code does not exist" + "Promotion code does not exist": "Promotion code does not exist", + "Go to wishlist": "Go to wishlist", + "Tap any heart next to a product to favorite.": "Tap any heart next to a product to favorite.", + "We’ll save them for you here!": "We’ll save them for you here!", + "No favourites yet": "No favourites yet", + "Wishlist": "Wishlist" }