Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Strict null checks #49

Merged
merged 6 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@typestrong/ts-mockito",
"version": "2.6.8",
"version": "2.7.1",
"description": "Mocking library for TypeScript",
"main": "lib/ts-mockito.js",
"typings": "lib/ts-mockito",
Expand Down
6 changes: 3 additions & 3 deletions src/MethodStubCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export class MethodStubCollection {
return matchingGroup ? matchingGroup.getGroupIndex() : -1;
}

public getFirstMatchingFromGroupAndRemoveIfNotLast(groupIndex: number, args: any[]): MethodStub {
public getFirstMatchingFromGroupAndRemoveIfNotLast(groupIndex: number, args: any[]): MethodStub | null {
const result = this.getFirstMatchingFromGroup(groupIndex, args);
this.removeIfNotLast(groupIndex, args);
return result;
Expand All @@ -30,8 +30,8 @@ export class MethodStubCollection {
}
}

private getFirstMatchingFromGroup(groupIndex: number, args: any[]): MethodStub {
return this.items.find((item: MethodStub) => item.getGroupIndex() === groupIndex && item.isApplicable(args));
private getFirstMatchingFromGroup(groupIndex: number, args: any[]): MethodStub | null {
return this.items.find((item: MethodStub) => item.getGroupIndex() === groupIndex && item.isApplicable(args)) ?? null;
}

private getFirstMatchingIndexFromGroup(groupIndex: number, args: any[]): number {
Expand Down
2 changes: 1 addition & 1 deletion src/MethodStubSetter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export class MethodStubSetter<T, ResolveType = void, RejectType = Error> {
return this;
}

public thenResolve(...rest: ResolveType[]): this {
public thenResolve(...rest: (ResolveType | undefined)[]): this {
this.convertToPropertyIfIsNotAFunction();
// Resolves undefined if no resolve values are given.
if (rest.length === 0) {
Expand Down
10 changes: 5 additions & 5 deletions src/Mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,11 +125,11 @@ export class Mocker {
return;
}
const descriptor = Object.getOwnPropertyDescriptor(obj, name);
if (descriptor.get) {
if (descriptor?.get) {
this.createPropertyStub(name);
this.createInstancePropertyDescriptorListener(name, descriptor, obj);
this.createInstanceActionListener(name, obj);
} else if (typeof descriptor.value === "function") {
} else if (typeof descriptor?.value === "function") {
this.createMethodStub(name);
this.createInstanceActionListener(name, obj);
} else {
Expand Down Expand Up @@ -164,8 +164,8 @@ export class Mocker {
const action: MethodAction = new MethodAction(key, args);
this.methodActions.push(action);
const methodStub = this.getMethodStub(key, args);
methodStub.execute(args);
return methodStub.getValue();
methodStub?.execute(args);
return methodStub?.getValue();
};
}

Expand Down Expand Up @@ -225,7 +225,7 @@ export class Mocker {
};
}

private getMethodStub(key: string, args: any[]): MethodStub {
private getMethodStub(key: string, args: any[]): MethodStub | null {
const methodStub: MethodStubCollection = this.methodStubCollections[key];
if (methodStub && methodStub.hasMatchingInAnyGroup(args)) {
const groupIndex = methodStub.getLastMatchingGroupIndex(args);
Expand Down
4 changes: 2 additions & 2 deletions src/Spy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export class Spy extends Mocker {
public reset(): void {
_.forEach(this.realMethods, (method, key) => {
if (method.instance) {
Object.defineProperty(this.instance, key, method.descriptor);
Object.defineProperty(this.instance, key, method.descriptor ?? {});
} else {
delete this.instance[key];
}
Expand All @@ -31,7 +31,7 @@ export class Spy extends Mocker {
const realMethod = this.realMethods[key];

if (realMethod) {
const method = realMethod.descriptor.get || realMethod.descriptor.value;
const method = realMethod.descriptor?.get || realMethod.descriptor?.value;
return new CallThroughMethodStub(this.instance, method);
}

Expand Down
2 changes: 1 addition & 1 deletion src/matcher/type/DeepEqualMatcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export class DeepEqualMatcher<T> extends Matcher {

public match(value: any): boolean {
return _.isEqualWith(this.expectedValue, value,
(expected: any, actual: any): boolean => {
(expected: any, actual: any) => {
if (expected instanceof Matcher) {
return expected.match(actual);
}
Expand Down
2 changes: 1 addition & 1 deletion src/spy/RealMethod.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export class RealMethod {
constructor(public descriptor: PropertyDescriptor,
constructor(public descriptor: PropertyDescriptor | undefined,
public instance: boolean) {}
}
1 change: 1 addition & 0 deletions src/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"target": "es5",
"removeComments": true,
"sourceMap": true,
"strictNullChecks": true,
"declaration": true,
"lib": [
"es5",
Expand Down
24 changes: 15 additions & 9 deletions src/utils/MockableFunctionsFinder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const methodTokenName = new Set([
const isFunctionNode = (node: _babel_types.Statement | FunctionNode): node is FunctionNode => methodTokenName.has(node.type);


function getAssignmentName(node: _babel_types.LVal) {
function getAssignmentName(node: _babel_types.LVal): string | null {
if (node.type === "Identifier")
return node.name;

Expand All @@ -38,28 +38,30 @@ function getAssignmentName(node: _babel_types.LVal) {
return null;
}

function handleClassProp(node: _babel_types.ClassProperty): string {
if (node.value.type !== 'ArrowFunctionExpression' && node.value.type !== 'FunctionExpression') return null;
function handleClassProp(node: _babel_types.ClassProperty): string | null {
if (node.value?.type !== 'ArrowFunctionExpression' && node.value?.type !== 'FunctionExpression') return null;

if ('name' in node.key) return node.key.name;
if ('value' in node.key) return node.key.value.toString();
return null;
}

function handleExpression(node: _babel_types.Expression): string {
function handleExpression(node: _babel_types.Expression): string | null {
if ('expression' in node && typeof node.expression !== 'boolean') return handleExpression(node.expression);

if (node.type === 'AssignmentExpression') {
return getAssignmentName(node.left);
}

return null;
}

function handleVariable(node: _babel_types.VariableDeclaration): string[] {
return node.declarations.filter(n => {
if (n.init.type === 'ArrowFunctionExpression') return true;
if (n.init.type === 'FunctionExpression') return true;
if (n.init?.type === 'ArrowFunctionExpression') return true;
if (n.init?.type === 'FunctionExpression') return true;
return false;
}).map(n => getAssignmentName(n.id));
}).map(n => getAssignmentName(n.id)).filter(Boolean) as string[];
}

function extractFunctionNames(nodes: (_babel_types.Statement | FunctionNode)[]) {
Expand All @@ -79,15 +81,19 @@ function extractFunctionNames(nodes: (_babel_types.Statement | FunctionNode)[])
}

if (node.type === "ExpressionStatement") {
names = [handleExpression(node.expression), ...names];
const name = handleExpression(node.expression);
if (name) names.push(name);
}

if (node.type === "VariableDeclaration") {
names = [...handleVariable(node), ...names];
}

if (node.type === "ClassProperty") {
names = [handleClassProp(node), ...extractFunctionNames([node.value]), ...names];
const propName = handleClassProp(node);
const funcNames = node.value ? extractFunctionNames([node.value]) : [];
if (propName) names.push(propName);
names = [...funcNames, ...names];
}
});

Expand Down
10 changes: 5 additions & 5 deletions src/utils/ObjectPropertyCodeRetriever.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ export class ObjectPropertyCodeRetriever {
${props.map(prop => {
let result = '';
const descriptor = Object.getOwnPropertyDescriptor(object, prop);
if (descriptor.get) {
if (descriptor?.get) {
result += `
${descriptor.get.toString()}
${descriptor?.get.toString()}
`;
}
if (descriptor.set) {
if (descriptor?.set) {
result += `
${descriptor.set.toString()}
${descriptor?.set.toString()}
`;
}
if (!descriptor.get && !descriptor.set && typeof object[prop] === 'function') {
if (!descriptor?.get && !descriptor?.set && typeof object[prop] === 'function') {
const propName = prop === 'constructor' ? 'mock_constructor' : '';
const fnStr = String(object[prop]);
result += `
Expand Down
1 change: 1 addition & 0 deletions test/matcher/type/AnyOfClassMatcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ describe("AnyOfClassMatcher", () => {
describe("checking if null matches null", () => {
it("throws error", () => {
try {
// @ts-ignore force type for test purposes
anyOfClass(null);
fail("If you reach this statement, the test failed.");
} catch (e) {
Expand Down
4 changes: 2 additions & 2 deletions test/matcher/type/DeepEqualMatcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ describe("deepEqual", () => {
describe("using in verify statements", () => {
it("can be used for equality", () => {
class Foo {
public add = (str: string, num: number, obj: {a: string}): number => null;
public add = (str: string, num: number, obj: {a: string}): number | null => null;
}
const foo = mock(Foo);
instance(foo).add("1", 2, {a: "sampleValue"});
Expand All @@ -138,7 +138,7 @@ describe("deepEqual", () => {
describe('when given circular dependency', () => {
type Bar = { bar?: Bar; };
class Foo {
public something = (bar: Bar): number => null;
public something = (bar: Bar): number | null => null;
}

it('should reject gracefully', async () => {
Expand Down
2 changes: 1 addition & 1 deletion test/mocking.types.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,6 @@ class SampleGeneric<T> {
}

public getGenericTypedValue(): T {
return null;
return null as unknown as T;
}
}
4 changes: 2 additions & 2 deletions test/recording.multiple.behaviors.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ describe("recording multiple behaviors", () => {

// when
const firstCallResult = foo.convertNumberToString(sampleValue);
let error: Error;
let error: Error | null = null;
try {
foo.convertNumberToString(sampleValue);
} catch (e) {
Expand All @@ -129,7 +129,7 @@ describe("recording multiple behaviors", () => {

// then
expect(firstCallResult).toEqual(firstMatchingStubResult);
expect(error.message).toEqual(firstMatchingError.message);
expect(error?.message).toEqual(firstMatchingError.message);
expect(thirdCallResult).toEqual(secondMatchingStubResult);
expect(fourthCallResult).toEqual(secondMatchingStubResult);
expect(fifthCallResult).toEqual(secondMatchingStubResult);
Expand Down
4 changes: 2 additions & 2 deletions test/stubbing.method.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,15 @@ describe("mocking", () => {
when(mockedFoo.convertNumberToString(sampleValue)).thenThrow(sampleError);

// when
let error = null;
let error:Error | null = null;
try {
foo.convertNumberToString(sampleValue);
} catch (e) {
error = e;
}

// then
expect(error.message).toEqual("sampleError");
expect(error?.message).toEqual("sampleError");
});
});

Expand Down
4 changes: 2 additions & 2 deletions test/utils/Foo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,11 @@ export class Foo {
}

public sampleMethodWithOptionalArgument(a: number, b?: number): number {
return a + b;
return a + (b ?? 0);
}

public sampleMethodWithTwoOptionalArguments(a?: number, b?: number): number {
return a + b;
return (a ?? 0) + (b ?? 0);
}

public sampleMethodReturningPromise(value: string): Promise<string> {
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"removeComments": true,
"sourceMap": true,
"declaration": true,
"strictNullChecks": true,
"lib": [
"es5",
"es6",
Expand Down
Loading