Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 28, 2025

MSW was always active in Storybook, but we need production builds to use live APIs while preserving mocking for tests and providing a developer toggle.

Changes

  • Custom MSW loader (conditionalMswLoader): Wraps the standard loader to check environment and toolbar state before applying mocks
  • Environment detection: MSW enabled by default in dev/test, disabled in production builds. Uses a type-safe hasVitestBrowser type guard for detecting vitest browser environment.
  • Toolbar toggle: Lightning icon in Storybook toolbar to switch mocking on/off at runtime
  • Lazy initialization: MSW only initializes when actually needed, supporting dynamic toggling
  • Auto-reload on toggle: Story automatically reloads when MSW toggle changes to ensure the new mocking state takes effect immediately. The previousMswState is initialized to match the effective initial state to avoid unnecessary reloads on first render.
// Type guard for vitest browser detection
function hasVitestBrowser(
  obj: typeof globalThis
): obj is typeof globalThis & {__vitest_browser__: unknown} {
  return Object.hasOwn(obj, '__vitest_browser__');
}

// MSW enabled by default in:
// - Test environments (VITEST, STORYBOOK_TEST, __vitest_browser__)
// - Dev mode (import.meta.env.DEV)
// - CDN mode (VITE_IS_CDN === 'true')
const isMswEnabledByDefault = isTestEnvironment || import.meta.env.DEV || import.meta.env.VITE_IS_CDN === 'true';

// Toolbar toggle via globalTypes
export const globalTypes = {
  msw: {
    toolbar: {
      icon: 'lightning',
      items: [
        {value: true, title: 'MSW Enabled'},
        {value: false, title: 'MSW Disabled'},
      ],
    },
  },
};

Acceptance Criteria

  • Production build of Storybook → no mocking

  • Developer toolbar toggle → easy switch between mocked/live

  • Vitest story tests → always mocked

  • Non-MSW stories → unaffected

  • Story reloads when MSW toggle changes

  • Fixes [DXUI]: Improve MSW setup in Storybook #6676

Original prompt

This section details on the original issue you should resolve

<issue_title>[DXUI]: Improve MSW setup in Storybook</issue_title>
<issue_description>### Task Description

Currently, we have set up MSW in a couple of stories, and it has replaced the alternative of targeting the API.
This is great for testing, and we want to reserve it for our own tests.

