Brayden Langley
Brayden Langley

Reputation: 1

How to properly mock import.meta.url in Jest with TypeScript and NodeNext module system?

I'm trying to mock the import.meta.url property in a Jest setup file within a TypeScript project using the NodeNext module system. My project is configured with "module": "NodeNext" in tsconfig.json, which supports import.meta. However, I'm encountering issues where Jest fails to handle import.meta.url correctly in the setup file. Specifically, I receive TypeScript errors when trying to use import.meta.url, despite having the correct module settings.

Usage code:

import { promises as fs } from 'fs'
import { dirname, join } from 'path'
import { fileURLToPath } from 'url'

const __filename = fileURLToPath(import.meta.url)
const __dirname = dirname(__filename)

/**
 * Returns documentation specific to the provided filename
 * @param {string} filename - The name of the markdown file
 * @returns A promise that resolves to the documentation string
 */
async function getDocumentation(filename: string): Promise<string> {
  const filePath = join(__dirname, filename)
  try {
    const data = await fs.readFile(filePath, 'utf-8')
    return data
  } catch (error) {
    console.error('Error reading documentation file:', error)
    throw new Error('Failed to read documentation file')
  }
}

export { getDocumentation }

Here's my tsconfig.json:

{
  "compilerOptions": {
    "lib": ["dom", "ESNext"],
    "module": "NodeNext",
    "target": "esnext",
    "moduleResolution": "NodeNext",
    "moduleDetection": "force",
    "rootDir": "./",
    "baseUrl": "./",
    "outDir": "./dist",
    "strict": true,
    "isolatedModules": true,
    "strictNullChecks": true,
    "downlevelIteration": false,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "incremental": true,
    "sourceMap": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "resolveJsonModule": true,
    "esModuleInterop": true
  },
  "files": ["src/index.ts", "src/types.d.ts"],
  "include": ["src", "knexfile.ts", "src/types.d.ts"],
  "exclude": ["dist", "*/**/__tests"]
}

jest.config.js (also tried .cjs and .mjs)

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
  setupFilesAfterEnv: ['./jest.setup.ts'],
  globals: {
    'ts-jest': {
      tsconfig: 'tsconfig.json'
    }
  }
}

Current jest.setup.ts file:

import { URL } from 'url';
import path from 'path';

const metaUrl = new URL('', import.meta.url).toString();

(global as any).__filename = path.resolve(new URL(metaUrl).pathname);
(global as any).__dirname = path.dirname((global as any).__filename);

jest.mock('url', () => ({
  ...jest.requireActual('url'),
  fileURLToPath: jest.fn((url: string) => {
    if (url === 'file://' + (global as any).__filename) {
      return (global as any).__filename;
    }
    return url;
  }),
}));

I expected the Jest setup file to correctly mock the import.meta.url property, allowing Jest to run tests without TypeScript errors. Specifically, I was trying to set up global variables __filename and __dirname based on import.meta.url. However, I encountered the following TypeScript errors among other similar errors:

jest.setup.ts:7:12 - error TS1343: The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'es2022', 'esnext', 'system', 'node16', or 'nodenext'.

Despite setting "module": "NodeNext" in tsconfig.json, Jest and TypeScript still report issues when using import.meta.url. I have tried various workarounds, including conditionally checking import.meta and setting fallback values, but none have resolved the errors.

Upvotes: 0

Views: 647

Answers (2)

Jonas Wiebe
Jonas Wiebe

Reputation: 26

There is an npm package for this exact problem: ts-jest-mock-import-meta.

They provide this configuration for ts-jest >= 29.0.0:

// jest.config
{
  // [...]
  transform: {
    '^.+\\.tsx?$': [
      'ts-jest',
      {
        diagnostics: {
          ignoreCodes: [1343]
        },
        astTransformers: {
          before: [
            {
              path: 'node_modules/ts-jest-mock-import-meta',  // or, alternatively, 'ts-jest-mock-import-meta' directly, without node_modules.
              options: {
                metaObjectReplacement: {
                  url: ({ fileName }) => `file://${fileName}`,
                  file: ({ fileName }) => fileName
                }
              }
            }
          ]
        }
      }
    ]
  }
}

For ts-jest < 29.0.0 you can use:

// jest.config
{
  // [...]
  globals: {
    'ts-jest': {
      diagnostics: {
        ignoreCodes: [1343]
      },
      astTransformers: {
        before: [
          {
            path: 'node_modules/ts-jest-mock-import-meta',  // or, alternatively, 'ts-jest-mock-import-meta' directly, without node_modules.
            options: {
                metaObjectReplacement: {
                  url: ({ fileName }) => `file://${fileName}`,
                  file: ({ fileName }) => fileName
                }
              }
          }
        ]
      }
    }
  }
}

Upvotes: 0

Brayden Langley
Brayden Langley

Reputation: 1

I was unable to solve the import.meta.url related errors, but I was able to solve the problem by just switching the way I was reading in the file data.

Here is my solution which works in a Node environment for those that might find it useful.

import { promises as fs } from 'fs'
import { resolve } from 'path'
/**
 * Returns documentation specific to the provided filename
 * @param {string} filePath - The path of the markdown file
 * @returns A promise that resolves to the documentation string
 */
async function getDocumentation(filePath: string): Promise<string> {
  try {
    const resolvedPath = resolve(process.cwd(), filePath)
    const data = await fs.readFile(resolvedPath, 'utf-8')
    return data
  } catch (error) {
    console.error('Error reading documentation file:', error)
    throw new Error('Failed to read documentation file')
  }
}

export { getDocumentation }

Upvotes: 0

Related Questions