Skip to content

Conversation

@kasperpeulen
Copy link
Contributor

@kasperpeulen kasperpeulen commented Dec 12, 2025

Closes #

What I did

Resolved the issue where meta.input.play in CSF factories was undefined by fixing its type.

Checklist for Contributors

Testing

The changes in this PR are covered in the following automated tests:

  • stories
  • unit tests
  • integration tests
  • end-to-end tests

Manual testing

This section is mandatory for all contributions. If you believe no manual test is necessary, please state so explicitly. Thanks!

Documentation

  • Add or update documentation reflecting your changes
  • If you are deprecating/removing a feature, make sure to update
    MIGRATION.MD

Checklist for Maintainers

  • When this PR is ready for testing, make sure to add ci:normal, ci:merged or ci:daily GH label to it to run a specific set of sandboxes. The particular set of sandboxes can be found in code/lib/cli-storybook/src/sandbox-templates.ts

  • Make sure this PR contains one of the labels below:

    Available labels
    • bug: Internal changes that fixes incorrect behavior.
    • maintenance: User-facing maintenance tasks.
    • dependencies: Upgrading (sometimes downgrading) dependencies.
    • build: Internal-facing build tooling & test updates. Will not show up in release changelog.
    • cleanup: Minor cleanup style change. Will not show up in release changelog.
    • documentation: Documentation only changes. Will not show up in release changelog.
    • feature request: Introducing a new feature.
    • BREAKING CHANGE: Changes that break compatibility in some way with current major version.
    • other: Changes that don't fit in the above categories.

🦋 Canary release

This PR does not have a canary release associated. You can request a canary release of this pull request by mentioning the @storybookjs/core team here.

core team members can create a canary release here or locally with gh workflow run --repo storybookjs/storybook publish.yml --field pr=<PR_NUMBER>

Summary by CodeRabbit

  • New Features

    • meta.input now includes a play hook, enabling developers to access shared play functions from story-level play hooks with merged arguments.
  • Tests

    • Added test verifying the meta.input.play functionality and composition of interactions.

✏️ Tip: You can customize this high-level summary in your review settings.

Copilot AI review requested due to automatic review settings December 12, 2025 11:29
@kasperpeulen kasperpeulen changed the title Fix undefined type of meta.input.play in CSF factories React: Fix undefined type of meta.input.play in CSF factories Dec 12, 2025
@nx-cloud
Copy link

nx-cloud bot commented Dec 12, 2025

View your CI Pipeline Execution ↗ for commit 56aa06d

Command Status Duration Result
nx run-many -t compile,check,knip,test,pretty-d... ❌ Failed 8m 21s View ↗

☁️ Nx Cloud last updated this comment at 2025-12-12 11:39:47 UTC

@nx-cloud
Copy link

nx-cloud bot commented Dec 12, 2025

View your CI Pipeline Execution ↗ for commit 56aa06d

Command Status Duration Result
nx run-many -t compile,check,knip,test,pretty-d... ❌ Failed 8m 21s View ↗

☁️ Nx Cloud last updated this comment at 2025-12-12 11:39:01 UTC

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 fixes a TypeScript typing issue where meta.input.play was incorrectly typed as undefined in CSF (Component Story Format) factories for the React renderer. The fix ensures that all properties from ComponentAnnotations (including the play function) are properly available on meta.input.

Key Changes

  • Updated the type definition for ReactPreview.meta() return type to include all ComponentAnnotations properties in the MetaInput type parameter
  • Added a test case demonstrating that meta.input.play can now be safely accessed and called

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
code/renderers/react/src/preview.tsx Changed the second type parameter of ReactMeta from a simple { args: ... } object to Omit<ComponentAnnotations<...>, 'args'> & { args: ... }, ensuring all ComponentAnnotations properties (including play) are properly typed
code/renderers/react/src/csf-factories.test.tsx Added test case verifying that meta.input.play is accessible and can be invoked from within a story's play function, demonstrating the common pattern of composing play functions

Comment on lines +388 to +403
it('meta.input also contains play', () => {
const meta = preview.meta({
/** Title, component, etc... */
play: async ({ canvas }) => {
/** Do some common interactions */
},
});

const ExtendedInteractionsStory = meta.story({
play: async ({ canvas, ...rest }) => {
await meta.input.play?.({ canvas, ...rest });

/** Do some extra interactions */
},
});
});
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

