reduxjs / reselect

Selector library for Redux
MIT License
19.04k stars 670 forks source link

TypeError: (0 , _reselect.createSelector) is not a function #692

Closed ceafive closed 9 months ago

ceafive commented 9 months ago

I am using createSelector from reselect 5.1.0 and it works when I run the app but when I run my Jest tests I get an error like in the image below. I really really need help with this one. When I downgrade to 4.1.8, the tests pass but the createSelector doesn't memoize properly when I run the app

Screenshot 2024-01-28 at 19 17 10

This is how I am importing it

Screenshot 2024-01-28 at 19 21 07
markerikson commented 9 months ago

This generally suggests that you have some kind of a build tool setup / configuration problem.

What build tools are you using?

Can you share a Github repo that shows this problem happening?

edit sorry, just realized you said "it works fine when running, but fails with tests". What test tool and version are you using?

ceafive commented 9 months ago

Unfortunately can't share a Github repo. Build tools: ejected CRA with Webpack 5. We use Jest v29.5.0

markerikson commented 9 months ago

Not sure what to tell you without some kind of reproduction to look at, other than to try logging reselect and createSelector in that file and see what's actually being imported. It's definitely some kind of a module loading / import issue.

ceafive commented 9 months ago

So I get reselect.cjs when I console.log reselect and I get undefined when i console.log reselect.createSelector

markerikson commented 9 months ago

as in, you're getting a string of "reselect.cjs"?

If so, yeah, that's 100% broken. I would expect it to either be an object, or undefined.

I still don't have an answer for why this is happening - I'd need to see a repro to be able to investigate what's going on.

To be clear, this sounds like it's an issue with Jest and loading the module, specifically.

ceafive commented 9 months ago

Yeah getting a string of reselect.cjs. This is how 5.1.0 looks like in node_modules

Screenshot 2024-01-28 at 20 20 03

This is how 4.1.8 looks like in node_modules

Screenshot 2024-01-28 at 20 18 52
markerikson commented 9 months ago

Yeah, both of those are expected - we changed the packaging setup for Reselect 5, and we tested it pretty thoroughly against a variety of build tools.

All I can say atm is that apparently something is causing Jest to drastically misinterpret the library, but I don't know what. If you can provide a repo that demonstrates this happening, I can take a look, but without that I can't do anything.

Like, does this happen in a brand new repo with Jest 19 + Reselect? Does this only happen in your repo? Do you have a Jest config that's doing a bunch of complex setup, or is it basic Jest out of the box?

aryaemami59 commented 9 months ago

can you share what your jest.config.js looks like?

ceafive commented 9 months ago

Here you go @aryaemami59

import fs from "fs"
import type {Config} from "@jest/types"

import {projects} from "./tools/jest/project-definitions"

type ArrayElement<A> = A extends readonly (infer T)[] ? T : never

type SingleJestProjectType = Exclude<ArrayElement<Config.InitialOptions["projects"]>, string>

const genReactAppJestConfig = (
    _pkg: Record<string, any>,
    projectPath: string,
    srcDir: string,
): SingleJestProjectType => {
    const setupFiles = [fs.existsSync(`${projectPath}/setupTests.ts`) && `${projectPath}/setupTests.ts`].filter(
        Boolean,
    ) as string[]
    const setupFilesAfterEnv = [
        fs.existsSync(`${projectPath}/setupTestsAfterEnv.ts`) && `${projectPath}/setupTestsAfterEnv.ts`,
    ].filter(Boolean) as string[]
    return {
        rootDir: `${projectPath}${srcDir}`,
        roots: [`${projectPath}${srcDir}`],
        setupFiles,
        setupFilesAfterEnv,
        moduleNameMapper: {
            "@mockData/(.*)": `${projectPath}/__mocks__/$1`,
            "^@app/(.*)$": `<rootDir>/$1`,
            "^react-native$": "react-native-web",
            "^.+\\.module\\.(css|sass|scss)$": "identity-obj-proxy",
            "\\.(css|less|sass|scss)$": path.resolve(__dirname, "./config/jest/cssMock.js"),
            "@next/core-logger": "@next/core-logger/lib/client",
            "^@manifest/constants$": `<rootDir>/templating/constants`,
        },
        moduleFileExtensions: ["web.js", "js", "web.ts", "ts", "web.tsx", "tsx", "json", "web.jsx", "jsx", "node"],
        modulePaths: [],
        testEnvironment: "jsdom",
        testMatch: [`<rootDir>/**/__tests__/**/*.{js,jsx,ts,tsx}`, `<rootDir>/**/*.{spec,test}.{js,jsx,ts,tsx}`],
        // TODO: 52076 - Refactor transform ignore patterns
        transformIgnorePatterns: [
            "/node_modules/(?!launchdarkly-node-server-sdk|applicationinsights)/",
            "\\.pnp\\.[^\\/]+$",
        ],
        transform: {
            "\\.[jt]sx?$": [
                "babel-jest",
                {
                    rootMode: "upward",
                },
            ],
            "^.+\\.css$": path.resolve(__dirname, `./config/jest/cssTransform.js`),
            "^(?!.*\\.(css|json)$)": path.resolve(__dirname, `./config/jest/fileTransform.js`),
        },
        snapshotSerializers: ["@emotion/jest/serializer"],
    }
}

const config = async (relativePathToRoot = "."): Promise<Config.InitialOptions> => {
    return {
        collectCoverage: true,
        collectCoverageFrom: [
            "**/*.{ts,tsx}",
            "!**/*.cy.{ts,tsx}",
            "!**/*.stories.{ts,tsx}",
            "!**/*.d.ts",
            "!**/templating/manifest.ts",
            "!**/cypress/**",
            "!**/components/plp/categoryPills/*.{ts,tsx}", // Excluded for coverage (POC)
            "!**/components/plp/tabScrollNavigation/*.{ts,tsx}", // Excluded for coverage (POC)
        ],
        coverageReporters: ["json-summary", "lcov", "text"],
        watchPlugins: ["jest-watch-typeahead/filename", "jest-watch-typeahead/testname"],
        projects: await Promise.all(
            projects
                .map(project => ({
                    ...project,
                    projectDir: path.resolve(project.rootDir.replace("./", `${relativePathToRoot}/`)),
                }))
                .filter(project => fs.existsSync(`${project.projectDir}/package.json`))
                .map(async project => {
                    const pkg = await import(`${project.projectDir}/package.json`)
                    return {
                        displayName: pkg.name,
                        ...(genReactAppJestConfig(
                            pkg,
                            path.resolve(project.rootDir.replace("./", `${relativePathToRoot}/`)),
                            project.srcDir ?? "/src",
                        ) as Record<string, any>),
                    }
                })
                .filter(Boolean),
        ),
    }
}

export default config
markerikson commented 9 months ago

Okay, I don't know what about that config is an issue, but given that you do have a large complex custom Jest config, I would strongly suspect the issue is in that config somewhere.

ceafive commented 9 months ago

Read up on jest configs and I have found we use a fileTransform.js that stringifies imports and that was the issue

markerikson commented 9 months ago

Yep, that's roughly what I assumed to be happening. Glad you got that figured out!