Skip to content

Commit

Permalink
Merge branch 'main' into dependabot/npm_and_yarn/ua-parser-js-0.7.33
Browse files Browse the repository at this point in the history
  • Loading branch information
Roaders authored May 7, 2024
2 parents 03f4888 + 5950114 commit cbc4821
Show file tree
Hide file tree
Showing 15 changed files with 917 additions and 99 deletions.
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ module.exports = {
plugins: ['@typescript-eslint'],
ignorePatterns: ['dist/', 'reports/', 'docs/'],
rules: {
'prettier/prettier': ['error', prettierConfig],
'prettier/prettier': ['error', { trailingComma: 'all', tabWidth: 4, singleQuote: true, printWidth: 120 }],
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': 'off',
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,3 @@ jobs:
node-version: ${{ matrix.node-version }}
- run: npm ci
- run: npm run build-release
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v2
3 changes: 3 additions & 0 deletions main/helper/lookup-helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export function getLookup<T, C extends ConstructorFunction<T>, U extends LookupT
lookupType: U,
): FunctionCallLookup<T, C, U> {
switch (lookupType) {
case 'constructorFunction':
return mock.constructorCallLookup as FunctionCallLookup<T, C, U>;
case 'function':
return mock.functionCallLookup as FunctionCallLookup<T, C, U>;
case 'getter':
Expand All @@ -18,5 +20,6 @@ export function getLookup<T, C extends ConstructorFunction<T>, U extends LookupT
case 'staticSetter':
return mock.staticSetterCallLookup as FunctionCallLookup<T, C, U>;
}

throw new Error(`Unknown lookup type: ${lookupType}`);
}
56 changes: 47 additions & 9 deletions main/mock/contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,34 +38,55 @@ export type FunctionCallLookup<T, C extends ConstructorFunction<T>, U extends Lo
[P in FunctionName<T, C, U>]?: LookupParams<T, C, U, P>[];
};

export type LookupFunction<
T,
C extends ConstructorFunction<T>,
U extends LookupType,
K extends FunctionName<T, C, U>,
> = VerifierTarget<T, C, U>[K];

export type LookupParams<
T,
C extends ConstructorFunction<T>,
U extends LookupType,
K extends FunctionName<T, C, U>,
> = U extends FunctionTypes
? FunctionParams<VerifierTarget<T, C, U>[K]>
> = U extends 'constructorFunction'
? ConstructorParams<C>
: U extends FunctionTypes
? FunctionParams<LookupFunction<T, C, U, K>>
: U extends SetterTypes
? [VerifierTarget<T, C, U>[K]]
? [LookupFunction<T, C, U, K>]
: [];

export type FunctionParams<T> = T extends (...args: infer P) => any ? P : never;

export type ConstructorFunction<T> = (abstract new (...args: any[]) => T) | (new (...args: any[]) => T);

export type ConstructorParams<T extends ConstructorFunction<any>> = T extends abstract new (
...args: infer RAbstract
) => T
? RAbstract
: T extends new (...args: infer R) => any
? R
: never;

export type StaticLookupTypes = 'staticFunction' | 'staticGetter' | 'staticSetter';
export type InstanceLookupTypes = 'function' | 'getter' | 'setter';
export type SetterTypes = 'staticSetter' | 'setter';
export type GetterTypes = 'staticGetter' | 'getter';
export type FunctionTypes = 'staticFunction' | 'function';
export type LookupType = StaticLookupTypes | InstanceLookupTypes;
export type LookupType = StaticLookupTypes | InstanceLookupTypes | 'constructorFunction';

export type VerifierTarget<T, C extends ConstructorFunction<T>, U extends LookupType> = U extends StaticLookupTypes
export type VerifierTarget<T, C extends ConstructorFunction<T>, U extends LookupType> = U extends 'constructorFunction'
? { constructorFunction: C }
: U extends StaticLookupTypes
? U extends FunctionTypes
? FunctionsOnly<C>
: C
: U extends FunctionTypes
? FunctionsOnly<T>
: T;

export type FunctionName<T, C extends ConstructorFunction<T>, U extends LookupType> = keyof VerifierTarget<T, C, U>;

