Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,62 +35,156 @@ describe('Search Engine SSR', () => {
vi.clearAllMocks();
});

it('should return the expected search engine definition structure', () => {
const engineDefinition = defineSearchEngine(definitionOptions);
expect(engineDefinition).toHaveProperty('fetchStaticState');
expect(engineDefinition).toHaveProperty('hydrateStaticState');
expect(engineDefinition).toHaveProperty('getAccessToken');
expect(engineDefinition).toHaveProperty('setAccessToken');
it('should return an object with searchEngineDefinition and standaloneEngineDefinition', () => {
const engineDefinitions = defineSearchEngine(definitionOptions);
expect(engineDefinitions).toHaveProperty('searchEngineDefinition');
expect(engineDefinitions).toHaveProperty('standaloneEngineDefinition');
});

it('#getAccessToken should return the access token', () => {
const engineDefinition = defineSearchEngine(definitionOptions);
const {getAccessToken} = engineDefinition;
expect(getAccessToken()).toBe('some-token');
});
describe('searchEngineDefinition', () => {
it('should have all required properties', () => {
const {searchEngineDefinition} = defineSearchEngine(definitionOptions);
expect(searchEngineDefinition).toHaveProperty('fetchStaticState');
expect(searchEngineDefinition).toHaveProperty('hydrateStaticState');
expect(searchEngineDefinition).toHaveProperty('getAccessToken');
expect(searchEngineDefinition).toHaveProperty('setAccessToken');
});

it('#setAccessToken should update the access token', () => {
const engineDefinition = defineSearchEngine(definitionOptions);
const {getAccessToken, setAccessToken} = engineDefinition;
setAccessToken('new-access-token');
expect(getAccessToken()).toBe('new-access-token');
});
it('#getAccessToken should return the access token', () => {
const {searchEngineDefinition} = defineSearchEngine(definitionOptions);
const {getAccessToken} = searchEngineDefinition;
expect(getAccessToken()).toBe('some-token');
});

it('should always return parameter manager controller as well as the ones provided', async () => {
const engineDefinition = defineSearchEngine(definitionOptions);
const staticState = await engineDefinition.fetchStaticState({
navigatorContext: mockNavigatorContext,
searchParams: {q: 'test'},
it('#setAccessToken should update the access token', () => {
const {searchEngineDefinition} = defineSearchEngine(definitionOptions);
const {getAccessToken, setAccessToken} = searchEngineDefinition;
setAccessToken('new-access-token');
expect(getAccessToken()).toBe('new-access-token');
});

it('should always return parameter manager controller as well as the ones provided', async () => {
const {searchEngineDefinition} = defineSearchEngine(definitionOptions);
const staticState = await searchEngineDefinition.fetchStaticState({
navigatorContext: mockNavigatorContext,
searchParams: {q: 'test'},
});
expect(staticState.controllers).toHaveProperty('parameterManager');
expect(staticState.controllers).toHaveProperty('controller1');
expect(staticState.controllers).toHaveProperty('controller2');
});

it('should fetch static state successfully', async () => {
const {searchEngineDefinition} = defineSearchEngine(definitionOptions);
const staticState = await searchEngineDefinition.fetchStaticState({
navigatorContext: mockNavigatorContext,
searchParams: {q: 'test query'},
});
expect(staticState).toBeTruthy();
expect(staticState.controllers).toBeDefined();
});

it('should hydrate static state successfully', async () => {
const {searchEngineDefinition} = defineSearchEngine(definitionOptions);
const staticState = await searchEngineDefinition.fetchStaticState({
navigatorContext: mockNavigatorContext,
searchParams: {q: 'test'},
});

const hydratedState =
await searchEngineDefinition.hydrateStaticState(staticState);
expect(hydratedState).toBeTruthy();
expect(hydratedState.engine).toBeDefined();
expect(hydratedState.controllers).toBeDefined();
expect(hydratedState.engine.state.configuration.organizationId).toBe(
'some-org-id'
);
});
expect(staticState.controllers).toHaveProperty('parameterManager');
expect(staticState.controllers).toHaveProperty('controller1');
expect(staticState.controllers).toHaveProperty('controller2');
});

it('should fetch static state successfully', async () => {
const engineDefinition = defineSearchEngine(definitionOptions);
const staticState = await engineDefinition.fetchStaticState({
navigatorContext: mockNavigatorContext,
searchParams: {q: 'test query'},
describe('standaloneEngineDefinition', () => {
it('should have all required properties', () => {
const {standaloneEngineDefinition} =
defineSearchEngine(definitionOptions);
expect(standaloneEngineDefinition).toHaveProperty('fetchStaticState');
expect(standaloneEngineDefinition).toHaveProperty('hydrateStaticState');
expect(standaloneEngineDefinition).toHaveProperty('getAccessToken');
expect(standaloneEngineDefinition).toHaveProperty('setAccessToken');
});

it('#getAccessToken should return the access token', () => {
const {standaloneEngineDefinition} =
defineSearchEngine(definitionOptions);
const {getAccessToken} = standaloneEngineDefinition;
expect(getAccessToken()).toBe('some-token');
});

it('#setAccessToken should update the access token', () => {
const {standaloneEngineDefinition} =
defineSearchEngine(definitionOptions);
const {getAccessToken, setAccessToken} = standaloneEngineDefinition;
setAccessToken('new-standalone-token');
expect(getAccessToken()).toBe('new-standalone-token');
});

it('should always return parameter manager controller as well as the ones provided', async () => {
const {standaloneEngineDefinition} =
defineSearchEngine(definitionOptions);
const staticState = await standaloneEngineDefinition.fetchStaticState({
navigatorContext: mockNavigatorContext,
});
expect(staticState.controllers).toHaveProperty('parameterManager');
expect(staticState.controllers).toHaveProperty('controller1');
expect(staticState.controllers).toHaveProperty('controller2');
});

it('should fetch static state successfully', async () => {
const {standaloneEngineDefinition} =
defineSearchEngine(definitionOptions);
const staticState = await standaloneEngineDefinition.fetchStaticState({
navigatorContext: mockNavigatorContext,
});
expect(staticState).toBeTruthy();
expect(staticState.controllers).toBeDefined();
});

it('should hydrate static state successfully', async () => {
const {standaloneEngineDefinition} =
defineSearchEngine(definitionOptions);
const staticState = await standaloneEngineDefinition.fetchStaticState({
navigatorContext: mockNavigatorContext,
});

const hydratedState =
await standaloneEngineDefinition.hydrateStaticState(staticState);
expect(hydratedState).toBeTruthy();
expect(hydratedState.engine).toBeDefined();
expect(hydratedState.controllers).toBeDefined();
expect(hydratedState.engine.state.configuration.organizationId).toBe(
'some-org-id'
);
});
expect(staticState).toBeTruthy();
expect(staticState.controllers).toBeDefined();
});

it('should hydrate static state successfully', async () => {
const engineDefinition = defineSearchEngine(definitionOptions);
const staticState = await engineDefinition.fetchStaticState({
navigatorContext: mockNavigatorContext,
searchParams: {q: 'test'},
describe('backward compatibility', () => {
it('should allow destructuring only searchEngineDefinition', () => {
const {searchEngineDefinition} = defineSearchEngine(definitionOptions);
expect(searchEngineDefinition).toBeDefined();
expect(searchEngineDefinition.fetchStaticState).toBeDefined();
});

const hydratedState =
await engineDefinition.hydrateStaticState(staticState);
expect(hydratedState).toBeTruthy();
expect(hydratedState.engine).toBeDefined();
expect(hydratedState.controllers).toBeDefined();
expect(hydratedState.engine.state.configuration.organizationId).toBe(
'some-org-id'
);
it('should allow destructuring only standaloneEngineDefinition', () => {
const {standaloneEngineDefinition} =
defineSearchEngine(definitionOptions);
expect(standaloneEngineDefinition).toBeDefined();
expect(standaloneEngineDefinition.fetchStaticState).toBeDefined();
});

it('should allow destructuring both definitions', () => {
const {searchEngineDefinition, standaloneEngineDefinition} =
defineSearchEngine(definitionOptions);
expect(searchEngineDefinition).toBeDefined();
expect(standaloneEngineDefinition).toBeDefined();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import {defineSearchParameterManager} from '../controllers/search-parameter-manager/headless-search-parameter-manager.ssr.js';
import {hydratedStaticStateFactory} from '../factories/hydrated-state-factory.js';
import {fetchStandaloneStaticStateFactory} from '../factories/standalone-static-state-factory.js';
import {fetchStaticStateFactory} from '../factories/static-state-factory.js';
import type {SSRSearchEngine} from '../types/build.js';
import type {AugmentedControllerDefinition} from '../types/controller-definition.js';
Expand All @@ -16,17 +17,26 @@ import type {
* Initializes a Search engine definition in SSR with given controllers definitions and search engine config.
*
* @param options - The search engine definition
* @returns Two utility functions to fetch the initial state of the engine in SSR and hydrate the state in CSR.
* @returns An object containing two engine definitions:
* - `searchEngineDefinition`: For pages with search results (executes server-side search)
* - `standaloneEngineDefinition`: For pages with standalone components only (no server-side search execution)
*
* @remarks
* You can use the {@link InferStaticState} and {@link InferHydratedState} utility types with the returned engine definition
* You can use the {@link InferStaticState} and {@link InferHydratedState} utility types with the returned engine definitions
* to infer the types of static and hydrated state for your controllers.
*
* @example
* ```ts
* const searchEngineDefinition = defineSearchEngine(config);
* const {searchEngineDefinition, standaloneEngineDefinition} = defineSearchEngine(config);
*
* const staticState = await searchEngineDefinition.fetchStaticState({
* // For search results pages
* const searchState = await searchEngineDefinition.fetchStaticState({
* navigatorContext: {/*...* /},
* searchParams: {q: 'query'}
* });
*
* // For pages with standalone search box only (e.g., homepage)
* const standaloneState = await standaloneEngineDefinition.fetchStaticState({
* navigatorContext: {/*...* /},
* });
*
Expand All @@ -40,7 +50,16 @@ export function defineSearchEngine<
TControllerDefinitions extends SearchControllerDefinitionsMap = {},
>(
options: SearchEngineDefinitionOptions<TControllerDefinitions>
): SearchEngineDefinition<SSRSearchEngine, TControllerDefinitions> {
): {
searchEngineDefinition: SearchEngineDefinition<
SSRSearchEngine,
TControllerDefinitions
>;
standaloneEngineDefinition: SearchEngineDefinition<
SSRSearchEngine,
TControllerDefinitions
>;
} {
const {controllers: controllerDefinitions, ...engineOptions} = options;

const getOptions = () => engineOptions;
Expand All @@ -62,15 +81,32 @@ export function defineSearchEngine<
getOptions()
);

const fetchStandaloneStaticState =
fetchStandaloneStaticStateFactory<TControllerDefinitions>(
augmentedControllerDefinition,
getOptions()
);

const hydrateStaticState = hydratedStaticStateFactory<TControllerDefinitions>(
augmentedControllerDefinition,
getOptions()
);

return {
fetchStaticState,
hydrateStaticState,
const commonMethods = {
getAccessToken,
setAccessToken,
};

return {
searchEngineDefinition: {
fetchStaticState,
hydrateStaticState,
...commonMethods,
},
standaloneEngineDefinition: {
fetchStaticState: fetchStandaloneStaticState,
hydrateStaticState,
...commonMethods,
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@ export function hydratedStaticStateFactory<
engine.dispatch(action);
});

await engine.waitForSearchCompletedAction();
if (params.searchActions.length > 0) {
await engine.waitForSearchCompletedAction();
}

return {engine, controllers};
};
Expand Down
Loading
Loading