From 96c623690a7a1d9cb999f26e05d183133230ef59 Mon Sep 17 00:00:00 2001 From: Anthoula Wojczak Date: Thu, 8 Apr 2021 11:04:24 -0500 Subject: [PATCH] [PWA-632] My Account: Wishlist: Actions Menu and Edit Flow (#3049) * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - create new dialog modals for list actions, and edit wishlist * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - add GQL for updateWishlist * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - add new dialogs to wishlist page - add button handlers for modals * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - add modal styles * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - pass id from mockdata to talon - add error handling for updateWishlist - pre-populate edit modal values - add visibility field to customer wishlist query - add visibility field to mock data * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - use error message from mutation - update translations - remove generic error handling - update error message styles * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - update modal styles * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - add loading state while updateWishlist is in progress * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - add test coverage for new dialogs * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - remove unnecessary deps - use public interfaces - add names to mocks * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - update signature - mock mutation - name function mocks - update snapshots * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - add test coverage for dialog handlers * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - add translations to sample pack - remove unnecessary deps - add test coverage around update wishlist mutation - update snapshot * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - refetch query to update ui - update snapshot * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - remove GraphQLDoucment AST from snap * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - resolve unsupported field for CE * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - remove wishlist actions for CE * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - clarify check for name field * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - create new actionMenu component and hook to separate EE functionality * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - update tests for new ActionMenu component * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - remove unnecessary await * PWA-632: My Account: Wishlist: Actions Menu and Edit Flow - remove mutation in test Co-authored-by: Devagouda <40405790+dpatil-magento@users.noreply.github.com> --- .../i18n/fr_FR.json | 3 + .../__snapshots__/useActionMenu.spec.js.snap | 38 + .../__snapshots__/useWishlist.spec.js.snap | 1 - .../__tests__/useActionMenu.spec.js | 147 ++++ .../lib/talons/WishlistPage/useActionMenu.js | 93 +++ .../lib/talons/WishlistPage/useWishlist.js | 8 +- .../lib/talons/WishlistPage/wishlist.gql.js | 39 ++ .../__tests__/peregrine-targets.spec.js | 1 + packages/venia-ui/i18n/en_US.json | 3 + .../__snapshots__/actionMenu.ee.spec.js.snap | 60 ++ .../__snapshots__/wishlist.spec.js.snap | 89 +-- ...shlistEditFavoritesListDialog.spec.js.snap | 649 ++++++++++++++++++ .../wishlistListActionsDialog.spec.js.snap | 145 ++++ .../__tests__/actionMenu.ee.spec.js | 38 + .../WishlistPage/__tests__/wishlist.spec.js | 45 +- .../wishlistEditFavoritesListDialog.spec.js | 56 ++ .../wishlistListActionsDialog.spec.js | 27 + .../components/WishlistPage/actionMenu.ee.js | 49 ++ .../lib/components/WishlistPage/actionMenu.js | 7 + .../lib/components/WishlistPage/wishlist.js | 11 +- .../wishlistEditFavoritesListDialog.css | 27 + .../wishlistEditFavoritesListDialog.js | 127 ++++ .../WishlistPage/wishlistListActionsDialog.js | 61 ++ 23 files changed, 1611 insertions(+), 113 deletions(-) create mode 100644 packages/peregrine/lib/talons/WishlistPage/__tests__/__snapshots__/useActionMenu.spec.js.snap create mode 100644 packages/peregrine/lib/talons/WishlistPage/__tests__/useActionMenu.spec.js create mode 100644 packages/peregrine/lib/talons/WishlistPage/useActionMenu.js create mode 100644 packages/peregrine/lib/talons/WishlistPage/wishlist.gql.js create mode 100644 packages/venia-ui/lib/components/WishlistPage/__tests__/__snapshots__/actionMenu.ee.spec.js.snap create mode 100644 packages/venia-ui/lib/components/WishlistPage/__tests__/__snapshots__/wishlistEditFavoritesListDialog.spec.js.snap create mode 100644 packages/venia-ui/lib/components/WishlistPage/__tests__/__snapshots__/wishlistListActionsDialog.spec.js.snap create mode 100644 packages/venia-ui/lib/components/WishlistPage/__tests__/actionMenu.ee.spec.js create mode 100644 packages/venia-ui/lib/components/WishlistPage/__tests__/wishlistEditFavoritesListDialog.spec.js create mode 100644 packages/venia-ui/lib/components/WishlistPage/__tests__/wishlistListActionsDialog.spec.js create mode 100644 packages/venia-ui/lib/components/WishlistPage/actionMenu.ee.js create mode 100644 packages/venia-ui/lib/components/WishlistPage/actionMenu.js create mode 100644 packages/venia-ui/lib/components/WishlistPage/wishlistEditFavoritesListDialog.css create mode 100644 packages/venia-ui/lib/components/WishlistPage/wishlistEditFavoritesListDialog.js create mode 100644 packages/venia-ui/lib/components/WishlistPage/wishlistListActionsDialog.js diff --git a/packages/extensions/venia-sample-language-packs/i18n/fr_FR.json b/packages/extensions/venia-sample-language-packs/i18n/fr_FR.json index 8c10ea63e9..d376546018 100644 --- a/packages/extensions/venia-sample-language-packs/i18n/fr_FR.json +++ b/packages/extensions/venia-sample-language-packs/i18n/fr_FR.json @@ -350,8 +350,11 @@ "validation.mustBeChecked": "Doit être vérifié.", "validation.validatePassword": "Un mot de passe doit contenir au moins 3 des éléments suivants: minuscules, majuscules, chiffres, caractères spéciaux.", "wishlist.emptyListText": "Il n'y a actuellement aucun élément dans cette liste", + "wishlistEditFavoritesListDialog.title": "Modifier la liste des favoris", "wishlistItem.addToCart": "Ajouter au panier", "wishlistItem.addToCartError": "Un problème est survenu. Veuillez actualiser et réessayer.", + "wishlistListActionsDialog.title_initial": "Actions de liste", + "wishlistListActionsDialog.edit": "Liste d'édition", "wishlistPage.disabledMessage": "Désolé, cette fonctionnalité a été désactivée.", "wishlistPage.fetchErrorMessage": "Un problème est survenu. Veuillez actualiser et réessayer.", "wishlistPage.headingText": "Listes de favoris", diff --git a/packages/peregrine/lib/talons/WishlistPage/__tests__/__snapshots__/useActionMenu.spec.js.snap b/packages/peregrine/lib/talons/WishlistPage/__tests__/__snapshots__/useActionMenu.spec.js.snap new file mode 100644 index 0000000000..ad684825d8 --- /dev/null +++ b/packages/peregrine/lib/talons/WishlistPage/__tests__/__snapshots__/useActionMenu.spec.js.snap @@ -0,0 +1,38 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`it handles editing a wishlist handleEditWishlist() runs the mutation with a thrown error 1`] = ` +Array [ + [Error: Wish list "Favorites" already exists.], +] +`; + +exports[`it handles editing a wishlist handleEditWishlist() runs the mutation with the expected values 1`] = ` +Object { + "awaitRefetchQueries": true, + "refetchQueries": Array [ + Object { + "query": [MockFunction getCustomerWishlistQuery], + }, + ], + "variables": Object { + "name": "Birthday Party", + "visibility": "PRIVATE", + "wishlistId": "5", + }, +} +`; + +exports[`returns correct shape 1`] = ` +Object { + "editFavoritesListIsOpen": false, + "formErrors": Array [ + false, + ], + "handleActionMenuClick": [Function], + "handleEditWishlist": [Function], + "handleHideDialogs": [Function], + "handleShowEditFavorites": [Function], + "isEditInProgress": false, + "listActionsIsOpen": false, +} +`; diff --git a/packages/peregrine/lib/talons/WishlistPage/__tests__/__snapshots__/useWishlist.spec.js.snap b/packages/peregrine/lib/talons/WishlistPage/__tests__/__snapshots__/useWishlist.spec.js.snap index 8f108be4eb..e09e16d634 100644 --- a/packages/peregrine/lib/talons/WishlistPage/__tests__/__snapshots__/useWishlist.spec.js.snap +++ b/packages/peregrine/lib/talons/WishlistPage/__tests__/__snapshots__/useWishlist.spec.js.snap @@ -2,7 +2,6 @@ exports[`returns correct shape 1`] = ` Object { - "handleActionMenuClick": [Function], "handleContentToggle": [Function], "isOpen": true, } diff --git a/packages/peregrine/lib/talons/WishlistPage/__tests__/useActionMenu.spec.js b/packages/peregrine/lib/talons/WishlistPage/__tests__/useActionMenu.spec.js new file mode 100644 index 0000000000..7c933e91b0 --- /dev/null +++ b/packages/peregrine/lib/talons/WishlistPage/__tests__/useActionMenu.spec.js @@ -0,0 +1,147 @@ +import React from 'react'; +import { act } from 'react-test-renderer'; + +import createTestInstance from '../../../util/createTestInstance'; +import { useActionMenu } from '../useActionMenu'; +import { useMutation } from '@apollo/client'; + +jest.mock('@apollo/client', () => { + return { + ...jest.requireActual('@apollo/client'), + useMutation: jest.fn(() => [ + jest.fn(), + { + error: false, + loading: false + } + ]) + }; +}); + +jest.mock('../wishlist.gql', () => ({ + getCustomerWishlistQuery: jest.fn().mockName('getCustomerWishlistQuery'), + updateWishlistMutation: jest.fn().mockName('updateWishlistMutation') +})); + +const Component = props => { + const talonProps = useActionMenu({ ...props }); + + return ; +}; + +const baseProps = { + id: '5', + mutations: { updateWishlistMutation: 'updateWishlistMutation' }, + queries: { + getCustomerWishlistQuery: 'getCustomerWishlistQuery' + } +}; + +test('returns correct shape', () => { + const { root } = createTestInstance(); + const { talonProps } = root.findByType('i').props; + + expect(talonProps).toMatchSnapshot(); +}); + +test('it handles opening list actions dialog', () => { + const talonPropsResult = []; + + const { root } = createTestInstance(); + talonPropsResult.push(root.findByType('i').props.talonProps); + + act(() => { + talonPropsResult[0].handleActionMenuClick(); + }); + + talonPropsResult.push(root.findByType('i').props.talonProps); + + expect(talonPropsResult[0].editFavoritesListIsOpen).toBe(false); + expect(talonPropsResult[0].listActionsIsOpen).toBe(false); + expect(talonPropsResult[1].editFavoritesListIsOpen).toBe(false); + expect(talonPropsResult[1].listActionsIsOpen).toBe(true); +}); + +test('it handles hiding dialog', () => { + const talonPropsResult = []; + + const { root } = createTestInstance(); + talonPropsResult.push(root.findByType('i').props.talonProps); + + act(() => { + talonPropsResult[0].handleHideDialogs(); + }); + + talonPropsResult.push(root.findByType('i').props.talonProps); + + expect(talonPropsResult[0].editFavoritesListIsOpen).toBe(false); + expect(talonPropsResult[0].listActionsIsOpen).toBe(false); + expect(talonPropsResult[1].editFavoritesListIsOpen).toBe(false); + expect(talonPropsResult[1].listActionsIsOpen).toBe(false); +}); + +test('it handles showing edit favorites dialog', () => { + const talonPropsResult = []; + + const { root } = createTestInstance(); + talonPropsResult.push(root.findByType('i').props.talonProps); + + act(() => { + talonPropsResult[0].handleShowEditFavorites(); + }); + + talonPropsResult.push(root.findByType('i').props.talonProps); + + expect(talonPropsResult[0].editFavoritesListIsOpen).toBe(false); + expect(talonPropsResult[0].listActionsIsOpen).toBe(false); + expect(talonPropsResult[1].editFavoritesListIsOpen).toBe(true); + expect(talonPropsResult[1].listActionsIsOpen).toBe(false); +}); + +describe('it handles editing a wishlist', () => { + test('handleEditWishlist() runs the mutation with the expected values', () => { + const editWishlist = jest.fn(); + useMutation.mockReturnValueOnce([editWishlist, {}]); + + const tree = createTestInstance(); + + const { root } = tree; + const { talonProps } = root.findByType('i').props; + + const { handleEditWishlist } = talonProps; + + const data = { + name: 'Birthday Party', + visibility: 'PRIVATE', + wishlistId: 5 + }; + + act(() => { + handleEditWishlist(data); + }); + + expect(editWishlist.mock.calls[0][0]).toMatchSnapshot(); + }); + + test('handleEditWishlist() runs the mutation with a thrown error', () => { + const error = new Error('Wish list "Favorites" already exists.'); + const editWishlist = jest.fn(() => { + throw error; + }); + useMutation.mockReturnValueOnce([ + editWishlist, + { + called: true, + error: error, + loading: false + } + ]); + + const tree = createTestInstance(); + + const { root } = tree; + const { talonProps } = root.findByType('i').props; + + expect(talonProps.formErrors).toMatchSnapshot(); + }); +}); diff --git a/packages/peregrine/lib/talons/WishlistPage/useActionMenu.js b/packages/peregrine/lib/talons/WishlistPage/useActionMenu.js new file mode 100644 index 0000000000..2a07047940 --- /dev/null +++ b/packages/peregrine/lib/talons/WishlistPage/useActionMenu.js @@ -0,0 +1,93 @@ +import { useCallback, useState } from 'react'; +import { useMutation } from '@apollo/client'; +import DEFAULT_OPERATIONS from './wishlist.gql'; +import mergeOperations from '../../util/shallowMerge'; + +const dialogs = { + NONE: 1, + LIST_ACTIONS: 2, + EDIT_WISHLIST: 3 +}; + +/** + * @function + * + * @param {{id}} props + * @param {ID} props.id - The unique identifier of the wish list + * @param {Object} props.operations - GraphQL operations to be run by the talon. + * + * @returns {ActionMenuProps} + */ +export const useActionMenu = (props = {}) => { + const { id } = props; + const operations = mergeOperations(DEFAULT_OPERATIONS, props.operations); + const { getCustomerWishlistQuery, updateWishlistMutation } = operations; + const [currentDialog, setCurrentDialog] = useState(dialogs.NONE); + + const handleActionMenuClick = useCallback(() => { + setCurrentDialog(dialogs.LIST_ACTIONS); + }, []); + + const handleHideDialogs = useCallback(() => { + setCurrentDialog(dialogs.NONE); + }, []); + + const listActionsIsOpen = currentDialog === dialogs.LIST_ACTIONS; + const editFavoritesListIsOpen = currentDialog === dialogs.EDIT_WISHLIST; + + const handleShowEditFavorites = useCallback(() => { + setCurrentDialog(dialogs.EDIT_WISHLIST); + }, []); + + const [ + updateWishlist, + { error: updateWishlistErrors, loading: isEditInProgress } + ] = useMutation(updateWishlistMutation); + + const handleEditWishlist = useCallback( + async data => { + try { + await updateWishlist({ + variables: { + name: data.name, + visibility: data.visibility, + wishlistId: id + }, + refetchQueries: [{ query: getCustomerWishlistQuery }], + awaitRefetchQueries: true + }); + setCurrentDialog(dialogs.NONE); + } catch (error) { + if (process.env.NODE_ENV !== 'production') { + console.error(error); + } + } + }, + [getCustomerWishlistQuery, id, updateWishlist] + ); + + return { + editFavoritesListIsOpen, + formErrors: [updateWishlistErrors], + handleActionMenuClick, + handleEditWishlist, + handleHideDialogs, + handleShowEditFavorites, + isEditInProgress, + listActionsIsOpen + }; +}; + +/** + * Props data to use when rendering the Wishlist Action Menu component. + * + * @typedef {Object} ActionMenuProps + * + * @property {Boolean} editFavoritesListIsOpen Whether the Edit Favorites List dialog is open + * @property {Function} handleActionMenuClick Callback to handle action menu clicks + * @property {Function} handleEditWishlist Callback to handle edit wishlist + * @property {Function} handleHideDialogs Callback to handle hiding all dialogs + * @property {Function} handleShowEditFavorites Callback to handle showing the Edit Favorites List Dialog + * @property {Boolean} isEditInProgress Whether the update wishlist operation is in progress + * @property {Boolean} listActionsIsOpen Whether the list actions dialog is open + */ diff --git a/packages/peregrine/lib/talons/WishlistPage/useWishlist.js b/packages/peregrine/lib/talons/WishlistPage/useWishlist.js index 7906a6e5a8..41a89cda30 100644 --- a/packages/peregrine/lib/talons/WishlistPage/useWishlist.js +++ b/packages/peregrine/lib/talons/WishlistPage/useWishlist.js @@ -1,4 +1,4 @@ -import { useCallback, useState } from 'react'; +import { useState } from 'react'; /** * @function @@ -12,12 +12,7 @@ export const useWishlist = () => { setIsOpen(currentValue => !currentValue); }; - const handleActionMenuClick = useCallback(() => { - console.log('To be handled by PWA-632'); - }, []); - return { - handleActionMenuClick, handleContentToggle, isOpen }; @@ -32,7 +27,6 @@ export const useWishlist = () => { * * @typedef {Object} WishListProps * - * @property {Function} handleActionMenuClick Callback to handle action menu clicks * @property {Function} handleContentToggle Callback to handle list expand toggle * @property {Boolean} isOpen Boolean which represents if the content is expanded or not */ diff --git a/packages/peregrine/lib/talons/WishlistPage/wishlist.gql.js b/packages/peregrine/lib/talons/WishlistPage/wishlist.gql.js new file mode 100644 index 0000000000..934b996e04 --- /dev/null +++ b/packages/peregrine/lib/talons/WishlistPage/wishlist.gql.js @@ -0,0 +1,39 @@ +import { gql } from '@apollo/client'; + +import { WishlistFragment } from './wishlistFragment.gql'; + +export const GET_CUSTOMER_WISHLIST = gql` + query GetCustomerWishlist { + customer { + id + wishlists { + id + ...WishlistFragment + } + } + } + ${WishlistFragment} +`; + +export const UPDATE_WISHLIST = gql` + mutation UpdateWishlist( + $name: String! + $visibility: WishlistVisibilityEnum! + $wishlistId: ID! + ) { + updateWishlist( + name: $name + visibility: $visibility + wishlistId: $wishlistId + ) { + name + uid + visibility + } + } +`; + +export default { + getCustomerWishlistQuery: GET_CUSTOMER_WISHLIST, + updateWishlistMutation: UPDATE_WISHLIST +}; diff --git a/packages/peregrine/lib/targets/__tests__/peregrine-targets.spec.js b/packages/peregrine/lib/targets/__tests__/peregrine-targets.spec.js index 9ce3e685c3..c696bdfb9f 100644 --- a/packages/peregrine/lib/targets/__tests__/peregrine-targets.spec.js +++ b/packages/peregrine/lib/targets/__tests__/peregrine-targets.spec.js @@ -206,6 +206,7 @@ test('exposes all hooks and targets', async () => { talons.Wishlist.WishlistButton.useWishlistButton.ee.wrapWith() wraps export "useWishlistButton.ee" from "Wishlist/WishlistButton/useWishlistButton.ee.js" talons.Wishlist.WishlistDialog.CreateWishlistForm.useCreateWishlistForm.wrapWith() wraps export "useCreateWishlistForm" from "Wishlist/WishlistDialog/CreateWishlistForm/useCreateWishlistForm.js" talons.Wishlist.WishlistDialog.useWishlistDialog.wrapWith() wraps export "useWishlistDialog" from "Wishlist/WishlistDialog/useWishlistDialog.js" + talons.WishlistPage.useActionMenu.wrapWith() wraps export "useActionMenu" from "WishlistPage/useActionMenu.js" talons.WishlistPage.useCreateWishlist.wrapWith() wraps export "useCreateWishlist" from "WishlistPage/useCreateWishlist.js" talons.WishlistPage.useWishlist.wrapWith() wraps export "useWishlist" from "WishlistPage/useWishlist.js" talons.WishlistPage.useWishlistItem.wrapWith() wraps export "useWishlistItem" from "WishlistPage/useWishlistItem.js" diff --git a/packages/venia-ui/i18n/en_US.json b/packages/venia-ui/i18n/en_US.json index 6975492dfb..a6923ee25c 100644 --- a/packages/venia-ui/i18n/en_US.json +++ b/packages/venia-ui/i18n/en_US.json @@ -386,8 +386,11 @@ "wishlistConfirmRemoveProductDialog.title": "Remove Product from Wishlist", "wishlistDialog.createButton": "+ Create a new list", "wishlistDialog.title": "Add to Favorites", + "wishlistEditFavoritesListDialog.title": "Edit Favorites List", "wishlistItem.addToCart": "Add to Cart", "wishlistItem.addToCartError": "Something went wrong. Please refresh and try again.", + "wishlistListActionsDialog.title_initial": "List Actions", + "wishlistListActionsDialog.edit": "Edit List", "wishlistMoreActionsDialog.copy": "Copy to", "wishlistMoreActionsDialog.delete": "Remove", "wishlistMoreActionsDialog.move": "Move to", diff --git a/packages/venia-ui/lib/components/WishlistPage/__tests__/__snapshots__/actionMenu.ee.spec.js.snap b/packages/venia-ui/lib/components/WishlistPage/__tests__/__snapshots__/actionMenu.ee.spec.js.snap new file mode 100644 index 0000000000..ddd523989b --- /dev/null +++ b/packages/venia-ui/lib/components/WishlistPage/__tests__/__snapshots__/actionMenu.ee.spec.js.snap @@ -0,0 +1,60 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
+ + + +
+`; diff --git a/packages/venia-ui/lib/components/WishlistPage/__tests__/__snapshots__/wishlist.spec.js.snap b/packages/venia-ui/lib/components/WishlistPage/__tests__/__snapshots__/wishlist.spec.js.snap index 3371c364d0..7e352ab3e9 100644 --- a/packages/venia-ui/lib/components/WishlistPage/__tests__/__snapshots__/wishlist.spec.js.snap +++ b/packages/venia-ui/lib/components/WishlistPage/__tests__/__snapshots__/wishlist.spec.js.snap @@ -18,49 +18,17 @@ exports[`render closed with items 1`] = ` - Private + Public
- +
@@ -121,49 +90,17 @@ exports[`render open with no items 1`] = ` - Private + Public
- + +
+ +`; + +exports[`renders correctly when open 1`] = ` + +
+ +
+
+`; diff --git a/packages/venia-ui/lib/components/WishlistPage/__tests__/actionMenu.ee.spec.js b/packages/venia-ui/lib/components/WishlistPage/__tests__/actionMenu.ee.spec.js new file mode 100644 index 0000000000..25e1430a86 --- /dev/null +++ b/packages/venia-ui/lib/components/WishlistPage/__tests__/actionMenu.ee.spec.js @@ -0,0 +1,38 @@ +import React from 'react'; +import { createTestInstance } from '@magento/peregrine'; + +import { useActionMenu } from '@magento/peregrine/lib/talons/WishlistPage/useActionMenu'; +import ActionMenu from '../actionMenu.ee'; + +jest.mock('@magento/peregrine/lib/talons/WishlistPage/useActionMenu'); +jest.mock('../wishlistListActionsDialog', () => 'WishlistListActionsDialog'); +jest.mock( + '../wishlistEditFavoritesListDialog', + () => 'WishlistEditFavoritesListDialog' +); + +const baseProps = { + data: { + id: 5, + name: 'Favorites List', + visibility: 'PUBLIC' + } +}; + +const baseTalonProps = { + editFavoritesListIsOpen: jest.fn().mockName('editFavoritesListIsOpen'), + formErrors: jest.fn().mockName('formErrors'), + handleActionMenuClick: jest.fn().mockName('handleActionMenuClick'), + handleEditWishlist: jest.fn().mockName('handleEditWishlist'), + handleHideDialogs: jest.fn().mockName('handleHideDialogs'), + handleShowEditFavorites: jest.fn().mockName('handleShowEditFavorites'), + isEditInProgress: jest.fn().mockName('isEditInProgress'), + listActionsIsOpen: jest.fn().mockName('listActionsIsOpen') +}; + +test('renders correctly', () => { + useActionMenu.mockReturnValue(baseTalonProps); + const instance = createTestInstance(); + + expect(instance.toJSON()).toMatchSnapshot(); +}); diff --git a/packages/venia-ui/lib/components/WishlistPage/__tests__/wishlist.spec.js b/packages/venia-ui/lib/components/WishlistPage/__tests__/wishlist.spec.js index f1aec1596a..ce77df78e8 100644 --- a/packages/venia-ui/lib/components/WishlistPage/__tests__/wishlist.spec.js +++ b/packages/venia-ui/lib/components/WishlistPage/__tests__/wishlist.spec.js @@ -7,43 +7,44 @@ import { useWishlist } from '@magento/peregrine/lib/talons/WishlistPage/useWishl jest.mock('@magento/peregrine/lib/talons/WishlistPage/useWishlist'); jest.mock('../../../classify'); jest.mock('../wishlistItems', () => 'WishlistItems'); +jest.mock('../actionMenu.ee', () => 'ActionMenu'); + +const baseProps = { + data: { + id: 5, + items_count: 0, + items_v2: { items: [] }, + name: 'Favorites List', + sharing_code: null, + visibility: 'PUBLIC' + } +}; + +const baseTalonProps = { + handleContentToggle: jest.fn().mockName('handleContentToggle'), + isOpen: true +}; test('render open with no items', () => { - useWishlist.mockReturnValue({ - handleActionMenuClick: jest.fn().mockName('handleActionMenuClick'), - handleContentToggle: jest.fn().mockName('handleContentToggle'), - isOpen: true - }); + useWishlist.mockReturnValue(baseTalonProps); - const props = { - data: { - items_count: 0, - items_v2: { items: [] }, - name: 'Favorites List', - sharing_code: null - } - }; - const tree = createTestInstance(); + const tree = createTestInstance(); expect(tree.toJSON()).toMatchSnapshot(); }); test('render closed with items', () => { - useWishlist.mockReturnValue({ - handleActionMenuClick: jest.fn().mockName('handleActionMenuClick'), - handleContentToggle: jest.fn().mockName('handleContentToggle'), - isOpen: false - }); + useWishlist.mockReturnValue({ ...baseTalonProps, isOpen: false }); - const props = { + const myProps = { data: { + ...baseProps.data, items_count: 20, items_v2: { items: ['item1', 'item2'] }, - name: 'Favorites List', sharing_code: 'abc123' } }; - const tree = createTestInstance(); + const tree = createTestInstance(); expect(tree.toJSON()).toMatchSnapshot(); }); diff --git a/packages/venia-ui/lib/components/WishlistPage/__tests__/wishlistEditFavoritesListDialog.spec.js b/packages/venia-ui/lib/components/WishlistPage/__tests__/wishlistEditFavoritesListDialog.spec.js new file mode 100644 index 0000000000..bc124eef9d --- /dev/null +++ b/packages/venia-ui/lib/components/WishlistPage/__tests__/wishlistEditFavoritesListDialog.spec.js @@ -0,0 +1,56 @@ +import React from 'react'; +import { createTestInstance } from '@magento/peregrine'; + +import WishlistEditFavoritesListDialog from '../wishlistEditFavoritesListDialog'; + +jest.mock('../../../classify'); +jest.mock('../../Dialog', () => 'Dialog'); +jest.mock('../../FormError', () => 'FormError'); + +const props = { + formErrors: new Map([]), + isOpen: false, + isEditInProgress: false, + onCancel: jest.fn().mockName('onCancel'), + onConfirm: jest.fn().mockName('onConfirm') +}; + +it('renders correctly when closed', () => { + const instance = createTestInstance( + + ); + + expect(instance.toJSON()).toMatchSnapshot(); +}); + +it('renders correctly when opened', () => { + const myProps = { ...props, isOpen: true }; + + const instance = createTestInstance( + + ); + + expect(instance.toJSON()).toMatchSnapshot(); +}); + +test('renders correctly with error', () => { + const myProps = { + ...props, + formErrors: new Map([['updateWishlistMutation', 'Unit Test Error 1']]) + }; + const instance = createTestInstance( + + ); + + expect(instance.toJSON()).toMatchSnapshot(); +}); + +test('renders correctly when edit is in progress', () => { + const myProps = { ...props, isEditInProgress: true }; + + const tree = createTestInstance( + + ); + + expect(tree.toJSON()).toMatchSnapshot(); +}); diff --git a/packages/venia-ui/lib/components/WishlistPage/__tests__/wishlistListActionsDialog.spec.js b/packages/venia-ui/lib/components/WishlistPage/__tests__/wishlistListActionsDialog.spec.js new file mode 100644 index 0000000000..47a105c1d8 --- /dev/null +++ b/packages/venia-ui/lib/components/WishlistPage/__tests__/wishlistListActionsDialog.spec.js @@ -0,0 +1,27 @@ +import React from 'react'; +import { createTestInstance } from '@magento/peregrine'; + +import WishlistListActionsDialog from '../wishlistListActionsDialog'; + +jest.mock('../../../classify'); +jest.mock('../../Dialog', () => 'Dialog'); + +const props = { + isOpen: false, + onCancel: jest.fn(), + onEdit: jest.fn() +}; + +test('renders correctly when closed', () => { + const tree = createTestInstance(); + + expect(tree.toJSON()).toMatchSnapshot(); +}); + +test('renders correctly when open', () => { + const myProps = { ...props, isOpen: true }; + + const tree = createTestInstance(); + + expect(tree.toJSON()).toMatchSnapshot(); +}); diff --git a/packages/venia-ui/lib/components/WishlistPage/actionMenu.ee.js b/packages/venia-ui/lib/components/WishlistPage/actionMenu.ee.js new file mode 100644 index 0000000000..87eaaac1a6 --- /dev/null +++ b/packages/venia-ui/lib/components/WishlistPage/actionMenu.ee.js @@ -0,0 +1,49 @@ +import React from 'react'; +import Icon from '../Icon'; +import { MoreHorizontal } from 'react-feather'; +import { useActionMenu } from '@magento/peregrine/lib/talons/WishlistPage/useActionMenu'; +import WishlistListActionsDialog from './wishlistListActionsDialog'; +import WishlistEditFavoritesListDialog from './wishlistEditFavoritesListDialog'; + +const ActionMenu = props => { + const { id, name, visibility } = props; + const talonProps = useActionMenu({ id }); + const { + editFavoritesListIsOpen, + formErrors, + handleActionMenuClick, + handleEditWishlist, + handleHideDialogs, + handleShowEditFavorites, + isEditInProgress, + listActionsIsOpen + } = talonProps; + + return ( +
+ + + +
+ ); +}; + +export default ActionMenu; diff --git a/packages/venia-ui/lib/components/WishlistPage/actionMenu.js b/packages/venia-ui/lib/components/WishlistPage/actionMenu.js new file mode 100644 index 0000000000..1f1b7f7f87 --- /dev/null +++ b/packages/venia-ui/lib/components/WishlistPage/actionMenu.js @@ -0,0 +1,7 @@ +/** + * Action Menu for wishlist is an EE-only feature. + * Here in CE, don't render the Action Menu at all. + */ +export default () => { + return null; +}; diff --git a/packages/venia-ui/lib/components/WishlistPage/wishlist.js b/packages/venia-ui/lib/components/WishlistPage/wishlist.js index ef14127509..05c1fb592e 100644 --- a/packages/venia-ui/lib/components/WishlistPage/wishlist.js +++ b/packages/venia-ui/lib/components/WishlistPage/wishlist.js @@ -1,14 +1,13 @@ import React from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; -import { ChevronDown, ChevronUp, MoreHorizontal } from 'react-feather'; +import { ChevronDown, ChevronUp } from 'react-feather'; import { useWishlist } from '@magento/peregrine/lib/talons/WishlistPage/useWishlist'; import { mergeClasses } from '../../classify'; import Icon from '../Icon'; import WishlistItems from './wishlistItems'; import defaultClasses from './wishlist.css'; - -const ActionMenuIcon = ; +import ActionMenu from './actionMenu'; const Wishlist = props => { const { data } = props; @@ -22,7 +21,7 @@ const Wishlist = props => { } = data; const talonProps = useWishlist(); - const { handleActionMenuClick, handleContentToggle, isOpen } = talonProps; + const { handleContentToggle, isOpen } = talonProps; const classes = mergeClasses(defaultClasses, props.classes); const contentClass = isOpen ? classes.content : classes.content_hidden; @@ -63,9 +62,7 @@ const Wishlist = props => {
{wishlistName}
- + diff --git a/packages/venia-ui/lib/components/WishlistPage/wishlistEditFavoritesListDialog.css b/packages/venia-ui/lib/components/WishlistPage/wishlistEditFavoritesListDialog.css new file mode 100644 index 0000000000..7f3539c2b3 --- /dev/null +++ b/packages/venia-ui/lib/components/WishlistPage/wishlistEditFavoritesListDialog.css @@ -0,0 +1,27 @@ +.root { + padding: 1rem; +} + +.cancelButton { + composes: root_lowPriority from '../Button/button.css'; + + min-width: 9rem; +} + +.confirmButton { + composes: confirmButton from '../Dialog/dialog.css'; +} + +.errorMessage { + composes: errorMessage from './wishlistConfirmRemoveProductDialog.css'; +} + +.form { + display: grid; + gap: 1.5rem; +} + +.radioRoot { + composes: root from '../RadioGroup/radioGroup.css'; + grid-template-columns: auto; +} diff --git a/packages/venia-ui/lib/components/WishlistPage/wishlistEditFavoritesListDialog.js b/packages/venia-ui/lib/components/WishlistPage/wishlistEditFavoritesListDialog.js new file mode 100644 index 0000000000..a70b1a0a05 --- /dev/null +++ b/packages/venia-ui/lib/components/WishlistPage/wishlistEditFavoritesListDialog.js @@ -0,0 +1,127 @@ +import React from 'react'; +import { array, bool, func, object, shape, string } from 'prop-types'; +import { useIntl } from 'react-intl'; + +import { mergeClasses } from '../../classify'; +import Dialog from '../Dialog'; +import defaultClasses from './wishlistEditFavoritesListDialog.css'; +import TextInput from '../TextInput'; +import { isRequired } from '../../util/formValidators'; +import Field from '../Field'; +import RadioGroup from '../RadioGroup'; +import FormError from '../FormError'; + +const WishlistEditFavoritesListDialog = props => { + const { + formErrors, + formProps, + isOpen, + isEditInProgress, + onCancel, + onConfirm + } = props; + + const { formatMessage } = useIntl(); + + const classes = mergeClasses(defaultClasses, props.classes); + + const dialogTitle = formatMessage({ + id: 'wishlistEditFavoritesListDialog.title', + defaultMessage: 'Edit Favorites List' + }); + + const dialogClasses = { + cancelButton: classes.cancelButton, + confirmButton: classes.confirmButton + }; + + const listName = formatMessage({ + id: 'createWishlist.listName', + defaultMessage: 'List Name' + }); + + const radioGroupClasses = { + message: classes.radioMessage, + radioLabel: classes.radioLabel, + root: classes.radioRoot + }; + + const radioGroupItems = [ + { + label: formatMessage({ + id: 'global.private', + defaultMessage: 'Private' + }), + value: 'PRIVATE' + }, + { + label: formatMessage({ + id: 'global.public', + defaultMessage: 'Public' + }), + value: 'PUBLIC' + } + ]; + + return ( + +
+ +
+ + + + +
+
+
+ ); +}; + +export default WishlistEditFavoritesListDialog; + +WishlistEditFavoritesListDialog.propTypes = { + classes: shape({ + cancelButton: string, + confirmButton: string, + errorMessage: string, + form: string, + radioLabel: string, + radioMessage: string, + radioRoot: string, + root: string + }), + formErrors: array, + formProps: object, + isOpen: bool, + isEditInProgress: bool, + onCancel: func, + onConfirm: func +}; diff --git a/packages/venia-ui/lib/components/WishlistPage/wishlistListActionsDialog.js b/packages/venia-ui/lib/components/WishlistPage/wishlistListActionsDialog.js new file mode 100644 index 0000000000..bbbc046df9 --- /dev/null +++ b/packages/venia-ui/lib/components/WishlistPage/wishlistListActionsDialog.js @@ -0,0 +1,61 @@ +import React from 'react'; +import { bool, func, shape, string } from 'prop-types'; +import { ChevronRight, Edit2 } from 'react-feather'; +import { FormattedMessage, useIntl } from 'react-intl'; + +import { mergeClasses } from '../../classify'; +import Dialog from '../Dialog'; +import Icon from '../Icon'; +import defaultClasses from './wishlistMoreActionsDialog.css'; + +const WishlistListActionsDialog = props => { + const { isOpen, onCancel, onEdit } = props; + + const { formatMessage } = useIntl(); + + const classes = mergeClasses(defaultClasses, props.classes); + + const dialogTitle = formatMessage({ + id: 'wishlistListActionsDialog.title_initial', + defaultMessage: 'List Actions' + }); + + return ( + +
+ +
+
+ ); +}; + +export default WishlistListActionsDialog; + +WishlistListActionsDialog.propTypes = { + classes: shape({ + root: string, + rowButton: string, + row: string, + text: string + }), + isOpen: bool, + onCancel: func, + onEdit: func +};