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:
parent
03c3feef16
commit
a2071bfed2
@ -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
3
.gitignore
vendored
@ -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
|
||||||
|
@ -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;
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
@ -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();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user