Is there an existing issue for this?
Current behavior
Real issue in my code, explanation, workaround and root cause created with the help of an IA.
Since @suites/unit@3.1.0 and @suites/doubles.jest@3.1.0, projects compiling with skipLibCheck: false fail with TS2300 Duplicate identifier 'Stub' and TS2300 Duplicate identifier 'Mocked'. @suites/unit@3.0.1 works fine.
The cause is that @suites/unit 3.1.0 now exports Stub and Mocked directly from its barrel, while @suites/doubles.jest 3.1.0 still augments the @suites/unit module with the same identifiers via declare module '@suites/unit'. The same names are therefore declared twice in the same module.
Root cause
node_modules/@suites/unit/dist/esm/index.d.ts in 3.1.0:
/// <reference types="@suites/doubles.jest/unit" />
export { TestBed } from './testbed.js';
export type { UnitTestBed, TestBedBuilder, Mocked, SolitaryTestBedBuilder, SociableTestBedBuilder, } from './types.js';
export type { UnitReference, MockOverride } from '@suites/core.unit';
export type { Stub } from '@suites/types.doubles';
node_modules/@suites/doubles.jest/unit.d.ts in 3.1.0 (kept from 3.0.x):
declare module '@suites/unit' {
export type Stub<TArgs extends any[] = any[]> = JestStub<TArgs>;
export type Mocked<T> = JestMocked<T>;
// ...
}
Stub and Mocked are now declared twice in the @suites/unit module — once via direct export, once via module augmentation — which TypeScript reports as a duplicate identifier when lib-check is on.
In 3.0.1, @suites/unit/index.d.ts did not export Stub/Mocked; the only source was the augmentation in @suites/doubles.jest. No conflict.
Suggested fix
Either:
- (a) drop the direct exports of
Stub and Mocked from @suites/unit/index.d.ts (revert to the 3.0.x layout where adapters own these types via augmentation), or
- (b) drop the
declare module '@suites/unit' { export type Stub … } block from each adapter (@suites/doubles.jest, @suites/doubles.sinon, @suites/doubles.vitest, …) and let @suites/unit own them.
Option (b) is probably preferred since Mocked is already exported from @suites/unit/types.js directly.
Workaround
Pin to 3.0.1 with a ~ range in package.json:
"@suites/unit": "~3.0.1",
"@suites/doubles.jest": "~3.0.1",
"@suites/di.nestjs": "~3.0.1"
Environment
- TypeScript: 5.9.3
- Node: 22.x
- OS: Linux
tsconfig: skipLibCheck: false, strict: false
Minimum reproduction code
https://github.com/ghislain-sn/suites-issue-types/
Steps to reproduce
mkdir suites-repro && cd suites-repro
npm init -y
npm i -D typescript @suites/unit@3.1.0 @suites/doubles.jest@3.1.0 @suites/di.nestjs@3.1.0 jest @types/jest
tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"strict": false,
"skipLibCheck": false
},
"include": ["src"]
}
src/index.ts:
import { TestBed } from '@suites/unit'
export { TestBed }
$ npx tsc --noEmit
node_modules/@suites/doubles.jest/unit.d.ts:21:15 - error TS2300: Duplicate identifier 'Stub'.
21 export type Stub<TArgs extends any[] = any[]> = JestStub<TArgs>;
~~~~
node_modules/@suites/unit/dist/esm/index.d.ts:5:15
5 export type { Stub } from '@suites/types.doubles';
~~~~
'Stub' was also declared here.
node_modules/@suites/doubles.jest/unit.d.ts:39:15 - error TS2300: Duplicate identifier 'Mocked'.
39 export type Mocked<T> = JestMocked<T>;
~~~~~~
node_modules/@suites/unit/dist/esm/index.d.ts:3:44
3 export type { UnitTestBed, TestBedBuilder, Mocked, SolitaryTestBedBuilder, SociableTestBedBuilder, } from './types.js';
~~~~~~
'Mocked' was also declared here.
node_modules/@suites/unit/dist/esm/index.d.ts:3:44 - error TS2300: Duplicate identifier 'Mocked'.
3 export type { UnitTestBed, TestBedBuilder, Mocked, SolitaryTestBedBuilder, SociableTestBedBuilder, } from './types.js';
~~~~~~
node_modules/@suites/doubles.jest/unit.d.ts:39:15
39 export type Mocked<T> = JestMocked<T>;
~~~~~~
'Mocked' was also declared here.
node_modules/@suites/unit/dist/esm/index.d.ts:5:15 - error TS2300: Duplicate identifier 'Stub'.
5 export type { Stub } from '@suites/types.doubles';
~~~~
node_modules/@suites/doubles.jest/unit.d.ts:21:15
21 export type Stub<TArgs extends any[] = any[]> = JestStub<TArgs>;
~~~~
'Stub' was also declared here.
Found 4 errors in 2 files.
Errors Files
2 node_modules/@suites/doubles.jest/unit.d.ts:21
2 node_modules/@suites/unit/dist/esm/index.d.ts:3
Expected behavior
npx tsc --noEmit exits 0 (as it does on 3.0.1).
Suites version
3.1.0
Node.js version
22.22.0
In which operating systems have you tested?
Other
No response
Is there an existing issue for this?
Current behavior
Since
@suites/unit@3.1.0and@suites/doubles.jest@3.1.0, projects compiling withskipLibCheck: falsefail withTS2300 Duplicate identifier 'Stub'andTS2300 Duplicate identifier 'Mocked'.@suites/unit@3.0.1works fine.The cause is that
@suites/unit3.1.0 now exportsStubandMockeddirectly from its barrel, while@suites/doubles.jest3.1.0 still augments the@suites/unitmodule with the same identifiers viadeclare module '@suites/unit'. The same names are therefore declared twice in the same module.Root cause
node_modules/@suites/unit/dist/esm/index.d.tsin 3.1.0:node_modules/@suites/doubles.jest/unit.d.tsin 3.1.0 (kept from 3.0.x):StubandMockedare now declared twice in the@suites/unitmodule — once via direct export, once via module augmentation — which TypeScript reports as a duplicate identifier when lib-check is on.In 3.0.1,
@suites/unit/index.d.tsdid not exportStub/Mocked; the only source was the augmentation in@suites/doubles.jest. No conflict.Suggested fix
Either:
StubandMockedfrom@suites/unit/index.d.ts(revert to the 3.0.x layout where adapters own these types via augmentation), ordeclare module '@suites/unit' { export type Stub … }block from each adapter (@suites/doubles.jest,@suites/doubles.sinon,@suites/doubles.vitest, …) and let@suites/unitown them.Option (b) is probably preferred since
Mockedis already exported from@suites/unit/types.jsdirectly.Workaround
Pin to 3.0.1 with a
~range inpackage.json:Environment
tsconfig:skipLibCheck: false,strict: falseMinimum reproduction code
https://github.com/ghislain-sn/suites-issue-types/
Steps to reproduce
tsconfig.json:{ "compilerOptions": { "target": "ES2022", "module": "NodeNext", "moduleResolution": "NodeNext", "strict": false, "skipLibCheck": false }, "include": ["src"] }src/index.ts:Expected behavior
npx tsc --noEmit exits 0 (as it does on 3.0.1).
Suites version
3.1.0
Node.js version
22.22.0
In which operating systems have you tested?
Other
No response