Files
pnpm/installing/env-installer/test/resolveAndInstallConfigDeps.test.ts
Zoltan Kochan 187049055f chore: upgrade @typescript/native-preview to 7.0.0-dev.20260421.2 (#11332)
* chore: upgrade @typescript/native-preview to 7.0.0-dev.20260421.2

- Add explicit `types: ["node"]` to the shared tsconfig because tsgo
  20260421 no longer auto-acquires `@types/*` from `node_modules`.
- Refactor test files to explicitly import jest globals (`describe`,
  `it`, `test`, `expect`, `beforeEach`, etc.) from `@jest/globals`
  instead of relying on `@types/jest` ambient declarations. Under the
  new tsgo build, `import { jest } from '@jest/globals'` shadows the
  ambient `jest` namespace, breaking `@types/jest`'s `declare var
  describe: jest.Describe;` globals.
- Add `@jest/globals` to each package's devDependencies where tests
  now import from it, and add `@types/node` to packages that need it
  but were relying on hoisted resolution.
- Replace `fail()` calls with `throw new Error(...)` since `fail` is
  no longer globally available.

* chore: fix remaining tsgo type-strictness errors

- Strip `as <PnpmType>` casts on objects passed to toMatchObject /
  toStrictEqual / toEqual; @jest/globals rejects the typed objects
  (which include AsymmetricMatchers) vs. the repo-specific type.
- Type `jest.fn<...>()` explicitly where the mock's signature matters
  for toHaveBeenCalledWith.
- Replace `beforeEach(() => X)` with `beforeEach(() => { X })` so the
  return value is void, as the stricter jest typing requires.
- Use `expect.objectContaining({...})` in one place where the full
  expected object triggered stricter type resolution.
- Cast `prompt.mock.calls` arg through `as unknown as Record<...>[]`
  for patch.test.ts's nested-array matchers.
- Fix off-by-one `<reference path>` in pnpm/test/getConfig.test.ts
  that only surfaced now.
- Move `@jest/globals` from devDependencies to dependencies in the
  two `__utils__` packages that import it from `src/`.
- Clean up unused imports from the @jest/globals migration.

* chore: address Copilot review on #11332

- Move misplaced `@jest/globals` imports to the top import block in
  checkEngine, run.ts, and workspace/root-finder tests where the
  script dropped them below executable code.
- Replace `try { await x(); throw new Error('should have thrown') } catch`
  in bins/linker, lockfile/fs, and resolving/local-resolver tests with
  `await expect(x()).rejects.toMatchObject({...})`. The old pattern
  swallowed an unrelated `throw` if the under-test call silently
  succeeded, which would fail on the catch-block assertion with a
  misleading message.
2026-04-21 22:50:40 +02:00

249 lines
9.2 KiB
TypeScript

import path from 'node:path'
import { expect, test } from '@jest/globals'
import { resolveAndInstallConfigDeps } from '@pnpm/installing.env-installer'
import { createEnvLockfile, readEnvLockfile, writeEnvLockfile } from '@pnpm/lockfile.fs'
import { prepareEmpty } from '@pnpm/prepare'
import { getIntegrity, REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
import { createTempStore } from '@pnpm/testing.temp-store'
import { loadJsonFileSync } from 'load-json-file'
const registry = `http://localhost:${REGISTRY_MOCK_PORT}/`
function createOpts () {
const { storeController, storeDir } = createTempStore()
return {
registries: { default: registry },
rootDir: process.cwd(),
cacheDir: path.resolve('cache'),
userConfig: {},
store: storeController,
storeDir,
}
}
test('resolves and installs config dep when no env lockfile exists', async () => {
prepareEmpty()
const opts = createOpts()
// Simulate a config dep manually added to pnpm-workspace.yaml (clean specifier, no lockfile)
await resolveAndInstallConfigDeps({
'@pnpm.e2e/foo': '100.0.0',
}, opts)
// Package should be installed
const manifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
expect(manifest.name).toBe('@pnpm.e2e/foo')
expect(manifest.version).toBe('100.0.0')
// Env lockfile should be created with resolved info
const envLockfile = await readEnvLockfile(process.cwd())
expect(envLockfile).not.toBeNull()
expect(envLockfile!.importers['.'].configDependencies['@pnpm.e2e/foo']).toStrictEqual({
specifier: '100.0.0',
version: '100.0.0',
})
expect(envLockfile!.packages['@pnpm.e2e/foo@100.0.0']).toStrictEqual({
resolution: {
integrity: getIntegrity('@pnpm.e2e/foo', '100.0.0'),
},
})
})
test('resolves newly added config dep when env lockfile already has other deps', async () => {
prepareEmpty()
const opts = createOpts()
// Pre-create env lockfile with one dep
const existingLockfile = createEnvLockfile()
existingLockfile.importers['.'].configDependencies['@pnpm.e2e/foo'] = {
specifier: '100.0.0',
version: '100.0.0',
}
existingLockfile.packages['@pnpm.e2e/foo@100.0.0'] = {
resolution: { integrity: getIntegrity('@pnpm.e2e/foo', '100.0.0') },
}
existingLockfile.snapshots['@pnpm.e2e/foo@100.0.0'] = {}
await writeEnvLockfile(process.cwd(), existingLockfile)
// Now install with an additional dep
await resolveAndInstallConfigDeps({
'@pnpm.e2e/foo': '100.0.0',
'@pnpm.e2e/bar': '100.0.0',
}, opts)
// Both packages should be installed
const fooManifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
expect(fooManifest.name).toBe('@pnpm.e2e/foo')
expect(fooManifest.version).toBe('100.0.0')
const barManifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/bar/package.json')
expect(barManifest.name).toBe('@pnpm.e2e/bar')
expect(barManifest.version).toBe('100.0.0')
// Env lockfile should have both deps
const envLockfile = await readEnvLockfile(process.cwd())
expect(envLockfile!.importers['.'].configDependencies['@pnpm.e2e/foo']).toBeDefined()
expect(envLockfile!.importers['.'].configDependencies['@pnpm.e2e/bar']).toBeDefined()
})
test('skips resolution when all deps are already in env lockfile', async () => {
prepareEmpty()
const opts = createOpts()
// Pre-create complete env lockfile
const lockfile = createEnvLockfile()
lockfile.importers['.'].configDependencies['@pnpm.e2e/foo'] = {
specifier: '100.0.0',
version: '100.0.0',
}
lockfile.packages['@pnpm.e2e/foo@100.0.0'] = {
resolution: { integrity: getIntegrity('@pnpm.e2e/foo', '100.0.0') },
}
lockfile.snapshots['@pnpm.e2e/foo@100.0.0'] = {}
await writeEnvLockfile(process.cwd(), lockfile)
// Install should work without network (using lockfile data)
await resolveAndInstallConfigDeps({
'@pnpm.e2e/foo': '100.0.0',
}, opts)
const manifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
expect(manifest.name).toBe('@pnpm.e2e/foo')
expect(manifest.version).toBe('100.0.0')
})
test('re-resolves and reinstalls when config dep version changes in pnpm-workspace.yaml', async () => {
prepareEmpty()
const opts = createOpts()
// Pre-create env lockfile with foo@100.0.0
const lockfile = createEnvLockfile()
lockfile.importers['.'].configDependencies['@pnpm.e2e/foo'] = {
specifier: '100.0.0',
version: '100.0.0',
}
lockfile.packages['@pnpm.e2e/foo@100.0.0'] = {
resolution: { integrity: getIntegrity('@pnpm.e2e/foo', '100.0.0') },
}
lockfile.snapshots['@pnpm.e2e/foo@100.0.0'] = {}
await writeEnvLockfile(process.cwd(), lockfile)
// Install first with the old version
await resolveAndInstallConfigDeps({
'@pnpm.e2e/foo': '100.0.0',
}, opts)
// Now simulate user changing the version in pnpm-workspace.yaml
await resolveAndInstallConfigDeps({
'@pnpm.e2e/foo': '100.1.0',
}, opts)
// The new version should be installed
const manifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
expect(manifest.name).toBe('@pnpm.e2e/foo')
expect(manifest.version).toBe('100.1.0')
// Env lockfile should be updated with the new version
const envLockfile = await readEnvLockfile(process.cwd())
expect(envLockfile).not.toBeNull()
expect(envLockfile!.importers['.'].configDependencies['@pnpm.e2e/foo']).toStrictEqual({
specifier: '100.1.0',
version: '100.1.0',
})
expect(envLockfile!.packages['@pnpm.e2e/foo@100.1.0']).toStrictEqual({
resolution: {
integrity: getIntegrity('@pnpm.e2e/foo', '100.1.0'),
},
})
// Old version should be cleaned up from the lockfile
expect(envLockfile!.packages['@pnpm.e2e/foo@100.0.0']).toBeUndefined()
})
test('handles old format config deps via migration path', async () => {
prepareEmpty()
const opts = createOpts()
const integrity = getIntegrity('@pnpm.e2e/foo', '100.0.0')
await resolveAndInstallConfigDeps({
'@pnpm.e2e/foo': `100.0.0+${integrity}`,
}, opts)
const manifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
expect(manifest.name).toBe('@pnpm.e2e/foo')
expect(manifest.version).toBe('100.0.0')
})
test('handles mixed old-format and new-format config deps together', async () => {
prepareEmpty()
const opts = createOpts()
// One dep in old inline-integrity format, another as a clean specifier
const integrity = getIntegrity('@pnpm.e2e/foo', '100.0.0')
await resolveAndInstallConfigDeps({
'@pnpm.e2e/foo': `100.0.0+${integrity}`,
'@pnpm.e2e/bar': '100.0.0',
}, opts)
// Both packages should be installed
const fooManifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
expect(fooManifest.name).toBe('@pnpm.e2e/foo')
expect(fooManifest.version).toBe('100.0.0')
const barManifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/bar/package.json')
expect(barManifest.name).toBe('@pnpm.e2e/bar')
expect(barManifest.version).toBe('100.0.0')
// Env lockfile should have both deps
const envLockfile = await readEnvLockfile(process.cwd())
expect(envLockfile).not.toBeNull()
expect(envLockfile!.importers['.'].configDependencies['@pnpm.e2e/foo']).toBeDefined()
expect(envLockfile!.importers['.'].configDependencies['@pnpm.e2e/bar']).toBeDefined()
expect(envLockfile!.packages['@pnpm.e2e/foo@100.0.0']).toBeDefined()
expect(envLockfile!.packages['@pnpm.e2e/bar@100.0.0']).toBeDefined()
})
test('fails with frozenLockfile when old-format deps need migration', async () => {
prepareEmpty()
const opts = createOpts()
const integrity = getIntegrity('@pnpm.e2e/foo', '100.0.0')
await expect(resolveAndInstallConfigDeps({
'@pnpm.e2e/foo': `100.0.0+${integrity}`,
}, { ...opts, frozenLockfile: true })).rejects.toThrow('Cannot update configDependencies with "frozen-lockfile"')
})
test('fails with frozenLockfile when new-format deps need resolution', async () => {
prepareEmpty()
const opts = createOpts()
await expect(resolveAndInstallConfigDeps({
'@pnpm.e2e/foo': '100.0.0',
}, { ...opts, frozenLockfile: true })).rejects.toThrow('Cannot update configDependencies with "frozen-lockfile"')
})
test('succeeds with frozenLockfile when env lockfile is up-to-date', async () => {
prepareEmpty()
const opts = createOpts()
// Pre-create complete env lockfile
const lockfile = createEnvLockfile()
lockfile.importers['.'].configDependencies['@pnpm.e2e/foo'] = {
specifier: '100.0.0',
version: '100.0.0',
}
lockfile.packages['@pnpm.e2e/foo@100.0.0'] = {
resolution: { integrity: getIntegrity('@pnpm.e2e/foo', '100.0.0') },
}
lockfile.snapshots['@pnpm.e2e/foo@100.0.0'] = {}
await writeEnvLockfile(process.cwd(), lockfile)
await resolveAndInstallConfigDeps({
'@pnpm.e2e/foo': '100.0.0',
}, { ...opts, frozenLockfile: true })
const manifest = loadJsonFileSync<{ name: string, version: string }>('node_modules/.pnpm-config/@pnpm.e2e/foo/package.json')
expect(manifest.name).toBe('@pnpm.e2e/foo')
expect(manifest.version).toBe('100.0.0')
})