Files
pnpm/installing/commands/test/update/interactive.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

366 lines
9.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import path from 'node:path'
import { expect, jest, test } from '@jest/globals'
import type { LockfileObject } from '@pnpm/lockfile.types'
import { prepare, preparePackages } from '@pnpm/prepare'
import { addDistTag, REGISTRY_MOCK_PORT } from '@pnpm/registry-mock'
import { filterProjectsBySelectorObjectsFromDir } from '@pnpm/workspace.projects-filter'
import chalk from 'chalk'
import { readYamlFileSync } from 'read-yaml-file'
jest.unstable_mockModule('enquirer', () => ({ default: { prompt: jest.fn() } }))
const { default: enquirer } = await import('enquirer')
const { add, install, update } = await import('@pnpm/installing.commands')
const prompt = jest.mocked(enquirer.prompt)
const REGISTRY_URL = `http://localhost:${REGISTRY_MOCK_PORT}`
const DEFAULT_OPTIONS = {
argv: {
original: [],
},
bail: false,
bin: 'node_modules/.bin',
excludeLinksFromLockfile: false,
extraEnv: {},
cliOptions: {},
deployAllFiles: false,
include: {
dependencies: true,
devDependencies: true,
optionalDependencies: true,
},
lock: true,
pnpmfile: ['.pnpmfile.cjs'],
pnpmHomeDir: '',
preferWorkspacePackages: true,
configByUri: {},
registries: {
default: REGISTRY_URL,
},
rootProjectManifestDir: '',
sort: true,
userConfig: {},
workspaceConcurrency: 1,
virtualStoreDirMaxLength: process.platform === 'win32' ? 60 : 120,
}
test('interactively update', async () => {
const project = prepare({
dependencies: {
// has 1.0.0 and 1.0.1 that satisfy this range
'is-negative': '^1.0.0',
// only 2.0.0 satisfies this range
'is-positive': '^2.0.0',
// has many versions that satisfy ^3.0.0
micromatch: '^3.0.0',
},
})
const storeDir = path.resolve('pnpm-store')
const headerChoice = {
name: 'Package Current Target URL ',
disabled: true,
hint: '',
value: '',
}
await Promise.all([
addDistTag({ package: 'is-negative', version: '2.1.0', distTag: 'latest' }),
addDistTag({ package: 'micromatch', version: '4.0.0', distTag: 'latest' }),
])
await add.handler(
{
...DEFAULT_OPTIONS,
cacheDir: path.resolve('cache'),
dir: process.cwd(),
linkWorkspacePackages: true,
save: false,
storeDir,
},
['is-negative@1.0.0', 'is-positive@2.0.0', 'micromatch@3.0.0']
)
prompt.mockResolvedValue({
updateDependencies: [
{
value: 'is-negative',
name: `is-negative 1.0.0 1.0.${chalk.greenBright.bold('1')} https://pnpm.io/ `,
},
],
})
prompt.mockClear()
// Update to compatible versions
await update.handler({
...DEFAULT_OPTIONS,
cacheDir: path.resolve('cache'),
dir: process.cwd(),
interactive: true,
linkWorkspacePackages: true,
storeDir,
})
// eslint-disable-next-line
expect((prompt.mock.calls[0][0] as any).choices).toStrictEqual([
{
choices: [
headerChoice,
{
message: `is-negative 1.0.0 1.0.${chalk.greenBright.bold('1')} `,
value: 'is-negative',
name: 'is-negative',
},
{
message: `micromatch 3.0.0 3.${chalk.yellowBright.bold('1.10')} `,
value: 'micromatch',
name: 'micromatch',
},
],
name: '[dependencies]',
message: 'dependencies',
},
])
expect(prompt).toHaveBeenCalledWith(
expect.objectContaining({
footer: '\nEnter to start updating. Ctrl-c to cancel.',
message:
'Choose which packages to update ' +
`(Press ${chalk.cyan('<space>')} to select, ` +
`${chalk.cyan('<a>')} to toggle all, ` +
`${chalk.cyan('<i>')} to invert selection)`,
name: 'updateDependencies',
type: 'multiselect',
})
)
{
const lockfile = project.readLockfile()
expect(lockfile.packages['micromatch@3.0.0']).toBeTruthy()
expect(lockfile.packages['is-negative@1.0.1']).toBeTruthy()
expect(lockfile.packages['is-positive@2.0.0']).toBeTruthy()
}
// Update to latest versions
prompt.mockClear()
await update.handler({
...DEFAULT_OPTIONS,
cacheDir: path.resolve('cache'),
dir: process.cwd(),
interactive: true,
latest: true,
linkWorkspacePackages: true,
storeDir,
})
// eslint-disable-next-line
expect((prompt.mock.calls[0][0] as any).choices).toStrictEqual([
{
choices: [
headerChoice,
{
message: `is-negative 1.0.1 ${chalk.redBright.bold('2.1.0')} `,
value: 'is-negative',
name: 'is-negative',
},
{
message: `is-positive 2.0.0 ${chalk.redBright.bold('3.1.0')} `,
value: 'is-positive',
name: 'is-positive',
},
{
message: `micromatch 3.0.0 ${chalk.redBright.bold('4.0.0')} `,
value: 'micromatch',
name: 'micromatch',
},
],
name: '[dependencies]',
message: 'dependencies',
},
])
expect(prompt).toHaveBeenCalledWith(
expect.objectContaining({
footer: '\nEnter to start updating. Ctrl-c to cancel.',
message:
'Choose which packages to update ' +
`(Press ${chalk.cyan('<space>')} to select, ` +
`${chalk.cyan('<a>')} to toggle all, ` +
`${chalk.cyan('<i>')} to invert selection)`,
name: 'updateDependencies',
type: 'multiselect',
})
)
{
const lockfile = project.readLockfile()
expect(lockfile.packages['micromatch@3.0.0']).toBeTruthy()
expect(lockfile.packages['is-negative@2.1.0']).toBeTruthy()
expect(lockfile.packages['is-positive@2.0.0']).toBeTruthy()
}
})
test('interactive update of dev dependencies only', async () => {
preparePackages([
{
name: 'project1',
dependencies: {
'is-negative': '^1.0.1',
},
},
{
name: 'project2',
devDependencies: {
'is-negative': '^1.0.0',
},
},
])
const storeDir = path.resolve('store')
prompt.mockResolvedValue({
updateDependencies: [
{
value: 'is-negative',
name: `is-negative 1.0.0 1.0.${chalk.greenBright.bold('1')} https://pnpm.io/ `,
},
],
})
const { allProjects, selectedProjectsGraph } = await filterProjectsBySelectorObjectsFromDir(
process.cwd(),
[]
)
await install.handler({
...DEFAULT_OPTIONS,
cacheDir: path.resolve('cache'),
allProjects,
dir: process.cwd(),
linkWorkspacePackages: true,
lockfileDir: process.cwd(),
recursive: true,
selectedProjectsGraph,
storeDir,
workspaceDir: process.cwd(),
})
await update.handler({
...DEFAULT_OPTIONS,
cacheDir: path.resolve('cache'),
allProjects,
cliOptions: {
dev: true,
optional: false,
production: false,
},
dir: process.cwd(),
interactive: true,
latest: true,
linkWorkspacePackages: true,
lockfileDir: process.cwd(),
recursive: true,
selectedProjectsGraph,
storeDir,
workspaceDir: process.cwd(),
})
const lockfile = readYamlFileSync<LockfileObject>('pnpm-lock.yaml')
expect(Object.keys(lockfile.packages ?? {})).toStrictEqual([
'is-negative@1.0.1',
'is-negative@2.1.0',
])
})
test('interactively update should ignore dependencies from the ignoreDependencies field', async () => {
const project = prepare({
dependencies: {
// has 1.0.0 and 1.0.1 that satisfy this range
'is-negative': '^1.0.0',
// only 2.0.0 satisfies this range
'is-positive': '^2.0.0',
// has many versions that satisfy ^3.0.0
micromatch: '^3.0.0',
},
})
const storeDir = path.resolve('pnpm-store')
await add.handler(
{
...DEFAULT_OPTIONS,
cacheDir: path.resolve('cache'),
dir: process.cwd(),
linkWorkspacePackages: true,
save: false,
storeDir,
},
['is-negative@1.0.0', 'is-positive@2.0.0', 'micromatch@3.0.0']
)
prompt.mockResolvedValue({
updateDependencies: [{ value: 'micromatch', name: 'anything' }],
})
prompt.mockClear()
await update.handler({
...DEFAULT_OPTIONS,
cacheDir: path.resolve('cache'),
dir: process.cwd(),
interactive: true,
linkWorkspacePackages: true,
storeDir,
updateConfig: {
ignoreDependencies: ['is-negative'],
},
})
// eslint-disable-next-line
expect((prompt.mock.calls[0][0] as any).choices as any).toStrictEqual(
[
{
choices: [
{
disabled: true,
hint: '',
name: 'Package Current Target URL ',
value: '',
},
{
message: `micromatch 3.0.0 3.${chalk.yellowBright.bold('1.10')} `,
value: 'micromatch',
name: 'micromatch',
},
],
name: '[dependencies]',
message: 'dependencies',
},
]
)
expect(prompt).toHaveBeenCalledWith(
expect.objectContaining({
footer: '\nEnter to start updating. Ctrl-c to cancel.',
message:
'Choose which packages to update ' +
`(Press ${chalk.cyan('<space>')} to select, ` +
`${chalk.cyan('<a>')} to toggle all, ` +
`${chalk.cyan('<i>')} to invert selection)`,
name: 'updateDependencies',
type: 'multiselect',
})
)
{
const lockfile = project.readLockfile()
expect(lockfile.packages['micromatch@3.1.10']).toBeTruthy()
expect(lockfile.packages['is-negative@1.0.0']).toBeTruthy()
expect(lockfile.packages['is-positive@2.0.0']).toBeTruthy()
}
})