export interface IFunctionWithParametersVerification<
Expand Down Expand Up @@ -137,6 +158,7 @@ export interface IMocked<T, C extends ConstructorFunction<T> = never> {
*/
mockConstructor: C;

constructorCallLookup: FunctionCallLookup<T, C, 'constructorFunction'>;
functionCallLookup: FunctionCallLookup<T, C, 'function'>;
setterCallLookup: FunctionCallLookup<T, C, 'setter'>;
getterCallLookup: FunctionCallLookup<T, C, 'getter'>;
Expand All @@ -160,6 +182,8 @@ export interface IMocked<T, C extends ConstructorFunction<T> = never> {
*/
setup(...operators: OperatorFunction<T, C>[]): IMocked<T, C>;

setupConstructor(): IFunctionWithParametersVerification<ConstructorParams<C>, T, 'constructorFunction', C>;

/**
* Sets up a single function and returns a function verifier to verify calls made and parameters passed.
*
Expand All @@ -176,7 +200,10 @@ export interface IMocked<T, C extends ConstructorFunction<T> = never> {
* @param propertyname
* @param value
*/
setupProperty<K extends keyof T>(propertyname: K, value?: T[K]): IFunctionVerifier<T, 'getter', C>;
setupProperty<K extends keyof T>(
propertyname: K,
value?: T[K],
): { getter: IFunctionVerifier<T, 'getter', C>; setter: IFunctionVerifier<T, 'setter', C> };
/**
* Defines a single property and allows getters and setters to be defined.
* Returns a function verifier to verify get and set operations
Expand All @@ -189,7 +216,7 @@ export interface IMocked<T, C extends ConstructorFunction<T> = never> {
propertyname: K,
getter?: () => T[K],
setter?: (value: T[K]) => void,
): IFunctionVerifier<T, 'getter', C>;
): { getter: IFunctionVerifier<T, 'getter', C>; setter: IFunctionVerifier<T, 'setter', C> };

/**
* Sets up a single static function and returns a function verifier to verify calls made and parameters passed.
Expand All @@ -207,7 +234,10 @@ export interface IMocked<T, C extends ConstructorFunction<T> = never> {
* @param propertyname
* @param value
*/
setupStaticProperty<K extends keyof C>(propertyname: K, value?: C[K]): IFunctionVerifier<T, 'staticGetter', C>;
setupStaticProperty<K extends keyof C>(
propertyname: K,
value?: C[K],
): { getter: IFunctionVerifier<T, 'staticGetter', C>; setter: IFunctionVerifier<T, 'staticSetter', C> };
/**
* Defines a single static property and allows getters and setters to be defined.
* Returns a function verifier to verify get and set operations
Expand All @@ -220,7 +250,15 @@ export interface IMocked<T, C extends ConstructorFunction<T> = never> {
propertyname: K,
getter?: () => C[K],
setter?: (value: C[K]) => void,
): IFunctionVerifier<T, 'staticGetter', C>;
): { getter: IFunctionVerifier<T, 'staticGetter', C>; setter: IFunctionVerifier<T, 'staticSetter', C> };

/**
* Verifies calls to constructor.
* expect(myMock.withFunction("functionName")).wasNotCalled():
* expect(myMock.withFunction("functionName")).wasCalledOnce():
* expect(myMock.withFunction("functionName").withParameters("one", 2)).wasCalledOnce():
*/
withConstructor(): IFunctionWithParametersVerification<ConstructorParams<C>, T, 'constructorFunction', C>;

/**
* Verifies calls to a previously setup function.
Expand Down
24 changes: 17 additions & 7 deletions main/mock/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,23 @@ import { addMatchers } from './matchers';
import {
defineProperty,
defineStaticProperty,
setupConstructor,
setupFunction,
setupProperty,
setupStaticFunction,
setupStaticProperty,
} from './operators';
import { createFunctionParameterVerifier, createFunctionVerifier } from './verifiers';
import {
createConstructorParameterVerifier,
createFunctionParameterVerifier,
createFunctionVerifier,
} from './verifiers';

export class Mock {
public static create<T, C extends ConstructorFunction<T> = never>(): IMocked<T, C> {
addMatchers();
const mocked: IMocked<T, C> = {
constructorCallLookup: {},
functionCallLookup: {},
setterCallLookup: {},
getterCallLookup: {},
Expand All @@ -26,30 +32,33 @@ export class Mock {

mock: {} as T,

// eslint-disable-next-line @typescript-eslint/no-empty-function
mockConstructor: ((..._args: any[]) => {}) as any,
mockConstructor: class MockConstructor {} as C,

setup: (...operators: OperatorFunction<T, C>[]) => {
let operatorMocked = mocked;
operators.forEach((operator) => (operatorMocked = operator(mocked)));
return operatorMocked;
},

setupConstructor: () => {
setupConstructor<T, C>()(mocked);
return mocked.withConstructor();
},
setupFunction: <K extends keyof FunctionsOnly<T>>(functionName: K, mockFunction?: any) => {
setupFunction<T, C, K>(functionName, mockFunction)(mocked);
return mocked.withFunction(functionName);
},
setupProperty: <K extends keyof T>(propertyName: K, value?: T[K]) => {
setupProperty<T, C, K>(propertyName, value)(mocked);
return mocked.withGetter(propertyName);
return { getter: mocked.withGetter(propertyName), setter: mocked.withSetter(propertyName) };
},
defineProperty: <K extends keyof T>(
propertyName: K,
getter?: () => T[K],
setter?: (value: T[K]) => void,
) => {
defineProperty<T, C, K>(propertyName, getter, setter)(mocked);
return mocked.withGetter(propertyName);
return { getter: mocked.withGetter(propertyName), setter: mocked.withSetter(propertyName) };
},

setupStaticFunction: <K extends keyof FunctionsOnly<C>>(functionName: K, mockFunction?: any) => {
Expand All @@ -58,17 +67,18 @@ export class Mock {
},
setupStaticProperty: <K extends keyof C>(propertyName: K, value?: C[K]) => {
setupStaticProperty<T, C, K>(propertyName, value)(mocked);
return mocked.withStaticGetter(propertyName);
return { getter: mocked.withStaticGetter(propertyName), setter: mocked.withStaticSetter(propertyName) };
},
defineStaticProperty: <K extends keyof C>(
propertyName: K,
getter?: () => C[K],
setter?: (value: C[K]) => void,
) => {
defineStaticProperty<T, C, K>(propertyName, getter, setter)(mocked);
return mocked.withStaticGetter(propertyName);
return { getter: mocked.withStaticGetter(propertyName), setter: mocked.withStaticSetter(propertyName) };
},

withConstructor: () => createConstructorParameterVerifier(mocked),
withFunction: <U extends keyof FunctionsOnly<T>>(functionName: U) =>
createFunctionParameterVerifier(mocked, 'function', functionName),
withSetter: <U extends keyof T>(functionName: U) =>
Expand Down
31 changes: 31 additions & 0 deletions main/mock/operators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import { getLookup } from '../helper';
import {
ConstructorFunction,
ConstructorParams,
FunctionCallLookup,
FunctionName,
FunctionTypes,
Expand All @@ -13,6 +14,27 @@ import {
SetterTypes,
} from './contracts';

/**
* Mocks a function on an existing Mock.
* Allows function call verification to be performed later in the test.
* You can optionally set a mock function implementation that will be called.
*
* @param functionName
* @param mockFunction
*/
export function setupConstructor<T, C extends ConstructorFunction<T>>(): OperatorFunction<T, C> {
return (mocked: IMocked<T, C>) => {
mocked.mockConstructor = class MockConstructor {
constructor(...args: ConstructorParams<C>) {
trackConstructorCall(mocked, args as any);
}
} as C;
mocked.constructorCallLookup['constructorFunction'] = [];

return mocked;
};
}

/**
* Mocks a function on an existing Mock.
* Allows function call verification to be performed later in the test.
Expand Down Expand Up @@ -242,6 +264,15 @@ function definePropertyImpl<
return mocked;
}

export function trackConstructorCall<T, C extends ConstructorFunction<T>>(
mock: IMocked<T, C>,
params: LookupParams<T, C, 'constructorFunction', 'constructorFunction'>,
) {
const lookup = getLookup(mock, 'constructorFunction');

trackCall(lookup, 'constructorFunction', params);
}

function trackFunctionCall<
T,
C extends ConstructorFunction<T>,
Expand Down
46 changes: 40 additions & 6 deletions main/mock/verifiers.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { getLookup, runningInJest } from '../helper';
import {
ConstructorFunction,
ConstructorParams,
FunctionName,
FunctionParams,
IFunctionVerifier,
Expand All @@ -10,12 +11,12 @@ import {
IMocked,
IParameterMatcher,
IStrictFunctionVerification,
LookupFunction,
LookupParams,
LookupType,
MatchFunction,
ParameterMatcher,
SetterTypes,
VerifierTarget,
} from './contracts';
import { isParameterMatcher, mapItemToString, toBe, toEqual } from './parameterMatchers';

Expand All @@ -24,7 +25,30 @@ export type VerifierParams<
C extends ConstructorFunction<T>,
U extends LookupType,
K extends FunctionName<T, C, U>,
> = U extends SetterTypes ? [VerifierTarget<T, C, U>[K]] : FunctionParams<VerifierTarget<T, C, U>[K]>;
> = U extends SetterTypes ? [LookupFunction<T, C, U, K>] : FunctionParams<LookupFunction<T, C, U, K>>;

export function createConstructorParameterVerifier<T, C extends ConstructorFunction<T>>(
mocked: IMocked<T, C>,
): IFunctionWithParametersVerification<ConstructorParams<C>, T, 'constructorFunction', C> {
return {
...createFunctionVerifier(mocked, 'constructorFunction', 'constructorFunction'),
/**
* withParameters and withParametersEqualTo should have signatures:
*
* withParameters: (...parameters: FunctionParameterMatchers<VerifierParams<T, C, U, K>>)
* withParametersEqualTo: (...parameters: FunctionParameterMatchers<VerifierParams<T, C, U, K>>)
*
* but this gives the error: [ts] A rest parameter must be of an array type. [2370]
* https://github.com/microsoft/TypeScript/issues/29919
*
* so we internally type the function as any. This does not affect the extrnal facing function type
*/
withParameters: ((...parameters: ParameterMatcher<any>[]) =>
verifyParameters(parameters, mocked, 'constructorFunction', 'constructorFunction', false)) as any,
withParametersEqualTo: ((...parameters: ParameterMatcher<any>[]) =>
verifyParameters(parameters, mocked, 'constructorFunction', 'constructorFunction', true)) as any,
};
}

