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

feat(queryRules): add QueryRuleCustomData widget [part 2] #2212

Merged
Merged
Show file tree
Hide file tree
Changes from 3 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import PropTypes from 'prop-types';
import classNames from 'classnames';
import { CustomUserData } from 'react-instantsearch-core';
import { createClassNames } from '../core/utils';

const cx = createClassNames('QueryRuleCustomData');

type QueryRuleCustomDataRenderProps<TItem> = {
items: TItem[];
};

export type QueryRuleCustomDataProps<TItem> = {
items: TItem[];
className?: string;
children: (options: QueryRuleCustomDataRenderProps<TItem>) => React.ReactNode;
};

const QueryRuleCustomData: React.FC<
QueryRuleCustomDataProps<CustomUserData>
> = ({ items, className, children }) => (
<div className={classNames(cx(''), className)}>{children({ items })}</div>
);

QueryRuleCustomData.propTypes = {
items: PropTypes.arrayOf(PropTypes.object).isRequired,
className: PropTypes.string,
children: PropTypes.func.isRequired,
};

export default QueryRuleCustomData;
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React from 'react';
import Enzyme, { shallow } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
import QueryRuleCustomData, {
QueryRuleCustomDataProps,
} from '../QueryRuleCustomData';

Enzyme.configure({ adapter: new Adapter() });

type CustomDataItem = {
title: string;
banner: string;
};

type CustomDataProps = QueryRuleCustomDataProps<CustomDataItem>;

