Skip to content

Commit

Permalink
feat: Make call to actual toJSON optional (#276)
Browse files Browse the repository at this point in the history
Resolves #268

* 🔧 Remove trailing semicolons
* ♻️  make actual toJson call optional
* 🚨 Remove more trailing semicolons
* 🔧 Streamline ts config
  • Loading branch information
eratio08 committed Sep 29, 2023
1 parent 2fd3748 commit 5294833
Show file tree
Hide file tree
Showing 12 changed files with 824 additions and 802 deletions.
3 changes: 2 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"printWidth": 140,
"tabWidth": 4,
"singleQuote": true
"singleQuote": true,
"semi": false
}
14 changes: 7 additions & 7 deletions example/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { mockDeep, DeepMockProxy } from 'vitest-mock-extended';
import { mockDeep, DeepMockProxy } from 'vitest-mock-extended'

type thing = {
property: string;
};
property: string
}

export type MockContext = {
prisma: DeepMockProxy<thing>;
};
prisma: DeepMockProxy<thing>
}

export const createMockContext = (): MockContext => {
return {
prisma: mockDeep<thing>(),
};
};
}
}
28 changes: 14 additions & 14 deletions example/src/mock-example.spec.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import { describe, expect, it } from 'vitest';
import { mock, mockDeep } from 'vitest-mock-extended';
import { describe, expect, it } from 'vitest'
import { mock, mockDeep } from 'vitest-mock-extended'

describe('Use mock example', () => {
type SomeType = { fieldC: string };
type SomeType = { fieldC: string }
interface SomeInterface {
fieldA: string;
fieldB: SomeType;
fieldA: string
fieldB: SomeType
}

it('should mock interfaces and types', () => {
const mockedInterface = mock<SomeInterface>({ fieldA: 'valueA', fieldB: { fieldC: 'valueC' } });
const mockedInterface = mock<SomeInterface>({ fieldA: 'valueA', fieldB: { fieldC: 'valueC' } })

expect(mockedInterface.fieldA).toBe('valueA');
expect(mockedInterface.fieldB).toContain({ fieldC: 'valueC' });
});
expect(mockedInterface.fieldA).toBe('valueA')
expect(mockedInterface.fieldB).toContain({ fieldC: 'valueC' })
})

it('should mock returned object', () => {
const mockedInterface = mockDeep<SomeInterface>({ fieldA: 'valueA' });
const mockedInterface = mockDeep<SomeInterface>({ fieldA: 'valueA' })

expect(mockedInterface.fieldA).toBe('valueA');
expect(mockedInterface.fieldB.fieldC).not.toBeNull(); // returns spy function
});
});
expect(mockedInterface.fieldA).toBe('valueA')
expect(mockedInterface.fieldB.fieldC).not.toBeNull() // returns spy function
})
})
2 changes: 1 addition & 1 deletion example/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "NodeNext",
"moduleResolution": "Bundler",
"declaration": true,
"lib": ["ES2022"],
"strict": true,
Expand Down
56 changes: 28 additions & 28 deletions src/CalledWithFn.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { CalledWithMock } from './Mock';
import { Matcher, MatchersOrLiterals } from './Matchers';
import { vi, Mock } from 'vitest';
import { CalledWithMock } from './Mock'
import { Matcher, MatchersOrLiterals } from './Matchers'
import { vi, Mock } from 'vitest'

interface CalledWithStackItem<T, Y extends any[]> {
args: MatchersOrLiterals<Y>;
calledWithFn: Mock<Y, T>;
args: MatchersOrLiterals<Y>
calledWithFn: Mock<Y, T>
}

interface VitestAsymmetricMatcher {
asymmetricMatch(...args: any[]): boolean;
asymmetricMatch(...args: any[]): boolean
}
function isVitestAsymmetricMatcher(obj: any): obj is VitestAsymmetricMatcher {
return !!obj && typeof obj === 'object' && 'asymmetricMatch' in obj && typeof obj.asymmetricMatch === 'function';
return !!obj && typeof obj === 'object' && 'asymmetricMatch' in obj && typeof obj.asymmetricMatch === 'function'
}

