addon-react-native-web icon indicating copy to clipboard operation
addon-react-native-web copied to clipboard

[Bug] @storybook/test package not working

Open Bronowsm opened this issue 1 year ago • 15 comments

Describe the bug

Whenever I add @storybook/addon-react-native-web package to addons array, @storybook/test package stops working. When I comment out or remove @storybook/addon-react-native-web from addons array, the error is gone (screenshot of error below)

Here is my setup:

.storybook/main.js file:

const NodePolyfillPlugin = require("node-polyfill-webpack-plugin");

const config = {
  stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
  addons: [
    "@storybook/addon-links",
    "@storybook/addon-essentials",
    "@storybook/addon-webpack5-compiler-swc",
    "@storybook/addon-onboarding",
    "@chromatic-com/storybook",
    "@storybook/addon-interactions",
    "storybook-dark-mode",
    "@storybook/addon-react-native-web", // it makes @storybook/test not working :D
  ],
  framework: {
    name: "@storybook/react-webpack5",
    options: {},
  },
  core: {
    builder: "webpack5",
  },
  webpackFinal: async (config, { configType }) => {
    config.plugins.push(new NodePolyfillPlugin());

    return config;
  },
};
export default config;

.storybook/preview:

const preview = {
  parameters: {
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/i,
      },
    },
  },
};

export default preview;

.babelrc

{
  "presets": [
    "@babel/preset-env",
    "@babel/preset-react",
    "@babel/preset-typescript"
  ],
  "plugins": ["react-native-web"]
}

package.json:

{
  "name": "before-storybook",
  "version": "1.0.0",
  "description": "",
  "private": true,
  "author": "",
  "scripts": {
    "dev": "webpack serve --open",
    "build": "webpack build",
    "storybook": "storybook dev -p 6006",
    "build-storybook": "storybook build"
  },
  "dependencies": {
    "@babel/core": "latest",
    "@babel/preset-env": "latest",
    "@babel/preset-react": "latest",
    "@babel/preset-typescript": "latest",
    "@react-native/babel-preset": "^0.74.86",
    "@storybook/addon-react-native-web": "^0.0.24",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "babel-loader": "latest",
    "babel-plugin-react-native-web": "^0.19.12",
    "babel-preset-react-app": "latest",
    "babel-preset-react-native": "^4.0.1",
    "html-webpack-plugin": "latest",
    "metro-react-native-babel-preset": "^0.77.0",
    "node-polyfill-webpack-plugin": "^4.0.0",
    "react": "^18",
    "react-dom": "^18.3.1",
    "react-native-web": "^0.19.12",
    "storybook-dark-mode": "^4.0.2",
    "tty-browserify": "^0.0.1",
    "typescript": "^4.8",
    "webpack": "^5",
    "webpack-cli": "latest",
    "webpack-dev-server": "latest"
  },
  "devDependencies": {
    "@chromatic-com/storybook": "^1.6.1",
    "@storybook/addon-a11y": "8.2.6",
    "@storybook/addon-actions": "8.2.6",
    "@storybook/addon-backgrounds": "8.2.6",
    "@storybook/addon-controls": "8.2.6",
    "@storybook/addon-docs": "8.2.6",
    "@storybook/addon-essentials": "^8.2.6",
    "@storybook/addon-highlight": "8.2.6",
    "@storybook/addon-interactions": "^8.2.6",
    "@storybook/addon-jest": "8.2.6",
    "@storybook/addon-links": "^8.2.6",
    "@storybook/addon-mdx-gfm": "8.2.6",
    "@storybook/addon-measure": "8.2.6",
    "@storybook/addon-onboarding": "^8.2.6",
    "@storybook/addon-outline": "8.2.6",
    "@storybook/addon-storysource": "8.2.6",
    "@storybook/addon-themes": "8.2.6",
    "@storybook/addon-toolbars": "8.2.6",
    "@storybook/addon-viewport": "8.2.6",
    "@storybook/addon-webpack5-compiler-swc": "^1.0.5",
    "@storybook/blocks": "^8.2.6",
    "@storybook/builder-manager": "8.2.6",
    "@storybook/builder-vite": "8.2.6",
    "@storybook/builder-webpack5": "8.2.6",
    "@storybook/channels": "8.2.6",
    "@storybook/cli": "8.2.6",
    "@storybook/client-logger": "8.2.6",
    "@storybook/codemod": "8.2.6",
    "@storybook/components": "8.2.6",
    "@storybook/core": "8.2.6",
    "@storybook/core-common": "8.2.6",
    "@storybook/core-events": "8.2.6",
    "@storybook/core-server": "8.2.6",
    "@storybook/core-webpack": "8.2.6",
    "@storybook/csf-plugin": "8.2.6",
    "@storybook/csf-tools": "8.2.6",
    "@storybook/docs-tools": "8.2.6",
    "@storybook/ember": "8.2.6",
    "@storybook/html": "8.2.6",
    "@storybook/html-vite": "8.2.6",
    "@storybook/html-webpack5": "8.2.6",
    "@storybook/instrumenter": "8.2.6",
    "@storybook/manager": "8.2.6",
    "@storybook/manager-api": "8.2.6",
    "@storybook/nextjs": "8.2.6",
    "@storybook/node-logger": "8.2.6",
    "@storybook/preact": "8.2.6",
    "@storybook/preact-vite": "8.2.6",
    "@storybook/preact-webpack5": "8.2.6",
    "@storybook/preset-create-react-app": "8.2.6",
    "@storybook/preset-html-webpack": "8.2.6",
    "@storybook/preset-preact-webpack": "8.2.6",
    "@storybook/preset-react-webpack": "8.2.6",
    "@storybook/preset-server-webpack": "8.2.6",
    "@storybook/preset-svelte-webpack": "8.2.6",
    "@storybook/preset-vue3-webpack": "8.2.6",
    "@storybook/preview": "8.2.6",
    "@storybook/preview-api": "8.2.6",
    "@storybook/react": "^8.2.6",
    "@storybook/react-dom-shim": "8.2.6",
    "@storybook/react-vite": "8.2.6",
    "@storybook/react-webpack5": "8.2.6",
    "@storybook/router": "8.2.6",
    "@storybook/server": "8.2.6",
    "@storybook/server-webpack5": "8.2.6",
    "@storybook/source-loader": "8.2.6",
    "@storybook/svelte": "8.2.6",
    "@storybook/svelte-vite": "8.2.6",
    "@storybook/svelte-webpack5": "8.2.6",
    "@storybook/sveltekit": "8.2.6",
    "@storybook/telemetry": "8.2.6",
    "@storybook/test": "^8.2.6",
    "@storybook/theming": "8.2.6",
    "@storybook/types": "8.2.6",
    "@storybook/vue3": "8.2.6",
    "@storybook/vue3-vite": "8.2.6",
    "@storybook/vue3-webpack5": "8.2.6",
    "@storybook/web-components": "8.2.6",
    "@storybook/web-components-vite": "8.2.6",
    "@storybook/web-components-webpack5": "8.2.6",
    "jackspeak": "2.1.1",
    "sb": "8.2.6",
    "storybook": "^8.2.6"
  }
}

