Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Addon-contexts: component tests and readability improvements #6716

Merged
merged 3 commits into from
May 22, 2019
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
100 changes: 100 additions & 0 deletions addons/contexts/src/manager/components/ToolBar.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import React from 'react';
import { shallow } from 'enzyme';
import { ToolBar } from './ToolBar';

describe('Tests on addon-contexts component: ToolBar', () => {
it('should render nothing if receive an empty contextNodes', () => {
// when
const result = shallow(<ToolBar nodes={[]} state={{}} setSelected={jest.fn} />);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these better expressed as storybook stories & tested with storyshots?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shilman, I was thinking about this previously, and even try to use react-testing-library at very beginning... But later I found if I really want to test them, I have to find its ThemeProvider first; otherwise, I will get some object node missed and React get crashed.

That is why I roll it back to more traditional shallow testing approach (to stay inside the addon itself). Right now everything is in the monorepo and user will have to use @storybook/theme implicitly so I guess it should be fine?! But I somehow think the @storybook/components should have some components out-of-the-box for the addon creator.

To be more specific on the scope here, the only thing really need to get tested is the ToolBarControl component. This component is responsible for validating the selected name (and some states as you may see there). I added other testing for parity.

There are some part I want to test but not get it tested. Anyway I will need to rethink about testing here. Thanks for suggestion though 🙏.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check out my addon improvements PR @leoyli

#6759

It adds a component for addon creation, If you have idea on what components would be useful for addon creators, let's chat about that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Woo nice, I will take a look, I will also migrate @storybook/components first, and reach you out 👍


// then
expect(result).toMatchInlineSnapshot(`""`);
});

it('should spawn ToolBarControl based on the given contextNodes', () => {
// given
const someContextNodes = [
{
components: ['span'],
icon: 'box' as const,
nodeId: 'Some Context A',
options: { cancelable: false, deep: false, disable: false },
params: [{ name: '', props: {} }],
title: 'Some Context A',
},
{
components: ['div'],
icon: 'box' as const,
nodeId: 'Some Context B',
options: { cancelable: true, deep: false, disable: false },
params: [{ name: 'Some Param X', props: {} }, { name: 'Some Param Y', props: {} }],
title: 'Some Context B',
},
];
const someSelectionState = {
'Some Context B': 'Some Param Y',
};

// when
const result = shallow(
<ToolBar nodes={someContextNodes} state={someSelectionState} setSelected={jest.fn} />
);

// then
expect(result).toMatchInlineSnapshot(`
<Fragment>
<Separator />
<ToolBarControl
icon="box"
key="Some Context A"
nodeId="Some Context A"
options={
Object {
"cancelable": false,
"deep": false,
"disable": false,
}
}
params={
Array [
Object {
"name": "",
"props": Object {},
},
]
}
selected=""
setSelected={[Function]}
title="Some Context A"
/>
<ToolBarControl
icon="box"
key="Some Context B"
nodeId="Some Context B"
options={
Object {
"cancelable": true,
"deep": false,
"disable": false,
}
}
params={
Array [
Object {
"name": "Some Param X",
"props": Object {},
},
Object {
"name": "Some Param Y",
"props": Object {},
},
]
}
selected="Some Param Y"
setSelected={[Function]}
title="Some Context B"
/>
</Fragment>
`);
});
});
8 changes: 4 additions & 4 deletions addons/contexts/src/manager/components/ToolBar.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import React, { ComponentProps } from 'react';
import { Separator } from '@storybook/components';
import { ToolbarControl } from './ToolbarControl';
import { ToolBarControl } from './ToolBarControl';
import { ContextNode, FCNoChildren, SelectionState } from '../../shared/types.d';

type ToolBar = FCNoChildren<{
nodes: ContextNode[];
state: SelectionState;
setSelected: ComponentProps<typeof ToolbarControl>['setSelected'];
setSelected: ComponentProps<typeof ToolBarControl>['setSelected'];
}>;