This test verifies that meta.input.play is properly typed but doesn't actually verify that the play function is callable or has the correct behavior at runtime. Consider adding a runtime assertion to verify that meta.input.play is actually defined and callable. For example, you could add expect(meta.input.play).toBeDefined(); or use a spy to verify the play function is called when invoked.

Copilot uses AI. Check for mistakes.
Comment on lines +396 to +402
const ExtendedInteractionsStory = meta.story({
play: async ({ canvas, ...rest }) => {
await meta.input.play?.({ canvas, ...rest });

/** Do some extra interactions */
},
});
Copy link

Copilot AI Dec 12, 2025

Choose a reason for hiding this comment

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

Unused variable ExtendedInteractionsStory.

Suggested change
const ExtendedInteractionsStory = meta.story({
play: async ({ canvas, ...rest }) => {
await meta.input.play?.({ canvas, ...rest });
/** Do some extra interactions */
},
});

Copilot uses AI. Check for mistakes.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 12, 2025

📝 Walkthrough

Walkthrough

The PR adds a test verifying that meta.input.play is available and callable from story-level play functions, and refactors the ReactPreview.meta return type signature to use an Omit<ComponentAnnotations> pattern for more controlled typing of the args property.

Changes

Cohort / File(s) Summary
Test: meta.input.play composition
code/renderers/react/src/csf-factories.test.tsx
Adds test demonstrating that meta.input.play can be invoked from a story-level play function with merged arguments, enabling interaction composition through a shared play context.
Type refactoring: ReactPreview.meta return type
code/renderers/react/src/preview.tsx
Refactors the second generic parameter of ReactMeta in the meta() method return type from a direct object to Omit<ComponentAnnotations<...>, 'args'> & { args: Partial<TArgs> extends TMetaArgs ? {} : TMetaArgs }, preserving shape while using explicit args typing.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

  • preview.tsx type refactoring: Requires careful review of TypeScript generic constraints and the conditional args type logic to ensure the new Omit + intersection pattern maintains backward compatibility and correct type inference.
  • csf-factories.test.tsx: Straightforward test addition validating the new meta.input.play API.

Possibly related issues

Possibly related PRs

✨ Finishing touches
  • 📝 Generate docstrings

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
code/renderers/react/src/csf-factories.test.tsx (1)

387-403: Strengthen the test to assert meta.input.play is callable (and optionally non-optional when meta.play is provided).
Right now, meta.input.play?.(...) proves the property exists on the type, but doesn’t strongly validate call signature (or presence when meta.play is set). Consider adding an expectTypeOf(meta.input.play) assertion and/or using meta.input.play! in this case.

Example:

 it('meta.input also contains play', () => {
   const meta = preview.meta({
     /** Title, component, etc... */
     play: async ({ canvas }) => {
       /** Do some common interactions */
     },
   });

   const ExtendedInteractionsStory = meta.story({
     play: async ({ canvas, ...rest }) => {
-      await meta.input.play?.({ canvas, ...rest });
+      expectTypeOf(meta.input.play).toMatchTypeOf<Function>();
+      await meta.input.play!({ canvas, ...rest });
 
       /** Do some extra interactions */
     },
   });
 });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6f262ad and 56aa06d.

📒 Files selected for processing (2)
  • code/renderers/react/src/csf-factories.test.tsx (1 hunks)
  • code/renderers/react/src/preview.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use ESLint and Prettier configurations that are enforced in the codebase

Files:

  • code/renderers/react/src/preview.tsx
  • code/renderers/react/src/csf-factories.test.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Enable TypeScript strict mode

Files:

  • code/renderers/react/src/preview.tsx
  • code/renderers/react/src/csf-factories.test.tsx
code/**/*.{ts,tsx,js,jsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/*.{ts,tsx,js,jsx,mjs}: Use server-side logger from 'storybook/internal/node-logger' for Node.js code
Use client-side logger from 'storybook/internal/client-logger' for browser code
Do not use console.log, console.warn, or console.error directly unless in isolated files where importing loggers would significantly increase bundle size

Files:

  • code/renderers/react/src/preview.tsx
  • code/renderers/react/src/csf-factories.test.tsx
code/**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Export functions that need to be tested from their modules

Files:

  • code/renderers/react/src/preview.tsx
  • code/renderers/react/src/csf-factories.test.tsx
code/**/*.{js,jsx,json,html,ts,tsx,mjs}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/*.{js,jsx,json,html,ts,tsx,mjs}: Run Prettier with --write flag to format code before committing
Run ESLint with yarn lint:js:cmd to check for linting issues and fix errors before committing

Files:

  • code/renderers/react/src/preview.tsx
  • code/renderers/react/src/csf-factories.test.tsx
**/*.{test,spec}.{ts,tsx}

📄 CodeRabbit inference engine (.cursorrules)

**/*.{test,spec}.{ts,tsx}: Test files should follow the naming pattern *.test.ts, *.test.tsx, *.spec.ts, or *.spec.tsx
Follow the spy mocking rules defined in .cursor/rules/spy-mocking.mdc for consistent mocking patterns with Vitest

Files:

  • code/renderers/react/src/csf-factories.test.tsx
**/*.test.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.cursor/rules/spy-mocking.mdc)