export function createFunctionParameterVerifier<
T,
Expand Down Expand Up @@ -143,9 +167,16 @@ export function verifyFunctionCalled<T, C extends ConstructorFunction<T>, U exte
let errorMessageSetupFunction: string;
let errorMessageDescription: string;

const functionCalls: LookupParams<T, C, U, any>[] | undefined = getLookup(mock, type)[functionName];
const functionCallsLookup = getLookup(mock, type);
const functionCalls: LookupParams<T, C, U, any>[] | undefined = functionCallsLookup[functionName];

switch (type) {
case 'constructorFunction':
expectationMessage = `Expected constructor to be called`;
errorMessageSetupFunction = `Mock.setupConstructor()`;
errorMessageDescription = `Constructor`;
break;

case 'staticGetter':
expectationMessage = `Expected static property "${functionName}" getter to be called`;
errorMessageSetupFunction = `Mock.setupStaticProperty()`;
Expand Down Expand Up @@ -183,9 +214,12 @@ export function verifyFunctionCalled<T, C extends ConstructorFunction<T>, U exte
}

if (functionCalls === undefined) {
return createCustomMatcherFailResult(
`${errorMessageDescription} "${functionName}" has not been setup. Please setup using ${errorMessageSetupFunction} before verifying calls.`,
);
const message =
type === 'constructorFunction'
? `Constructor has not been setup. Please setup using ${errorMessageSetupFunction} before verifying calls.`
: `${errorMessageDescription} "${functionName}" has not been setup. Please setup using ${errorMessageSetupFunction} before verifying calls.`;

return createCustomMatcherFailResult(message);
}

const parameterMatchResults = functionCalls.map((params) => matchParameters(params, parameterMatchers));
Expand Down
2 changes: 1 addition & 1 deletion main/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"extends": "../tsconfig.json",
"exclude": [
"dist"
"../dist"
],
"files": [
"index.ts"
Expand Down
Loading

0 comments on commit cbc4821

Please sign in to comment.