Skip to content

Commit f1a96d8

Browse files
committed
fix(poweredBy): Let users define their own poweredBy template
Fixes: #1047 You can now pass an object to `poweredBy` like this: ```javascript poweredBy: { template: 'Link to {{data.url}}', // Also accepts function cssClasses: { root: 'applied to wrapper', link: 'applied to link' } } ``` Everything is handled directly in the widget, without the need for a React component. This was overkill because we never update the render of the `poweredBy` so it was easier to just generate the template at init time without going through all the process of creating a template. I've also added more thorough tests. Users will be able to independently change CSS classes or template.
1 parent bf170a3 commit f1a96d8

File tree

5 files changed

+250
-104
lines changed

5 files changed

+250
-104
lines changed

src/components/PoweredBy/PoweredBy.js

Lines changed: 0 additions & 26 deletions
This file was deleted.

src/components/PoweredBy/__tests__/PoweredBy-test.js

Lines changed: 0 additions & 45 deletions
This file was deleted.

src/widgets/search-box/__tests__/search-box-test.js

Lines changed: 193 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -182,25 +182,205 @@ describe('searchBox()', () => {
182182
});
183183
});
184184

185-
context('adds a PoweredBy', () => {
185+
context('poweredBy', () => {
186+
let defaultInitOptions;
187+
let defaultWidgetOptions;
188+
let $;
189+
186190
beforeEach(() => {
187191
container = document.createElement('div');
192+
$ = container.querySelectorAll.bind(container);
193+
defaultWidgetOptions = {container};
194+
defaultInitOptions = {state, helper, onHistoryChange};
188195
});
189196

190-
it('do not add the poweredBy if not specified', () => {
191-
widget = searchBox({container});
192-
widget.init({state, helper, onHistoryChange});
193-
expect(container.querySelector('.ais-search-box--powered-by')).toBe(null);
197+
it('should not add the element with default options', () => {
198+
// Given
199+
widget = searchBox(defaultWidgetOptions);
200+
201+
// When
202+
widget.init(defaultInitOptions);
203+
204+
// Then
205+
expect($('.ais-search-box--powered-by').length).toEqual(0);
194206
});
195207

196-
it('adds the poweredBy if specified', () => {
197-
widget = searchBox({container, poweredBy: true});
198-
widget.init({state, helper, onHistoryChange});
199-
const poweredBy = container.querySelector('.ais-search-box--powered-by');
200-
const poweredByLink = poweredBy.querySelector('a');
201-
const expectedLink = `https://www.algolia.com/?utm_source=instantsearch.js&utm_medium=website&utm_content=${location.hostname}&utm_campaign=poweredby`;
202-
expect(poweredBy).toNotBe(null);
203-
expect(poweredByLink.getAttribute('href')).toBe(expectedLink);
208+
it('should not add the element with poweredBy: false', () => {
209+
// Given
210+
widget = searchBox({
211+
...defaultWidgetOptions,
212+
poweredBy: false
213+
});
214+
215+
// When
216+
widget.init(defaultInitOptions);
217+
218+
// Then
219+
expect($('.ais-search-box--powered-by').length).toEqual(0);
220+
});
221+
222+
it('should add the element with poweredBy: true', () => {
223+
// Given
224+
widget = searchBox({
225+
...defaultWidgetOptions,
226+
poweredBy: true
227+
});
228+
229+
// When
230+
widget.init(defaultInitOptions);
231+
232+
// Then
233+
expect($('.ais-search-box--powered-by').length).toEqual(1);
234+
});
235+
236+
it('should contain a link to Algolia with poweredBy: true', () => {
237+
// Given
238+
widget = searchBox({
239+
...defaultWidgetOptions,
240+
poweredBy: true
241+
});
242+
243+
// When
244+
widget.init(defaultInitOptions);
245+
246+
// Then
247+
let actual = $('.ais-search-box--powered-by-link');
248+
let url = `https://www.algolia.com/?utm_source=instantsearch.js&utm_medium=website&utm_content=${location.hostname}&utm_campaign=poweredby`;
249+
expect(actual.length).toEqual(1);
250+
expect(actual[0].tagName).toEqual('A');
251+
expect(actual[0].innerHTML).toEqual('Algolia');
252+
expect(actual[0].getAttribute('href')).toEqual(url);
253+
});
254+
255+
it('should let user add its own CSS classes with poweredBy.cssClasses', () => {
256+
// Given
257+
widget = searchBox({
258+
...defaultWidgetOptions,
259+
poweredBy: {
260+
cssClasses: {
261+
root: 'myroot',
262+
link: 'mylink'
263+
}
264+
}
265+
});
266+
267+
// When
268+
widget.init(defaultInitOptions);
269+
270+
// Then
271+
let root = $('.myroot');
272+
let link = $('.mylink');
273+
expect(root.length).toEqual(1);
274+
expect(link.length).toEqual(1);
275+
expect(link[0].tagName).toEqual('A');
276+
expect(link[0].innerHTML).toEqual('Algolia');
277+
});
278+
279+
it('should still apply default CSS classes even if user provides its own', () => {
280+
// Given
281+
widget = searchBox({
282+
...defaultWidgetOptions,
283+
poweredBy: {
284+
cssClasses: {
285+
root: 'myroot',
286+
link: 'mylink'
287+
}
288+
}
289+
});
290+
291+
// When
292+
widget.init(defaultInitOptions);
293+
294+
// Then
295+
let root = $('.ais-search-box--powered-by');
296+
let link = $('.ais-search-box--powered-by-link');
297+
expect(root.length).toEqual(1);
298+
expect(link.length).toEqual(1);
299+
});
300+
301+
it('should let the user define its own string template', () => {
302+
// Given
303+
widget = searchBox({
304+
...defaultWidgetOptions,
305+
poweredBy: {
306+
template: '<div>Foobar</div>'
307+
}
308+
});
309+
310+
// When
311+
widget.init(defaultInitOptions);
312+
313+
// Then
314+
expect(container.innerHTML).toContain('Foobar');
315+
});
316+
317+
it('should let the user define its own Hogan template', () => {
318+
// Given
319+
widget = searchBox({
320+
...defaultWidgetOptions,
321+
poweredBy: {
322+
template: '<div>Foobar--{{url}}</div>'
323+
}
324+
});
325+
326+
// When
327+
widget.init(defaultInitOptions);
328+
329+
// Then
330+
expect(container.innerHTML).toContain('Foobar--https://www.algolia.com/');
331+
});
332+
333+
it('should let the user define its own function template', () => {
334+
// Given
335+
widget = searchBox({
336+
...defaultWidgetOptions,
337+
poweredBy: {
338+
template: (data) => {
339+
return `<div>Foobar--${data.url}</div>`;
340+
}
341+
}
342+
});
343+
344+
// When
345+
widget.init(defaultInitOptions);
346+
347+
// Then
348+
expect(container.innerHTML).toContain('Foobar--https://www.algolia.com/');
349+
});
350+
351+
it('should gracefully handle templates with leading spaces', () => {
352+
// Given
353+
widget = searchBox({
354+
...defaultWidgetOptions,
355+
poweredBy: {
356+
template: `
357+
358+
<div>Foobar</div>`
359+
}
360+
});
361+
362+
// When
363+
widget.init(defaultInitOptions);
364+
365+
// Then
366+
expect(container.innerHTML).toContain('Foobar');
367+
});
368+
369+
it('should handle templates not wrapped in a node', () => {
370+
// Given
371+
widget = searchBox({
372+
...defaultWidgetOptions,
373+
poweredBy: {
374+
template: 'Foobar <img src="./test.gif" class="should-be-found"/>'
375+
}
376+
});
377+
378+
// When
379+
widget.init(defaultInitOptions);
380+
381+
// Then
382+
expect(container.innerHTML).toContain('Foobar');
383+
expect($('.should-be-found').length).toEqual(1);
204384
});
205385
});
206386

@@ -363,7 +543,6 @@ describe('searchBox()', () => {
363543
expect(container.value).toBe('iphone');
364544
});
365545

366-
367546
it('handles external updates', () => {
368547
container = document.body.appendChild(document.createElement('input'));
369548
container.value = 'initial';
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default {
2+
poweredBy: `
3+
<div class="{{cssClasses.root}}">
4+
Search by
5+
<a class="{{cssClasses.link}}" href="{{url}}" target="_blank">Algolia</a>
6+
</div>`
7+
};

0 commit comments

Comments
 (0)