feat(atomic): add request transformer support to MSW endpoint harness - #7624
Conversation
|
✅ Snyk checks have passed. No issues have been found so far.
💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse. |
|
Tip All tests passed and all changes approved!🟢 UI Tests: 367 tests unchanged |
@coveo/atomic
@coveo/atomic-hosted-page
@coveo/atomic-legacy
@coveo/atomic-react
@coveo/auth
@coveo/bueno
@coveo/create-atomic
@coveo/create-atomic-component
@coveo/create-atomic-component-project
@coveo/create-atomic-result-component
@coveo/create-atomic-rollup-plugin
@coveo/headless
@coveo/headless-react
@coveo/shopify
commit: |
0c30586 to
2e27277
Compare
2e27277 to
2edec45
Compare
There was a problem hiding this comment.
Pull request overview
Extends the EndpointHarness MSW mocking infrastructure in packages/atomic/storybook-utils to support request-aware response shaping via a requestTransformer, and wires new “interactive facets/pagination” behavior into the Atomic search and commerce mock APIs for Storybook.
Changes:
- Add
RequestTransformer+withRequestTransformer()support toEndpointHarnessand apply it during handler execution. - Introduce new transformer modules for Search facet building/facet search, and Commerce facet selection + pagination + facet search.
- Wire interactive transformers into
MockSearchApiandMockCommerceApi, and register transformer modules as Knip entry points.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/atomic/storybook-utils/api/_base.ts | Adds RequestTransformer and applies it in generateHandler() via request body parsing. |
| packages/atomic/storybook-utils/api/search/mock.ts | Adds enableInteractiveFacets() and registers search/facet-search transformers. |
| packages/atomic/storybook-utils/api/search/facet-transformer.ts | New search transformers for dynamic facet responses and facet search filtering. |
| packages/atomic/storybook-utils/api/commerce/mock.ts | Adds commerce facet-search endpoint and enableInteractiveFacets() wiring (facet + pagination + facet search). |
| packages/atomic/storybook-utils/api/commerce/facet-transformer.ts | New commerce facet transformer + facet-search transformer factory. |
| packages/atomic/storybook-utils/api/commerce/pagination-transformer.ts | New pagination transformer reflecting request page/perPage into responses. |
| knip.js | Registers *-transformer.ts modules as entry points for Knip tracing. |
2edec45 to
f6e1ac9
Compare
When preventAutoSelect is true (after facet search selection), navigate up one level: select the parent value and show its children as idle. This matches the real Coveo API behavior where preventAutoSelect causes the response to show the navigated-to level without auto-selecting. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
802c754 to
8f7e97a
Compare
| private nextResponseInit: HttpResponseInit[] = []; | ||
| private baseResponse: Readonly<TResponse>; | ||
| private initialBaseResponse: Readonly<TResponse>; | ||
| private requestTransformer?: RequestTransformer<TResponse>; |
There was a problem hiding this comment.
I think we should allow multiple transformers
| withRequestTransformer(transformer: RequestTransformer<TResponse>) { | ||
| this.requestTransformer = transformer; | ||
| return this; |
There was a problem hiding this comment.
Because of https://github.com/coveo/ui-kit/pull/7624/changes#r3313378727, I would change this to a add/removeRequestTransformer
| export type RequestTransformer<TResponse> = ( | ||
| body: unknown, | ||
| response: TResponse | ||
| ) => TResponse; | ||
|
|
||
| /** | ||
| * Wraps a transformer so it skips API error responses (objects with `statusCode`). | ||
| * This prevents transformers from corrupting error response shapes. | ||
| */ | ||
| export function skipOnError<TResponse>( | ||
| transformer: RequestTransformer<TResponse> | ||
| ): RequestTransformer<TResponse> { | ||
| return (body, response) => { | ||
| if ( | ||
| typeof response === 'object' && | ||
| response !== null && | ||
| 'statusCode' in response | ||
| ) { | ||
| return response; | ||
| } | ||
| return transformer(body, response); | ||
| }; | ||
| } |
There was a problem hiding this comment.
I think we are experiencing scope creep in this file. This should be in one or two other files dedicated to Request Transformers
|
|
||
| /** | ||
| * Enables interactive facet transformers on search and facet search endpoints. | ||
| * Call this when stories need to reflect facet selections and facet search in responses. | ||
| */ | ||
| enableInteractiveFacets(): void { | ||
| this.searchEndpoint.withRequestTransformer( | ||
| skipOnError(searchFacetTransformer) as ( | ||
| body: unknown, | ||
| response: SearchResponse | APIErrorWithStatusCode | ||
| ) => SearchResponse | APIErrorWithStatusCode | ||
| ); | ||
| this.facetSearchEndpoint.withRequestTransformer( | ||
| searchFacetSearchTransformer | ||
| ); | ||
| } | ||
| } |
There was a problem hiding this comment.
I don't think adding "opt-in" method is a good idea, as it bloat the mock class, and requires to modify them each time we add up a transformer.
This will creates a large ripple effect on the module tree, which in turns is likely to increase our Chromatic usage because TurboSnap will not operate nicely.
Instead, the stories should add-up the transformer they need.
| enableInteractiveFacets(): void { | ||
| const transformer = skipOnError( | ||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
| (body: unknown, response: any) => { | ||
| let result = commerceFacetTransformer(body, response); | ||
| result = commercePaginationTransformer(body, result); | ||
| return result; | ||
| } | ||
| ); | ||
| this.searchEndpoint.withRequestTransformer(transformer); | ||
| this.productListingEndpoint.withRequestTransformer(transformer); | ||
| this.facetSearchEndpoint.withRequestTransformer( | ||
| createFacetSearchTransformer(baseSearchResponse) | ||
| ); |
There was a problem hiding this comment.
Problem
Multiple MSW draft PRs (
msw-commerce-facets,msw-facets,msw-navigation,msw-search-controls,msw-commerce-pagination,msw-commerce-query-status) each duplicate a large 490-540 line custom handler file to support interactive mocking (facet selections, pagination, facet search). This is architecturally misaligned with the existingEndpointHarnesspattern and creates maintenance burden.Solution
Extend
EndpointHarnesswith arequestTransformerconcept — a function that takes the request body + resolved response and returns a modified response. This keeps the harness pattern intact while enabling dynamic, request-aware mocking.New transformer modules:
commerce/facet-transformer.ts— facet selections, hierarchical navigation, range facets, show-more, facet searchcommerce/pagination-transformer.ts— reflects page/perPage from requestssearch/facet-transformer.ts— dynamic facet building, sorting (alphanumeric, natural, occurrences), numeric ranges, facet searchTransformers are wired into
MockCommerceApiandMockSearchApiso all MSW-based stories get interactive behavior automatically without needing custom handler files.How it works
Migration path
The other MSW draft branches should rebase on this branch and delete their custom
facet-aware-handler.ts/dynamic-handler.tsfiles.