webpack.config.js

const HtmlWebpackPlugin = require("html-webpack-plugin");

process.env.NODE_ENV = "development";
const host = process.env.HOST || "localhost";

module.exports = {
  mode: "development",
  devtool: "inline-source-map",
  entry: "./src/index.tsx",
  output: {
    filename: "static/js/bundle.js",
  },
  devServer: {
    compress: true,
    hot: true,
    host,
    port: 3000,
  },
  plugins: [new HtmlWebpackPlugin()],
  module: {
    rules: [
      {
        test: /\.(js|jsx|ts|tsx|mjs)$/,
        exclude: /node_modules/,
        use: {
          loader: "babel-loader",
        },
      },
    ],
  },
  resolve: {
    extensions: [".mjs", ".js", ".cjs", ".jsx", ".tsx", ".ts"],
    modules: ["node_modules"],
  },
};

Screenshots and/or logs

Zrzut ekranu 2024-07-31 o 10 36 30

Environment

  • OS: macOS
  • Node.js version: 18.19.0
  • NPM version: 8.5.5

Bronowsm avatar Jul 31 '24 08:07 Bronowsm

@Bronowsm hey, thanks for raising this issue. I have actually seen a similar issue but I'm not familiar with the test package implementation so I'll have to defer to others. Will look into it.

dannyhw avatar Jul 31 '24 10:07 dannyhw

+1 👀

JavanPoirier avatar Sep 10 '24 17:09 JavanPoirier

Sorry for the delay! This slipped off my radar but I will make sure to ask around tomorrow.

dannyhw avatar Sep 10 '24 20:09 dannyhw

so looking back in my chat history with some people the information that I have is that it may have something to do with the CJS of the storybook/test package being messed up and forcing the mjs version could help. Not sure how to do that with webpack yet but leaving this note here for context.

dannyhw avatar Sep 11 '24 13:09 dannyhw

I think first step would be to create a more minimal reproduction of the problem and then to verify which entrypoint is being used (i.e cjs/mjs)

one thing to try is adding to webpack final something like:

config.resolve.extensions = ['mjs', ...config.resolve.extensions]

