Skip to content

Commit 142660a

Browse files
shortcutsfrancoischalifour
andauthoredMay 18, 2021
feat(ts): convert poweredBy widget (#4756)
* feat(ts): convert poweredBy widget Co-authored-by: François Chalifour <francoischalifour@users.noreply.github.com>
1 parent 4a9ac35 commit 142660a

File tree

5 files changed

+136
-106
lines changed

5 files changed

+136
-106
lines changed
 

‎src/components/PoweredBy/PoweredBy.tsx

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
/** @jsx h */
22

33
import { h } from 'preact';
4+
import { PoweredByCSSClasses } from '../../widgets/powered-by/powered-by';
45

5-
type CSSClasses = {
6-
root: string;
7-
link: string;
8-
logo: string;
6+
export type PoweredByComponentCSSClasses = {
7+
[TClassName in keyof PoweredByCSSClasses]: string;
98
};
109

11-
type Props = {
10+
export type PoweredByProps = {
1211
url: string;
1312
theme: string;
14-
cssClasses: CSSClasses;
13+
cssClasses: PoweredByComponentCSSClasses;
1514
};
1615

17-
const PoweredBy = ({ url, theme, cssClasses }: Props) => (
16+
const PoweredBy = ({ url, theme, cssClasses }: PoweredByProps) => (
1817
<div className={cssClasses.root}>
1918
<a
2019
href={url}

‎src/widgets/powered-by/__tests__/__snapshots__/powered-by-test.js.snap

-13
This file was deleted.

‎src/widgets/powered-by/__tests__/powered-by-test.js

-52
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import algoliasearchHelper, { AlgoliaSearchHelper } from 'algoliasearch-helper';
2+
import { render as preactRender, VNode } from 'preact';
3+
import { createSearchClient } from '../../../../test/mock/createSearchClient';
4+
import {
5+
createInitOptions,
6+
createRenderOptions,
7+
} from '../../../../test/mock/createWidget';
8+
import { castToJestMock } from '../../../../test/utils/castToJestMock';
9+
import { PoweredByProps } from '../../../components/PoweredBy/PoweredBy';
10+
import poweredBy from '../powered-by';
11+
12+
const render = castToJestMock(preactRender);
13+
jest.mock('preact', () => {
14+
const module = jest.requireActual('preact');
15+
16+
module.render = jest.fn();
17+
18+
return module;
19+
});
20+
21+
describe('poweredBy call', () => {
22+
it('throws an exception when no container', () => {
23+
expect(poweredBy).toThrowErrorMatchingInlineSnapshot(`
24+
"The \`container\` option is required.
25+
26+
See documentation: https://www.algolia.com/doc/api-reference/widgets/powered-by/js/"
27+
`);
28+
});
29+
});
30+
31+
describe('poweredBy', () => {
32+
let widget: ReturnType<typeof poweredBy>;
33+
let container: HTMLElement;
34+
let helper: AlgoliaSearchHelper;
35+
36+
beforeEach(() => {
37+
render.mockClear();
38+
39+
container = document.createElement('div');
40+
widget = poweredBy({
41+
container,
42+
cssClasses: {
43+
root: 'root',
44+
link: 'link',
45+
logo: 'logo',
46+
},
47+
});
48+
49+
helper = algoliasearchHelper(createSearchClient(), '', {});
50+
51+
widget.init!(createInitOptions({ helper }));
52+
});
53+
54+
it('renders only once at init', () => {
55+
widget.render!(createRenderOptions({ helper }));
56+
widget.render!(createRenderOptions({ helper }));
57+
58+
const firstRender = render.mock.calls[0][0] as VNode<PoweredByProps>;
59+
const firstContainer = render.mock.calls[0][1];
60+
61+
expect(render).toHaveBeenCalledTimes(1);
62+
expect(firstRender.props).toEqual({
63+
cssClasses: {
64+
link: 'ais-PoweredBy-link link',
65+
logo: 'ais-PoweredBy-logo logo',
66+
root: 'ais-PoweredBy ais-PoweredBy--light root',
67+
},
68+
theme: 'light',
69+
url:
70+
'https://www.algolia.com/?utm_source=instantsearch.js&utm_medium=website&utm_content=localhost&utm_campaign=poweredby',
71+
});
72+
expect(firstContainer).toEqual(container);
73+
});
74+
});

‎src/widgets/powered-by/powered-by.js ‎src/widgets/powered-by/powered-by.tsx

+56-34
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,30 @@
33
import { h, render } from 'preact';
44
import cx from 'classnames';
55
import PoweredBy from '../../components/PoweredBy/PoweredBy';
6-
import connectPoweredBy from '../../connectors/powered-by/connectPoweredBy';
6+
import connectPoweredBy, {
7+
PoweredByConnectorParams,
8+
PoweredByRenderState,
9+
PoweredByWidgetDescription,
10+
} from '../../connectors/powered-by/connectPoweredBy';
711
import {
812
getContainerNode,
913
createDocumentationMessageGenerator,
1014
} from '../../lib/utils';
1115
import { component } from '../../lib/suit';
16+
import { Renderer, WidgetFactory } from '../../types';
1217

1318
const suit = component('PoweredBy');
1419
const withUsage = createDocumentationMessageGenerator({ name: 'powered-by' });
1520

16-
const renderer = ({ containerNode, cssClasses }) => (
21+
const renderer = ({
22+
containerNode,
23+
cssClasses,
24+
}): Renderer<PoweredByRenderState, Partial<PoweredByWidgetParams>> => (
1725
{ url, widgetParams },
1826
isFirstRendering
1927
) => {
2028
if (isFirstRendering) {
21-
const { theme } = widgetParams;
29+
const { theme = 'light' } = widgetParams;
2230

2331
render(
2432
<PoweredBy cssClasses={cssClasses} url={url} theme={theme} />,
@@ -29,36 +37,48 @@ const renderer = ({ containerNode, cssClasses }) => (
2937
}
3038
};
3139

32-
/**
33-
* @typedef {Object} PoweredByWidgetCssClasses
34-
* @property {string|string[]} [root] CSS classes added to the root element of the widget.
35-
* @property {string|string[]} [link] CSS class to add to the link.
36-
* @property {string|string[]} [logo] CSS class to add to the SVG logo.
37-
*/
38-
39-
/**
40-
* @typedef {Object} PoweredByWidgetParams
41-
* @property {string|HTMLElement} container Place where to insert the widget in your webpage.
42-
* @property {string} [theme] The theme of the logo ("light" or "dark").
43-
* @property {PoweredByWidgetCssClasses} [cssClasses] CSS classes to add.
44-
*/
45-
46-
/**
47-
* The `poweredBy` widget is used to display the logo to redirect to Algolia.
48-
* @type {WidgetFactory}
49-
* @devNovel PoweredBy
50-
* @category metadata
51-
* @param {PoweredByWidgetParams} widgetParams PoweredBy widget options. Some keys are mandatory: `container`,
52-
* @return {Widget} A new poweredBy widget instance
53-
* @example
54-
* search.addWidgets([
55-
* instantsearch.widgets.poweredBy({
56-
* container: '#poweredBy-container',
57-
* theme: 'dark',
58-
* })
59-
* ]);
60-
*/
61-
export default function poweredBy(widgetParams) {
40+
export type PoweredByCSSClasses = {
41+
/**
42+
* CSS class to add to the wrapping element.
43+
*/
44+
root: string | string[];
45+
46+
/**
47+
* CSS class to add to the link.
48+
*/
49+
link: string | string[];
50+
51+
/**
52+
* CSS class to add to the SVG logo.
53+
*/
54+
logo: string | string[];
55+
};
56+
57+
export type PoweredByWidgetParams = {
58+
/**
59+
* CSS Selector or HTMLElement to insert the widget.
60+
*/
61+
container: string | HTMLElement;
62+
63+
/**
64+
* The theme of the logo.
65+
* @default 'light'
66+
*/
67+
theme?: 'light' | 'dark';
68+
69+
/**
70+
* CSS classes to add.
71+
*/
72+
cssClasses?: Partial<PoweredByCSSClasses>;
73+
};
74+
75+
export type PoweredByWidget = WidgetFactory<
76+
PoweredByWidgetDescription & { $$widgetType: 'ais.poweredBy' },
77+
PoweredByConnectorParams,
78+
PoweredByWidgetParams
79+
>;
80+
81+
const poweredBy: PoweredByWidget = function poweredBy(widgetParams) {
6282
const { container, cssClasses: userCssClasses = {}, theme = 'light' } =
6383
widgetParams || {};
6484

@@ -91,4 +111,6 @@ export default function poweredBy(widgetParams) {
91111
...makeWidget({ theme }),
92112
$$widgetType: 'ais.poweredBy',
93113
};
94-
}
114+
};
115+
116+
export default poweredBy;

0 commit comments

Comments
 (0)
Please sign in to comment.