diff --git a/.storybook/decorators/withHits.ts b/.storybook/decorators/withHits.ts index 639fa3cb0c..47d18a2977 100644 --- a/.storybook/decorators/withHits.ts +++ b/.storybook/decorators/withHits.ts @@ -1,6 +1,6 @@ import { action } from '@storybook/addon-actions'; import algoliasearch from 'algoliasearch/lite'; -import instantsearch from '../../src/index'; +import instantsearch from '../../src'; import defaultPlayground from '../playgrounds/default'; import { configure } from '../../src/widgets'; import { InstantSearch, InstantSearchOptions } from '../../src/types'; diff --git a/src/__tests__/index-es-test.ts b/src/__tests__/index-es-test.ts new file mode 100644 index 0000000000..f11abea482 --- /dev/null +++ b/src/__tests__/index-es-test.ts @@ -0,0 +1,46 @@ +import instantsearch from '../index.es'; + +describe('instantsearch()', () => { + it('includes a version', () => { + expect(instantsearch.version).toMatch( + /^(\d+\.)?(\d+\.)?(\*|\d+)(-beta.\d+)?$/ + ); + }); + + it('does not include the widget functions', () => { + // @ts-expect-error + expect(() => instantsearch.widgets).toThrowErrorMatchingInlineSnapshot(` + "\\"instantsearch.widgets\\" are not available from the ES build. + + To import the widgets: + + import { searchBox } from 'instantsearch.js/es/widgets'" + `); + }); + + it('does not include the connectors functions', () => { + // @ts-expect-error + expect(() => instantsearch.connectors).toThrowErrorMatchingInlineSnapshot(` + "\\"instantsearch.connectors\\" are not available from the ES build. + + To import the connectors: + + import { connectSearchBox } from 'instantsearch.js/es/connectors'" + `); + }); + + it('includes the helper functions', () => { + expect(Object.keys(instantsearch)).toMatchInlineSnapshot(` + Array [ + "version", + "createInfiniteHitsSessionStorageCache", + "highlight", + "reverseHighlight", + "snippet", + "reverseSnippet", + "insights", + "getInsightsAnonymousUserToken", + ] + `); + }); +}); diff --git a/src/lib/__tests__/main-test.ts b/src/__tests__/index-test.ts similarity index 53% rename from src/lib/__tests__/main-test.ts rename to src/__tests__/index-test.ts index c25b2e6e2c..e0bc53965e 100644 --- a/src/lib/__tests__/main-test.ts +++ b/src/__tests__/index-test.ts @@ -1,4 +1,4 @@ -import instantsearch from '../main'; +import instantsearch from '..'; describe('instantsearch()', () => { it('includes a version', () => { @@ -19,7 +19,22 @@ describe('instantsearch()', () => { }); }); - it('includes the highlight helper function', () => { - expect(instantsearch.highlight).toBeInstanceOf(Function); + it('includes the API and the helper functions', () => { + expect(Object.keys(instantsearch)).toMatchInlineSnapshot(` + Array [ + "version", + "connectors", + "widgets", + "middlewares", + "routers", + "stateMappings", + "createInfiniteHitsSessionStorageCache", + "highlight", + "reverseHighlight", + "snippet", + "reverseSnippet", + "insights", + ] + `); }); }); diff --git a/src/connectors/configure-related-items/__tests__/connectConfigureRelatedItems-test.ts b/src/connectors/configure-related-items/__tests__/connectConfigureRelatedItems-test.ts index f31fb68532..93eee86938 100644 --- a/src/connectors/configure-related-items/__tests__/connectConfigureRelatedItems-test.ts +++ b/src/connectors/configure-related-items/__tests__/connectConfigureRelatedItems-test.ts @@ -1,5 +1,5 @@ import connectConfigureRelatedItems from '../connectConfigureRelatedItems'; -import instantsearch from '../../../lib/main'; +import instantsearch from '../../..'; import { createSearchClient } from '../../../../test/mock/createSearchClient'; import { AlgoliaHit } from '../../../types'; import { noop } from '../../../lib/utils'; diff --git a/src/connectors/range/__tests__/connectRange-test.ts b/src/connectors/range/__tests__/connectRange-test.ts index b9b90ae1cc..179e4c5c7b 100644 --- a/src/connectors/range/__tests__/connectRange-test.ts +++ b/src/connectors/range/__tests__/connectRange-test.ts @@ -12,7 +12,7 @@ import { import { createSearchClient } from '../../../../test/mock/createSearchClient'; import { createSingleSearchResponse } from '../../../../test/mock/createAPIResponse'; import { createInstantSearch } from '../../../../test/mock/createInstantSearch'; -import instantsearch from '../../../lib/main'; +import instantsearch from '../../..'; function createFacetStatsResults({ helper, diff --git a/src/index.es.ts b/src/index.es.ts index 306216183d..9427896485 100644 --- a/src/index.es.ts +++ b/src/index.es.ts @@ -1,5 +1,5 @@ -import { Expand, InstantSearchOptions, UiState } from './types'; -import InstantSearch from './lib/InstantSearch'; +import { Expand, UiState } from './types'; +import InstantSearch, { InstantSearchOptions } from './lib/InstantSearch'; import version from './lib/version'; import { snippet, @@ -10,22 +10,73 @@ import { getInsightsAnonymousUserToken, } from './helpers'; import { createInfiniteHitsSessionStorageCache } from './lib/infiniteHitsCache'; +import { deprecate } from './lib/utils'; -const instantsearch = < - TUiState = Record, - TRouteState = TUiState ->( - options: InstantSearchOptions, TRouteState> -) => new InstantSearch(options); +type InstantSearchModule = { + , TRouteState = TUiState>( + options: InstantSearchOptions, TRouteState> + ): InstantSearch, TRouteState>; + version: string; + + // @major remove these in favour of the exports + /** @deprecated */ + createInfiniteHitsSessionStorageCache: typeof createInfiniteHitsSessionStorageCache; + /** @deprecated */ + highlight: typeof highlight; + /** @deprecated */ + reverseHighlight: typeof reverseHighlight; + /** @deprecated */ + snippet: typeof snippet; + /** @deprecated */ + reverseSnippet: typeof reverseSnippet; + /** @deprecated */ + insights: typeof insights; + /** @deprecated */ + getInsightsAnonymousUserToken: typeof getInsightsAnonymousUserToken; +}; + +/** + * InstantSearch is the main component of InstantSearch.js. This object + * manages the widget and lets you add new ones. + * + * Two parameters are required to get you started with InstantSearch.js: + * - `indexName`: the main index that you will use for your new search UI + * - `searchClient`: the search client to plug to InstantSearch.js + * + * The [search client provided by Algolia](algolia.com/doc/api-client/getting-started/what-is-the-api-client/javascript/) + * needs an `appId` and an `apiKey`. Those parameters can be found in your + * [Algolia dashboard](https://www.algolia.com/api-keys). + * + * If you want to get up and running quickly with InstantSearch.js, have a + * look at the [getting started](https://www.algolia.com/doc/guides/building-search-ui/getting-started/js/). + */ +const instantsearch: InstantSearchModule = options => + new InstantSearch(options); instantsearch.version = version; -instantsearch.snippet = snippet; -instantsearch.reverseSnippet = reverseSnippet; -instantsearch.highlight = highlight; -instantsearch.reverseHighlight = reverseHighlight; + +instantsearch.createInfiniteHitsSessionStorageCache = deprecate( + createInfiniteHitsSessionStorageCache, + "import { createInfiniteHitsSessionStorageCache } from 'instantsearch.js/es/helpers'" +); +instantsearch.highlight = deprecate( + highlight, + "import { highlight } from 'instantsearch.js/es/helpers'" +); +instantsearch.reverseHighlight = deprecate( + reverseHighlight, + "import { reverseHighlight } from 'instantsearch.js/es/helpers'" +); +instantsearch.snippet = deprecate( + snippet, + "import { snippet } from 'instantsearch.js/es/helpers'" +); +instantsearch.reverseSnippet = deprecate( + reverseSnippet, + "import { reverseSnippet } from 'instantsearch.js/es/helpers'" +); instantsearch.insights = insights; instantsearch.getInsightsAnonymousUserToken = getInsightsAnonymousUserToken; -instantsearch.createInfiniteHitsSessionStorageCache = createInfiniteHitsSessionStorageCache; Object.defineProperty(instantsearch, 'widgets', { get() { diff --git a/src/index.ts b/src/index.ts index 5c8c0b642d..86054d831f 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,70 @@ -import instantsearch from './lib/main'; +import InstantSearch, { InstantSearchOptions } from './lib/InstantSearch'; +import { Expand, UiState } from './types'; + +import version from './lib/version'; + +import * as connectors from './connectors/index'; +import * as widgets from './widgets/index'; +import * as helpers from './helpers/index'; +import * as middlewares from './middlewares/index'; + +import * as routers from './lib/routers/index'; +import * as stateMappings from './lib/stateMappings/index'; +import { createInfiniteHitsSessionStorageCache } from './lib/infiniteHitsCache/index'; + +type InstantSearchModule = { + , TRouteState = TUiState>( + options: InstantSearchOptions, TRouteState> + ): InstantSearch, TRouteState>; + version: string; + + connectors: typeof connectors; + widgets: typeof widgets; + middlewares: typeof middlewares; + + routers: typeof routers; + stateMappings: typeof stateMappings; + + createInfiniteHitsSessionStorageCache: typeof createInfiniteHitsSessionStorageCache; + highlight: typeof helpers.highlight; + reverseHighlight: typeof helpers.reverseHighlight; + snippet: typeof helpers.snippet; + reverseSnippet: typeof helpers.reverseSnippet; + insights: typeof helpers.insights; +}; + +/** + * InstantSearch is the main component of InstantSearch.js. This object + * manages the widget and lets you add new ones. + * + * Two parameters are required to get you started with InstantSearch.js: + * - `indexName`: the main index that you will use for your new search UI + * - `searchClient`: the search client to plug to InstantSearch.js + * + * The [search client provided by Algolia](algolia.com/doc/api-client/getting-started/what-is-the-api-client/javascript/) + * needs an `appId` and an `apiKey`. Those parameters can be found in your + * [Algolia dashboard](https://www.algolia.com/api-keys). + * + * If you want to get up and running quickly with InstantSearch.js, have a + * look at the [getting started](https://www.algolia.com/doc/guides/building-search-ui/getting-started/js/). + */ +const instantsearch: InstantSearchModule = options => + new InstantSearch(options); + +instantsearch.version = version; + +instantsearch.connectors = connectors; +instantsearch.widgets = widgets; +instantsearch.middlewares = middlewares; + +instantsearch.routers = routers; +instantsearch.stateMappings = stateMappings; + +instantsearch.createInfiniteHitsSessionStorageCache = createInfiniteHitsSessionStorageCache; +instantsearch.highlight = helpers.highlight; +instantsearch.reverseHighlight = helpers.reverseHighlight; +instantsearch.snippet = helpers.snippet; +instantsearch.reverseSnippet = helpers.reverseSnippet; +instantsearch.insights = helpers.insights; export default instantsearch; diff --git a/src/lib/__tests__/RoutingManager-test.ts b/src/lib/__tests__/RoutingManager-test.ts index 4ad696efdd..fbe7ad3e48 100644 --- a/src/lib/__tests__/RoutingManager-test.ts +++ b/src/lib/__tests__/RoutingManager-test.ts @@ -12,7 +12,7 @@ import { IndexUiState, } from '../../types'; import historyRouter from '../routers/history'; -import instantsearch from '../main'; +import instantsearch from '../..'; const createFakeRouter = (args: Partial = {}): Router => ({ onUpdate(..._args) {}, diff --git a/src/lib/main.ts b/src/lib/main.ts deleted file mode 100644 index 820d903b94..0000000000 --- a/src/lib/main.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { Expand, InstantSearchOptions, UiState } from '../types'; - -import InstantSearch from './InstantSearch'; -import version from './version'; - -import * as connectors from '../connectors/index'; -import * as widgets from '../widgets/index'; -import * as helpers from '../helpers/index'; -import * as middlewares from '../middlewares/index'; - -import * as routers from './routers/index'; -import * as stateMappings from './stateMappings/index'; -import { createInfiniteHitsSessionStorageCache } from './infiniteHitsCache/index'; - -/** - * InstantSearch is the main component of InstantSearch.js. This object - * manages the widget and lets you add new ones. - * - * Two parameters are required to get you started with InstantSearch.js: - * - `indexName`: the main index that you will use for your new search UI - * - `searchClient`: the search client to plug to InstantSearch.js - * - * The [search client provided by Algolia](https://github.com/algolia/algoliasearch-client-javascript) - * needs an `appId` and an `apiKey`. Those parameters can be found in your - * [Algolia dashboard](https://www.algolia.com/api-keys). - * - * If you want to get up and running quickly with InstantSearch.js, have a - * look at the [getting started](getting-started.html). - * @function instantsearch - * @param {InstantSearchOptions} options The options - */ -const instantsearch = < - TUiState = Record, - TRouteState = TUiState ->( - options: InstantSearchOptions, TRouteState> -) => new InstantSearch(options); - -instantsearch.routers = routers; -instantsearch.stateMappings = stateMappings; -instantsearch.connectors = connectors; -instantsearch.widgets = widgets; -instantsearch.version = version; -instantsearch.createInfiniteHitsSessionStorageCache = createInfiniteHitsSessionStorageCache; -instantsearch.highlight = helpers.highlight; -instantsearch.reverseHighlight = helpers.reverseHighlight; -instantsearch.snippet = helpers.snippet; -instantsearch.reverseSnippet = helpers.reverseSnippet; -instantsearch.insights = helpers.insights; -instantsearch.middlewares = middlewares; - -export default instantsearch; diff --git a/src/lib/utils/__tests__/logger-test.ts b/src/lib/utils/__tests__/logger-test.ts index 2df0a53cfd..4c8b51a047 100644 --- a/src/lib/utils/__tests__/logger-test.ts +++ b/src/lib/utils/__tests__/logger-test.ts @@ -2,10 +2,19 @@ import { deprecate, warning } from '../logger'; describe('deprecate', () => { const sum = (...args: number[]) => args.reduce((acc, _) => acc + _, 0); + let warn: jest.SpiedFunction; - it('expect to call initial function and print message', () => { - const warn = jest.spyOn(global.console, 'warn'); + beforeEach(() => { + warn = jest.spyOn(global.console, 'warn'); warn.mockImplementation(() => {}); + }); + + afterEach(() => { + warn.mockReset(); + warn.mockRestore(); + }); + + it('expect to call initial function and print message', () => { const fn = deprecate(sum, 'message'); const expectation = fn(1, 2, 3); @@ -13,14 +22,9 @@ describe('deprecate', () => { expect(actual).toBe(expectation); expect(warn).toHaveBeenCalledWith('[InstantSearch.js]: message'); - - warn.mockReset(); - warn.mockRestore(); }); it('expect to call initial function twice and print message once', () => { - const warn = jest.spyOn(global.console, 'warn'); - warn.mockImplementation(() => {}); const fn = deprecate(sum, 'message'); const expectation0 = fn(1, 2, 3); @@ -30,9 +34,6 @@ describe('deprecate', () => { expect(actual).toBe(expectation0); expect(actual).toBe(expectation1); expect(warn).toHaveBeenCalledTimes(1); - - warn.mockReset(); - warn.mockRestore(); }); }); diff --git a/src/lib/utils/logger.ts b/src/lib/utils/logger.ts index bd08924428..57ee7ad970 100644 --- a/src/lib/utils/logger.ts +++ b/src/lib/utils/logger.ts @@ -1,10 +1,5 @@ import noop from './noop'; -type Deprecate any> = ( - fn: TCallback, - message: string -) => TCallback; - type Warn = (message: string) => void; type Warning = { @@ -15,7 +10,12 @@ type Warning = { /** * Logs a warning when this function is called, in development environment only. */ -let deprecate: Deprecate = fn => fn; +let deprecate = any>( + fn: TCallback, + // @ts-ignore this parameter is used in the __DEV__ branch + // eslint-disable-next-line @typescript-eslint/no-unused-vars + message: string +) => fn; /** * Logs a warning @@ -46,7 +46,7 @@ if (__DEV__) { } return fn(...args); - }; + } as typeof fn; }; warning = ((condition, message) => { diff --git a/src/middlewares/__tests__/createMetadataMiddleware.ts b/src/middlewares/__tests__/createMetadataMiddleware.ts index b243c5c923..ca6edfd0fd 100644 --- a/src/middlewares/__tests__/createMetadataMiddleware.ts +++ b/src/middlewares/__tests__/createMetadataMiddleware.ts @@ -4,7 +4,7 @@ import algoliasearchV3 from 'algoliasearch-v3'; import { createMetadataMiddleware } from '..'; import { createSearchClient } from '../../../test/mock/createSearchClient'; import { wait } from '../../../test/utils/wait'; -import instantsearch from '../../lib/main'; +import instantsearch from '../..'; import { configure, hits, index, pagination, searchBox } from '../../widgets'; import { isMetadataEnabled } from '../createMetadataMiddleware';