describe('QueryRuleCustomData', () => {
it('expects to render the empty container with empty items', () => {
const props: CustomDataProps = {
items: [],
children: jest.fn(({ items }) =>
items.map(item => (
<section key={item.title}>
<img src={item.banner} alt={item.title} />
</section>
))
),
};

const wrapper = shallow(<QueryRuleCustomData {...props} />);

expect(props.children).toHaveBeenCalledTimes(1);
expect(props.children).toHaveBeenCalledWith({ items: props.items });
expect(wrapper).toMatchSnapshot();
});

it('expects to render multiple items', () => {
const props: CustomDataProps = {
items: [
{ title: 'Image 1', banner: 'image-1.png' },
{ title: 'Image 2', banner: 'image-2.png' },
],
children: jest.fn(({ items }) =>
items.map(item => (
<section key={item.title}>
<img src={item.banner} alt={item.title} />
</section>
))
),
};

const wrapper = shallow(<QueryRuleCustomData {...props} />);

expect(props.children).toHaveBeenCalledTimes(1);
expect(props.children).toHaveBeenCalledWith({ items: props.items });
expect(wrapper).toMatchSnapshot();
});

it('expects to render with custom className', () => {
const props: CustomDataProps = {
items: [],
className: 'CustomClassName',
children: jest.fn(() => null),
};

const wrapper = shallow(<QueryRuleCustomData {...props} />);

expect(wrapper.props().className).toContain('CustomClassName');
expect(wrapper).toMatchSnapshot();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`QueryRuleCustomData expects to render multiple items 1`] = `
<div
className="ais-QueryRuleCustomData"
>
<section
key="Image 1"
>
<img
alt="Image 1"
src="image-1.png"
/>
</section>
<section
key="Image 2"
>
<img
alt="Image 2"
src="image-2.png"
/>
</section>
</div>
`;

exports[`QueryRuleCustomData expects to render the empty container with empty items 1`] = `
<div
className="ais-QueryRuleCustomData"
/>
`;

exports[`QueryRuleCustomData expects to render with custom className 1`] = `
<div
className="ais-QueryRuleCustomData CustomClassName"
/>
`;
1 change: 1 addition & 0 deletions packages/react-instantsearch-dom/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export { default as Snippet } from './widgets/Snippet';
export { default as SortBy } from './widgets/SortBy';
export { default as Stats } from './widgets/Stats';
export { default as ToggleRefinement } from './widgets/ToggleRefinement';
export { default as QueryRuleCustomData } from './widgets/QueryRuleCustomData';

// Utils
export { createClassNames } from './core/utils';
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import React from 'react';
import { connectQueryRules, CustomUserData } from 'react-instantsearch-core';
import PanelCallbackHandler from '../components/PanelCallbackHandler';
import QueryRuleCustomData, {
QueryRuleCustomDataProps,
} from '../components/QueryRuleCustomData';

const QueryRuleCustomDataWidget: React.FC<
QueryRuleCustomDataProps<CustomUserData>
> = props => (
<PanelCallbackHandler {...props}>
<QueryRuleCustomData {...props} />
</PanelCallbackHandler>
);

export default connectQueryRules(QueryRuleCustomDataWidget);
149 changes: 149 additions & 0 deletions stories/QueryRuleCustomData.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
import { connectHits } from 'react-instantsearch-core';
import { QueryRuleCustomData, Panel, Highlight } from 'react-instantsearch-dom';
import { WrapWithHits } from './util';

type CustomDataItem = {
title: string;
banner: string;
link: string;
};

type MovieHit = {
actors: string[];
color: string;
genre: string[];
image: string;
objectID: string;
score: number;
title: string;
};

const stories = storiesOf('QueryRuleCustomData', module);

const StoryHits = connectHits(({ hits }: { hits: MovieHit[] }) => (
<div className="hits">
{hits.map(hit => (
<div key={hit.objectID} className="hit">
<div className="hit-picture">
<img src={hit.image} />
</div>

<div className="hit-content">
<div>
<Highlight attribute="title" hit={hit} />
</div>
</div>
</div>
))}
</div>
));

const storyProps = {
appId: 'latency',
apiKey: 'af044fb0788d6bb15f807e4420592bc5',
indexName: 'instant_search_movies',
linkedStoryGroup: 'QueryRuleCustomData',
hitsElement: <StoryHits />,
};

stories
.add('default', () => (
<WrapWithHits {...storyProps}>
<p>
Type <q>music</q> and a banner will appear.
</p>

<QueryRuleCustomData>
{({ items }: { items: CustomDataItem[] }) =>
items.map(({ banner, title, link }) => {
if (!banner) {
return null;
}

return (
<section key={title}>
<h2>{title}</h2>

<a href={link}>
<img src={banner} alt={title} />
</a>
</section>
);
})
}
</QueryRuleCustomData>
</WrapWithHits>
))
.add('with default banner', () => (
<WrapWithHits {...storyProps}>
<p>
Kill Bill appears whenever no other results are promoted. Type{' '}
<q>music</q> to see another movie promoted.
</p>

<QueryRuleCustomData
transformItems={(items: CustomDataItem[]) => {
if (items.length > 0) {
return items;
}

return [
{
title: 'Kill Bill',
banner: 'http://static.bobatv.net/IMovie/mv_2352/poster_2352.jpg',
link: 'https://www.netflix.com/title/60031236',
},
];
}}
>
{({ items }: { items: CustomDataItem[] }) =>
items.map(({ banner, title, link }) => {
if (!banner) {
return null;
}

return (
<section key={title}>
<h2>{title}</h2>

<a href={link}>
<img src={banner} alt={title} />
</a>
</section>
);
})
}
</QueryRuleCustomData>
</WrapWithHits>
))
.add('with Panel', () => (
<WrapWithHits {...storyProps}>
<p>
Type <q>music</q> and a banner will appear.
</p>

<Panel header="QueryRuleCustomData" footer="footer">
<QueryRuleCustomData>
{({ items }: { items: CustomDataItem[] }) =>
items.map(({ banner, title, link }) => {
if (!banner) {
return null;
}

return (
<section key={title}>
<h2>{title}</h2>

<a href={link}>
<img src={banner} alt={title} />
</a>
</section>
);
})
}
</QueryRuleCustomData>
</Panel>
</WrapWithHits>
));
1 change: 1 addition & 0 deletions storybook/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ module.exports = {
loader: 'babel-loader',
options: {
rootMode: 'upward',
presets: [['react-app', { typescript: true }]],
francoischalifour marked this conversation as resolved.
Show resolved Hide resolved
},
},
],
Expand Down
3 changes: 2 additions & 1 deletion tslint.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"no-parameter-reassignment": false,
"object-literal-sort-keys": false,
"ordered-imports": false,
"prettier": true
"prettier": true,
"jsx-no-lambda": false
}
}