Skip to content

feat(atomic): add request transformer support to MSW endpoint harness - #7624

Merged
alexprudhomme merged 4 commits into
mainfrom
msw-request-transformers
May 27, 2026
Merged

feat(atomic): add request transformer support to MSW endpoint harness#7624
alexprudhomme merged 4 commits into
mainfrom
msw-request-transformers

Conversation

@alexprudhomme

Copy link
Copy Markdown
Contributor

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 existing EndpointHarness pattern and creates maintenance burden.

Solution

Extend EndpointHarness with a requestTransformer concept — 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 search
  • commerce/pagination-transformer.ts — reflects page/perPage from requests
  • search/facet-transformer.ts — dynamic facet building, sorting (alphanumeric, natural, occurrences), numeric ranges, facet search

Transformers are wired into MockCommerceApi and MockSearchApi so all MSW-based stories get interactive behavior automatically without needing custom handler files.

How it works

// Before: each story had its own 500+ line handler
// After: transformers are registered on the harness
this.searchEndpoint.withRequestTransformer((body, response) => {
  let result = commerceFacetTransformer(body, response);
  result = commercePaginationTransformer(body, result);
  return result;
});

Migration path

The other MSW draft branches should rebase on this branch and delete their custom facet-aware-handler.ts / dynamic-handler.ts files.

@changeset-bot

changeset-bot Bot commented May 22, 2026

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 9cd2e0e

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@svcsnykcoveo

svcsnykcoveo commented May 22, 2026

Copy link
Copy Markdown

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues
Code Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@chromatic-com

chromatic-com Bot commented May 22, 2026

Copy link
Copy Markdown

Tip

All tests passed and all changes approved!

🟢 UI Tests: 367 tests unchanged
🟢 UI Review: 367 stories published -- no changes
Storybook icon Storybook Publish: 367 stories published

@pkg-pr-new

pkg-pr-new Bot commented May 22, 2026

Copy link
Copy Markdown
@coveo/atomic

npm i https://pkg.pr.new/@coveo/atomic@7624

@coveo/atomic-hosted-page

npm i https://pkg.pr.new/@coveo/atomic-hosted-page@7624

@coveo/atomic-legacy

npm i https://pkg.pr.new/@coveo/atomic-legacy@7624

@coveo/atomic-react

npm i https://pkg.pr.new/@coveo/atomic-react@7624

@coveo/auth

npm i https://pkg.pr.new/@coveo/auth@7624

@coveo/bueno

npm i https://pkg.pr.new/@coveo/bueno@7624

@coveo/create-atomic

npm i https://pkg.pr.new/@coveo/create-atomic@7624

@coveo/create-atomic-component

npm i https://pkg.pr.new/@coveo/create-atomic-component@7624

@coveo/create-atomic-component-project

npm i https://pkg.pr.new/@coveo/create-atomic-component-project@7624

@coveo/create-atomic-result-component

npm i https://pkg.pr.new/@coveo/create-atomic-result-component@7624

@coveo/create-atomic-rollup-plugin

npm i https://pkg.pr.new/@coveo/create-atomic-rollup-plugin@7624

@coveo/headless

npm i https://pkg.pr.new/@coveo/headless@7624

@coveo/headless-react

npm i https://pkg.pr.new/@coveo/headless-react@7624

@coveo/shopify

npm i https://pkg.pr.new/@coveo/shopify@7624

commit: 9cd2e0e

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 to EndpointHarness and 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 MockSearchApi and MockCommerceApi, 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.

Comment thread packages/atomic/storybook-utils/api/_base.ts
Comment thread packages/atomic/storybook-utils/api/search/mock.ts
Comment thread packages/atomic/storybook-utils/api/commerce/mock.ts
Comment thread packages/atomic/storybook-utils/api/commerce/facet-transformer.ts Outdated
Comment thread packages/atomic/storybook-utils/api/commerce/facet-transformer.ts Outdated
Comment thread packages/atomic/storybook-utils/api/search/facet-transformer.ts Outdated
Comment thread packages/atomic/storybook-utils/api/search/facet-transformer.ts Outdated
@alexprudhomme
alexprudhomme force-pushed the msw-request-transformers branch from 2edec45 to f6e1ac9 Compare May 22, 2026 17:49
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>
@alexprudhomme
alexprudhomme added this pull request to the merge queue May 27, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks May 27, 2026
@alexprudhomme
alexprudhomme added this pull request to the merge queue May 27, 2026
Merged via the queue into main with commit 024459e May 27, 2026
69 checks passed
@alexprudhomme
alexprudhomme deleted the msw-request-transformers branch May 27, 2026 17:30
private nextResponseInit: HttpResponseInit[] = [];
private baseResponse: Readonly<TResponse>;
private initialBaseResponse: Readonly<TResponse>;
private requestTransformer?: RequestTransformer<TResponse>;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should allow multiple transformers

Comment on lines +69 to +71
withRequestTransformer(transformer: RequestTransformer<TResponse>) {
this.requestTransformer = transformer;
return this;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because of https://github.com/coveo/ui-kit/pull/7624/changes#r3313378727, I would change this to a add/removeRequestTransformer

Comment on lines +19 to +41
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);
};
}

@louis-bompart louis-bompart May 27, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are experiencing scope creep in this file. This should be in one or two other files dedicated to Request Transformers

Comment on lines +70 to 86

/**
* 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
);
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +93 to +106
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)
);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants