Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/components/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -571,7 +571,7 @@ function MenuItem({
rightIconWrapperStyle,
titleAccessibilityRole,
}: MenuItemProps) {
const icons = useMemoizedLazyExpensifyIcons(['ArrowRight', 'FallbackAvatar', 'DotIndicator', 'Checkmark']);
const icons = useMemoizedLazyExpensifyIcons(['ArrowRight', 'FallbackAvatar', 'DotIndicator', 'Checkmark', 'NewWindow']);
const {translate} = useLocalize();
const theme = useTheme();
const styles = useThemeStyles();
Expand All @@ -581,7 +581,6 @@ function MenuItem({
const {isExecuting, singleExecution, waitForNavigate} = useContext(MenuItemGroupContext) ?? {};
const popoverAnchor = useRef<View>(null);
const deviceHasHoverSupport = hasHoverSupport();

const isCompact = viewMode === CONST.OPTION_MODE.COMPACT;
const isDeleted = style && Array.isArray(style) ? style.includes(styles.offlineFeedbackDeleted) : false;
const descriptionVerticalMargin = shouldShowDescriptionOnTop ? styles.mb1 : styles.mt1;
Expand Down Expand Up @@ -722,6 +721,12 @@ function MenuItem({

const isIDPassed = !!iconReportID || !!iconAccountID || iconAccountID === CONST.DEFAULT_NUMBER_ID;

const isNewWindowIcon = iconRight === icons.NewWindow;
let enhancedAccessibilityLabel = accessibilityLabel ?? defaultAccessibilityLabel;
if (isNewWindowIcon) {
enhancedAccessibilityLabel = `${enhancedAccessibilityLabel}. ${translate('common.opensInNewTab')}`;
}

return (
<View
style={rootWrapperStyle}
Expand Down Expand Up @@ -773,7 +778,7 @@ function MenuItem({
disabled={disabled || isExecuting}
ref={mergeRefs(ref, popoverAnchor)}
role={role}
accessibilityLabel={`${accessibilityLabel ?? defaultAccessibilityLabel}${brickRoadIndicator ? `. ${translate('common.yourReviewIsRequired')}` : ''}`}
accessibilityLabel={`${enhancedAccessibilityLabel}${brickRoadIndicator ? `. ${translate('common.yourReviewIsRequired')}` : ''}`}
accessible={shouldBeAccessible}
tabIndex={tabIndex}
onFocus={onFocus}
Expand Down
1 change: 1 addition & 0 deletions src/languages/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ const translations: TranslationDeepObject<typeof en> = {
exchangeRate: 'Wechselkurs',
reimbursableTotal: 'Erstattungsfähiger Gesamtbetrag',
nonReimbursableTotal: 'Nicht erstattungsfähige Gesamtsumme',
opensInNewTab: 'Wird in einem neuen Tab geöffnet',
locked: 'Gesperrt',
month: 'Monat',
week: 'Woche',
Expand Down
1 change: 1 addition & 0 deletions src/languages/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,7 @@ const translations = {
exchangeRate: 'Exchange rate',
reimbursableTotal: 'Reimbursable total',
nonReimbursableTotal: 'Non-reimbursable total',
opensInNewTab: 'Opens in a new tab',
locked: 'Locked',
month: 'Month',
week: 'Week',
Expand Down
1 change: 1 addition & 0 deletions src/languages/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,7 @@ const translations: TranslationDeepObject<typeof en> = {
exchangeRate: 'Tipo de cambio',
reimbursableTotal: 'Total reembolsable',
nonReimbursableTotal: 'Total no reembolsable',
opensInNewTab: 'Se abre en una nueva pestaña',
locked: 'Bloqueado',
month: 'Monat',
week: 'Semana',
Expand Down
1 change: 1 addition & 0 deletions src/languages/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ const translations: TranslationDeepObject<typeof en> = {
exchangeRate: 'Taux de change',
reimbursableTotal: 'Total remboursable',
nonReimbursableTotal: 'Total non remboursable',
opensInNewTab: 'S’ouvre dans un nouvel onglet',
locked: 'Verrouillé',
month: 'Mois',
week: 'Semaine',
Expand Down
1 change: 1 addition & 0 deletions src/languages/it.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ const translations: TranslationDeepObject<typeof en> = {
exchangeRate: 'Tasso di cambio',
reimbursableTotal: 'Totale rimborsabile',
nonReimbursableTotal: 'Totale non rimborsabile',
opensInNewTab: 'Si apre in una nuova scheda',
locked: 'Bloccato',
month: 'Mese',
week: 'Settimana',
Expand Down
1 change: 1 addition & 0 deletions src/languages/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -530,6 +530,7 @@ const translations: TranslationDeepObject<typeof en> = {
exchangeRate: '為替レート',
reimbursableTotal: '精算対象合計',
nonReimbursableTotal: '未払い対象外の合計',
opensInNewTab: '新しいタブで開きます',
locked: 'ロックされています',
month: '月',
week: '週',
Expand Down
1 change: 1 addition & 0 deletions src/languages/nl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@ const translations: TranslationDeepObject<typeof en> = {
actionRequired: 'Actie vereist',
duplicate: 'Dupliceren',
duplicated: 'Gedupliceerd',
opensInNewTab: 'Opent in een nieuw tabblad',
locked: 'Vergrendeld',
duplicateExpense: 'Dubbele uitgave',
exchangeRate: 'Wisselkoers',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@ const translations: TranslationDeepObject<typeof en> = {
actionRequired: 'Wymagane działanie',
duplicate: 'Duplikuj',
duplicated: 'Zduplikowano',
opensInNewTab: 'Otwiera się w nowej karcie',
locked: 'Zablokowane',
duplicateExpense: 'Zduplikowany wydatek',
exchangeRate: 'Kurs wymiany',
Expand Down
1 change: 1 addition & 0 deletions src/languages/pt-BR.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@ const translations: TranslationDeepObject<typeof en> = {
exchangeRate: 'Taxa de câmbio',
reimbursableTotal: 'Total reembolsável',
nonReimbursableTotal: 'Total não reembolsável',
opensInNewTab: 'Abre em uma nova aba',
locked: 'Bloqueado',
month: 'Mês',
week: 'Semana',
Expand Down
1 change: 1 addition & 0 deletions src/languages/zh-hans.ts
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,7 @@ const translations: TranslationDeepObject<typeof en> = {
exchangeRate: '汇率',
reimbursableTotal: '可报销总额',
nonReimbursableTotal: '不可报销总额',
opensInNewTab: '在新标签页中打开',
locked: '已锁定',
month: '月',
week: '周',
Expand Down
86 changes: 86 additions & 0 deletions tests/ui/components/MenuItemTest.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import {render, screen} from '@testing-library/react-native';
import React from 'react';
import type {SvgProps} from 'react-native-svg';
import * as Expensicons from '@components/Icon/Expensicons';
import {LocaleContextProvider} from '@components/LocaleContextProvider';
import MenuItem from '@components/MenuItem';
import {translateLocal} from '../../utils/TestHelper';

// Mock useLazyAsset hook
jest.mock('@hooks/useLazyAsset', () => {
const ExpensiconsModule = jest.requireActual<Record<string, React.FC<SvgProps>>>('@components/Icon/Expensicons');
return {
useMemoizedLazyExpensifyIcons: jest.fn(() => ({
NewWindow: ExpensiconsModule.NewWindow,
})),
};
});

function Wrapper({children}: {children: React.ReactNode}) {
return <LocaleContextProvider>{children}</LocaleContextProvider>;
}

describe('MenuItem', () => {
describe('accessibility label with NewWindow icon', () => {
it('appends "Opens in a new tab" to the accessibility label when iconRight is NewWindow', () => {
const customLabel = 'Open external link';
const opensInNewTabText = translateLocal('common.opensInNewTab');
const expectedAccessibilityLabel = `${customLabel}. ${opensInNewTabText}`;

render(
<Wrapper>
<MenuItem
title={customLabel}
icon={Expensicons.Link}
iconRight={Expensicons.NewWindow}
accessibilityLabel={customLabel}
onPress={() => {}}
/>
</Wrapper>,
);

const menuItem = screen.getByLabelText(expectedAccessibilityLabel);
expect(menuItem).toBeOnTheScreen();
});

it('uses default accessibility label (from title) with "Opens in a new tab" when iconRight is NewWindow and no accessibilityLabel is passed', () => {
const title = 'Download app';
const opensInNewTabText = translateLocal('common.opensInNewTab');
const expectedAccessibilityLabel = `${title}. ${opensInNewTabText}`;

render(
<Wrapper>
<MenuItem
title={title}
icon={Expensicons.Download}
iconRight={Expensicons.NewWindow}
onPress={() => {}}
/>
</Wrapper>,
);

const menuItem = screen.getByLabelText(expectedAccessibilityLabel);
expect(menuItem).toBeOnTheScreen();
});

it('does not append "Opens in a new tab" when iconRight is not NewWindow', () => {
const customLabel = 'Go to next';

render(
<Wrapper>
<MenuItem
title={customLabel}
icon={Expensicons.Checkmark}
iconRight={Expensicons.Checkmark}
accessibilityLabel={customLabel}
onPress={() => {}}
/>
</Wrapper>,
);

const menuItem = screen.getByLabelText(customLabel);
expect(menuItem).toBeOnTheScreen();
expect(screen.queryByLabelText(`${customLabel}. ${translateLocal('common.opensInNewTab')}`)).not.toBeOnTheScreen();
});
});
});
Loading