diff --git a/src/components/MoneyRequestConfirmationList/sections/DescriptionField.tsx b/src/components/MoneyRequestConfirmationList/sections/DescriptionField.tsx index 6c38d0d39377..2c23c4f2299d 100644 --- a/src/components/MoneyRequestConfirmationList/sections/DescriptionField.tsx +++ b/src/components/MoneyRequestConfirmationList/sections/DescriptionField.tsx @@ -1,6 +1,7 @@ import MentionReportContext from '@components/HTMLEngineProvider/HTMLRenderers/MentionReportRenderer/MentionReportContext'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import {useConfirmationFields} from '@components/MoneyRequestConfirmationFields/context'; +import usePolicyCategoriesForConfirmation from '@components/MoneyRequestConfirmationList/hooks/usePolicyCategoriesForConfirmation'; import {ShowContextMenuActionsContext, ShowContextMenuStateContext} from '@components/ShowContextMenuContext'; import TextInput from '@components/TextInput'; @@ -29,7 +30,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import React, {useRef} from 'react'; import {View} from 'react-native'; -import {descriptionStateSelector} from './selectors'; +import {categoryStateSelector, descriptionStateSelector} from './selectors'; import useTransactionSelector from './useTransactionSelector'; type DescriptionFieldProps = { @@ -49,6 +50,12 @@ function DescriptionField({isDescriptionRequired, policy}: DescriptionFieldProps const [splitDraftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`); const descriptionState = useTransactionSelector(transactionID, descriptionStateSelector); + const categoryState = useTransactionSelector(transactionID, categoryStateSelector); + const policyCategories = usePolicyCategoriesForConfirmation(policy?.id); + + // A category can carry a hint telling the user what to write in the description, so show it under the input once + // that category is selected, the same way the dedicated description step does. + const descriptionHint = categoryState?.category ? (policyCategories?.[categoryState.category]?.commentHint ?? '') : ''; // `getDescription` returns raw `transaction.comment.comment`, which can be HTML for saved transactions. // We normalize to markdown so both the read-only and editable inputs receive a consistent format. @@ -116,6 +123,8 @@ function DescriptionField({isDescriptionRequired, policy}: DescriptionFieldProps maxAutoGrowHeight={variables.textInputAutoGrowMaxHeight} type="markdown" excludedMarkdownStyles={!policy ? ['mentionReport'] : []} + hint={descriptionHint} + shouldRenderHintAsHTML={!!descriptionHint} /> ) : ( diff --git a/tests/unit/components/MoneyRequestConfirmationList/DescriptionField.test.tsx b/tests/unit/components/MoneyRequestConfirmationList/DescriptionField.test.tsx new file mode 100644 index 000000000000..58bceea0a5a7 --- /dev/null +++ b/tests/unit/components/MoneyRequestConfirmationList/DescriptionField.test.tsx @@ -0,0 +1,96 @@ +import {render, screen} from '@testing-library/react-native'; + +import ConfirmationFieldsProvider from '@components/MoneyRequestConfirmationFields/Provider'; +import DescriptionField from '@components/MoneyRequestConfirmationList/sections/DescriptionField'; + +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {PolicyCategories} from '@src/types/onyx'; + +import React from 'react'; +import Onyx from 'react-native-onyx'; + +import createRandomPolicy from '../../../utils/collections/policies'; +import createRandomTransaction from '../../../utils/collections/transaction'; +import waitForBatchedUpdatesWithAct from '../../../utils/waitForBatchedUpdatesWithAct'; + +jest.mock('@components/TextInput', () => { + const {Text} = jest.requireActual>>('react-native'); + return ({label, hint}: {label?: string; hint?: string}) => ( + <> + {label} + {hint ? {hint} : null} + + ); +}); + +jest.mock('@hooks/useLocalize', () => () => ({translate: (key: string) => key.replace('common.', '')})); +jest.mock('@hooks/useThemeStyles', () => () => ({})); + +const transactionID = 'transactionID'; +const policyID = 'POLICY_WITH_CATEGORY_HINTS'; +const policy = {...createRandomPolicy(0), id: policyID}; + +const policyCategories: PolicyCategories = { + Advertising: {name: 'Advertising', enabled: true, areCommentsRequired: true, commentHint: 'Client name', externalID: '', origin: ''}, + Benefits: {name: 'Benefits', enabled: true, areCommentsRequired: true, externalID: '', origin: ''}, +}; + +const renderDescriptionField = () => + render( + + + , + ); + +const setUpDraftTransactionWithCategory = async (category: string) => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {...createRandomTransaction(0), transactionID, category}); + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, policyCategories); + await waitForBatchedUpdatesWithAct(); +}; + +describe('DescriptionField', () => { + beforeAll(() => { + Onyx.init({keys: ONYXKEYS}); + }); + + afterEach(async () => { + await Onyx.clear(); + await waitForBatchedUpdatesWithAct(); + }); + + it("displays the selected category's description hint while creating an expense", async () => { + await setUpDraftTransactionWithCategory('Advertising'); + + renderDescriptionField(); + + expect(await screen.findByText('Client name')).toBeOnTheScreen(); + }); + + it('displays no hint when the selected category has no description hint', async () => { + await setUpDraftTransactionWithCategory('Benefits'); + + renderDescriptionField(); + + await waitForBatchedUpdatesWithAct(); + expect(screen.queryByText('Client name')).not.toBeOnTheScreen(); + }); + + it('displays no hint when no category is selected', async () => { + await Onyx.merge(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`, policyCategories); + await waitForBatchedUpdatesWithAct(); + + renderDescriptionField(); + + await waitForBatchedUpdatesWithAct(); + expect(screen.queryByText('Client name')).not.toBeOnTheScreen(); + }); +});