storybookjs / addon-react-native-web

Build react-native-web projects in Storybook for React
MIT License
83 stars 24 forks source link

[Bug] @storybook/test package not working #92

Open Bronowsm opened 3 months ago

Bronowsm commented 3 months ago

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

dannyhw commented 3 months ago

@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.

JavanPoirier commented 2 months ago

+1 👀

dannyhw commented 2 months ago

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

dannyhw commented 2 months ago

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 commented 2 months ago

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

Bronowsm commented 1 month ago

@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'),
    };
JavanPoirier commented 1 month ago

'@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!

dannyhw commented 1 month ago

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

JavanPoirier commented 1 month ago

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: () => '',
});
dannyhw commented 1 month ago

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

JavanPoirier commented 1 month ago

@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.

kasperpeulen commented 1 month ago

@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"
  },
JavanPoirier commented 1 month ago

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

yannbf commented 1 month ago

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?

JavanPoirier commented 1 month ago

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