1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-02 12:47:41 +02:00

Chore: Mobile: Plugin settings screen: Improve accessibility and tests (#10267)

This commit is contained in:
Henry Heino 2024-04-08 04:36:40 -07:00 committed by GitHub
parent 03c3feef16
commit a2071bfed2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 73 additions and 37 deletions

View File

@ -577,7 +577,8 @@ packages/app-mobile/components/screens/ConfigScreen/SettingItem.js
packages/app-mobile/components/screens/ConfigScreen/SettingsButton.js packages/app-mobile/components/screens/ConfigScreen/SettingsButton.js
packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js
packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/ActionButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.test.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.test.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginToggle.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginToggle.js

3
.gitignore vendored
View File

@ -557,7 +557,8 @@ packages/app-mobile/components/screens/ConfigScreen/SettingItem.js
packages/app-mobile/components/screens/ConfigScreen/SettingsButton.js packages/app-mobile/components/screens/ConfigScreen/SettingsButton.js
packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js
packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/ActionButton.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginBox/index.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.test.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.test.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginStates.js
packages/app-mobile/components/screens/ConfigScreen/plugins/PluginToggle.js packages/app-mobile/components/screens/ConfigScreen/plugins/PluginToggle.js

View File

@ -0,0 +1,35 @@
import * as React from 'react';
import { useCallback } from 'react';
import { ItemEvent, PluginItem } from '@joplin/lib/components/shared/config/plugins/types';
import { Button, ButtonProps } from 'react-native-paper';
export type PluginCallback = (event: ItemEvent)=> void;
interface Props extends Omit<ButtonProps, 'item'|'onPress'|'children'> {
item: PluginItem;
onPress?: PluginCallback;
title: string;
}
const ActionButton: React.FC<Props> = props => {
const onPress = useCallback(() => {
props.onPress?.({ item: props.item });
}, [props.onPress, props.item]);
// Include additional information about the button when using a screen
// reader (helps make it clear which plugin a delete/enable button).
//
// Because this is being read by a screen reader and to reduce load on
// translators, the method of joining the title and manifest name is not
// marked as translatable.
const accessibilityLabel = `${props.title} ${props.item.manifest.name}`;
return (
<Button
{...props}
onPress={onPress}
accessibilityLabel={accessibilityLabel}
>{props.title}</Button>
);
};
export default ActionButton;

View File

@ -1,10 +1,11 @@
import * as React from 'react'; import * as React from 'react';
import { Icon, Button, Card, Chip } from 'react-native-paper'; import { Icon, Card, Chip } from 'react-native-paper';
import { _ } from '@joplin/lib/locale'; import { _ } from '@joplin/lib/locale';
import { View } from 'react-native'; import { View } from 'react-native';
import { ItemEvent, PluginItem } from '@joplin/lib/components/shared/config/plugins/types'; import { PluginItem } from '@joplin/lib/components/shared/config/plugins/types';
import shim from '@joplin/lib/shim'; import shim from '@joplin/lib/shim';
import PluginService from '@joplin/lib/services/plugins/PluginService'; import PluginService from '@joplin/lib/services/plugins/PluginService';
import ActionButton, { PluginCallback } from './ActionButton';
export enum InstallState { export enum InstallState {
NotInstalled, NotInstalled,
@ -19,8 +20,6 @@ export enum UpdateState {
HasBeenUpdated = 4, HasBeenUpdated = 4,
} }
type PluginCallback = (event: ItemEvent)=> void;
interface Props { interface Props {
item: PluginItem; item: PluginItem;
isCompatible: boolean; isCompatible: boolean;
@ -52,41 +51,42 @@ const PluginBox: React.FC<Props> = props => {
}; };
const installButton = ( const installButton = (
<Button <ActionButton
onPress={() => props.onInstall?.({ item })} item={item}
onPress={props.onInstall}
disabled={props.installState !== InstallState.NotInstalled || !props.isCompatible} disabled={props.installState !== InstallState.NotInstalled || !props.isCompatible}
loading={props.installState === InstallState.Installing} loading={props.installState === InstallState.Installing}
> title={installButtonTitle()}
{installButtonTitle()} />
</Button>
); );
const updateButtonTitle = () => { const getUpdateButtonTitle = () => {
if (props.updateState === UpdateState.Updating) return _('Updating...'); if (props.updateState === UpdateState.Updating) return _('Updating...');
if (props.updateState === UpdateState.HasBeenUpdated) return _('Updated'); if (props.updateState === UpdateState.HasBeenUpdated) return _('Updated');
return _('Update'); return _('Update');
}; };
const updateButton = ( const updateButton = (
<Button <ActionButton
onPress={() => props.onUpdate?.({ item })} item={item}
onPress={props.onUpdate}
disabled={props.updateState !== UpdateState.CanUpdate || !props.isCompatible} disabled={props.updateState !== UpdateState.CanUpdate || !props.isCompatible}
loading={props.updateState === UpdateState.Updating} loading={props.updateState === UpdateState.Updating}
> title={getUpdateButtonTitle()}
{updateButtonTitle()} />
</Button>
); );
const deleteButton = ( const deleteButton = (
<Button <ActionButton
onPress={() => props.onDelete?.({ item })} item={item}
onPress={props.onDelete}
disabled={props.item.deleted} disabled={props.item.deleted}
> title={props.item.deleted ? _('Deleted') : _('Delete')}
{props.item.deleted ? _('Deleted') : _('Delete')} />
</Button>
); );
const disableButton = <Button onPress={() => props.onToggle?.({ item })}>{_('Disable')}</Button>; const disableButton = <ActionButton item={item} onPress={props.onToggle} title={_('Disable')}/>;
const enableButton = <Button onPress={() => props.onToggle?.({ item })}>{_('Enable')}</Button>; const enableButton = <ActionButton item={item} onPress={props.onToggle} title={_('Enable')}/>;
const aboutButton = <Button icon='web' onPress={() => props.onAboutPress?.({ item })}>{_('About')}</Button>; const aboutButton = <ActionButton icon='web' item={item} onPress={props.onAboutPress} title={_('About')}/>;
const renderErrorsChip = () => { const renderErrorsChip = () => {
if (!props.hasErrors) return null; if (!props.hasErrors) return null;
@ -97,7 +97,7 @@ const PluginBox: React.FC<Props> = props => {
mode='outlined' mode='outlined'
onPress={() => props.onShowPluginLog({ item })} onPress={() => props.onShowPluginLog({ item })}
> >
Error {_('Error')}
</Chip> </Chip>
); );
}; };

View File

@ -2,7 +2,7 @@ import * as React from 'react';
import RepositoryApi from '@joplin/lib/services/plugins/RepositoryApi'; import RepositoryApi from '@joplin/lib/services/plugins/RepositoryApi';
import { afterAllCleanUp, afterEachCleanUp, createTempDir, mockMobilePlatform, setupDatabaseAndSynchronizer, supportDir, switchClient } from '@joplin/lib/testing/test-utils'; import { afterAllCleanUp, afterEachCleanUp, createTempDir, mockMobilePlatform, setupDatabaseAndSynchronizer, supportDir, switchClient } from '@joplin/lib/testing/test-utils';
import { render, screen, waitFor } from '@testing-library/react-native'; import { render, screen } from '@testing-library/react-native';
import '@testing-library/react-native/extend-expect'; import '@testing-library/react-native/extend-expect';
import Setting from '@joplin/lib/models/Setting'; import Setting from '@joplin/lib/models/Setting';
@ -124,18 +124,17 @@ describe('PluginStates', () => {
initialPluginSettings={defaultPluginSettings} initialPluginSettings={defaultPluginSettings}
/>, />,
); );
expect(await screen.findByText('ABC Sheet Music')).not.toBeNull(); expect(await screen.findByText('ABC Sheet Music')).toBeVisible();
expect(await screen.findByText('Backlinks to note')).not.toBeNull(); expect(await screen.findByText('Backlinks to note')).toBeVisible();
const shouldBothBeUpdatable = platform === 'android'; expect(await screen.findByRole('button', { name: 'Update ABC Sheet Music', disabled: false })).toBeVisible();
await waitFor(async () => {
const updateButtons = await screen.findAllByText('Update');
expect(updateButtons).toHaveLength(shouldBothBeUpdatable ? 2 : 1);
});
const updateButtons = await screen.findAllByText('Update'); // Backlinks to note should not be updatable on iOS (it's not _recommended).
for (const button of updateButtons) { const backlinksToNoteQuery = { name: 'Update Backlinks to note', disabled: false };
expect(button).not.toBeDisabled(); if (platform === 'android') {
expect(await screen.findByRole('button', backlinksToNoteQuery)).toBeVisible();
} else {
expect(await screen.queryByRole('button', backlinksToNoteQuery)).toBeNull();
} }
}); });
}); });