export const ToolBar: ToolBar = React.memo(({ nodes, state, setSelected }) =>
nodes.length ? (
<>
<Separator />
{nodes.map(({ components, ...forwardProps }) => (
<ToolbarControl
<ToolBarControl
{...forwardProps}
setSelected={setSelected}
selected={state[forwardProps.nodeId]}
selected={state[forwardProps.nodeId] || ''}
key={forwardProps.nodeId}
/>
))}
Expand Down
99 changes: 99 additions & 0 deletions addons/contexts/src/manager/components/ToolBarControl.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React from 'react';
import { shallow } from 'enzyme';
import { ToolBarControl } from './ToolBarControl';
import { OPT_OUT } from '../../shared/constants';

describe('Tests on addon-contexts component: ToolBarControl', () => {
// given
const someBasicProps = {
icon: 'box' as const,
nodeId: 'Some Context',
options: { cancelable: true, deep: false, disable: false },
params: [{ name: 'A', props: {} }, { name: 'B', props: {} }],
title: 'Some Context',
selected: '',
setSelected: jest.fn,
};

it('should control menu: set as inactive if being out-out (if cancelable)', () => {
// when
const result = shallow(<ToolBarControl {...someBasicProps} selected={OPT_OUT} />);

// then
expect(result.props().active).toBe(false);
});

it('should control menu: valid "selected" to give "activeName"', () => {
// given
const selected = 'C';
const anotherSelected = 'B';

// when
const result = shallow(<ToolBarControl {...someBasicProps} selected={selected} />);
const anotherResult = shallow(
<ToolBarControl {...someBasicProps} selected={anotherSelected} />
);

// then
expect(result.props().optionsProps.activeName).not.toBe(selected);
expect(anotherResult.props().optionsProps.activeName).toBe(anotherSelected);
});

it('should control menu: fallback "activeName" to the default param', () => {
// given
const name = 'C';
const params = [...someBasicProps.params, { name, props: {}, default: true }];

// when
const result = shallow(<ToolBarControl {...someBasicProps} params={params} />);

// then
expect(result.props().optionsProps.activeName).toBe(name);
});

it('should control menu: fallback "activeName" to the first (if default not found)', () => {
// when
const result = shallow(<ToolBarControl {...someBasicProps} />);

// then
expect(result.props().optionsProps.activeName).toBe(someBasicProps.params[0].name);
});

it('should render nothing if being disabled', () => {
// given
const options = { ...someBasicProps.options, disable: true };

// when
const result = shallow(<ToolBarControl {...someBasicProps} options={options} />);

// then
expect(result).toMatchInlineSnapshot(`""`);
});

it('should document the shallowly rendered result', () => {
// when
const result = shallow(<ToolBarControl {...someBasicProps} />);

// then
expect(result).toMatchInlineSnapshot(`
<ToolBarMenu
active={true}
expanded={false}
icon="box"
optionsProps={
Object {
"activeName": "A",
"list": Array [
"__OPT_OUT__",
"A",
"B",
],
"onSelectOption": [Function],
}
}
setExpanded={[Function]}
title="Some Context"
/>
`);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ToolBarMenu } from './ToolBarMenu';
import { OPT_OUT } from '../../shared/constants';
import { ContextNode, FCNoChildren, Omit } from '../../shared/types.d';

type ToolbarControl = FCNoChildren<
type ToolBarControl = FCNoChildren<
Omit<
ContextNode & {
selected: string;
Expand All @@ -13,7 +13,7 @@ type ToolbarControl = FCNoChildren<
>
>;

export const ToolbarControl: ToolbarControl = ({
export const ToolBarControl: ToolBarControl = ({
nodeId,
icon,
title,
Expand Down
56 changes: 56 additions & 0 deletions addons/contexts/src/manager/components/ToolBarMenu.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React from 'react';
import { shallow } from 'enzyme';
import { ToolBarMenu } from './ToolBarMenu';

describe('Tests on addon-contexts component: ToolBarMenu', () => {
it('should glue `@storybook/ui` components to produce a context menu', () => {
// given
const someProps = {
icon: 'globe' as const,
title: 'Some Context',
active: true,
expanded: false,
setExpanded: jest.fn,
optionsProps: {
activeName: 'A',
list: ['A', 'B'],
onSelectOption: jest.fn,
},
};

// when
const result = shallow(<ToolBarMenu {...someProps} />);

// then
expect(result).toMatchInlineSnapshot(`
<lifecycle(WithTooltipPure)
closeOnClick={true}
onVisibilityChange={[Function]}
placement="top"
tooltip={
<ToolBarMenuOptions
activeName="A"
list={
Array [
"A",
"B",
]
}
onSelectOption={[Function]}
/>
}
tooltipShown={false}
trigger="click"
>
<IconButton
active={true}
title="Some Context"
>
<Icons
icon="globe"
/>
</IconButton>
</lifecycle(WithTooltipPure)>
`);
});
});
51 changes: 51 additions & 0 deletions addons/contexts/src/manager/components/ToolBarMenuOptions.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import { shallow } from 'enzyme';
import { ToolBarMenuOptions } from './ToolBarMenuOptions';
import { OPT_OUT } from '../../shared/constants';

describe('Tests on addon-contexts component: ToolBarMenuOptions', () => {
it('should glue TooltipLinkList and set the active item correspondingly', () => {
// given
const list = [OPT_OUT, 'A', 'B'];
const activeName = 'B';

// when
const result = shallow(
<ToolBarMenuOptions activeName={activeName} list={list} onSelectOption={jest.fn} />
);

// then
expect(result.props().links.length).toBe(list.length);
expect(result.props().links.find((link: any) => link.title === activeName).active).toBe(true);
expect(result).toMatchInlineSnapshot(`
<TooltipLinkList
LinkWrapper={null}
links={
Array [
Object {
"active": false,
"id": "__OPT_OUT__",
"key": "__OPT_OUT__",
"onClick": [MockFunction],
"title": "Off",
},
Object {
"active": false,
"id": "A",
"key": "A",
"onClick": [MockFunction],
"title": "A",
},
Object {
"active": true,
"id": "B",
"key": "B",
"onClick": [MockFunction],
"title": "B",
},
]
}
/>
`);
});
});
Loading