mirror of
https://github.com/Kong/insomnia
synced 2024-11-08 06:39:48 +00:00
Improved accessibility for activity toggle. (#4928)
* Fix issue with activity toggle accessibility * add unit tests * update styled components to the object format Co-authored-by: Mark Kim <mark.kim@konghq.com> Co-authored-by: gatzjames <jamesgatzos@gmail.com>
This commit is contained in:
parent
57b5493e9e
commit
0ab0d72462
@ -1,58 +0,0 @@
|
||||
import { MultiSwitch } from 'insomnia-components';
|
||||
import React, { FunctionComponent, useCallback } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import type { GlobalActivity } from '../../common/constants';
|
||||
import { ACTIVITY_DEBUG, ACTIVITY_SPEC, ACTIVITY_UNIT_TEST } from '../../common/constants';
|
||||
import { isDesign } from '../../models/workspace';
|
||||
import { selectActiveActivity, selectActiveWorkspace } from '../redux/selectors';
|
||||
import { HandleActivityChange } from './wrapper';
|
||||
|
||||
interface Props {
|
||||
handleActivityChange: HandleActivityChange;
|
||||
}
|
||||
|
||||
export const ActivityToggle: FunctionComponent<Props> = ({ handleActivityChange }) => {
|
||||
const choices = [
|
||||
{
|
||||
label: 'Design',
|
||||
value: ACTIVITY_SPEC,
|
||||
},
|
||||
{
|
||||
label: 'Debug',
|
||||
value: ACTIVITY_DEBUG,
|
||||
},
|
||||
{
|
||||
label: 'Test',
|
||||
value: ACTIVITY_UNIT_TEST,
|
||||
},
|
||||
];
|
||||
|
||||
const activeActivity = useSelector(selectActiveActivity);
|
||||
const activeWorkspace = useSelector(selectActiveWorkspace);
|
||||
|
||||
const onChange = useCallback((nextActivity: string) => {
|
||||
handleActivityChange({
|
||||
workspaceId: activeWorkspace?._id,
|
||||
// TODO: unsound cast
|
||||
nextActivity: nextActivity as GlobalActivity,
|
||||
});
|
||||
}, [handleActivityChange, activeWorkspace]);
|
||||
|
||||
if (!activeActivity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!activeWorkspace || !isDesign(activeWorkspace)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<MultiSwitch
|
||||
name="activity-toggle"
|
||||
onChange={onChange}
|
||||
choices={choices}
|
||||
selectedValue={activeActivity}
|
||||
/>
|
||||
);
|
||||
};
|
@ -0,0 +1,100 @@
|
||||
import { describe } from '@jest/globals';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import { ACTIVITY_DEBUG, GlobalActivity } from '../../../common/constants';
|
||||
import { Workspace } from '../../../models/workspace';
|
||||
import { ActivityToggle } from './activity-toggle';
|
||||
|
||||
const mockWorkspace: Workspace = {
|
||||
_id: 'wrk_fddff92a88ce4cd2b05bf00506384321',
|
||||
type: 'Workspace',
|
||||
parentId: 'proj_default-project',
|
||||
modified: 1655301684731,
|
||||
created: 1655301684731,
|
||||
name: 'testing',
|
||||
description: '',
|
||||
scope: 'design',
|
||||
isPrivate: false,
|
||||
};
|
||||
|
||||
describe('<ActivityToggle />', () => {
|
||||
test('renders without exploding', async () => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<ActivityToggle
|
||||
activity={ACTIVITY_DEBUG}
|
||||
workspace={mockWorkspace}
|
||||
handleActivityChange={async () => {}}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
const debug = screen.getByText('Debug');
|
||||
expect(debug).toBeDefined();
|
||||
expect(debug).toHaveClass('active');
|
||||
expect(screen.getByText('Test')).toBeDefined();
|
||||
expect(screen.getByText('Design')).toBeDefined();
|
||||
});
|
||||
|
||||
test('toggles to a different activity', async () => {
|
||||
let activity = ACTIVITY_DEBUG;
|
||||
|
||||
const user = userEvent.setup();
|
||||
const handleActivityChange = async ({ nextActivity }: { nextActivity: GlobalActivity }) => {
|
||||
activity = nextActivity;
|
||||
};
|
||||
|
||||
const { rerender } = render(
|
||||
<MemoryRouter>
|
||||
<ActivityToggle
|
||||
activity={activity}
|
||||
workspace={mockWorkspace}
|
||||
handleActivityChange={handleActivityChange}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
const debug = screen.getByText('Debug');
|
||||
expect(debug).toBeDefined();
|
||||
expect(debug).toHaveClass('active');
|
||||
|
||||
const test = screen.getByText('Test');
|
||||
expect(test).toBeDefined();
|
||||
|
||||
const design = screen.getByText('Design');
|
||||
expect(design).toBeDefined();
|
||||
|
||||
await user.click(test);
|
||||
|
||||
rerender(
|
||||
<MemoryRouter>
|
||||
<ActivityToggle
|
||||
activity={activity}
|
||||
workspace={mockWorkspace}
|
||||
handleActivityChange={handleActivityChange}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(debug).not.toHaveClass('active');
|
||||
expect(test).toHaveClass('active');
|
||||
|
||||
await user.click(design);
|
||||
|
||||
rerender(
|
||||
<MemoryRouter>
|
||||
<ActivityToggle
|
||||
activity={activity}
|
||||
workspace={mockWorkspace}
|
||||
handleActivityChange={handleActivityChange}
|
||||
/>
|
||||
</MemoryRouter>
|
||||
);
|
||||
|
||||
expect(test).not.toHaveClass('active');
|
||||
expect(debug).not.toHaveClass('active');
|
||||
expect(design).toHaveClass('active');
|
||||
});
|
||||
});
|
@ -0,0 +1,105 @@
|
||||
import React, { FunctionComponent, useCallback } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import type { GlobalActivity } from '../../../common/constants';
|
||||
import { ACTIVITY_DEBUG, ACTIVITY_SPEC, ACTIVITY_UNIT_TEST } from '../../../common/constants';
|
||||
import { isDesign, Workspace } from '../../../models/workspace';
|
||||
import { HandleActivityChange } from '../wrapper';
|
||||
|
||||
const StyledNav = styled.nav({
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignContent: 'space-evenly',
|
||||
fontWeight: '500',
|
||||
color: 'var(--color-font)',
|
||||
background: 'var(--hl-xs)',
|
||||
border: '0',
|
||||
borderRadius: '100px',
|
||||
padding: 'var(--padding-xxs)',
|
||||
transform: 'scale(0.9)',
|
||||
transformOrigin: 'center',
|
||||
'& > * :not(:last-child)': {
|
||||
marginRight: 'var(--padding-xs)',
|
||||
},
|
||||
});
|
||||
|
||||
const StyledLink = styled(Link)({
|
||||
minWidth: '4rem',
|
||||
margin: '0 auto',
|
||||
textTransform: 'uppercase',
|
||||
textAlign: 'center',
|
||||
fontSize: 'var(--font-size-xs)',
|
||||
padding: 'var(--padding-xs) var(--padding-xxs)',
|
||||
borderRadius: 'var(--line-height-sm)',
|
||||
color: 'var(--hl)!important',
|
||||
background: 'transparent',
|
||||
'&.active': {
|
||||
color: 'var(--color-font)!important',
|
||||
background: 'var(--color-bg)',
|
||||
},
|
||||
'&:hover,&:active': {
|
||||
textDecoration: 'none',
|
||||
},
|
||||
});
|
||||
|
||||
interface Props {
|
||||
activity: GlobalActivity;
|
||||
workspace: Workspace;
|
||||
handleActivityChange: HandleActivityChange;
|
||||
}
|
||||
|
||||
export const ActivityToggle: FunctionComponent<Props> = ({
|
||||
activity,
|
||||
workspace,
|
||||
handleActivityChange,
|
||||
}) => {
|
||||
const onChange = useCallback((e: React.MouseEvent<HTMLAnchorElement, MouseEvent>, nextActivity: GlobalActivity) => {
|
||||
// Prevent the default behavior in order to avoid extra re-render.
|
||||
e.preventDefault();
|
||||
handleActivityChange({
|
||||
workspaceId: workspace?._id,
|
||||
nextActivity,
|
||||
});
|
||||
}, [handleActivityChange, workspace]);
|
||||
|
||||
if (!activity) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!workspace || !isDesign(workspace)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledNav>
|
||||
<StyledLink
|
||||
to={ACTIVITY_SPEC}
|
||||
className={activity === ACTIVITY_SPEC ? 'active' : undefined }
|
||||
onClick={e => {
|
||||
onChange(e, ACTIVITY_SPEC);
|
||||
}}
|
||||
>
|
||||
Design
|
||||
</StyledLink>
|
||||
<StyledLink
|
||||
to={ACTIVITY_DEBUG}
|
||||
className={activity === ACTIVITY_DEBUG ? 'active' : undefined }
|
||||
onClick={e => {
|
||||
onChange(e, ACTIVITY_DEBUG);
|
||||
}}
|
||||
>
|
||||
Debug
|
||||
</StyledLink>
|
||||
<StyledLink
|
||||
to={ACTIVITY_UNIT_TEST}
|
||||
className={activity === ACTIVITY_UNIT_TEST ? 'active' : undefined }
|
||||
onClick={e => {
|
||||
onChange(e, ACTIVITY_UNIT_TEST);
|
||||
}}
|
||||
>
|
||||
Test
|
||||
</StyledLink>
|
||||
</StyledNav>
|
||||
);
|
||||
};
|
@ -3,7 +3,7 @@ import { useSelector } from 'react-redux';
|
||||
|
||||
import { ACTIVITY_HOME } from '../../common/constants';
|
||||
import { selectActiveActivity, selectActiveApiSpec, selectActiveProjectName, selectActiveWorkspace } from '../redux/selectors';
|
||||
import { ActivityToggle } from './activity-toggle';
|
||||
import { ActivityToggle } from './activity-toggle/activity-toggle';
|
||||
import { AppHeader } from './app-header';
|
||||
import { WorkspaceDropdown } from './dropdowns/workspace-dropdown';
|
||||
import { HandleActivityChange } from './wrapper';
|
||||
@ -38,7 +38,13 @@ export const WorkspacePageHeader: FunctionComponent<Props> = ({
|
||||
return (
|
||||
<AppHeader
|
||||
breadcrumbProps={{ crumbs }}
|
||||
gridCenter={<ActivityToggle handleActivityChange={handleActivityChange} />}
|
||||
gridCenter={
|
||||
<ActivityToggle
|
||||
workspace={activeWorkspace}
|
||||
activity={activity}
|
||||
handleActivityChange={handleActivityChange}
|
||||
/>
|
||||
}
|
||||
gridRight={gridRight}
|
||||
/>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user