From 9fe7d21f04e8b6ba9dfd17c00415b4a28806c6e3 Mon Sep 17 00:00:00 2001 From: JF-Cozy Date: Thu, 24 Jun 2021 16:49:14 +0200 Subject: [PATCH 1/9] feat: Add SelectionContext to be able to add item in a selection, and use them in a SelectionBar --- src/AppContainer.jsx | 9 ++- src/ducks/context/SelectionContext.jsx | 85 ++++++++++++++++++++++++++ test/AppLike.jsx | 5 +- 3 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 src/ducks/context/SelectionContext.jsx diff --git a/src/AppContainer.jsx b/src/AppContainer.jsx index 52f9a66182..c392cd9fa2 100644 --- a/src/AppContainer.jsx +++ b/src/AppContainer.jsx @@ -17,6 +17,7 @@ import flag from 'cozy-flags' import { TrackerProvider } from 'ducks/tracking/browser' import JobsProvider from 'ducks/context/JobsContext' import BanksProvider from 'ducks/context/BanksContext' +import SelectionProvider from 'ducks/context/SelectionContext' import Alerter from 'cozy-ui/transpiled/react/Alerter' import { initTranslation } from 'cozy-ui/transpiled/react/I18n' @@ -68,9 +69,11 @@ const AppContainer = ({ store, lang, history, client }) => { > - - - + + + + + diff --git a/src/ducks/context/SelectionContext.jsx b/src/ducks/context/SelectionContext.jsx new file mode 100644 index 0000000000..f2a835fded --- /dev/null +++ b/src/ducks/context/SelectionContext.jsx @@ -0,0 +1,85 @@ +import React, { + createContext, + useState, + useCallback, + useEffect, + useContext, + useMemo +} from 'react' + +import flag from 'cozy-flags' + +export const SelectionContext = createContext() + +export const useSelectionContext = () => { + return useContext(SelectionContext) +} + +const SelectionProvider = ({ children }) => { + const [isSelectionModeActive, setIsSelectionModeActive] = useState(false) + const [selected, setSelected] = useState([]) + const isSelectionModeEnabled = flag('banks.selectionMode.enabled') + + const activateSelectionMode = useCallback( + () => isSelectionModeEnabled && setIsSelectionModeActive(true), + [isSelectionModeEnabled] + ) + + const deactivateSelectionMode = () => setIsSelectionModeActive(false) + + const addToSelection = useCallback( + item => { + setSelected(v => [...v, item]) + }, + [setSelected] + ) + + const removeFromSelection = useCallback( + item => { + setSelected(selected.filter(e => e._id !== item._id)) + }, + [selected] + ) + + const emptySelection = useCallback(() => setSelected([]), [setSelected]) + + const isSelected = useCallback(item => selected.includes(item), [selected]) + + useEffect(() => { + if (isSelectionModeActive && selected.length === 0) { + deactivateSelectionMode() + } + if (!isSelectionModeActive && selected.length > 0) { + activateSelectionMode() + } + }, [selected, activateSelectionMode, isSelectionModeActive]) + + const value = useMemo( + () => ({ + isSelectionModeActive, + selected, + addToSelection, + isSelected, + emptySelection, + removeFromSelection, + isSelectionModeEnabled + }), + [ + addToSelection, + emptySelection, + isSelected, + isSelectionModeActive, + isSelectionModeEnabled, + removeFromSelection, + selected + ] + ) + + return ( + + {children} + + ) +} + +export default SelectionProvider diff --git a/test/AppLike.jsx b/test/AppLike.jsx index d385ac4bcc..16ca09078e 100644 --- a/test/AppLike.jsx +++ b/test/AppLike.jsx @@ -10,6 +10,7 @@ import RouterContext from 'components/RouterContext' import { TrackerProvider } from 'ducks/tracking/browser' import { JobsContext } from 'ducks/context/JobsContext' import BanksProvider from 'ducks/context/BanksContext' +import SelectionProvider from 'ducks/context/SelectionContext' export const TestI18n = ({ children }) => { return ( @@ -29,7 +30,9 @@ const AppLike = ({ children, store, client, router, jobsInProgress }) => { - {children} + + {children} + From 3dd6b59fe84b6e565c6dfb857335e6b5cc862ee7 Mon Sep 17 00:00:00 2001 From: JF-Cozy Date: Thu, 24 Jun 2021 14:20:57 +0200 Subject: [PATCH 2/9] feat: Add SelectionBar --- src/ducks/selection/SelectionBar.jsx | 63 ++++++++++++++++++++++++++++ src/ducks/selection/helpers.js | 11 +++++ src/locales/en.json | 10 +++++ src/locales/fr.json | 10 +++++ 4 files changed, 94 insertions(+) create mode 100644 src/ducks/selection/SelectionBar.jsx create mode 100644 src/ducks/selection/helpers.js diff --git a/src/ducks/selection/SelectionBar.jsx b/src/ducks/selection/SelectionBar.jsx new file mode 100644 index 0000000000..ecd0ee35b7 --- /dev/null +++ b/src/ducks/selection/SelectionBar.jsx @@ -0,0 +1,63 @@ +import React, { useCallback, useMemo } from 'react' + +import UISelectionBar from 'cozy-ui/transpiled/react/SelectionBar' +import Alerter from 'cozy-ui/transpiled/react/Alerter' +import { useI18n } from 'cozy-ui/transpiled/react/I18n' + +import { useSelectionContext } from 'ducks/context/SelectionContext' +import { makeSelectionBarActions } from 'ducks/selection/helpers' +import { useTransactionCategoryModal } from 'ducks/transactions/TransactionRow' + +const SelectionBar = () => { + const { + isSelectionModeEnabled, + isSelectionModeActive, + selected, + emptySelection + } = useSelectionContext() + + const { t } = useI18n() + + const beforeUpdate = useCallback(() => { + emptySelection() + }, [emptySelection]) + + const afterUpdates = () => { + Alerter.success( + t('Categorization.success', { + smart_count: selected.length + }) + ) + } + + const [ + showTransactionCategoryModal, + , + transactionCategoryModal + ] = useTransactionCategoryModal({ + transactions: selected, + beforeUpdate, + afterUpdates + }) + + const actions = useMemo( + () => makeSelectionBarActions(showTransactionCategoryModal), + [showTransactionCategoryModal] + ) + + if (!isSelectionModeEnabled) return null + return ( + <> + {isSelectionModeActive && ( + + )} + {transactionCategoryModal} + + ) +} + +export default SelectionBar diff --git a/src/ducks/selection/helpers.js b/src/ducks/selection/helpers.js new file mode 100644 index 0000000000..5e1f51c131 --- /dev/null +++ b/src/ducks/selection/helpers.js @@ -0,0 +1,11 @@ +import GraphCircleIcon from 'cozy-ui/transpiled/react/Icons/GraphCircle' + +export const makeSelectionBarActions = showTransactionCategoryModal => { + return { + categorize: { + action: () => showTransactionCategoryModal(), + displayCondition: selected => selected.length > 0, + icon: GraphCircleIcon + } + } +} diff --git a/src/locales/en.json b/src/locales/en.json index 1aefcde232..7c85b6fb95 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -916,5 +916,15 @@ "deleting": "Deleting associated data, do not close the window...", "successfully-deleted": "All associated data has been deleted" } + }, + + "Categorization": { + "success": "%{smart_count} operation has been recategorized |||| %{smart_count} operations have been recategorized" + }, + + "SelectionBar": { + "selected_count": "item selected |||| items selected", + "close": "Close", + "categorize": "Categorize" } } diff --git a/src/locales/fr.json b/src/locales/fr.json index 0be95edbbc..f87ac73cf9 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -917,5 +917,15 @@ "deleting": "Suppression des données associées, ne pas fermer la fenêtre...", "successfully-deleted": "Toutes les données associées ont bien été supprimées" } + }, + + "Categorization": { + "success": "%{smart_count} operation a été recatégorisée |||| %{smart_count} operations ont été recatégorisées" + }, + + "SelectionBar": { + "selected_count": "élément sélectionné |||| éléments sélectionnés", + "close": "Fermer", + "categorize": "Categorisation" } } From 18512a724f5067167d99ccbfddfa9795100bde72 Mon Sep 17 00:00:00 2001 From: JF-Cozy Date: Thu, 24 Jun 2021 15:14:12 +0200 Subject: [PATCH 3/9] feat: TransactionCategoryEditor accept multiple transactions and add afterUpdates props to trigger something after all transaction update finished --- .../TransactionCategoryEditor.jsx | 72 +++++++++++++------ 1 file changed, 50 insertions(+), 22 deletions(-) diff --git a/src/ducks/transactions/TransactionCategoryEditor.jsx b/src/ducks/transactions/TransactionCategoryEditor.jsx index 8ceb60f57f..95a8ee7b2c 100644 --- a/src/ducks/transactions/TransactionCategoryEditor.jsx +++ b/src/ducks/transactions/TransactionCategoryEditor.jsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useMemo, useCallback } from 'react' import { useClient } from 'cozy-client' import { updateTransactionCategory, @@ -9,32 +9,60 @@ import CategoryChoice from 'ducks/categories/CategoryChoice' /** * Edits a transaction's category through CategoryChoice */ -const TransactionCategoryEditor = props => { +const TransactionCategoryEditor = ({ + transactions, + beforeUpdate, + afterUpdate, + afterUpdates, + onCancel +}) => { const client = useClient() - const { transaction, beforeUpdate, afterUpdate, onCancel } = props - - const handleSelect = async category => { - if (beforeUpdate) { - await beforeUpdate() - } - const newTransaction = await updateTransactionCategory( - client, - transaction, - category - ) - if (afterUpdate) { - await afterUpdate(newTransaction) - } - } - - const handleCancel = async () => { + const categoryId = useMemo(() => getCategoryId(transactions[0]), [ + transactions + ]) + + const handleUpdate = useCallback( + async (transaction, category) => { + const newTransaction = await updateTransactionCategory( + client, + transaction, + category + ) + + if (afterUpdate) { + await afterUpdate(newTransaction) + } + }, + [afterUpdate, client] + ) + + const handleSelect = useCallback( + async category => { + if (beforeUpdate) { + await beforeUpdate() + } + + const promises = transactions.map(transaction => + handleUpdate(transaction, category) + ) + + await Promise.all(promises) + + if (afterUpdates) { + afterUpdates() + } + }, + [afterUpdates, beforeUpdate, handleUpdate, transactions] + ) + + const handleCancel = useCallback(async () => { await onCancel() - } + }, [onCancel]) return ( @@ -45,4 +73,4 @@ TransactionCategoryEditor.defaultProps = { modal: false } -export default TransactionCategoryEditor +export default React.memo(TransactionCategoryEditor) From 4001d0e5d1c73e6c671d92c178b9bfc6683934c1 Mon Sep 17 00:00:00 2001 From: JF-Cozy Date: Thu, 24 Jun 2021 12:22:01 +0200 Subject: [PATCH 4/9] feat: UseTransactionCategoryModal use obj argument and can pass specific beforeUpdate and afterUpdates to TransactionCategoryEditor --- src/ducks/transactions/TransactionModal.jsx | 2 +- .../TransactionRow/TransactionRowDesktop.jsx | 2 +- .../transactions/TransactionRow/index.jsx | 18 ++++++++++++++---- 3 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/ducks/transactions/TransactionModal.jsx b/src/ducks/transactions/TransactionModal.jsx index 47d0872736..579f9ba434 100644 --- a/src/ducks/transactions/TransactionModal.jsx +++ b/src/ducks/transactions/TransactionModal.jsx @@ -125,7 +125,7 @@ const TransactionCategoryEditorDialog = ({ transaction, onClose }) => { beforeUpdate={handlePop} afterUpdate={onAfterUpdate} onCancel={handlePop} - transaction={transaction} + transactions={[transaction]} /> ) } diff --git a/src/ducks/transactions/TransactionRow/TransactionRowDesktop.jsx b/src/ducks/transactions/TransactionRow/TransactionRowDesktop.jsx index ac5b4ee3f1..4dd44d9d63 100644 --- a/src/ducks/transactions/TransactionRow/TransactionRowDesktop.jsx +++ b/src/ducks/transactions/TransactionRow/TransactionRowDesktop.jsx @@ -61,7 +61,7 @@ const TransactionRowDesktop = props => { showTransactionCategoryModal, , categoryModal - ] = useTransactionCategoryModal(transaction) + ] = useTransactionCategoryModal({ transactions: [transaction] }) const handleClickCategory = useCallback( ev => { diff --git a/src/ducks/transactions/TransactionRow/index.jsx b/src/ducks/transactions/TransactionRow/index.jsx index f223fb459b..3ec20b8bf6 100644 --- a/src/ducks/transactions/TransactionRow/index.jsx +++ b/src/ducks/transactions/TransactionRow/index.jsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, { useCallback } from 'react' import flag from 'cozy-flags' @@ -19,13 +19,23 @@ export const useTransactionModal = transaction => { return [show, hide, modal] } -export const useTransactionCategoryModal = transaction => { +export const useTransactionCategoryModal = ({ + transactions, + beforeUpdate, + afterUpdates +}) => { const [modalOpened, show, hide] = useSwitch(false) + const handleBeforeUpdate = useCallback(() => { + hide() + beforeUpdate && beforeUpdate() + }, [beforeUpdate, hide]) + const modal = modalOpened ? ( ) : null From 4c9c0c152d9528b7aeef989c3120e8cc79617a9d Mon Sep 17 00:00:00 2001 From: JF-Cozy Date: Thu, 24 Jun 2021 16:49:40 +0200 Subject: [PATCH 5/9] feat: Add longPress on TransactionRowMobile + manage multiple selection --- package.json | 1 + .../TransactionRow/TransactionRowMobile.jsx | 175 +++++++++++++----- yarn.lock | 5 + 3 files changed, 134 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 8f0e2d0871..c150724667 100644 --- a/package.json +++ b/package.json @@ -216,6 +216,7 @@ "react-router": "3.2.4", "react-side-effect": "2.1.0", "react-swipeable-views": "0.13.9", + "react-tappable": "1.0.4", "redux": "4", "redux-logger": "3.0.6", "redux-raven-middleware": "1.2.0", diff --git a/src/ducks/transactions/TransactionRow/TransactionRowMobile.jsx b/src/ducks/transactions/TransactionRow/TransactionRowMobile.jsx index cf9f406177..02f6c63d80 100644 --- a/src/ducks/transactions/TransactionRow/TransactionRowMobile.jsx +++ b/src/ducks/transactions/TransactionRow/TransactionRowMobile.jsx @@ -1,6 +1,7 @@ import React, { useCallback, useMemo } from 'react' import PropTypes from 'prop-types' import cx from 'classnames' +import Tappable from 'react-tappable/lib/Tappable' import flag from 'cozy-flags' import { Media, Bd, Img } from 'cozy-ui/transpiled/react/Media' @@ -9,6 +10,7 @@ import ListItem from 'cozy-ui/transpiled/react/MuiCozyTheme/ListItem' import ListItemText from 'cozy-ui/transpiled/react/ListItemText' import Typography from 'cozy-ui/transpiled/react/Typography' import Figure from 'cozy-ui/transpiled/react/Figure' +import Checkbox from 'cozy-ui/transpiled/react/Checkbox' import TransactionActions from 'ducks/transactions/TransactionActions' @@ -28,6 +30,7 @@ import { import ApplicationDateCaption from 'ducks/transactions/TransactionRow/ApplicationDateCaption' import AccountCaption from 'ducks/transactions/TransactionRow/AccountCaption' import RecurrenceCaption from 'ducks/transactions/TransactionRow/RecurrenceCaption' +import { useSelectionContext } from 'ducks/context/SelectionContext' const TransactionRowMobile = props => { const { t } = useI18n() @@ -37,6 +40,13 @@ const TransactionRowMobile = props => { const [rawShowTransactionModal, , transactionModal] = useTransactionModal( transaction ) + const { + isSelectionModeEnabled, + isSelectionModeActive, + addToSelection, + isSelected, + removeFromSelection + } = useSelectionContext() const boundOnRef = useMemo(() => { return onRef.bind(null, transaction._id) @@ -59,60 +69,131 @@ const TransactionRowMobile = props => { const applicationDate = getApplicationDate(transaction) const recurrence = transaction.recurrence ? transaction.recurrence.data : null + const isTransactionSelected = useMemo(() => isSelected(transaction), [ + isSelected, + transaction + ]) + + const handleTap = useCallback( + ev => { + if (isSelectionModeActive) { + isTransactionSelected + ? removeFromSelection(transaction) + : addToSelection(transaction) + } else { + transaction._id && showTransactionModal(ev) + } + }, + [ + addToSelection, + isSelectionModeActive, + isTransactionSelected, + removeFromSelection, + showTransactionModal, + transaction + ] + ) + + const handlePress = useCallback(() => { + if (isSelectionModeEnabled) { + isTransactionSelected + ? removeFromSelection(transaction) + : addToSelection(transaction) + } + }, [ + addToSelection, + isSelectionModeEnabled, + isTransactionSelected, + removeFromSelection, + transaction + ]) + return ( <> - + + + + + )} + + + + + + + + + + + + {getLabel(transaction)} + + {!filteringOnAccount && ( + + )} + {applicationDate ? ( + + ) : null} + + + + + +
+ {recurrence && showRecurrence ? ( + + ) : null} + + + + + {showTransactionActions && ( + )} - onClick={transaction._id && showTransactionModal} - > - - - - - - {getLabel(transaction)} - - {!filteringOnAccount && } - {applicationDate ? ( - - ) : null} - - -
- {recurrence && showRecurrence ? ( - - ) : null} - - {showTransactionActions && ( - - )} {transactionModal} diff --git a/yarn.lock b/yarn.lock index 38714b7072..9be5039a63 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15834,6 +15834,11 @@ react-swipeable-views@0.13.9, react-swipeable-views@^0.13.3: react-swipeable-views-utils "^0.13.9" warning "^4.0.1" +react-tappable@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/react-tappable/-/react-tappable-1.0.4.tgz#282b7b3dc25e6170b55d1ea33d986b7624a19fb9" + integrity sha512-thWt8b11Hy1BmWDPuqbj+Qom5MmnMZDjfzgSE2L2Yh0Hoc9+I6DKNmWrOJGaZtH6fL4FWtRk4HwyMfUJ10M67g== + react-test-renderer@16.9.0, react-test-renderer@^16.0.0-0: version "16.9.0" resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.9.0.tgz#7ed657a374af47af88f66f33a3ef99c9610c8ae9" From 4bc1c74b85cbe81fdcba3bc7afe799c6e8b2d84e Mon Sep 17 00:00:00 2001 From: JF-Cozy Date: Wed, 23 Jun 2021 15:35:00 +0200 Subject: [PATCH 6/9] feat: Add SelectionBar in Transactions --- .../TransactionRow/index.spec.jsx | 2 + src/ducks/transactions/Transactions.jsx | 38 ++++++++++--------- src/ducks/transactions/Transactions.spec.jsx | 1 + 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/ducks/transactions/TransactionRow/index.spec.jsx b/src/ducks/transactions/TransactionRow/index.spec.jsx index a398860a0c..860c52733b 100644 --- a/src/ducks/transactions/TransactionRow/index.spec.jsx +++ b/src/ducks/transactions/TransactionRow/index.spec.jsx @@ -3,6 +3,8 @@ import React from 'react' import AppLike from 'test/AppLike' import TransactionRowMobile from './TransactionRowMobile' +jest.mock('react-tappable/lib/Tappable', () => ({ children }) => children) + describe('TransactionRowMobile', () => { const setup = ({ transactionAttributes } = {}) => { const SNACK_AND_WORK_MEALS = '400160' diff --git a/src/ducks/transactions/Transactions.jsx b/src/ducks/transactions/Transactions.jsx index 279198f0a0..9983dca660 100644 --- a/src/ducks/transactions/Transactions.jsx +++ b/src/ducks/transactions/Transactions.jsx @@ -23,6 +23,7 @@ import TransactionRowMobile from 'ducks/transactions/TransactionRow/TransactionR import TransactionRowDesktop from 'ducks/transactions/TransactionRow/TransactionRowDesktop' import { getDate } from 'ducks/transactions/helpers' import useVisible from 'hooks/useVisible' +import SelectionBar from 'ducks/selection/SelectionBar' export const sortByDate = (transactions = []) => sortBy(transactions, getDate).reverse() @@ -200,23 +201,26 @@ export class TransactionsDumb extends React.Component { } = this.props return ( - - {showTriggerErrors ? : null} - - + <> + + + {showTriggerErrors ? : null} + + + ) } } diff --git a/src/ducks/transactions/Transactions.spec.jsx b/src/ducks/transactions/Transactions.spec.jsx index a6fdadf2b1..71cf0381d3 100644 --- a/src/ducks/transactions/Transactions.spec.jsx +++ b/src/ducks/transactions/Transactions.spec.jsx @@ -14,6 +14,7 @@ import TransactionPageErrors from 'ducks/transactions/TransactionPageErrors' // No need to test this here jest.mock('ducks/transactions/TransactionPageErrors', () => () => null) +jest.mock('react-tappable/lib/Tappable', () => ({ children }) => children) const allTransactions = data['io.cozy.bank.operations'] From 88714432f67e2fcfb495ded75be9a7cc8b4cb346 Mon Sep 17 00:00:00 2001 From: JF-Cozy Date: Wed, 23 Jun 2021 16:03:41 +0200 Subject: [PATCH 7/9] fix: Duplicated keys in locales --- src/locales/en.json | 2 -- src/locales/fr.json | 3 +-- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/locales/en.json b/src/locales/en.json index 7c85b6fb95..ec13589273 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -538,7 +538,6 @@ "accounts": "Accounts", "groups": "Groups", "configuration": "Config.", - "security": "Security", "debug": "Debug", "personal-info": { "title": "Personal information", @@ -652,7 +651,6 @@ }, "Reimbursements": { - "title": "Health reimbursements", "title": { "healthExpenses": "Health reimbursements", "professionalExpenses": "Professional reimbursements", diff --git a/src/locales/fr.json b/src/locales/fr.json index f87ac73cf9..7ef96162e2 100644 --- a/src/locales/fr.json +++ b/src/locales/fr.json @@ -376,11 +376,10 @@ "bank": "Banque", "accounts": "Comptes", "new-group": "Nouveau groupe", - "create": "créer", + "create": "Créer un groupe", "edit": "éditer", "manage-groups": "Gérer mes groupes", "delete": "supprimer", - "create": "Créer un groupe", "edit-group": "Editer le groupe", "no-groups": "Avec les groupes, vous pouvez facilement rassembler plusieurs comptes, n'hésitez pas à en créer un !", "back-to-groups": "Revenir aux groupes", From f106993db8191390e87f99b376b9cb6e1978c00a Mon Sep 17 00:00:00 2001 From: JF-Cozy Date: Thu, 24 Jun 2021 14:44:57 +0200 Subject: [PATCH 8/9] test: Refactor to move tests from Transaction to TransactionRow --- .../TransactionRow/index.spec.jsx | 76 ++++++++++++++++- src/ducks/transactions/Transactions.spec.jsx | 82 ++----------------- 2 files changed, 80 insertions(+), 78 deletions(-) diff --git a/src/ducks/transactions/TransactionRow/index.spec.jsx b/src/ducks/transactions/TransactionRow/index.spec.jsx index 860c52733b..9785ba61c8 100644 --- a/src/ducks/transactions/TransactionRow/index.spec.jsx +++ b/src/ducks/transactions/TransactionRow/index.spec.jsx @@ -1,7 +1,16 @@ -import { mount } from 'enzyme' import React from 'react' +import { mount } from 'enzyme' +import { render } from '@testing-library/react' + import AppLike from 'test/AppLike' +import { getClient } from 'test/client' +import store, { normalizeData } from 'test/store' +import data from 'test/fixtures' + import TransactionRowMobile from './TransactionRowMobile' +import TransactionRowDesktop from './TransactionRowDesktop' + +const allTransactions = data['io.cozy.bank.operations'] jest.mock('react-tappable/lib/Tappable', () => ({ children }) => children) @@ -56,3 +65,68 @@ describe('TransactionRowMobile', () => { ) }) }) + +describe('Transaction rows', () => { + let client, transaction + + const setup = (row, withTable) => { + return render( + + {withTable ? ( + + {row} +
+ ) : ( + row + )} +
+ ) + } + + const rawTransaction = allTransactions[0] + rawTransaction._type = 'io.cozy.bank.operations' + + beforeEach(() => { + client = getClient() + client.ensureStore() + const datastore = normalizeData({ + 'io.cozy.bank.accounts': data['io.cozy.bank.accounts'] + }) + jest + .spyOn(client, 'getDocumentFromState') + .mockImplementation((doctype, id) => { + return datastore[doctype][id] + }) + transaction = client.hydrateDocument(rawTransaction) + }) + + it('should render correctly on desktop', () => { + const handleRef = jest.fn() + const root = setup( + , + true + ) + expect(root.getByText('Compte courant Isabelle - BNPP')).toBeTruthy() + }) + + it('should render correctly on mobile', () => { + const handleRef = jest.fn() + const root = setup( + , + false + ) + + expect(root.getByText('Compte courant Isabelle - BNPP')).toBeTruthy() + expect(handleRef).toHaveBeenCalled() + }) +}) diff --git a/src/ducks/transactions/Transactions.spec.jsx b/src/ducks/transactions/Transactions.spec.jsx index 71cf0381d3..4999f4012b 100644 --- a/src/ducks/transactions/Transactions.spec.jsx +++ b/src/ducks/transactions/Transactions.spec.jsx @@ -1,87 +1,15 @@ /* global mount */ import React from 'react' -import { render } from '@testing-library/react' -import TransactionRowDesktop from './TransactionRow/TransactionRowDesktop' -import TransactionRowMobile from './TransactionRow/TransactionRowMobile' -import { TransactionsDumb, sortByDate } from './Transactions' -import data from '../../../test/fixtures' -import store from '../../../test/store' -import AppLike from '../../../test/AppLike' -import { getClient } from 'test/client' -import { normalizeData } from 'test/store' + +import data from 'test/fixtures' +import AppLike from 'test/AppLike' + import TransactionPageErrors from 'ducks/transactions/TransactionPageErrors' +import { TransactionsDumb, sortByDate } from './Transactions' // No need to test this here jest.mock('ducks/transactions/TransactionPageErrors', () => () => null) -jest.mock('react-tappable/lib/Tappable', () => ({ children }) => children) - -const allTransactions = data['io.cozy.bank.operations'] - -describe('transaction row', () => { - let client, transaction - - const setup = (row, withTable) => { - return render( - - {withTable ? ( - - {row} -
- ) : ( - row - )} -
- ) - } - - const rawTransaction = allTransactions[0] - rawTransaction._type = 'io.cozy.bank.operations' - - beforeEach(() => { - client = getClient() - client.ensureStore() - const datastore = normalizeData({ - 'io.cozy.bank.accounts': data['io.cozy.bank.accounts'] - }) - jest - .spyOn(client, 'getDocumentFromState') - .mockImplementation((doctype, id) => { - return datastore[doctype][id] - }) - transaction = client.hydrateDocument(rawTransaction) - }) - - it('should render correctly on desktop', () => { - const handleRef = jest.fn() - const root = setup( - , - true - ) - expect(root.getByText('Compte courant Isabelle - BNPP')).toBeTruthy() - }) - - it('should render correctly on mobile', () => { - const handleRef = jest.fn() - const root = setup( - , - false - ) - - expect(root.getByText('Compte courant Isabelle - BNPP')).toBeTruthy() - expect(handleRef).toHaveBeenCalled() - }) -}) describe('Transactions', () => { let i = 0 From bc4e8d3ce731aad0ca988898cab05932940e8b4b Mon Sep 17 00:00:00 2001 From: JF-Cozy Date: Thu, 24 Jun 2021 16:51:09 +0200 Subject: [PATCH 9/9] test: Add tests for selecting multiple transactions and categorize them --- src/ducks/transactions/Transactions.spec.jsx | 99 ++++++++++++++++++++ test/fixtures/unit-tests.json | 1 + 2 files changed, 100 insertions(+) diff --git a/src/ducks/transactions/Transactions.spec.jsx b/src/ducks/transactions/Transactions.spec.jsx index 4999f4012b..0156f38758 100644 --- a/src/ducks/transactions/Transactions.spec.jsx +++ b/src/ducks/transactions/Transactions.spec.jsx @@ -1,9 +1,16 @@ /* global mount */ import React from 'react' +import Tappable from 'react-tappable/lib/Tappable' +import { render, fireEvent, wait } from '@testing-library/react' +import flag from 'cozy-flags' + +import useBreakpoints from 'cozy-ui/transpiled/react/hooks/useBreakpoints' +import Alerter from 'cozy-ui/transpiled/react/Alerter' import data from 'test/fixtures' import AppLike from 'test/AppLike' +import { createMockClient } from 'cozy-client/dist/mock' import TransactionPageErrors from 'ducks/transactions/TransactionPageErrors' import { TransactionsDumb, sortByDate } from './Transactions' @@ -11,6 +18,17 @@ import { TransactionsDumb, sortByDate } from './Transactions' // No need to test this here jest.mock('ducks/transactions/TransactionPageErrors', () => () => null) +jest.mock('react-tappable/lib/Tappable', () => ({ + default: jest.fn(), + __esModule: true +})) + +jest.mock('cozy-ui/transpiled/react/hooks/useBreakpoints', () => ({ + __esModule: true, + default: jest.fn(), + BreakpointsProvider: ({ children }) => children +})) + describe('Transactions', () => { let i = 0 const mockTransactions = data['io.cozy.bank.operations'].map(x => ({ @@ -62,3 +80,84 @@ describe('Transactions', () => { expect(instance2.transactions).toEqual(sortByDate(slicedTransactions)) }) }) + +describe('SelectionBar', () => { + beforeAll(() => { + flag('banks.selectionMode.enabled', true) + useBreakpoints.mockReturnValue({ isMobile: true }) + }) + + afterAll(() => { + flag('banks.selectionMode.enabled', undefined) + useBreakpoints.clearMock() + }) + + let i = 0 + const mockTransactions = data['io.cozy.bank.operations'].map(x => ({ + _id: `transaction-id-${i++}`, + ...x + })) + + // Mock tappable so that key down fires its onPress event + Tappable.mockImplementation(({ children, onPress, onTap }) => { + return ( +
+ {children} +
+ ) + }) + + const setup = () => { + const client = createMockClient({}) + client.save.mockImplementation(doc => ({ data: doc })) + + const root = render( + + + + + ) + + return { root, client } + } + + it('should show selection bar and open category modal', async () => { + const { root, client } = setup() + const { getByText, queryByText } = root + + fireEvent.keyDown(getByText('Remboursement Pret Lcl')) + let node = queryByText('item selected') + expect(node).toBeTruthy() + expect(node.parentNode.textContent).toBe('1 item selected') + + // should remove the selection bar + fireEvent.click(getByText('Remboursement Pret Lcl')) + expect(queryByText('item selected')).toBeFalsy() + + // should show 2 transactions selected + fireEvent.keyDown(getByText('Remboursement Pret Lcl')) + node = queryByText('item selected') + fireEvent.click(getByText('Edf Particuliers')) + expect(node.parentNode.textContent).toBe('2 items selected') + + // sould unselected transaction + fireEvent.click(getByText('Edf Particuliers')) + expect(node.parentNode.textContent).toBe('1 item selected') + fireEvent.click(getByText('Edf Particuliers')) + expect(node.parentNode.textContent).toBe('2 items selected') + + // selecting a category + fireEvent.click(getByText('Categorize')) + fireEvent.click(getByText('Everyday life')) + fireEvent.click(getByText('Supermarket')) + + // should remove the selection bar and show a success alert + expect(queryByText('item selected')).toBeFalsy() + await wait(() => expect(client.save).toHaveBeenCalledTimes(2)) + expect(queryByText('2 operations have been recategorized')).toBeTruthy() + }) +}) diff --git a/test/fixtures/unit-tests.json b/test/fixtures/unit-tests.json index 8566b61762..d5ff008a74 100644 --- a/test/fixtures/unit-tests.json +++ b/test/fixtures/unit-tests.json @@ -180,6 +180,7 @@ ], "io.cozy.bank.operations": [ { + "_id": "remboursement", "demo": true, "manualCategoryId": "401010", "account": "compteisa1",