Skip to content
This repository was archived by the owner on Dec 30, 2022. It is now read-only.

Commit c143145

Browse files
feat(queryRules): add QueryRuleCustomData widget
1 parent d2fd3ec commit c143145

File tree

8 files changed

+277
-1
lines changed

8 files changed

+277
-1
lines changed
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import PropTypes from 'prop-types';
3+
import classNames from 'classnames';
4+
import { CustomUserData } from 'react-instantsearch-core';
5+
import { createClassNames } from '../core/utils';
6+
7+
const cx = createClassNames('QueryRuleCustomData');
8+
9+
type QueryRuleCustomDataRenderProps<TItem> = {
10+
items: TItem[];
11+
};
12+
13+
export type QueryRuleCustomDataProps<TItem> = {
14+
items: TItem[];
15+
className?: string;
16+
children: (options: QueryRuleCustomDataRenderProps<TItem>) => React.ReactNode;
17+
};
18+
19+
const QueryRuleCustomData: React.SFC<
20+
QueryRuleCustomDataProps<CustomUserData>
21+
> = ({ items, className, children }) => (
22+
<div className={classNames(cx(''), className)}>{children({ items })}</div>
23+
);
24+
25+
QueryRuleCustomData.propTypes = {
26+
items: PropTypes.arrayOf(PropTypes.object).isRequired,
27+
className: PropTypes.string,
28+
children: PropTypes.func.isRequired,
29+
};
30+
31+
export default QueryRuleCustomData;
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import React from 'react';
2+
import Enzyme, { shallow } from 'enzyme';
3+
import Adapter from 'enzyme-adapter-react-16';
4+
import QueryRuleCustomData, {
5+
QueryRuleCustomDataProps,
6+
} from '../QueryRuleCustomData';
7+
8+
Enzyme.configure({ adapter: new Adapter() });
9+
10+
type CustomDataItem = {
11+
title: string;
12+
banner: string;
13+
};
14+
15+
type CustomDataProps = QueryRuleCustomDataProps<CustomDataItem>;
16+
17+
describe('QueryRuleCustomData', () => {
18+
it('expects to render the empty container with empty items', () => {
19+
const props: CustomDataProps = {
20+
items: [],
21+
children: jest.fn(({ items }) =>
22+
items.map(item => (
23+
<section key={item.title}>
24+
<img src={item.banner} alt={item.title} />
25+
</section>
26+
))
27+
),
28+
};
29+
30+
const wrapper = shallow(<QueryRuleCustomData {...props} />);
31+
32+
expect(props.children).toHaveBeenCalledTimes(1);
33+
expect(props.children).toHaveBeenCalledWith({ items: props.items });
34+
expect(wrapper).toMatchSnapshot();
35+
});
36+
37+
it('expects to render multiple items', () => {
38+
const props: CustomDataProps = {
39+
items: [
40+
{ title: 'Image 1', banner: 'image-1.png' },
41+
{ title: 'Image 2', banner: 'image-2.png' },
42+
],
43+
children: jest.fn(({ items }) =>
44+
items.map(item => (
45+
<section key={item.title}>
46+
<img src={item.banner} alt={item.title} />
47+
</section>
48+
))
49+
),
50+
};
51+
52+
const wrapper = shallow(<QueryRuleCustomData {...props} />);
53+
54+
expect(props.children).toHaveBeenCalledTimes(1);
55+
expect(props.children).toHaveBeenCalledWith({ items: props.items });
56+
expect(wrapper).toMatchSnapshot();
57+
});
58+
59+
it('expects to render with custom className', () => {
60+
const props: CustomDataProps = {
61+
items: [],
62+
className: 'CustomClassName',
63+
children: jest.fn(() => null),
64+
};
65+
66+
const wrapper = shallow(<QueryRuleCustomData {...props} />);
67+
68+
expect(wrapper.props().className).toContain('CustomClassName');
69+
expect(wrapper).toMatchSnapshot();
70+
});
71+
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`QueryRuleCustomData expects to render multiple items 1`] = `
4+
<div
5+
className="ais-QueryRuleCustomData"
6+
>
7+
<section
8+
key="Image 1"
9+
>
10+
<img
11+
alt="Image 1"
12+
src="image-1.png"
13+
/>
14+
</section>
15+
<section
16+
key="Image 2"
17+
>
18+
<img
19+
alt="Image 2"
20+
src="image-2.png"
21+
/>
22+
</section>
23+
</div>
24+
`;
25+
26+
exports[`QueryRuleCustomData expects to render the empty container with empty items 1`] = `
27+
<div
28+
className="ais-QueryRuleCustomData"
29+
/>
30+
`;
31+
32+
exports[`QueryRuleCustomData expects to render with custom className 1`] = `
33+
<div
34+
className="ais-QueryRuleCustomData CustomClassName"
35+
/>
36+
`;

packages/react-instantsearch-dom/src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export { default as Snippet } from './widgets/Snippet';
5858
export { default as SortBy } from './widgets/SortBy';
5959
export { default as Stats } from './widgets/Stats';
6060
export { default as ToggleRefinement } from './widgets/ToggleRefinement';
61+
export { default as QueryRuleCustomData } from './widgets/QueryRuleCustomData';
6162

6263
// Utils
6364
export { createClassNames } from './core/utils';
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React from 'react';
2+
import { connectQueryRules, CustomUserData } from 'react-instantsearch-core';
3+
import PanelCallbackHandler from '../components/PanelCallbackHandler';
4+
import QueryRuleCustomData, {
5+
QueryRuleCustomDataProps,
6+
} from '../components/QueryRuleCustomData';
7+
8+
const QueryRuleCustomDataWidget: React.SFC<
9+
QueryRuleCustomDataProps<CustomUserData>
10+
> = props => (
11+
<PanelCallbackHandler {...props}>
12+
<QueryRuleCustomData {...props} />
13+
</PanelCallbackHandler>
14+
);
15+
16+
export default connectQueryRules(QueryRuleCustomDataWidget);
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import React from 'react';
2+
import { storiesOf } from '@storybook/react';
3+
import { QueryRuleCustomData, Panel } from 'react-instantsearch-dom';
4+
import { WrapWithHits } from './util';
5+
6+
type CustomDataItem = {
7+
title: string;
8+
banner: string;
9+
link: string;
10+
};
11+
12+
const stories = storiesOf('QueryRuleCustomData', module);
13+
14+
const storyProps = {
15+
appId: 'latency',
16+
apiKey: 'af044fb0788d6bb15f807e4420592bc5',
17+
indexName: 'instant_search_movies',
18+
linkedStoryGroup: 'QueryRuleCustomData',
19+
};
20+
21+
stories
22+
.add('default', () => (
23+
<WrapWithHits {...storyProps}>
24+
<p>
25+
Type <q>music</q> and a banner will appear.
26+
</p>
27+
28+
<QueryRuleCustomData>
29+
{({ items }: { items: CustomDataItem[] }) =>
30+
items.map(({ banner, title, link }) => {
31+
if (!banner) {
32+
return null;
33+
}
34+
35+
return (
36+
<section key={title}>
37+
<h2>{title}</h2>
38+
39+
<a href={link}>
40+
<img src={banner} alt={title} />
41+
</a>
42+
</section>
43+
);
44+
})
45+
}
46+
</QueryRuleCustomData>
47+
</WrapWithHits>
48+
))
49+
.add('with default banner', () => (
50+
<WrapWithHits {...storyProps}>
51+
<p>
52+
Kill Bill appears whenever no other results are promoted. Type{' '}
53+
<q>music</q> to see another movie promoted.
54+
</p>
55+
56+
<QueryRuleCustomData
57+
transformItems={(items: CustomDataItem[]) => {
58+
if (items.length > 0) {
59+
return items;
60+
}
61+
62+
return [
63+
{
64+
title: 'Kill Bill',
65+
banner: 'http://static.bobatv.net/IMovie/mv_2352/poster_2352.jpg',
66+
link: 'https://www.netflix.com/title/60031236',
67+
},
68+
];
69+
}}
70+
>
71+
{({ items }: { items: CustomDataItem[] }) =>
72+
items.map(({ banner, title, link }) => {
73+
if (!banner) {
74+
return null;
75+
}
76+
77+
return (
78+
<section key={title}>
79+
<h2>{title}</h2>
80+
81+
<a href={link}>
82+
<img src={banner} alt={title} />
83+
</a>
84+
</section>
85+
);
86+
})
87+
}
88+
</QueryRuleCustomData>
89+
</WrapWithHits>
90+
))
91+
.add('with Panel', () => (
92+
<WrapWithHits {...storyProps}>
93+
<p>
94+
Type <q>music</q> and a banner will appear.
95+
</p>
96+
97+
<Panel header="QueryRuleCustomData" footer="footer">
98+
<QueryRuleCustomData>
99+
{({ items }: { items: CustomDataItem[] }) =>
100+
items.map(({ banner, title, link }) => {
101+
if (!banner) {
102+
return null;
103+
}
104+
105+
return (
106+
<section key={title}>
107+
<h2>{title}</h2>
108+
109+
<a href={link}>
110+
<img src={banner} alt={title} />
111+
</a>
112+
</section>
113+
);
114+
})
115+
}
116+
</QueryRuleCustomData>
117+
</Panel>
118+
</WrapWithHits>
119+
));

storybook/webpack.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module.exports = {
1111
loader: 'babel-loader',
1212
options: {
1313
rootMode: 'upward',
14+
presets: [['react-app', { typescript: true }]],
1415
},
1516
},
1617
],

tslint.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"no-parameter-reassignment": false,
1111
"object-literal-sort-keys": false,
1212
"ordered-imports": false,
13-
"prettier": true
13+
"prettier": true,
14+
"jsx-no-lambda": false
1415
}
1516
}

0 commit comments

Comments
 (0)