next.js/test/jest-setup-after-env.ts
jest-setup-after-env.ts125 lines4.1 KB
import * as matchers from 'jest-extended'
expect.extend(matchers)

// Patch jscodeshift testUtils to normalize line endings (fixes Windows CRLF issues)
// The issue: jscodeshift's printer (recast) outputs CRLF on Windows, but test fixtures use LF
// We need to patch both defineTest (which uses internal closure references) and runInlineTest
if (process.platform === 'win32') {
  try {
    const testUtils = require('jscodeshift/dist/testUtils')
    const fs = require('fs')
    const path = require('path')

    // Helper to normalize line endings
    // - Convert CRLF to LF
    // - Remove trailing whitespace from each line (not meaningful for code)
    // - Ensure exactly one trailing newline (POSIX convention for text files)
    //   Using \n*$ to handle case where transform output has no trailing newline
    const normalizeLF = (str: string) =>
      str
        .replace(/\r\n/g, '\n')
        .replace(/[ \t]+$/gm, '')
        .replace(/\n*$/, '\n')

    // Patch runInlineTest to normalize both transform output and expected
    testUtils.runInlineTest = function (
      module: any,
      options: any,
      input: any,
      expectedOutput: string,
      testOptions?: any
    ) {
      // Normalize input source
      const normalizedInput =
        typeof input === 'object' && input.source
          ? { ...input, source: normalizeLF(input.source) }
          : input
      // Normalize expected output
      const normalizedExpected = normalizeLF(expectedOutput)

      // Run the transform and normalize its output for comparison
      const output = testUtils.applyTransform(
        module,
        options,
        normalizedInput,
        testOptions
      )
      const normalizedOutput =
        typeof output === 'string' ? normalizeLF(output) : output

      // Do the comparison ourselves instead of letting the original do it
      // eslint-disable-next-line jest/no-standalone-expect -- called from within test blocks
      expect(normalizedOutput).toEqual(normalizedExpected)
      return normalizedOutput
    }

    // Replace defineTest entirely since it uses internal closure references
    // that bypass our exports patch
    testUtils.defineTest = function (
      dirName: string,
      transformName: string,
      options: any,
      testFilePrefix?: string,
      testOptions?: { parser?: string }
    ) {
      const testName = testFilePrefix
        ? `transforms correctly using "${testFilePrefix}" data`
        : 'transforms correctly'

      describe(transformName, () => {
        it(testName, () => {
          const fixtureDir = path.join(dirName, '..', '__testfixtures__')
          const prefix = testFilePrefix || transformName
          const module = require(path.join(dirName, '..', transformName))

          // Determine file extension based on parser option
          const parser = testOptions?.parser || module.parser
          const extension =
            parser === 'ts' ? 'ts' : parser === 'tsx' ? 'tsx' : 'js'

          const inputPath = path.join(
            fixtureDir,
            `${prefix}.input.${extension}`
          )
          const outputPath = path.join(
            fixtureDir,
            `${prefix}.output.${extension}`
          )

          const source = normalizeLF(fs.readFileSync(inputPath, 'utf8'))
          const expectedOutput = normalizeLF(
            fs.readFileSync(outputPath, 'utf8')
          )

          testUtils.runInlineTest(
            module,
            options,
            { path: inputPath, source },
            expectedOutput,
            testOptions
          )
        })
      })
    }
  } catch {
    // jscodeshift not available, skip patching
  }
}

// A default max-timeout of 90 seconds is allowed
// per test we should aim to bring this down though
jest.setTimeout((process.platform === 'win32' ? 180 : 60) * 1000)

// Polyfill for `using` https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html
if (!Symbol.dispose) {
  Object.defineProperty(Symbol, 'dispose', {
    value: Symbol('Symbol.dispose'),
  })
}

if (!Symbol.asyncDispose) {
  Object.defineProperty(Symbol, 'asyncDispose', {
    value: Symbol('Symbol.asyncDispose'),
  })
}
Quest for Codev2.0.0
/
SIGN IN