-
Notifications
You must be signed in to change notification settings - Fork 34
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(headless): added defineSearchParametersManager SSR controller (#…
- Loading branch information
1 parent
90765dc
commit 99cca68
Showing
10 changed files
with
439 additions
and
11 deletions.
There are no files selected for viewing
32 changes: 32 additions & 0 deletions
32
...ess/src/controllers/ssr/search-parameter-manager/headless-ssr-search-parameter-manager.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import {SearchEngine} from '../../../app/search-engine/search-engine'; | ||
import {ControllerDefinitionWithProps} from '../../../app/ssr-engine/types/common'; | ||
import { | ||
SearchParameterManager, | ||
SearchParameterManagerInitialState, | ||
buildSearchParameterManager, | ||
} from '../../search-parameter-manager/headless-search-parameter-manager'; | ||
|
||
export type { | ||
SearchParameterManagerInitialState, | ||
SearchParameterManagerState, | ||
SearchParameterManager, | ||
} from '../../search-parameter-manager/headless-search-parameter-manager'; | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export interface SearchParameterManagerBuildProps { | ||
initialState: SearchParameterManagerInitialState; | ||
} | ||
|
||
/** | ||
* @internal | ||
*/ | ||
export const defineSearchParameterManager = (): ControllerDefinitionWithProps< | ||
SearchEngine, | ||
SearchParameterManager, | ||
SearchParameterManagerBuildProps | ||
> => ({ | ||
buildWithProps: (engine, props) => | ||
buildSearchParameterManager(engine, {initialState: props.initialState}), | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
173 changes: 173 additions & 0 deletions
173
packages/samples/headless-ssr/cypress/e2e/ssr-search-parameter-manager.cy.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
import { | ||
ConsoleAliases, | ||
getResultTitles, | ||
spyOnConsole, | ||
waitForHydration, | ||
} from './ssr-e2e-utils'; | ||
|
||
const searchStateKey = 'search-state'; | ||
|
||
describe('headless ssr with search parameter manager example', () => { | ||
const route = '/generic'; | ||
describe('when loading a page without search parameters, after hydration', () => { | ||
beforeEach(() => { | ||
spyOnConsole(); | ||
cy.visit(route); | ||
waitForHydration(); | ||
getResultTitles() | ||
.should('have.length.greaterThan', 0) | ||
.as('initial-results'); | ||
}); | ||
|
||
it('should not update the search parameters', () => { | ||
cy.url().should((href) => | ||
expect(new URL(href).searchParams.size).to.equal(0) | ||
); | ||
}); | ||
|
||
describe('after submitting a search', () => { | ||
const query = 'abc'; | ||
beforeEach(() => | ||
cy.get('.search-box input').focus().type(`${query}{enter}`) | ||
); | ||
|
||
describe('after the url was updated', () => { | ||
beforeEach(() => { | ||
cy.url().should((href) => | ||
expect(new URL(href).searchParams.has(searchStateKey)).to.equal( | ||
true | ||
) | ||
); | ||
cy.get<string>('@initial-results').then((initialResults) => { | ||
getResultTitles().should('not.deep.equal', initialResults); | ||
}); | ||
}); | ||
|
||
it('should have the correct search parameters', () => { | ||
cy.url().should((href) => { | ||
const searchState = new URL(href).searchParams.get(searchStateKey); | ||
expect(searchState && JSON.parse(searchState)).to.deep.equal({ | ||
q: query, | ||
}); | ||
}); | ||
}); | ||
|
||
it('has only two history states', () => { | ||
cy.go('back'); | ||
cy.go('back'); | ||
cy.url().should('eq', 'about:blank'); | ||
}); | ||
|
||
describe('after pressing the back button', () => { | ||
beforeEach(() => cy.go('back')); | ||
|
||
it('should remove the search parameters', () => { | ||
cy.url().should((href) => | ||
expect(new URL(href).searchParams.size).to.be.equal(0) | ||
); | ||
}); | ||
|
||
it('should update the page', () => { | ||
cy.get('.search-box input').should('have.value', ''); | ||
cy.get<string>('@initial-results').then((initialResults) => { | ||
getResultTitles().should('deep.equal', initialResults); | ||
}); | ||
}); | ||
|
||
it('should not log an error nor warning', () => { | ||
cy.get(ConsoleAliases.error).should('not.be.called'); | ||
cy.get(ConsoleAliases.warn).should('not.be.called'); | ||
}); | ||
}); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('when loading a page with search parameters', () => { | ||
const query = 'def'; | ||
function getInitialUrl() { | ||
const searchParams = new URLSearchParams({ | ||
[searchStateKey]: JSON.stringify({q: query}), | ||
}); | ||
return `${route}?${searchParams.toString()}`; | ||
} | ||
|
||
it('renders page in SSR as expected', () => { | ||
cy.intercept('/**', (req) => { | ||
req.continue((resp) => { | ||
const dom = new DOMParser().parseFromString(resp.body, 'text/html'); | ||
expect(dom.querySelector('.search-box input')).to.have.value(query); | ||
}); | ||
}); | ||
cy.visit(getInitialUrl()); | ||
waitForHydration(); | ||
}); | ||
|
||
describe('after hydration', () => { | ||
beforeEach(() => { | ||
cy.visit(getInitialUrl()); | ||
waitForHydration(); | ||
}); | ||
|
||
it("doesn't update the page", () => { | ||
cy.wait(1000); | ||
cy.get('.search-box input').should('have.value', query); | ||
}); | ||
|
||
it('should not update the parameters', () => { | ||
cy.wait(1000); | ||
cy.url().should((href) => { | ||
expect(href.endsWith(getInitialUrl())).to.equal(true); | ||
}); | ||
}); | ||
|
||
it('has only one history state', () => { | ||
cy.go('back'); | ||
cy.url().should('eq', 'about:blank'); | ||
}); | ||
}); | ||
}); | ||
|
||
describe('when loading a page with invalid search parameters', () => { | ||
function getInitialUrl() { | ||
const searchParams = new URLSearchParams({ | ||
[searchStateKey]: JSON.stringify({q: ''}), | ||
}); | ||
return `${route}?${searchParams.toString()}`; | ||
} | ||
|
||
it('renders page in SSR as expected', () => { | ||
cy.intercept('/**', (req) => { | ||
req.continue((resp) => { | ||
const dom = new DOMParser().parseFromString(resp.body, 'text/html'); | ||
expect(dom.querySelector('.search-box input')).to.have.value(''); | ||
}); | ||
}); | ||
cy.visit(getInitialUrl()); | ||
waitForHydration(); | ||
}); | ||
|
||
describe('after hydration', () => { | ||
beforeEach(() => { | ||
cy.visit(getInitialUrl()); | ||
waitForHydration(); | ||
}); | ||
|
||
it("doesn't update the page", () => { | ||
cy.wait(1000); | ||
cy.get('.search-box input').should('have.value', ''); | ||
}); | ||
|
||
it('should correct the parameters', () => { | ||
cy.url().should((href) => { | ||
expect(new URL(href).searchParams.size).to.equal(0); | ||
}); | ||
}); | ||
|
||
it('has only one history state', () => { | ||
cy.go('back'); | ||
cy.url().should('eq', 'about:blank'); | ||
}); | ||
}); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
packages/samples/headless-ssr/src/app/generic/common/search-parameters-serializer.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import {SearchParameters} from '@coveo/headless'; | ||
import {ReadonlyURLSearchParams} from 'next/navigation'; | ||
|
||
export type NextJSServerSideSearchParams = Record< | ||
string, | ||
string | string[] | undefined | ||
>; | ||
|
||
const searchStateKey = 'search-state'; | ||
|
||
export class CoveoNextJsSearchParametersSerializer { | ||
public static fromServerSideUrlSearchParams( | ||
serverSideUrlSearchParams: NextJSServerSideSearchParams | ||
): CoveoNextJsSearchParametersSerializer { | ||
if (!(searchStateKey in serverSideUrlSearchParams)) { | ||
return new CoveoNextJsSearchParametersSerializer({}); | ||
} | ||
const stringifiedSearchParameters = | ||
serverSideUrlSearchParams[searchStateKey]; | ||
if ( | ||
!stringifiedSearchParameters || | ||
Array.isArray(stringifiedSearchParameters) | ||
) { | ||
return new CoveoNextJsSearchParametersSerializer({}); | ||
} | ||
return new CoveoNextJsSearchParametersSerializer( | ||
JSON.parse(stringifiedSearchParameters) | ||
); | ||
} | ||
|
||
public static fromClientSideUrlSearchParams( | ||
clientSideUrlSearchParams: URLSearchParams | ReadonlyURLSearchParams | ||
) { | ||
if (clientSideUrlSearchParams.getAll(searchStateKey).length !== 1) { | ||
return new CoveoNextJsSearchParametersSerializer({}); | ||
} | ||
return new CoveoNextJsSearchParametersSerializer( | ||
JSON.parse(clientSideUrlSearchParams.get(searchStateKey)!) | ||
); | ||
} | ||
|
||
public static fromCoveoSearchParameters( | ||
coveoSearchParameters: SearchParameters | ||
) { | ||
return new CoveoNextJsSearchParametersSerializer(coveoSearchParameters); | ||
} | ||
|
||
private constructor( | ||
public readonly coveoSearchParameters: SearchParameters | ||
) {} | ||
|
||
public applyToUrlSearchParams(urlSearchParams: URLSearchParams) { | ||
if (!Object.keys(this.coveoSearchParameters).length) { | ||
urlSearchParams.delete(searchStateKey); | ||
return; | ||
} | ||
urlSearchParams.set( | ||
searchStateKey, | ||
JSON.stringify(this.coveoSearchParameters) | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.