Skip to content

Commit

Permalink
feat(connectToggleRefinement): add support for array values (#4420)
Browse files Browse the repository at this point in the history
Co-authored-by: Haroen Viaene <fingebimus@me.com>
Co-authored-by: Eunjae Lee <eunjae.lee@algolia.com>
Co-authored-by: eunjae-lee <karis612@gmail.com>
  • Loading branch information
4 people authored Jun 4, 2020
1 parent 92bcc02 commit fe1fbee
Show file tree
Hide file tree
Showing 6 changed files with 165 additions and 38 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,29 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/toggle-refi
);
});

it('sets user-provided "off" value by default (array)', () => {
const makeWidget = connectToggleRefinement(() => {});
const widget = makeWidget({
attribute: 'whatever',
off: ['a', 'b'],
});

const helper = jsHelper(
{},
'',
widget.getWidgetSearchParameters(new SearchParameters({}), {
uiState: {},
})
);
widget.init({ helper, state: helper.state });

expect(helper.state.disjunctiveFacetsRefinements).toEqual(
expect.objectContaining({
whatever: ['a', 'b'],
})
);
});

it('sets user-provided "on" value on refine (falsy)', () => {
let caughtRefine;
const makeWidget = connectToggleRefinement(({ refine }) => {
Expand Down Expand Up @@ -571,6 +594,58 @@ See documentation: https://www.algolia.com/doc/api-reference/widgets/toggle-refi
);
});

it('sets user-provided "on" value on refine (array)', () => {
let caughtRefine;
const makeWidget = connectToggleRefinement(({ refine }) => {
caughtRefine = refine;
});
const widget = makeWidget({
attribute: 'whatever',
on: ['a', 'b'],
});

const helper = jsHelper(
{ search() {} },
'',
widget.getWidgetSearchParameters(new SearchParameters({}), {
uiState: {},
})
);
helper.search = jest.fn();

widget.init({ helper, state: helper.state });

expect(helper.state.disjunctiveFacetsRefinements).toEqual({
whatever: [],
});

widget.render({
results: new SearchResults(helper.state, [
{
facets: {
whatever: {
a: 45,
b: 20,
c: 20,
},
},
nbHits: 85,
},
]),
state: helper.state,
helper,
});

// toggle the value
caughtRefine();

expect(helper.state.disjunctiveFacetsRefinements).toEqual(
expect.objectContaining({
whatever: ['a', 'b'],
})
);
});

describe('dispose', () => {
test('calls the unmount function', () => {
const render = jest.fn();
Expand Down
107 changes: 71 additions & 36 deletions src/connectors/toggle-refinement/connectToggleRefinement.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
createDocumentationMessageGenerator,
find,
noop,
toArray,
} from '../../lib/utils';

const withUsage = createDocumentationMessageGenerator({
Expand Down Expand Up @@ -99,8 +100,10 @@ export default function connectToggleRefinement(renderFn, unmountFn = noop) {

const hasAnOffValue = userOff !== undefined;
const hasAnOnValue = userOn !== undefined;
const on = hasAnOnValue ? escapeRefinement(userOn) : undefined;
const off = hasAnOffValue ? escapeRefinement(userOff) : undefined;
const on = hasAnOnValue ? toArray(userOn).map(escapeRefinement) : undefined;
const off = hasAnOffValue
? toArray(userOff).map(escapeRefinement)
: undefined;

return {
$$type: 'ais.toggleRefinement',
Expand All @@ -109,48 +112,66 @@ export default function connectToggleRefinement(renderFn, unmountFn = noop) {
// Checking
if (!isRefined) {
if (hasAnOffValue) {
helper.removeDisjunctiveFacetRefinement(attribute, off);
off.forEach(v =>
helper.removeDisjunctiveFacetRefinement(attribute, v)
);
}
helper.addDisjunctiveFacetRefinement(attribute, on);

on.forEach(v => helper.addDisjunctiveFacetRefinement(attribute, v));
} else {
// Unchecking
helper.removeDisjunctiveFacetRefinement(attribute, on);
on.forEach(v =>
helper.removeDisjunctiveFacetRefinement(attribute, v)
);

if (hasAnOffValue) {
helper.addDisjunctiveFacetRefinement(attribute, off);
off.forEach(v =>
helper.addDisjunctiveFacetRefinement(attribute, v)
);
}
}

helper.search();
},

init({ state, helper, createURL, instantSearchInstance }) {
this._createURL = isCurrentlyRefined => () =>
createURL(
state
.removeDisjunctiveFacetRefinement(
attribute,
isCurrentlyRefined ? on : off
)
.addDisjunctiveFacetRefinement(
attribute,
isCurrentlyRefined ? off : on
)
);
this._createURL = isCurrentlyRefined => () => {
const valuesToRemove = isCurrentlyRefined ? on : off;
if (valuesToRemove) {
valuesToRemove.forEach(v => {
state.removeDisjunctiveFacetRefinement(attribute, v);
});
}

const valuesToAdd = isCurrentlyRefined ? off : on;
if (valuesToAdd) {
valuesToAdd.forEach(v => {
state.addDisjunctiveFacetRefinement(attribute, v);
});
}

return createURL(state);
};

this.toggleRefinement = opts => {
this._toggleRefinement(helper, opts);
};

const isRefined = state.isDisjunctiveFacetRefined(attribute, on);
const isRefined =
on && on.every(v => state.isDisjunctiveFacetRefined(attribute, v));

// no need to refine anything at init if no custom off values
if (hasAnOffValue) {
// Add filtering on the 'off' value if set
if (!isRefined) {
const currentPage = helper.state.page;
helper
.addDisjunctiveFacetRefinement(attribute, off)
.setPage(currentPage);
if (off) {
off.forEach(v =>
helper.addDisjunctiveFacetRefinement(attribute, v)
);
}

helper.setPage(currentPage);
}
}

Expand Down Expand Up @@ -185,7 +206,9 @@ export default function connectToggleRefinement(renderFn, unmountFn = noop) {
},

render({ helper, results, state, instantSearchInstance }) {
const isRefined = helper.state.isDisjunctiveFacetRefined(attribute, on);
const isRefined =
on &&
on.every(v => helper.state.isDisjunctiveFacetRefined(attribute, v));
const offValue = off === undefined ? false : off;
const allFacetValues = results.getFacetValues(attribute) || [];

Expand Down Expand Up @@ -246,10 +269,11 @@ export default function connectToggleRefinement(renderFn, unmountFn = noop) {
},

getWidgetState(uiState, { searchParameters }) {
const isRefined = searchParameters.isDisjunctiveFacetRefined(
attribute,
on
);
const isRefined =
on &&
on.every(v =>
searchParameters.isDisjunctiveFacetRefined(attribute, v)
);

if (!isRefined) {
return uiState;
Expand All @@ -265,25 +289,36 @@ export default function connectToggleRefinement(renderFn, unmountFn = noop) {
},

getWidgetSearchParameters(searchParameters, { uiState }) {
const withFacetConfiguration = searchParameters
let withFacetConfiguration = searchParameters
.clearRefinements(attribute)
.addDisjunctiveFacet(attribute);

const isRefined = Boolean(uiState.toggle && uiState.toggle[attribute]);

if (isRefined) {
return withFacetConfiguration.addDisjunctiveFacetRefinement(
attribute,
on
);
if (on) {
on.forEach(v => {
withFacetConfiguration = withFacetConfiguration.addDisjunctiveFacetRefinement(
attribute,
v
);
});
}

return withFacetConfiguration;
}

// It's not refined with an `off` value
if (hasAnOffValue) {
return withFacetConfiguration.addDisjunctiveFacetRefinement(
attribute,
off
);
if (off) {
off.forEach(v => {
withFacetConfiguration = withFacetConfiguration.addDisjunctiveFacetRefinement(
attribute,
v
);
});
}
return withFacetConfiguration;
}

// It's not refined without an `off` value
Expand Down
11 changes: 11 additions & 0 deletions src/lib/utils/__tests__/toArray-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import toArray from '../toArray';

describe('toArray', () => {
test('cast to array if necessary', () => {
expect(toArray).toBeInstanceOf(Function);
expect(toArray('a')).toEqual(['a']);
expect(toArray(['a'])).toEqual(['a']);
// Checks that `toArray` acts like a function.
expect(toArray.toString).toBe(Function.prototype.toString);
});
});
1 change: 1 addition & 0 deletions src/lib/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export { default as find } from './find';
export { default as findIndex } from './findIndex';
export { default as mergeSearchParameters } from './mergeSearchParameters';
export { default as resolveSearchParameters } from './resolveSearchParameters';
export { default as toArray } from './toArray';
export { warning, deprecate } from './logger';
export {
createDocumentationLink,
Expand Down
5 changes: 5 additions & 0 deletions src/lib/utils/toArray.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function toArray(value: any) {
return Array.isArray(value) ? value : [value];
}

export default toArray;
4 changes: 2 additions & 2 deletions src/widgets/toggle-refinement/toggle-refinement.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ const renderer = ({ containerNode, cssClasses, renderState, templates }) => (
* @typedef {Object} ToggleWidgetOptions
* @property {string|HTMLElement} container Place where to insert the widget in your webpage.
* @property {string} attribute Name of the attribute for faceting (eg. "free_shipping").
* @property {string|number|boolean} on Value to filter on when checked.
* @property {string|number|boolean} off Value to filter on when unchecked.
* @property {string|number|boolean|array} on Value to filter on when checked.
* @property {string|number|boolean|array} off Value to filter on when unchecked.
* element (when using the default template). By default when switching to `off`, no refinement will be asked. So you
* will get both `true` and `false` results. If you set the off value to `false` then you will get only objects
* having `false` has a value for the selected attribute.
Expand Down

0 comments on commit fe1fbee

Please sign in to comment.