Skip to content

Commit

Permalink
#91 add support for typeof variable
Browse files Browse the repository at this point in the history
  • Loading branch information
Pmyl committed Dec 8, 2019
1 parent f367cdf commit 317a778
Show file tree
Hide file tree
Showing 5 changed files with 111 additions and 10 deletions.
6 changes: 5 additions & 1 deletion src/transformer/descriptor/helper/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ export namespace TypescriptHelper {
export function GetDeclarationFromNode(node: ts.Node): ts.Declaration {
const typeChecker: ts.TypeChecker = TypeChecker();
const symbol: ts.Symbol = typeChecker.getSymbolAtLocation(node);
return GetDeclarationFromSymbol(symbol);
}

export function GetDeclarationFromSymbol(symbol: ts.Symbol): ts.Declaration {
const declaration: ts.Declaration = GetFirstValidDeclaration(symbol.declarations);

if (ts.isImportSpecifier(declaration)) {
Expand Down Expand Up @@ -60,6 +64,6 @@ export namespace TypescriptHelper {
function GetFirstValidDeclaration(declarations: ts.Declaration[]): ts.Declaration {
return declarations.find((declaration: ts.Declaration) => {
return !ts.isVariableDeclaration(declaration);
});
}) || declarations[0];
}
}
9 changes: 6 additions & 3 deletions src/transformer/descriptor/mock/mockProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ export function GetMockPropertiesFromSymbol(propertiesSymbol: ts.Symbol[], signa
const properties: ts.Declaration[] = propertiesSymbol.map((prop: ts.Symbol) => {
return prop.declarations[0];
});
const signaturesDeclarations: ts.Declaration[] = signatures.map((signature: ts.Signature) => {
return signature.declaration;
});

return GetMockPropertiesFromDeclarations(properties, signatures, scope);
return GetMockPropertiesFromDeclarations(properties, signaturesDeclarations, scope);
}

export function GetMockPropertiesFromDeclarations(list: ts.Declaration[], signatures: ReadonlyArray<ts.Signature>, scope: Scope): ts.CallExpression {
export function GetMockPropertiesFromDeclarations(list: ReadonlyArray<ts.Declaration>, signatures: ReadonlyArray<ts.Declaration>, scope: Scope): ts.CallExpression {
const propertiesFilter: ts.Declaration[] = list.filter((member: ts.PropertyDeclaration) => {
const hasModifiers: boolean = !!member.modifiers;

Expand Down Expand Up @@ -42,6 +45,6 @@ export function GetMockPropertiesFromDeclarations(list: ts.Declaration[], signat
},
);

const signaturesDescriptor: ts.Expression = signatures.length > 0 ? GetDescriptor(signatures[0].declaration, scope) : null;
const signaturesDescriptor: ts.Expression = signatures.length > 0 ? GetDescriptor(signatures[0], scope) : null;
return GetMockCall(variableDeclarations, accessorDeclaration, signaturesDescriptor);
}
32 changes: 27 additions & 5 deletions src/transformer/descriptor/properties/properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,38 @@ import { SignatureKind } from 'typescript';
import * as ts from 'typescript';
import { Scope } from '../../scope/scope';
import { TypeChecker } from '../../typeChecker/typeChecker';
import { GetMockPropertiesFromSymbol } from '../mock/mockProperties';
import { GetMockPropertiesFromDeclarations, GetMockPropertiesFromSymbol } from '../mock/mockProperties';

export function GetProperties(node: ts.Node, scope: Scope): ts.Expression {
const typeChecker: ts.TypeChecker = TypeChecker();
const type: ts.Type = typeChecker.getTypeAtLocation(node);
const symbols: ts.Symbol[] = typeChecker.getPropertiesOfType(type);

const signatures: Array<ts.Signature> = [];
Array.prototype.push.apply(signatures, typeChecker.getSignaturesOfType(type, SignatureKind.Call));
Array.prototype.push.apply(signatures, typeChecker.getSignaturesOfType(type, SignatureKind.Construct));
if (!symbols.length) {
return GetPropertiesFromMembers(node as ts.TypeLiteralNode, scope);
} else {
const signatures: Array<ts.Signature> = [];

return GetMockPropertiesFromSymbol(symbols, signatures, scope);
Array.prototype.push.apply(signatures, typeChecker.getSignaturesOfType(type, SignatureKind.Call));
Array.prototype.push.apply(signatures, typeChecker.getSignaturesOfType(type, SignatureKind.Construct));

return GetMockPropertiesFromSymbol(symbols, signatures, scope);
}
}

export function GetPropertiesFromMembers(node: ts.TypeLiteralNode, scope: Scope): ts.Expression {
const members: ts.NodeArray<ts.NamedDeclaration> = node.members;
const signatures: Array<ts.Declaration> = [];
const properties: Array<ts.Declaration> = [];

// tslint:disable-next-line
for (let i: number = 0; i < members.length; i++) {
if (members[i].kind === ts.SyntaxKind.CallSignature || members[i].kind === ts.SyntaxKind.ConstructSignature) {
signatures.push(members[i]);
} else if (members[i].kind === ts.SyntaxKind.PropertyDeclaration || members[i].kind === ts.SyntaxKind.PropertySignature || members[i].kind === ts.SyntaxKind.MethodSignature) {
properties.push(members[i]);
}
}

return GetMockPropertiesFromDeclarations(properties, signatures, scope);
}
20 changes: 19 additions & 1 deletion src/transformer/descriptor/typeQuery/typeQuery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,28 @@ import { TypescriptCreator } from '../../helper/creator';
import { TransformerLogger } from '../../logger/transformerLogger';
import { GetMockFactoryCallTypeofEnum } from '../../mockFactoryCall/mockFactoryCall';
import { Scope } from '../../scope/scope';
import { TypeChecker } from '../../typeChecker/typeChecker';
import { GetDescriptor } from '../descriptor';
import { TypescriptHelper } from '../helper/helper';
import { GetMethodDeclarationDescriptor } from '../method/methodDeclaration';
import { GetNullDescriptor } from '../null/null';
import { GetTypeReferenceDescriptor } from '../typeReference/typeReference';

export function GetTypeQueryDescriptor(node: ts.TypeQueryNode, scope: Scope): ts.Expression {
const declaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode(node.exprName);
const typeChecker: ts.TypeChecker = TypeChecker();
/*
TODO: Find different workaround without casting to any
Cast to any is been done because getSymbolAtLocation doesn't work when the node is an inferred identifier of a type query of a type query
Use case is:
```
const myVar = MyEnum;
createMock<typeof myVar>();
```
here `typeof myVar` is inferred `typeof MyEnum` and the `MyEnum` identifier doesn't play well with getSymbolAtLocation and it returns undefined.
*/
// tslint:disable-next-line no-any
const symbol: ts.Symbol = typeChecker.getSymbolAtLocation(node.exprName) || (node.exprName as any).symbol;
const declaration: ts.Declaration = TypescriptHelper.GetDeclarationFromSymbol(symbol);

switch (declaration.kind) {
case ts.SyntaxKind.ClassDeclaration:
Expand All @@ -26,6 +41,9 @@ export function GetTypeQueryDescriptor(node: ts.TypeQueryNode, scope: Scope): ts
return GetMockFactoryCallTypeofEnum(declaration as ts.EnumDeclaration);
case ts.SyntaxKind.FunctionDeclaration:
return GetMethodDeclarationDescriptor(declaration as ts.FunctionDeclaration, scope);
case ts.SyntaxKind.VariableDeclaration:
const typeNode: ts.TypeNode = (declaration as ts.VariableDeclaration).type || typeChecker.typeToTypeNode(typeChecker.getTypeFromTypeNode(node));
return GetDescriptor(typeNode, scope);
default:
TransformerLogger().typeNotSupported(`TypeQuery of ${ts.SyntaxKind[declaration.kind]}`);
return GetNullDescriptor();
Expand Down
54 changes: 54 additions & 0 deletions test/transformer/descriptor/typeQuery/typeQuery.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { createMock } from 'ts-auto-mock';
import { MyEnum } from '../../../playground/enums';
import { ImportInterface } from '../utils/interfaces/importInterface';
import {
ExportedClass,
ExportedDeclaredClass,
Expand Down Expand Up @@ -98,4 +100,56 @@ describe('typeQuery', () => {
expect(enumMock.C).toEqual('MaybeC');
});
});

describe('for variable', () => {
it('should create the imported interface mock from the type of a variable', () => {
let aVariable: ImportInterface;

const mock: typeof aVariable = createMock<typeof aVariable>();

expect(mock.a.b).toEqual('');
});

it('should create the imported typeof enum mock from the type of a variable', () => {
let aVariable: WrapExportedEnum;

const mock: typeof aVariable = createMock<typeof aVariable>();

expect(mock.A).toEqual(0);
});

describe('inferred type', () => {
it('should work for inferred object', () => {
const aVariable = { prop: 'asd' };

const mock: typeof aVariable = createMock<typeof aVariable>();

expect(mock.prop).toEqual('');
});

it('should work for enum', () => {
const aVariable = MyEnum;

const mock: typeof aVariable = createMock<typeof aVariable>();

expect(mock.A).toEqual(0);
});

it('should work for function call', () => {
function test(value) {
if(value) {
return { prop: 'asd' };
}

return { second: 7 };
}

const aVariable = test(true);

const mock: typeof aVariable = createMock<typeof aVariable>();

expect(mock.prop).toEqual('');
});
});
});
});

0 comments on commit 317a778

Please sign in to comment.