diff --git a/dev/app.js b/dev/app.js
index 0e9c7014f0..cad6cff70d 100644
--- a/dev/app.js
+++ b/dev/app.js
@@ -317,7 +317,7 @@ search.addWidget(
instantsearch.widgets.toggle({
container: '#free-shipping',
attributeName: 'free_shipping',
- label: 'Free Shipping',
+ label: 'Free Shipping (toggle single value)',
cssClasses: {
header: 'facet-title',
item: 'facet-value checkbox',
@@ -330,6 +330,27 @@ search.addWidget(
})
);
+search.addWidget(
+ instantsearch.widgets.toggle({
+ container: '#google-amazon',
+ attributeName: 'brand',
+ label: 'Canon (not checked) or sony (checked)',
+ cssClasses: {
+ header: 'facet-title',
+ item: 'facet-value checkbox',
+ count: 'facet-count pull-right',
+ active: 'facet-active',
+ },
+ values: {
+ on: 'Sony',
+ off: 'Canon',
+ },
+ templates: {
+ header: 'Google or amazon (toggle two values)',
+ },
+ })
+);
+
search.addWidget(
instantsearch.widgets.menu({
container: '#categories',
diff --git a/dev/index.html b/dev/index.html
index f2803a43e5..c632648077 100644
--- a/dev/index.html
+++ b/dev/index.html
@@ -27,6 +27,7 @@
Instant search demo using instantsearch.js
+
diff --git a/src/connectors/stats/__tests__/connectStats-test.js b/src/connectors/stats/__tests__/connectStats-test.js
index 4da9fcb64b..5339368f91 100644
--- a/src/connectors/stats/__tests__/connectStats-test.js
+++ b/src/connectors/stats/__tests__/connectStats-test.js
@@ -11,7 +11,7 @@ import connectStats from '../connectStats.js';
const fakeClient = {addAlgoliaAgent: () => {}};
describe('connectStats', () => {
- it.only('Renders during init and render', () => {
+ it('Renders during init and render', () => {
const container = document.createElement('div');
// test that the dummyRendering is called with the isFirstRendering
// flag set accordingly
diff --git a/src/connectors/toggle/__tests__/connectToggle-test.js b/src/connectors/toggle/__tests__/connectToggle-test.js
new file mode 100644
index 0000000000..70329f6a75
--- /dev/null
+++ b/src/connectors/toggle/__tests__/connectToggle-test.js
@@ -0,0 +1,389 @@
+/* eslint-env mocha */
+
+import expect from 'expect';
+import sinon from 'sinon';
+
+import jsHelper from 'algoliasearch-helper';
+const SearchResults = jsHelper.SearchResults;
+
+import connectToggle from '../connectToggle.js';
+
+const fakeClient = {addAlgoliaAgent: () => {}};
+
+describe('connectToggle', () => {
+ it('Renders during init and render', () => {
+ const container = document.createElement('div');
+ // test that the dummyRendering is called with the isFirstRendering
+ // flag set accordingly
+ const rendering = sinon.stub();
+ const makeWidget = connectToggle(rendering);
+
+ const attributeName = 'isShippingFree';
+ const label = 'Free shipping?';
+ const widget = makeWidget({
+ container,
+ attributeName,
+ label,
+ });
+
+ const config = widget.getConfiguration();
+ expect(config).toEqual({
+ disjunctiveFacets: [attributeName],
+ });
+
+ const helper = jsHelper(fakeClient, '', config);
+ helper.search = sinon.stub();
+
+ widget.init({
+ helper,
+ state: helper.state,
+ createURL: () => '#',
+ onHistoryChange: () => {},
+ });
+
+ { // should call the rendering once with isFirstRendering to true
+ expect(rendering.callCount).toBe(1);
+ const isFirstRendering = rendering.lastCall.args[1];
+ expect(isFirstRendering).toBe(true);
+
+ // should provide good values for the first rendering
+ const {containerNode, collapsible, value, shouldAutoHideContainer} = rendering.lastCall.args[0];
+ expect(containerNode).toBe(container);
+ expect(collapsible).toBe(false);
+ expect(value).toEqual({
+ name: label,
+ count: null,
+ isRefined: false,
+ onFacetValue: {
+ name: label,
+ isRefined: false,
+ count: 0,
+ },
+ offFacetValue: {
+ name: label,
+ isRefined: false,
+ count: 0,
+ },
+ });
+ expect(shouldAutoHideContainer).toBe(true);
+ }
+
+ widget.render({
+ results: new SearchResults(helper.state, [{
+ facets: {
+ isShippingFree: {
+ 'true': 45, // eslint-disable-line
+ 'false': 40, // eslint-disable-line
+ },
+ },
+ nbHits: 85,
+ }]),
+ state: helper.state,
+ helper,
+ createURL: () => '#',
+ });
+
+ { // Should call the rendering a second time, with isFirstRendering to false
+ expect(rendering.callCount).toBe(2);
+ const isFirstRendering = rendering.lastCall.args[1];
+ expect(isFirstRendering).toBe(false);
+
+ // should provide good values after the first search
+ const {containerNode, collapsible, value, shouldAutoHideContainer} = rendering.lastCall.args[0];
+ expect(containerNode).toBe(container);
+ expect(collapsible).toBe(false);
+ expect(value).toEqual({
+ name: label,
+ count: 45,
+ isRefined: false,
+ onFacetValue: {
+ name: label,
+ isRefined: false,
+ count: 45,
+ },
+ offFacetValue: {
+ name: label,
+ isRefined: false,
+ count: 85,
+ },
+ });
+ expect(shouldAutoHideContainer).toBe(false);
+ }
+ });
+
+ it('Provides a function to add/remove a facet value', () => {
+ const container = document.createElement('div');
+ const rendering = sinon.stub();
+ const makeWidget = connectToggle(rendering);
+
+ const attributeName = 'isShippingFree';
+ const label = 'Free shipping?';
+ const widget = makeWidget({
+ container,
+ attributeName,
+ label,
+ });
+
+ const helper = jsHelper(fakeClient, '', widget.getConfiguration());
+ helper.search = sinon.stub();
+
+ widget.init({
+ helper,
+ state: helper.state,
+ createURL: () => '#',
+ onHistoryChange: () => {},
+ });
+
+ { // first rendering
+ expect(helper.state.disjunctiveFacetsRefinements[attributeName]).toEqual(undefined);
+ const renderOptions = rendering.lastCall.args[0];
+ const {toggleRefinement, value} = renderOptions;
+ expect(value).toEqual({
+ name: label,
+ count: null,
+ isRefined: false,
+ onFacetValue: {
+ name: label,
+ isRefined: false,
+ count: 0,
+ },
+ offFacetValue: {
+ name: label,
+ isRefined: false,
+ count: 0,
+ },
+ });
+ toggleRefinement(value, value.isRefined);
+ expect(helper.state.disjunctiveFacetsRefinements[attributeName]).toEqual(['true']);
+ toggleRefinement(value, !value.isRefined);
+ expect(helper.state.disjunctiveFacetsRefinements[attributeName]).toEqual(undefined);
+ }
+
+ widget.render({
+ results: new SearchResults(helper.state, [{
+ facets: {
+ isShippingFree: {
+ 'true': 45, // eslint-disable-line
+ 'false': 40, // eslint-disable-line
+ },
+ },
+ nbHits: 85,
+ }]),
+ state: helper.state,
+ helper,
+ createURL: () => '#',
+ });
+
+ { // Second rendering
+ expect(helper.state.disjunctiveFacetsRefinements[attributeName]).toEqual(undefined);
+ const renderOptions = rendering.lastCall.args[0];
+ const {toggleRefinement, value} = renderOptions;
+ expect(value).toEqual({
+ name: label,
+ count: 45,
+ isRefined: false,
+ onFacetValue: {
+ name: label,
+ isRefined: false,
+ count: 45,
+ },
+ offFacetValue: {
+ name: label,
+ isRefined: false,
+ count: 85,
+ },
+ });
+ toggleRefinement(value, value.isRefined);
+ expect(helper.state.disjunctiveFacetsRefinements[attributeName]).toEqual(['true']);
+ }
+
+ widget.render({
+ results: new SearchResults(helper.state, [{
+ facets: {
+ isShippingFree: {
+ 'true': 45, // eslint-disable-line
+ },
+ },
+ nbHits: 85,
+ }, {
+ facets: {
+ isShippingFree: {
+ 'true': 45, // eslint-disable-line
+ 'false': 40, // eslint-disable-line
+ },
+ },
+ nbHits: 85,
+ }]),
+ state: helper.state,
+ helper,
+ createURL: () => '#',
+ });
+
+ { // Third rendering
+ expect(helper.state.disjunctiveFacetsRefinements[attributeName]).toEqual(['true']);
+ const renderOptions = rendering.lastCall.args[0];
+ const {toggleRefinement, value} = renderOptions;
+ expect(value).toEqual({
+ name: label,
+ count: 85,
+ isRefined: true,
+ onFacetValue: {
+ name: label,
+ isRefined: true,
+ count: 45,
+ },
+ offFacetValue: {
+ name: label,
+ isRefined: false,
+ count: 85,
+ },
+ });
+ toggleRefinement(value, value.isRefined);
+ expect(helper.state.disjunctiveFacetsRefinements[attributeName]).toEqual(undefined);
+ }
+ });
+
+ it('Provides a function to toggle between two values', () => {
+ const container = document.createElement('div');
+ const rendering = sinon.stub();
+ const makeWidget = connectToggle(rendering);
+
+ const attributeName = 'isShippingFree';
+ const label = 'Free shipping?';
+ const widget = makeWidget({
+ container,
+ attributeName,
+ label,
+ values: {
+ on: 'true',
+ off: 'false',
+ },
+ });
+
+ const helper = jsHelper(fakeClient, '', widget.getConfiguration());
+ helper.search = sinon.stub();
+
+ widget.init({
+ helper,
+ state: helper.state,
+ createURL: () => '#',
+ onHistoryChange: () => {},
+ });
+
+ { // first rendering
+ expect(helper.state.disjunctiveFacetsRefinements[attributeName]).toEqual(['false']);
+ const renderOptions = rendering.lastCall.args[0];
+ const {toggleRefinement, value} = renderOptions;
+ expect(value).toEqual({
+ name: label,
+ count: null,
+ isRefined: false,
+ onFacetValue: {
+ name: label,
+ isRefined: false,
+ count: 0,
+ },
+ offFacetValue: {
+ name: label,
+ isRefined: true,
+ count: 0,
+ },
+ });
+ toggleRefinement(value, value.isRefined);
+ expect(helper.state.disjunctiveFacetsRefinements[attributeName]).toEqual(['true']);
+ toggleRefinement(value, !value.isRefined);
+ expect(helper.state.disjunctiveFacetsRefinements[attributeName]).toEqual(['false']);
+ }
+
+ widget.render({
+ results: new SearchResults(helper.state, [{
+ facets: {
+ isShippingFree: {
+ 'false': 40, // eslint-disable-line
+ },
+ },
+ nbHits: 40,
+ }, {
+ facets: {
+ isShippingFree: {
+ 'true': 45, // eslint-disable-line
+ 'false': 40, // eslint-disable-line
+ },
+ },
+ nbHits: 85,
+ }]),
+ state: helper.state,
+ helper,
+ createURL: () => '#',
+ });
+
+ { // Second rendering
+ expect(helper.state.disjunctiveFacetsRefinements[attributeName]).toEqual(['false']);
+ const renderOptions = rendering.lastCall.args[0];
+ const {toggleRefinement, value} = renderOptions;
+ expect(value).toEqual({
+ name: label,
+ // the value is the one that is not selected
+ count: 45,
+ isRefined: false,
+ onFacetValue: {
+ name: label,
+ isRefined: false,
+ count: 45,
+ },
+ offFacetValue: {
+ name: label,
+ isRefined: true,
+ count: 40,
+ },
+ });
+ toggleRefinement(value, value.isRefined);
+ expect(helper.state.disjunctiveFacetsRefinements[attributeName]).toEqual(['true']);
+ }
+
+ widget.render({
+ results: new SearchResults(helper.state, [{
+ facets: {
+ isShippingFree: {
+ 'true': 45, // eslint-disable-line
+ },
+ },
+ nbHits: 85,
+ }, {
+ facets: {
+ isShippingFree: {
+ 'true': 45, // eslint-disable-line
+ 'false': 40, // eslint-disable-line
+ },
+ },
+ nbHits: 85,
+ }]),
+ state: helper.state,
+ helper,
+ createURL: () => '#',
+ });
+
+ { // Third rendering
+ expect(helper.state.disjunctiveFacetsRefinements[attributeName]).toEqual(['true']);
+ const renderOptions = rendering.lastCall.args[0];
+ const {toggleRefinement, value} = renderOptions;
+ expect(value).toEqual({
+ name: label,
+ count: 40,
+ isRefined: true,
+ onFacetValue: {
+ name: label,
+ isRefined: true,
+ count: 45,
+ },
+ offFacetValue: {
+ name: label,
+ isRefined: false,
+ count: 40,
+ },
+ });
+ toggleRefinement(value, value.isRefined);
+ expect(helper.state.disjunctiveFacetsRefinements[attributeName]).toEqual(['false']);
+ }
+ });
+});
diff --git a/src/connectors/toggle/connectToggle.js b/src/connectors/toggle/connectToggle.js
index 67314b3746..0603ceb406 100644
--- a/src/connectors/toggle/connectToggle.js
+++ b/src/connectors/toggle/connectToggle.js
@@ -137,7 +137,7 @@ const connectToggle = toggleRendering => ({
count: 0,
};
- const facetValue = {
+ const value = {
name: label,
isRefined,
count: null,
@@ -149,7 +149,7 @@ const connectToggle = toggleRendering => ({
collapsible,
createURL: this._createURL(state, isRefined),
cssClasses,
- facetValues: [facetValue],
+ value,
shouldAutoHideContainer: autoHideContainer,
templateProps: this._templateProps,
toggleRefinement: this.toggleRefinement,
@@ -178,7 +178,7 @@ const connectToggle = toggleRendering => ({
// if checkbox is checked, show: [x] free shipping (countWhenNotChecked)
const nextRefinement = isRefined ? offFacetValue : onFacetValue;
- const facetValue = {
+ const value = {
name: label,
isRefined,
count: nextRefinement === undefined ? null : nextRefinement.count,
@@ -190,8 +190,8 @@ const connectToggle = toggleRendering => ({
collapsible,
createURL: this._createURL(state, isRefined),
cssClasses,
- facetValues: [facetValue],
- shouldAutoHideContainer: autoHideContainer && (facetValue.count === 0 || facetValue.count === null),
+ value,
+ shouldAutoHideContainer: autoHideContainer && (value.count === 0 || value.count === null),
templateProps: this._templateProps,
toggleRefinement: this.toggleRefinement,
containerNode,
diff --git a/src/widgets/toggle/toggle.js b/src/widgets/toggle/toggle.js
index 3c04084b3c..71e254f8ae 100644
--- a/src/widgets/toggle/toggle.js
+++ b/src/widgets/toggle/toggle.js
@@ -47,7 +47,7 @@ function defaultRendering({
collapsible,
createURL,
cssClasses,
- facetValues,
+ value,
shouldAutoHideContainer,
templateProps,
toggleRefinement,
@@ -60,7 +60,7 @@ function defaultRendering({
collapsible={collapsible}
createURL={createURL}
cssClasses={cssClasses}
- facetValues={facetValues}
+ facetValues={[value]}
shouldAutoHideContainer={shouldAutoHideContainer}
templateProps={templateProps}
toggleRefinement={toggleRefinement}