reduxjs/reselect

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

ceafive opened this issue · 12 comments

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

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?

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

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.

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

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.

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

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?

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

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

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.

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

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