const checkCalledWith = <T, Y extends any[]>(
Expand All @@ -22,50 +22,50 @@ const checkCalledWith = <T, Y extends any[]>(
const calledWithInstance = calledWithStack.find((instance) =>
instance.args.every((matcher, i) => {
if (matcher instanceof Matcher) {
return matcher.asymmetricMatch(actualArgs[i]);
return matcher.asymmetricMatch(actualArgs[i])
}

if (isVitestAsymmetricMatcher(matcher)) {
return matcher.asymmetricMatch(actualArgs[i]);
return matcher.asymmetricMatch(actualArgs[i])
}

return actualArgs[i] === matcher;
return actualArgs[i] === matcher
}),
);
)

// @ts-ignore cannot return undefined, but this will fail the test if there is an expectation which is what we want

Check warning on line 36 in src/CalledWithFn.ts

View workflow job for this annotation

GitHub Actions / Run Unit-Tests

Do not use "@ts-ignore" because it alters compilation errors
return calledWithInstance
? calledWithInstance.calledWithFn(...actualArgs)
: fallbackMockImplementation && fallbackMockImplementation(...actualArgs);
};
: fallbackMockImplementation && fallbackMockImplementation(...actualArgs)
}

type FallbackImplementation<Y extends any[], T> = (...args: Y) => T;
type CalledWithFnArgs<Y extends any[], T> = { fallbackMockImplementation?: FallbackImplementation<Y, T> };
type FallbackImplementation<Y extends any[], T> = (...args: Y) => T
type CalledWithFnArgs<Y extends any[], T> = { fallbackMockImplementation?: FallbackImplementation<Y, T> }

const calledWithFn = <T, Y extends any[]>({ fallbackMockImplementation }: CalledWithFnArgs<Y, T> = {}): CalledWithMock<T, Y> => {
const fn: Mock<Y, T> = fallbackMockImplementation ? vi.fn(fallbackMockImplementation) : vi.fn();
let calledWithStack: CalledWithStackItem<T, Y>[] = [];
const fn: Mock<Y, T> = fallbackMockImplementation ? vi.fn(fallbackMockImplementation) : vi.fn()
let calledWithStack: CalledWithStackItem<T, Y>[] = []

(fn as CalledWithMock<T, Y>).calledWith = (...args) => {
;(fn as CalledWithMock<T, Y>).calledWith = (...args) => {
// We create new function to delegate any interactions (mockReturnValue etc.) to for this set of args.
// If that set of args is matched, we just call that vi.fn() for the result.
const calledWithFn: Mock<Y, T> = fallbackMockImplementation ? vi.fn(fallbackMockImplementation) : vi.fn();
const mockImplementation = fn.getMockImplementation();
const calledWithFn: Mock<Y, T> = fallbackMockImplementation ? vi.fn(fallbackMockImplementation) : vi.fn()
const mockImplementation = fn.getMockImplementation()
if (
!mockImplementation ||
fn.getMockImplementation()?.name === 'implementation' ||
mockImplementation === fallbackMockImplementation
) {
// Our original function gets a mock implementation which handles the matching
fn.mockImplementation((...args: Y) => checkCalledWith(calledWithStack, args, fallbackMockImplementation));
calledWithStack = [];
fn.mockImplementation((...args: Y) => checkCalledWith(calledWithStack, args, fallbackMockImplementation))
calledWithStack = []
}
calledWithStack.unshift({ args, calledWithFn });
calledWithStack.unshift({ args, calledWithFn })

return calledWithFn;
};
return calledWithFn
}

return fn as CalledWithMock<T, Y>;
};
return fn as CalledWithMock<T, Y>
}

export { calledWithFn };
export { calledWithFn }
Loading

0 comments on commit 5294833

Please sign in to comment.