minop1205 / react-dnd-treeview

A draggable / droppable React-based treeview component. You can use render props to create each node freely.
MIT License
531 stars 71 forks source link

Jest catches TypeError ... is not a function #205

Open stevenpollack opened 1 year ago

stevenpollack commented 1 year ago

I'm building a Next.JS app with typescript and testing with Jest and testing-library/react, and I'm noticing that Jest is failing to find getBackendOptions as a module export. It catches the following error:

    TypeError: (0 , _reactdndtreeview.getBackendOptions) is not a function

      16 |   return (
      17 |     <div className="app">
    > 18 |       <DndProvider backend={MultiBackend} options={getBackendOptions()}>
         |                                                                     ^
      19 |         <Tree
      20 |           tree={treeData}
      21 |           rootId={0}

      at NavTree (src/views/khub/nav-tree.test.tsx:18:69)

To Reproduce Using the minimal configuration example from storybook:

// nav-tree.test.tsx

import React, { useState } from 'react';
import { DndProvider } from 'react-dnd';
import {
  Tree,
  NodeModel,
  MultiBackend,
  getBackendOptions,
} from '@minoru/react-dnd-treeview';
import SampleData from './sample_data.json';
import { render } from '@testing-library/react';

function NavTree() {
  const [treeData, setTreeData] = useState<NodeModel[]>(SampleData);
  const handleDrop = (newTree: NodeModel[]) => setTreeData(newTree);

  return (
    <div className="app">
      <DndProvider backend={MultiBackend} options={getBackendOptions()}>
        <Tree
          tree={treeData}
          rootId={0}
          render={(node, { depth, isOpen, onToggle }) => (
            <div style={{ marginInlineStart: depth * 10 }}>
              {node.droppable && (
                <span onClick={onToggle}>{isOpen ? '[-]' : '[+]'}</span>
              )}
              {node.text}
            </div>
          )}
          dragPreviewRender={(monitorProps) => (
            <div>{monitorProps.item.text}</div>
          )}
          onDrop={handleDrop}
        />
      </DndProvider>
    </div>
  );
}

describe('NavTree', () => {
  it('should render', () => {
    const { getAllByText } = render(<NavTree />);
    expect(getAllByText('Folder 1')).toBeInTheDocument();
  });
});

I also had to install a couple cjs packages and modify my Jest config:

// jest.config.mjs
import nextJest from "next/jest.js";

const createJestConfig = nextJest({
  // Provide the path to your Next.js app to load next.config.js and .env files in your test environment
  dir: "./",
});

// Add any custom config to be passed to Jest
/** @type {import('jest').Config} */
const config = {
  // Add more setup options before each test is run
  setupFilesAfterEnv: ["<rootDir>/jest.setup.js"],
  testEnvironment: "jest-environment-jsdom",
  moduleNameMapper: {
    "^.+\\.(svg)$": "<rootDir>/src/mocks/svg.jsx",
    "react-markdown":
      "<rootDir>/node_modules/react-markdown/react-markdown.min.js",
    "react-dnd": "react-dnd-cjs",
    "react-dnd-html5-backend": "react-dnd-html5-backend-cjs",
    "react-dnd-touch-backend": "react-dnd-touch-backend-cjs",
  },
  collectCoverageFrom: ["src/**/*.{js,jsx,ts,tsx}", "!src/**/*.d.ts"],
  coverageReporters: ["text", "html", "cobertura"],
  coverageThreshold: {
    global: {
      statements: 77,
      branches: 67,
      functions: 65,
      lines: 77,
    },
  },
  snapshotSerializers: ["@emotion/jest/serializer"],
};

// createJestConfig is exported this way to ensure that next/jest can load the Next.js config which is async
export default createJestConfig(config);

Expected behavior I expect the module exports to be in sync with what I see in dist/index.js but when I put the following in the test

jest.mock('@minoru/react-dnd-treeview', () => {
  const originalModule = jest.requireActual('@minoru/react-dnd-treeview');
  console.log('original::', originalModule);
  return originalModule;
});

I see:

    original:: {
      DndContext: <ref *1> {
        '$$typeof': Symbol(react.context),
        _currentValue: { dragDropManager: undefined },
        _currentValue2: { dragDropManager: undefined },
        _threadCount: 0,
        Provider: { '$$typeof': Symbol(react.provider), _context: [Circular *1] },
        Consumer: { '$$typeof': Symbol(react.context), _context: [Circular *1] },
        _defaultValue: null,
        _globalName: null,
        _currentRenderer: null,
        _currentRenderer2: null
      },
      createDndContext: [Function: createDndContext],
      DndProvider: {
        '$$typeof': Symbol(react.memo),
        type: [Function (anonymous)] { displayName: 'DndProvider' },
        compare: null
      },
      DragPreviewImage: {
        '$$typeof': Symbol(react.memo),
        type: [Function (anonymous)] { displayName: 'DragPreviewImage' },
        compare: null
      },
      useDrag: [Function: useDrag],
      useDrop: [Function: useDrop],
      useDragLayer: [Function: useDragLayer],
      DragSource: [Function: DragSource],
      DropTarget: [Function: DropTarget],
      DragLayer: [Function: DragLayer]
    }

Desktop (please complete the following information):

Additional context Here's my package.json:

{
  "name": "accelerate-fe",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "test": "TZ=UTC jest --all --watchAll=false --testResultsProcessor=jest-junit --coverage",
    "test-watch": "TZ=UTC jest --watchAll --testResultsProcessor=jest-junit --coverage --colors",
    "format": "prettier --check --ignore-path .gitignore .",
    "format:fix": "prettier --write . --ignore-path .gitignore",
    "lint": "eslint --ext .js,.jsx,.ts,.tsx --ignore-path .gitignore src",
    "lint:fix": "eslint --ext .js,.jsx,.ts,.tsx --ignore-path .gitignore --fix src",
    "eject": "react-scripts eject",
    "prepare": "husky install",
    "pre-commit": "npm run build && npm test -- -o"
  },
  "dependencies": {
    "@apollo/client": "3.7.14",
    "@axe-core/react": "4.4.3",
    "@azure/msal-browser": "2.28.1",
    "@azure/msal-react": "1.4.5",
    "@bphxd/ds-core-react": "3.8.0-beta",
    "@emotion/react": "11.10.8",
    "@emotion/styled": "11.10.8",
    "@hookform/resolvers": "2.9.11",
    "@minoru/react-dnd-treeview": "3.4.4",
    "@opentelemetry/api": "1.2.0",
    "@opentelemetry/auto-instrumentations-web": "0.30.0",
    "@opentelemetry/context-zone": "1.7.0",
    "@opentelemetry/core": "1.7.0",
    "@opentelemetry/exporter-jaeger": "1.7.0",
    "@opentelemetry/exporter-trace-otlp-http": "0.33.0",
    "@opentelemetry/instrumentation": "0.33.0",
    "@opentelemetry/resources": "1.7.0",
    "@opentelemetry/sdk-trace-base": "1.7.0",
    "@opentelemetry/sdk-trace-web": "1.7.0",
    "@opentelemetry/semantic-conventions": "1.7.0",
    "@svgr/webpack": "7.0.0",
    "@types/luxon": "3.3.0",
    "@types/mixpanel-browser": "2.47.0",
    "@types/node": "20.1.4",
    "@types/react": "18.2.6",
    "@types/react-dom": "18.2.4",
    "@types/react-helmet": "6.1.6",
    "@types/react-router-dom": "5.3.3",
    "@types/url-join": "4.0.1",
    "@types/uuid": "9.0.1",
    "@typescript-eslint/eslint-plugin": "5.59.6",
    "@typescript-eslint/parser": "5.59.6",
    "apollo-upload-client": "17.0.0",
    "buffer": "6.0.3",
    "crypto-js": "4.1.1",
    "dotenv": "10.0.0",
    "eslint": "8.39.0",
    "eslint-config-next": "13.4.2",
    "eslint-plugin-jest": "27.2.1",
    "eslint-plugin-prettier": "3.4.1",
    "graphql": "16.6.0",
    "history": "4.10.1",
    "jest": "29.5.0",
    "jest-environment-jsdom": "29.5.0",
    "js-yaml": "4.1.0",
    "lodash": "4.17.21",
    "luxon": "3.3.0",
    "mixpanel-browser": "2.47.0",
    "msw": "0.35.0",
    "next": "13.4.2",
    "prettier": "2.8.8",
    "react": "18.2.0",
    "react-burger-menu": "3.0.8",
    "react-content-loader": "6.2.1",
    "react-dnd": "16.0.1",
    "react-dnd-html5-backend": "16.0.1",
    "react-dnd-touch-backend": "16.0.1",
    "react-dom": "18.2.0",
    "react-error-boundary": "3.1.4",
    "react-feather": "2.0.10",
    "react-helmet": "6.1.0",
    "react-hook-form": "7.43.9",
    "react-icons": "4.8.0",
    "react-markdown": "8.0.7",
    "react-markdown-editor-lite": "1.3.4",
    "react-query": "3.39.3",
    "react-router-dom": "5.3.4",
    "react-select": "5.7.3",
    "react-syntax-highlighter": "15.5.0",
    "react-table": "7.8.0",
    "reactstrap": "9.1.4",
    "rehype-raw": "6.1.1",
    "rehype-sanitize": "5.0.1",
    "remark-gfm": "3.0.1",
    "sass": "1.62.1",
    "sharp": "0.32.1",
    "socket.io-client": "4.6.1",
    "tsc-files": "1.1.3",
    "typescript": "5.0.4",
    "url-join": "4.0.1",
    "utf8": "3.0.0",
    "uuid": "9.0.0",
    "whatwg-fetch": "3.6.2",
    "yup": "0.32.11"
  },
  "devDependencies": {
    "@emotion/jest": "11.10.8",
    "@testing-library/jest-dom": "5.16.5",
    "@testing-library/react": "14.0.0",
    "@testing-library/user-event": "14.4.3",
    "husky": "7.0.4",
    "jest-junit": "16.0.0",
    "lint-staged": "13.0.3",
    "mocha-junit-reporter": "2.2.0",
    "npmlog": "6.0.1",
    "react-dnd-cjs": "9.5.1",
    "react-dnd-html5-backend-cjs": "9.5.1",
    "react-dnd-test-utils": "16.0.1",
    "react-dnd-touch-backend-cjs": "9.5.1",
    "react-select-event": "5.5.1"
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },
  "volta": {
    "node": "20.0.0",
    "npm": "9.6.4"
  }
}