Skip to content

Commit

Permalink
feat(indexedAccess): add indexed access support for mocks (#119)
Browse files Browse the repository at this point in the history
* feat(indexedAccess): add indexed access support for mocks

* update documentation
  • Loading branch information
Pmyl authored Dec 16, 2019
1 parent 6702f81 commit a3e9841
Show file tree
Hide file tree
Showing 6 changed files with 122 additions and 13 deletions.
11 changes: 11 additions & 0 deletions docs/DETAILS.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,14 @@ function AFunction(): number;
const mockFunction = createMock<typeof AFunction>();
mockFunction() // 0
```
## IndexedAccessType
```ts
class Class {
a: string
}
type KeyOf = {[key in keyof Class]: Class[key]};
const mock = createMock<KeyOf>();
mock.a // ''
```
13 changes: 0 additions & 13 deletions docs/NOT_SUPPORTED.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,5 @@
# Not supported types

## IndexedAccessType

[bug](https://github.com/uittorio/ts-auto-mock/issues/3)
```ts
class Class {
a: string
}

type KeyOf = {[key in keyof Class]: Class[key]};
const mock = createMock<KeyOf>();
mock.a // will be null
```

## ConditionalType

```ts
Expand Down
3 changes: 3 additions & 0 deletions src/transformer/descriptor/descriptor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { GetExpressionWithTypeArgumentsDescriptor } from './expression/expressio
import { GetIdentifierDescriptor } from './identifier/identifier';
import { GetImportDescriptor } from './import/import';
import { GetImportEqualsDescriptor } from './import/importEquals';
import { GetIndexedAccessTypeDescriptor } from './indexedAccess/indexedAccess';
import { GetInterfaceDeclarationDescriptor } from './interface/interfaceDeclaration';
import { GetIntersectionDescriptor } from './intersection/intersection';
import { GetLiteralDescriptor } from './literal/literal';
Expand Down Expand Up @@ -109,6 +110,8 @@ export function GetDescriptor(node: ts.Node, scope: Scope): ts.Expression {
return GetLiteralDescriptor(node as ts.LiteralTypeNode, scope);
case ts.SyntaxKind.ObjectLiteralExpression:
return GetObjectLiteralDescriptor(node as ts.ObjectLiteralExpression, scope);
case ts.SyntaxKind.IndexedAccessType:
return GetIndexedAccessTypeDescriptor(node as ts.IndexedAccessTypeNode, scope);
case ts.SyntaxKind.BooleanKeyword:
return GetBooleanDescriptor();
case ts.SyntaxKind.ObjectKeyword:
Expand Down
45 changes: 45 additions & 0 deletions src/transformer/descriptor/indexedAccess/indexedAccess.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import * as ts from 'typescript';
import { TransformerLogger } from '../../logger/transformerLogger';
import { Scope } from '../../scope/scope';
import { TypeChecker } from '../../typeChecker/typeChecker';
import { GetDescriptor } from '../descriptor';
import { TypescriptHelper } from '../helper/helper';
import { GetNullDescriptor } from '../null/null';
import { PropertySignatureCache } from '../property/cache';

export function GetIndexedAccessTypeDescriptor(node: ts.IndexedAccessTypeNode, scope: Scope): ts.Expression {
const typeChecker: ts.TypeChecker = TypeChecker();
let propertyName: string | null = null;

switch (node.indexType.kind) {
case ts.SyntaxKind.TypeReference:
const declaration: ts.Declaration = TypescriptHelper.GetDeclarationFromNode((node.indexType as ts.TypeReferenceNode).typeName);

switch (declaration.kind) {
case ts.SyntaxKind.TypeParameter:
const propertyNameIdentifier: ts.PropertyName = PropertySignatureCache.instance.get();
propertyName = (propertyNameIdentifier as ts.Identifier).escapedText as string;
break;
case ts.SyntaxKind.TypeAliasDeclaration:
propertyName = (((declaration as ts.TypeAliasDeclaration).type as ts.LiteralTypeNode).literal as ts.StringLiteral).text;
break;
default:
TransformerLogger().typeNotSupported('IndexedAccess of TypeReference of ' + ts.SyntaxKind[declaration.kind]);
break;
}
break;
case ts.SyntaxKind.LiteralType:
propertyName = ((node.indexType as ts.LiteralTypeNode).literal as ts.StringLiteral).text;
break;
default:
TransformerLogger().typeNotSupported('IndexedAccess of ' + ts.SyntaxKind[node.indexType.kind]);
break;
}

if (propertyName !== null) {
const propertySymbol: ts.Symbol = typeChecker.getPropertyOfType(typeChecker.getTypeFromTypeNode(node.objectType), propertyName);
return GetDescriptor(TypescriptHelper.GetDeclarationFromSymbol(propertySymbol), scope);
}

return GetNullDescriptor();
}
62 changes: 62 additions & 0 deletions test/transformer/descriptor/indexedAccess/indexedAccess.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { createMock } from 'ts-auto-mock';
import { Interface } from '../utils/interfaces/basic';
import { LiteralA } from '../utils/types/literals';

describe('indexedAccess', () => {
interface A {
a: string;
b: number;
}

it('should work with key in keyof', () => {
type AType = {[key in keyof A]: A[key]};
const mock: AType = createMock<AType>();
expect(mock.a).toEqual('');
expect(mock.b).toEqual(0);
});

it('should work with key in literal', () => {
type AType = {[key in 'a']: A[key]};
const mock: AType = createMock<AType>();
expect(mock.a).toEqual('');
expect((mock as any).b).toBeUndefined();
});

it('should work with key in keyof with literal index', () => {
type AType = {[key in keyof A]: A['b']};
const mock: AType = createMock<AType>();
expect(mock.a).toEqual(0);
expect(mock.b).toEqual(0);
});

it('should work with key in keyof with imported literal index', () => {
type AType = {[key in keyof A]: A[LiteralA]};
const mock: AType = createMock<AType>();
expect(mock.a).toEqual('');
expect(mock.b).toEqual('');
});

it('should work with key in keyof with imported interface and literal index', () => {
type AType = {[key in keyof Interface]: Interface[LiteralA]};
const mock: AType = createMock<AType>();
expect(mock.a).toEqual('');
expect(mock.b).toEqual('');
});

it('should work with key in keyof with interface having complex properties', () => {
interface InterfaceWithComplex {
a: A;
b: string;
c: InterfaceWithComplex;
}

type InterfaceType = {[key in keyof InterfaceWithComplex]: InterfaceWithComplex[key]};
const mock: InterfaceType = createMock<InterfaceType>();
expect(mock.a.a).toEqual('');
expect(mock.a.b).toEqual(0);
expect(mock.b).toEqual('');
expect(mock.c.a.a).toEqual('');
expect(mock.c.a.b).toEqual(0);
expect(mock.c.b).toEqual('');
});
});
1 change: 1 addition & 0 deletions test/transformer/descriptor/utils/types/literals.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type LiteralA = 'a';

0 comments on commit a3e9841

Please sign in to comment.