UNPKG

@code-to-json/core

Version:
415 lines (355 loc) 18 kB
import { mapDict } from '@code-to-json/utils-ts'; import { expect } from 'chai'; import { describe, it } from 'mocha'; import SingleFileAcceptanceTestCase from './helpers/test-case'; describe('Class serialization tests', () => { it('class Vehicle { numWheels: number = 4; drive() { return "vroom";} }', async () => { const code = 'export class Vehicle { numWheels: number = 4; drive() { return "vroom";} }'; const t = new SingleFileAcceptanceTestCase(code); await t.run(); const file = t.sourceFile(); const fileSymbol = t.resolveReference(file.symbol); const classSymbol = t.resolveReference(fileSymbol.exports!.Vehicle); expect(classSymbol.text).to.eql('Vehicle'); expect(classSymbol.flags).to.eql(['Class'], 'Regarded as a class'); expect(classSymbol.modifiers).to.include('export'); const classSymbolType = t.resolveReference(classSymbol.symbolType); expect(classSymbolType.text).to.eql('Vehicle'); expect(classSymbolType.flags).to.deep.eq(['Object']); expect(!!classSymbolType.constructorSignatures).to.eq(false, 'no constructor signatures'); const classValueDeclarationType = t.resolveReference(classSymbol.valueDeclarationType); expect(classValueDeclarationType.text).to.eql('typeof Vehicle'); expect(classValueDeclarationType.flags).to.deep.eq(['Object']); expect(classValueDeclarationType.constructorSignatures!.length).to.eq( 1, '1 constructor signature', ); const classPropNames = Object.keys(classSymbolType.properties!); expect(classPropNames).to.deep.eq(['numWheels', 'drive']); const [constructorSig] = classValueDeclarationType.constructorSignatures!; expect(constructorSig.text).to.eq('(): Vehicle'); const instanceType = t.resolveReference(constructorSig.returnType); expect(instanceType.text).to.eq('Vehicle'); const instancePropNames = Object.keys(instanceType.properties!); expect(instancePropNames).to.deep.eq(['numWheels', 'drive']); const props = instancePropNames.map(p => t.resolveReference(instanceType.properties![p])); const [numWheelsSym, driveSym] = props; expect(numWheelsSym.flags).to.deep.eq(['Property']); expect(driveSym.flags).to.deep.eq(['Method', 'Transient']); expect(numWheelsSym.text).to.eq('numWheels'); expect(driveSym.text).to.eq('drive'); const [numWheelsType, driveType] = props.map(s => t.resolveReference(s.valueDeclarationType)); expect(numWheelsType.text).to.eql('number'); expect(driveType.text).to.eql('() => string'); expect(numWheelsType.flags).to.deep.eq(['Number']); expect(driveType.flags).to.deep.eq(['Object']); expect(driveType.objectFlags).to.includes('Anonymous'); t.cleanup(); }); it('abstract class Vehicle { numWheels: number = 4; abstract drive(): string; }', async () => { const code = 'export abstract class Vehicle { numWheels: number = 4; abstract drive(): string; }'; const t = new SingleFileAcceptanceTestCase(code); await t.run(); const file = t.sourceFile(); const fileSymbol = t.resolveReference(file.symbol); const classSymbol = t.resolveReference(fileSymbol.exports!.Vehicle); expect(classSymbol.text).to.eql('Vehicle'); expect(classSymbol.flags).to.eql(['Class'], 'Regarded as a class'); expect(classSymbol.modifiers).to.include('export'); expect(classSymbol.isAbstract).to.eql(true); const classSymbolType = t.resolveReference(classSymbol.symbolType); expect(classSymbolType.text).to.eql('Vehicle'); expect(classSymbolType.flags).to.deep.eq(['Object']); expect(!!classSymbolType.constructorSignatures).to.eq(false, 'no constructor signatures'); const classValueDeclarationType = t.resolveReference(classSymbol.valueDeclarationType); expect(classValueDeclarationType.text).to.eql('typeof Vehicle'); expect(classValueDeclarationType.flags).to.deep.eq(['Object']); expect(classValueDeclarationType.constructorSignatures!.length).to.eq( 1, '1 constructor signature', ); const instancePropNames = Object.keys(classSymbolType.properties!); expect(instancePropNames).to.deep.eq(['numWheels', 'drive']); const props = instancePropNames.map(p => t.resolveReference(classSymbolType.properties![p])); const [numWheelsSym, driveSym] = props; expect(numWheelsSym.flags).to.deep.eq(['Property']); expect(driveSym.flags).to.deep.eq(['Method']); expect(numWheelsSym.text).to.eq('numWheels'); expect(driveSym.text).to.eq('drive'); expect(driveSym.isAbstract).to.eq(true); t.cleanup(); }); it('class that inherits from nothing with implied constructor', async () => { const code = 'export class Vehicle { }'; const t = new SingleFileAcceptanceTestCase(code); await t.run(); const file = t.sourceFile(); const fileSymbol = t.resolveReference(file.symbol); const classSymbol = t.resolveReference(fileSymbol.exports!.Vehicle); expect(classSymbol.text).to.eql('Vehicle'); expect(classSymbol.flags).to.eql(['Class'], 'Regarded as a class'); expect(classSymbol.modifiers).to.include('export'); const classSymbolType = t.resolveReference(classSymbol.symbolType); expect(classSymbolType.text).to.eql('Vehicle'); expect(classSymbolType.flags).to.deep.eq(['Object']); expect(!!classSymbolType.constructorSignatures).to.eq(false, 'no constructor signatures'); const classValueDeclarationType = t.resolveReference(classSymbol.valueDeclarationType); expect(classValueDeclarationType.text).to.eql('typeof Vehicle'); expect(classValueDeclarationType.flags).to.deep.eq(['Object']); expect(classValueDeclarationType.constructorSignatures!.length).to.eq( 1, '1 constructor signature', ); const classPropNames = Object.keys(classValueDeclarationType.properties!); expect(classPropNames).to.deep.eq([]); const [constructorSig] = classValueDeclarationType.constructorSignatures!; expect(constructorSig.text).to.eq('(): Vehicle'); t.cleanup(); }); it('access modifier keyword on class method', async () => { const code = `export class Foo { protected bar() {} }`; const t = new SingleFileAcceptanceTestCase(code); await t.run(); const file = t.sourceFile(); const fileSymbol = t.resolveReference(file.symbol!); const fileExports = mapDict(fileSymbol.exports!, e => t.resolveReference(e)); expect(Object.keys(fileExports)).to.deep.eq(['Foo']); const classSymbol = fileExports.Foo!; const instanceType = t.resolveReference(classSymbol.symbolType); const instanceMembers = mapDict(instanceType.properties!, p => t.resolveReference(p)); const { bar } = instanceMembers; expect(bar!.modifiers).to.deep.include('protected'); t.cleanup(); }); it('access modifier keyword via comment', async () => { const code = `export class Foo { /** * @protected */ bar() {} }`; const t = new SingleFileAcceptanceTestCase(code, 'js'); await t.run(); const file = t.sourceFile(); const fileSymbol = t.resolveReference(file.symbol!); const fileExports = mapDict(fileSymbol.exports!, e => t.resolveReference(e)); expect(Object.keys(fileExports)).to.deep.eq(['Foo']); const classSymbol = fileExports.Foo!; const instanceType = t.resolveReference(classSymbol.symbolType); const instanceMembers = mapDict(instanceType.properties!, p => t.resolveReference(p)); const { bar } = instanceMembers; expect(bar!.modifiers).to.deep.include('protected'); t.cleanup(); }); it('inheriting a constructor from a base class', async () => { const code = `class Vehicle { public readonly abc = 'def' constructor(n: number) { setTimeout(() => console.log('hello'), n)} } export class Car extends Vehicle {}`; const t = new SingleFileAcceptanceTestCase(code); await t.run(); const file = t.sourceFile(); const fileSymbol = t.resolveReference(file.symbol); const classSymbol = t.resolveReference(fileSymbol.exports!.Car); expect(classSymbol.text).to.eql('Car'); expect(classSymbol.flags).to.eql(['Class'], 'Regarded as a class'); expect(classSymbol.modifiers).to.include('export'); const classSymbolType = t.resolveReference(classSymbol.symbolType); expect(classSymbolType.text).to.eql('Car'); expect(classSymbolType.flags).to.deep.eq(['Object']); expect(!!classSymbolType.constructorSignatures).to.eq(false, 'no constructor signatures'); const classValueDeclarationType = t.resolveReference(classSymbol.valueDeclarationType); expect(classValueDeclarationType.text).to.eql('typeof Car'); expect(classValueDeclarationType.flags).to.deep.eq(['Object']); expect(classValueDeclarationType.constructorSignatures!.length).to.eq( 1, '1 constructor signature', ); const instancePropNames = Object.keys(classSymbolType.properties!); expect(instancePropNames).to.deep.eq(['abc']); const staticPropNames = Object.keys(classValueDeclarationType.properties!); expect(staticPropNames).to.deep.eq([]); const [constructorSig] = classValueDeclarationType.constructorSignatures!; expect(constructorSig.text).to.eq('(n: number): Car'); t.cleanup(); }); it('heritage clause serialization', async () => { const code = `export interface HasVinNumber { vin: number; } export class Vehicle { public readonly abc = 'def' constructor(n: number) { setTimeout(() => console.log('hello'), n)} } export class Car extends Vehicle implements HasVinNumber { constructor() { super(); this.vin = 4; } }`; const t = new SingleFileAcceptanceTestCase(code); await t.run(); const file = t.sourceFile(); const fileSymbol = t.resolveReference(file.symbol); const classSymbol = t.resolveReference(fileSymbol.exports!.Car); const classSymbolType = t.resolveReference(classSymbol.symbolType); const baseTypes = classSymbolType.baseTypes!.map(bc => t.resolveReference(bc)); expect(baseTypes.map(bt => bt.text).join(', ')).to.eq('Vehicle'); const { heritageClauses } = classSymbol; expect(heritageClauses!.length).to.eq(2); expect(heritageClauses![0].kind).to.eq('extends'); expect(heritageClauses![0].types.map(typ => t.resolveReference(typ).text).join(', ')).to.eq( 'Vehicle', ); expect(heritageClauses![1].kind).to.eq('implements'); expect(heritageClauses![1].types.map(typ => t.resolveReference(typ).text).join(', ')).to.eq( 'HasVinNumber', ); t.cleanup(); }); it('inheriting multiple constructor signatures from a base class', async () => { const code = `class Vehicle { public readonly abc = 'def' constructor(n: number); constructor(n: number, coeff: number); constructor(n: number, coeff: number = 1) { setTimeout(() => console.log('hello'), n * coeff)} } export class Car extends Vehicle {}`; const t = new SingleFileAcceptanceTestCase(code); await t.run(); const file = t.sourceFile(); const fileSymbol = t.resolveReference(file.symbol); const classSymbol = t.resolveReference(fileSymbol.exports!.Car); expect(classSymbol.text).to.eql('Car'); expect(classSymbol.flags).to.eql(['Class'], 'Regarded as a class'); expect(classSymbol.modifiers).to.include('export'); const classSymbolType = t.resolveReference(classSymbol.symbolType); expect(classSymbolType.text).to.eql('Car'); expect(classSymbolType.flags).to.deep.eq(['Object']); expect(!!classSymbolType.constructorSignatures).to.eq(false, 'no constructor signatures'); const classValueDeclarationType = t.resolveReference(classSymbol.valueDeclarationType); expect(classValueDeclarationType.text).to.eql('typeof Car'); expect(classValueDeclarationType.flags).to.deep.eq(['Object']); expect(classValueDeclarationType.constructorSignatures!.length).to.eq( 2, '2 constructor signatures', ); const instancePropNames = Object.keys(classSymbolType.properties!); expect(instancePropNames).to.deep.eq(['abc']); const classPropNames = Object.keys(classValueDeclarationType.properties!); expect(classPropNames).to.deep.eq([]); const [constructorSig1, constructorSig2] = classValueDeclarationType.constructorSignatures!; expect(constructorSig1.text).to.eq('(n: number): Car'); expect(constructorSig2.text).to.eq('(n: number, coeff: number): Car'); t.cleanup(); }); it('class with properties, methods and static functions', async () => { const code = `export class SimpleClass { constructor(bar: string) { console.log(bar); } public foo: string = 'bar'; static hello(): string { return this.planet; } static planet = "world"; }`; const t = new SingleFileAcceptanceTestCase(code); await t.run(); const file = t.sourceFile(); const fileSymbol = t.resolveReference(file.symbol); const classSymbol = t.resolveReference(fileSymbol.exports!.SimpleClass); expect(classSymbol.text).to.eql('SimpleClass'); expect(classSymbol.flags).to.eql(['Class'], 'Regarded as a class'); expect(classSymbol.modifiers).to.include('export'); const classSymbolType = t.resolveReference(classSymbol.symbolType); expect(classSymbolType.text).to.eql('SimpleClass'); expect(classSymbolType.flags).to.deep.eq(['Object']); expect(!!classSymbolType.constructorSignatures).to.eq(false, 'no constructor signatures'); const classValueDeclarationType = t.resolveReference(classSymbol.valueDeclarationType); expect(classValueDeclarationType.text).to.eql('typeof SimpleClass'); expect(classValueDeclarationType.flags).to.deep.eq(['Object']); expect(classValueDeclarationType.constructorSignatures!.length).to.eq( 1, '1 constructor signature', ); const classPropNames = Object.keys(classValueDeclarationType.properties!); expect(classPropNames).to.deep.eq(['hello', 'planet']); const [helloSym, planetSym] = classPropNames.map(n => t.resolveReference(classValueDeclarationType.properties![n]), ); expect(helloSym.text).to.eq('hello'); expect(helloSym.modifiers).to.deep.eq(['static']); expect(helloSym.flags).to.deep.eq(['Method']); expect(planetSym.text).to.eq('planet'); expect(planetSym.modifiers).to.deep.eq(['static']); expect(planetSym.flags).to.deep.eq(['Property']); const [constructorSig] = classValueDeclarationType.constructorSignatures!; expect(constructorSig.text).to.eq('(bar: string): SimpleClass'); const instanceType = t.resolveReference(constructorSig.returnType); expect(instanceType.text).to.eq('SimpleClass'); const instancePropNames = Object.keys(instanceType.properties!); expect(instancePropNames).to.deep.eq(['foo']); const props = instancePropNames.map(p => t.resolveReference(instanceType.properties![p])); const [fooSym] = props; expect(fooSym.flags).to.deep.eq(['Property']); expect(fooSym.text).to.eq('foo'); const [fooType] = props.map(s => t.resolveReference(s.valueDeclarationType)); expect(fooType.text).to.eql('string'); expect(fooType.flags).to.deep.eq(['String']); t.cleanup(); }); it('class with properties, methods and static functions using a variety of access modifier keywords', async () => { const code = `export abstract class SimpleClass { protected static hello(): string { return 'world'; } protected readonly foo: string = 'bar'; public myBar: string = 'bar'; private myBaz: string[] = ['baz']; private constructor(bar: string) { console.log(bar); } }`; const t = new SingleFileAcceptanceTestCase(code); await t.run(); const file = t.sourceFile(); const fileSymbol = t.resolveReference(file.symbol); const classSymbol = t.resolveReference(fileSymbol.exports!.SimpleClass); expect(classSymbol.text).to.eql('SimpleClass'); expect(classSymbol.flags).to.eql(['Class'], 'Regarded as a class'); expect(classSymbol.modifiers).to.include('export'); const classSymbolType = t.resolveReference(classSymbol.symbolType); expect(classSymbolType.text).to.eql('SimpleClass'); expect(classSymbolType.flags).to.deep.eq(['Object']); expect(!!classSymbolType.constructorSignatures).to.eq(false, 'no constructor signatures'); const classValueDeclarationType = t.resolveReference(classSymbol.valueDeclarationType); expect(classValueDeclarationType.text).to.eql('typeof SimpleClass'); expect(classValueDeclarationType.flags).to.deep.eq(['Object']); expect(classValueDeclarationType.constructorSignatures!.length).to.eq( 1, '1 constructor signature', ); const classPropNames = Object.keys(classValueDeclarationType.properties!); expect(classPropNames).to.deep.eq(['hello']); const [helloSym] = classPropNames.map(n => t.resolveReference(classValueDeclarationType.properties![n]), ); expect(helloSym.text).to.eq('hello'); expect(helloSym.modifiers).to.include('protected', 'static'); expect(helloSym.flags).to.deep.eq(['Method']); const [constructorSig] = classValueDeclarationType.constructorSignatures!; expect(constructorSig.text).to.eq('(bar: string): SimpleClass'); expect(constructorSig.modifiers).to.deep.eq(['private']); const instanceType = t.resolveReference(constructorSig.returnType); expect(instanceType.text).to.eq('SimpleClass'); const instancePropNames = Object.keys(instanceType.properties!); expect(instancePropNames).to.deep.eq(['foo', 'myBar', 'myBaz']); const props = instancePropNames.map(p => t.resolveReference(instanceType.properties![p])); const [fooSym, myBarSym, myBazSym] = props; expect(fooSym.flags).to.deep.eq(['Property']); expect(fooSym.text).to.eq('foo'); const [fooType] = props.map(s => t.resolveReference(s.valueDeclarationType)); expect(fooType.text).to.eql('string'); expect(fooType.flags).to.deep.eq(['String']); expect(myBarSym.modifiers).to.deep.eq(['public']); expect(myBazSym.modifiers).to.deep.eq(['private']); t.cleanup(); }); });