**/*.test.{ts,tsx,js,jsx}: Use vi.mock() with the spy: true option for all package and file mocks in Vitest tests
Place all mocks at the top of the test file before any test cases
Use vi.mocked() to type and access the mocked functions in Vitest tests
Implement mock behaviors in beforeEach blocks in Vitest tests
Mock all required dependencies that the test subject uses
Each mock implementation should return a Promise for async functions in Vitest
Mock implementations should match the expected return type of the original function
Mock all required properties and methods that the test subject uses in Vitest tests
Avoid direct function mocking without vi.mocked() in Vitest tests
Avoid mock implementations outside of beforeEach blocks in Vitest tests
Avoid mocking without the spy: true option in Vitest tests
Avoid inline mock implementations within test cases in Vitest tests
Avoid mocking only a subset of required dependencies in Vitest tests
Mock at the highest level of abstraction needed in Vitest tests
Keep mock implementations simple and focused in Vitest tests
Use type-safe mocking with vi.mocked() in Vitest tests
Document complex mock behaviors in Vitest tests
Group related mocks together in Vitest tests

Files:

  • code/renderers/react/src/csf-factories.test.tsx
code/**/*.{test,spec}.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

code/**/*.{test,spec}.{ts,tsx,js,jsx}: Write meaningful unit tests that actually import and call the functions being tested
Mock external dependencies using vi.mock() for file system, loggers, and other external dependencies in tests
Aim for high test coverage of business logic (75%+ for statements/lines) using coverage reports

Files:

  • code/renderers/react/src/csf-factories.test.tsx
🧠 Learnings (1)
📓 Common learnings
Learnt from: Sidnioulz
Repo: storybookjs/storybook PR: 32458
File: code/core/src/components/components/Tabs/Tabs.stories.tsx:222-227
Timestamp: 2025-11-05T09:36:55.944Z
Learning: Repo: storybookjs/storybook PR: 32458 — In code/core/src/components/components/Button/Button.tsx (React/TypeScript), ButtonProps includes ariaLabel?: string | false and the component maps it to the DOM aria-label. Convention: ariaLabel is mandatory on all Button usages — provide a descriptive string for icon-only buttons; set ariaLabel=false when the button’s children already serve as the accessible name. Do not suggest using a raw aria-label prop on Button call sites.
🧬 Code graph analysis (1)
code/renderers/react/src/csf-factories.test.tsx (1)
code/core/src/csf-tools/CsfFile.ts (1)
  • meta (964-966)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: normal
  • GitHub Check: Agent
  • GitHub Check: CodeQL analysis (javascript-typescript)
  • GitHub Check: nx
  • GitHub Check: nx
  • GitHub Check: Core Unit Tests, windows-latest
🔇 Additional comments (1)
code/renderers/react/src/preview.tsx (1)

79-91: Pass TArgs explicitly as the second generic to ComponentAnnotations in the return type.

The input parameter correctly uses ComponentAnnotations<ReactTypes & T, TArgs> (line 69) with both generics, but the return type (lines 80–85) only passes one generic: ComponentAnnotations<ReactTypes & T & { args: Simplify<...> }>. This causes the second generic TArgs to default to Args, breaking type inference and creating a mismatch between input and output parameter types. The Omit<..., 'args'> & { args: ... } pattern correctly preserves fields like play, but it must also pass TArgs explicitly to maintain proper type constraints.

Apply the suggested fix:

     Omit<
       ComponentAnnotations<
         ReactTypes &
           T & {
             args: Simplify<
               TArgs & Simplify<RemoveIndexSignature<DecoratorsArgs<ReactTypes & T, Decorators>>>
             >;
           }
+        ,
+        TArgs
       >,
       'args'
     > & {
       args: Partial<TArgs> extends TMetaArgs ? {} : TMetaArgs;
     }

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.

2 participants