Now, what we would like is for an "opt-out" option or toggle for MSW for those stories (knowing we'll migrate the old ones to that too eventually).

Moreover, when we "build" the SB we deploy, it ought to be opt-out all the way.

Acceptance Criteria

  • When I load the production build of Storybook, no requests are mocked
  • As a Developer, I have an easy way to switch between the MSW-mocked and non-mocked version of a given Story
  • All tests based on MSWed story still use the MSWed version of the Story
  • Stories that do not use MSW remains unaffected.

Technical Notes

No response

Jira Ticket

No response</issue_description>

<agent_instructions>Start by providing a Plan in markdown that I can review.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI and others added 3 commits November 28, 2025 17:31
…ction

Co-authored-by: louis-bompart <12366410+louis-bompart@users.noreply.github.com>
Co-authored-by: louis-bompart <12366410+louis-bompart@users.noreply.github.com>
Co-authored-by: louis-bompart <12366410+louis-bompart@users.noreply.github.com>
Copilot AI changed the title [WIP] Add opt-out option for MSW in Storybook feat(atomic): add MSW toggle for Storybook with auto-disable in production Nov 28, 2025
Copilot AI requested a review from louis-bompart November 28, 2025 17:39
Copilot finished work on behalf of louis-bompart November 28, 2025 17:39
@louis-bompart
Copy link
Collaborator

@copilot , not bad, can you make sure that when the user toggle MSW, the story does reload?

Co-authored-by: louis-bompart <12366410+louis-bompart@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Nov 28, 2025

@copilot , not bad, can you make sure that when the user toggle MSW, the story does reload?

Added automatic page reload when toggling MSW. The decorator now tracks the previous MSW state and triggers window.location.reload() when it detects a change. Also updated the toolbar description to indicate "(reloads story)". Commit: 003d151

@louis-bompart louis-bompart marked this pull request as ready for review November 28, 2025 19:17
@louis-bompart louis-bompart requested a review from a team as a code owner November 28, 2025 19:17
Copilot AI review requested due to automatic review settings November 28, 2025 19:17
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds conditional MSW (Mock Service Worker) support to Storybook with environment-aware defaults and a developer toggle. The changes ensure production builds use live APIs by default while preserving mocking capabilities for tests and development workflows.

Key changes:

  • MSW is automatically enabled in test/dev environments but disabled in production builds
  • Developers can toggle MSW on/off via a Storybook toolbar control
  • Stories automatically reload when the MSW state changes to ensure the new mocking configuration takes effect

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
}
// Check for vitest global (set by @vitest/browser)
if (typeof globalThis !== 'undefined' && '__vitest_browser__' in globalThis) {
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

Using in operator on globalThis doesn't provide type safety. Consider using optional chaining: globalThis.__vitest_browser__ !== undefined or type guard with hasOwnProperty.

Suggested change
if (typeof globalThis !== 'undefined' && '__vitest_browser__' in globalThis) {
if (typeof globalThis !== 'undefined' && globalThis.__vitest_browser__ !== undefined) {

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

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

@copilot implement, preferably with a typeGuard.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Implemented hasVitestBrowser type guard function that uses Object.hasOwn() for type-safe property checking. Commit: 9d055da

return mswLoader(context);
}

// Check if MSW is enabled via the global toggle
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

Nullish coalescing (??) will not handle false correctly. When context.globals.msw is explicitly set to false, this expression evaluates to false as intended. However, if it's null or undefined, it falls back to isMswEnabledByDefault. This is correct behavior, but the comment on line 91 says 'Check if MSW is enabled via the global toggle' which could be clearer about the fallback logic.

Suggested change
// Check if MSW is enabled via the global toggle
// Determine if MSW is enabled: use the global toggle if set (including false), otherwise fall back to the default

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

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

@copilot implement

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated comment to: "Determine if MSW is enabled: use the global toggle if set (including false), otherwise fall back to the default". Commit: 9d055da

Comment on lines +179 to +180
if (!isTestEnvironment && typeof window !== 'undefined') {
const currentMswState = context.globals?.msw;
Copy link

Copilot AI Nov 28, 2025

Choose a reason for hiding this comment

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

The previousMswState tracking logic compares against context.globals?.msw which could be undefined, true, or false. This creates three distinct states, but the initial value is undefined. On first render, if context.globals.msw is undefined (not yet set by user), this will match previousMswState and no reload occurs. However, if the default value from globalTypes.defaultValue applies, there could be a mismatch between previousMswState (undefined) and the actual effective state. Consider initializing previousMswState to match the effective initial state: let previousMswState: boolean | undefined = isMswEnabledByDefault;

Copilot uses AI. Check for mistakes.
Copy link
Collaborator

Choose a reason for hiding this comment

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

@copilot implement

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Initialized previousMswState to isMswEnabledByDefault to avoid unnecessary reloads on first render. Commit: 9d055da

Copy link
Collaborator

@louis-bompart louis-bompart left a comment

Choose a reason for hiding this comment

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

@copilot address the comments

Comment on lines +179 to +180
if (!isTestEnvironment && typeof window !== 'undefined') {
const currentMswState = context.globals?.msw;
Copy link
Collaborator

Choose a reason for hiding this comment

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

@copilot implement

return mswLoader(context);
}

// Check if MSW is enabled via the global toggle
Copy link
Collaborator

Choose a reason for hiding this comment

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

@copilot implement

}
}
// Check for vitest global (set by @vitest/browser)
if (typeof globalThis !== 'undefined' && '__vitest_browser__' in globalThis) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

@copilot implement, preferably with a typeGuard.

Co-authored-by: louis-bompart <12366410+louis-bompart@users.noreply.github.com>
Copilot AI requested a review from louis-bompart November 28, 2025 19:48
Copilot finished work on behalf of louis-bompart November 28, 2025 19:48
Copilot AI temporarily deployed to Prerelease (CDN) November 28, 2025 20:59 Inactive
Copy link
Contributor

@alexprudhomme alexprudhomme left a comment

Choose a reason for hiding this comment

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

When I toggle the switch to disable, it seems to break storybook. It refreshes continually the story with a flashing error. Does it work for you ?

@alexprudhomme
Copy link
Contributor

I don't think this would work for facets since the MSW facet might not exist in reality. We would have to make sure to use a 'real' facet for our MSW. Best would be to have two separate stories.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[DXUI]: Improve MSW setup in Storybook

3 participants