to get mjs loading before cjs

dannyhw avatar Sep 11 '24 13:09 dannyhw

@dannyhw thanks for hints, they were very useful - it was about forcing storybook/test package to use .mjs extension. But overriding config.resolve.extensions didn't do the trick. What I did instead, was to point out to the exact file in node_modules and storybook/test is working again!

  config.resolve.alias = {
      ...config.resolve.alias,
      '@storybook/test': path.resolve(__dirname, '../node_modules/@storybook/test/dist/index.mjs'),
    };

Bronowsm avatar Oct 07 '24 08:10 Bronowsm

'@storybook/test': path.resolve(__dirname, '../node_modules/@storybook/test/dist/index.mjs'),

Why didn't I think of that!? I moved on with the failure of the extension priority. Anywho, thanks @Bronowsm, works for me!

JavanPoirier avatar Oct 07 '24 08:10 JavanPoirier

Interesting, its so strange that the wrong extension is always getting resolved 😕 . I wonder if theres something wrong with the exports in package.json for the storybook/test package.

Or maybe it has something to do with the babel loader that addon-react-native-web is including, not sure

dannyhw avatar Oct 07 '24 11:10 dannyhw

Found another issue, it appears that the solution @Bronowsm proposed does work for stories, but not when mocking. The underlying implementation of fn appears to be just an export from @vitest/spy, so not immediately sure why this doesn't work.

Works: Button.stories.ts

import { fn } from '@storybook/test';
const meta = {
  title: 'Example/Button',
  component: Button,
  args: { onClick: fn() },
} satisfies Meta<typeof Button>;

Works: useRouter.mock.ts

import { fn } from '@vitest/spy';
import * as actual from './useRouter';

export const useRouter = fn(actual.useRouter).mockName('useRouter').mockReturnValue({ 
    back: () => null,
    push: () => null,
    replace: () => null,
    parseNextPath: () => '',
});

Does NOT Work:

import { fn } from '@storybook/test';
import * as actual from './useRouter';

export const useRouter = fn(actual.useRouter).mockName('useRouter').mockReturnValue({ 
    back: () => null,
    push: () => null,
    replace: () => null,
    parseNextPath: () => '',
});

JavanPoirier avatar Oct 08 '24 09:10 JavanPoirier

@JavanPoirier when you use the mock from storybook/test what problem/error do you get?

dannyhw avatar Oct 08 '24 11:10 dannyhw

@JavanPoirier when you use the mock from storybook/test what problem/error do you get?

Just that it does not exist. Changing the import resolves it.

JavanPoirier avatar Oct 08 '24 11:10 JavanPoirier

@JavanPoirier Does this happen in storybook 8.3? We changed the order of the export condition in 8.3. So that ESM always has highest prio:

  "exports": {
    ".": {
      "types": "./dist/index.d.ts",
      "import": "./dist/index.mjs",
      "require": "./dist/index.js",
      "node": "./dist/index.js",
      "default": "./dist/index.mjs"
    },
    "./package.json": "./package.json"
  },

kasperpeulen avatar Oct 08 '24 13:10 kasperpeulen

Looks like I sorted it out, using the @vitest/spy import made it so the default mockReturnValue I specified in the useRouter.mock.ts was applied without directly importing it into the story to use in beforeEach (as the docs do say to do). I could then overwrite the default mock by importing it and specifying a chained mockReturnValue call.

Now when using the @storybook/test export I must ALWAYS have a beforeEach statement, import the mock and call mockReturnValue.

I kinda liked the ability to not have to add the beforeEach call and having just function stubs in place.

I am on storybook@npm:8.3.4

Example:

useRouter.mock.ts

import { fn } from '@vitest/spy';
import * as actual from './useRouter';

export const useRouter = fn(actual.useRouter).mockName('useRouter').mockReturnValue({ 
    back: () => null,
    push: () => null,
    replace: () => null,
    parseNextPath: () => '',
});

I never needed to import it anywhere or call beforeEach, the mock just worked.

Now with @storybook/test, I must import the mock into preview.tsx or a story and call mockReturnValue

JavanPoirier avatar Oct 08 '24 13:10 JavanPoirier

Hey @JavanPoirier that's pretty weird. You should be using fn from @storybook/test instead. Could you share a reproduction repo so we can see what's going on?

yannbf avatar Oct 08 '24 16:10 yannbf

Hey @JavanPoirier that's pretty weird. You should be using fn from @storybook/test instead. Could you share a reproduction repo so we can see what's going on?

https://github.com/JavanPoirier/rn-web-sb-mock

JavanPoirier avatar Oct 08 '24 18:10 JavanPoirier