UNPKG

jsii-reflect

Version:

strongly-typed reflection library and tools for jsii

350 lines (341 loc) 12.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const spec = require("@jsii/spec"); const spec_1 = require("@jsii/spec"); const path = require("path"); const lib_1 = require("../lib"); const util_1 = require("./util"); let typesys; beforeAll(async () => { typesys = new lib_1.TypeSystem(); await typesys.loadModule(resolveModuleDir('jsii-calc')); }); test('TypeSystem.hasAssembly', () => { expect(typesys.includesAssembly('@foo/bar')).toBeFalsy(); expect(typesys.includesAssembly('jsii-calc')).toBeTruthy(); expect(typesys.includesAssembly('@scope/jsii-calc-lib')).toBeTruthy(); }); test('TypeSystem.assemblies lists all the loaded assemblies', () => expect(typesys.assemblies.map((a) => a.name).sort()).toMatchSnapshot()); test('TypeSystem.classes lists all the classes in the typesystem', () => expect(typesys.classes.map((c) => c.fqn).sort()).toMatchSnapshot()); test('findClass', () => { const calc = typesys.findClass('jsii-calc.Calculator'); const actual = new Array(); Object.values(calc.getMethods(/* inherited */ true)).forEach((method) => { actual.push(`${method.name} from ${method.parentType.name}`); }); expect(actual.join('\n')).toMatchSnapshot(); }); test('"roots" is a list of the directly loaded assemblies', async () => { expect(typesys.roots.length).toBe(1); expect(typesys.roots[0]).toBe(typesys.findAssembly('jsii-calc')); // now load another assembliy directly await typesys.load(resolveModuleDir('@scope/jsii-calc-lib')); return expect(typesys.roots.length).toBe(2); }); describe('Type', () => { test('.isClassType', () => { // GIVEN const clazz = typesys.findFqn('jsii-calc.AllTypes'); const iface = typesys.findFqn('jsii-calc.IPublicInterface'); const enumt = typesys.findFqn('jsii-calc.AllTypesEnum'); // THEN expect(clazz.isClassType()).toBeTruthy(); expect(iface.isClassType()).toBeFalsy(); expect(enumt.isClassType()).toBeFalsy(); }); test('.isDataType', () => { // GIVEN const clazz = typesys.findFqn('jsii-calc.AllTypes'); const iface = typesys.findFqn('jsii-calc.IInterfaceThatShouldNotBeADataType'); const datat = typesys.findFqn('jsii-calc.CalculatorProps'); const enumt = typesys.findFqn('jsii-calc.AllTypesEnum'); // THEN expect(clazz.isDataType()).toBeFalsy(); expect(iface.isDataType()).toBeFalsy(); expect(datat.isDataType()).toBeTruthy(); expect(enumt.isDataType()).toBeFalsy(); }); test('.isInterfaceType', () => { // GIVEN const clazz = typesys.findFqn('jsii-calc.AllTypes'); const iface = typesys.findFqn('jsii-calc.IPublicInterface'); const enumt = typesys.findFqn('jsii-calc.AllTypesEnum'); // THEN expect(clazz.isInterfaceType()).toBeFalsy(); expect(iface.isInterfaceType()).toBeTruthy(); expect(enumt.isInterfaceType()).toBeFalsy(); }); test('.isEnumType', () => { // GIVEN const clazz = typesys.findFqn('jsii-calc.AllTypes'); const iface = typesys.findFqn('jsii-calc.IPublicInterface'); const enumt = typesys.findFqn('jsii-calc.AllTypesEnum'); // THEN expect(clazz.isEnumType()).toBeFalsy(); expect(iface.isEnumType()).toBeFalsy(); expect(enumt.isEnumType()).toBeTruthy(); }); describe('.extends(base)', () => { test('with interfaces', () => { // GIVEN const base = typesys.findFqn('@scope/jsii-calc-base-of-base.VeryBaseProps'); const clazz = typesys.findFqn('jsii-calc.ImplictBaseOfBase'); const enumt = typesys.findFqn('jsii-calc.AllTypesEnum'); // THEN expect(base.extends(base)).toBeTruthy(); expect(clazz.extends(clazz)).toBeTruthy(); expect(clazz.extends(base)).toBeTruthy(); expect(enumt.extends(base)).toBeFalsy(); expect(base.extends(enumt)).toBeFalsy(); expect(base.extends(clazz)).toBeFalsy(); }); test('with a class and an interface', () => { // GIVEN const iface = typesys.findFqn('jsii-calc.IInterfaceImplementedByAbstractClass'); const clazz = typesys.findFqn('jsii-calc.AbstractClass'); // THEN expect(clazz.extends(iface)).toBeTruthy(); }); test('with two classes', () => { // GIVEN const base = typesys.findFqn('jsii-calc.AbstractClassBase'); const clazz = typesys.findFqn('jsii-calc.AbstractClass'); // THEN expect(clazz.extends(base)).toBeTruthy(); }); }); describe('.allImplementations', () => { test('with an interface', () => { // GIVEN const base = typesys.findFqn('jsii-calc.IInterfaceImplementedByAbstractClass'); // THEN expect(base.allImplementations).toEqual([ typesys.findFqn('jsii-calc.AbstractClass'), base, ]); }); test('with an enum', () => { // GIVEN const enumt = typesys.findFqn('jsii-calc.AllTypesEnum'); // THEN expect(enumt.allImplementations).toEqual([]); }); }); }); test('Three Inheritance Levels', () => { const iface = typesys.findInterface('@scope/jsii-calc-lib.IThreeLevelsInterface'); const methodnames = iface.allMethods.map((m) => m.name); methodnames.sort(); expect(methodnames).toEqual(['bar', 'baz', 'foo']); }); describe('@deprecated', () => { test('can be read on an item', () => { const klass = typesys.findClass('jsii-calc.Old'); expect(klass.docs.deprecated).toBeTruthy(); expect(klass.docs.stability).toBe(spec.Stability.Deprecated); }); test('is inherited from class', () => { const klass = typesys.findClass('jsii-calc.Old'); const method = klass.getMethods().doAThing; expect(method.docs.deprecated).toBeTruthy(); }); }); /** * This test is actually testing the combination of `jsii`, `jsii-calc` and `jsii-reflect`. */ test('Submodules can have a README', () => { const jsiiCalc = typesys.findAssembly('jsii-calc'); const submodule = jsiiCalc.submodules.find((m) => m.fqn === 'jsii-calc.submodule'); const isolated = submodule?.submodules.find((m) => m.fqn === 'jsii-calc.submodule.isolated'); expect(submodule?.readme?.markdown).toMatch(/This is the readme.*jsii-calc.submodule/); expect(isolated?.readme?.markdown).toMatch(/This is the readme.*jsii-calc.submodule.isolated/); }); test('overridden member knows about both parent types', () => { const ts = (0, util_1.typeSystemFromSource)(` export class Foo { public bar() { Array.isArray(3); } public boo(): boolean { return true; } } export class SubFoo extends Foo { public boo(): boolean { return false; } } `); const superType = ts.findClass('testpkg.Foo'); const subType = ts.findClass('testpkg.SubFoo'); const fooMethod = subType.allMethods.find((m) => m.name === 'bar'); const booMethod = subType.allMethods.find((m) => m.name === 'boo'); expect(fooMethod.parentType).toBe(subType); expect(fooMethod.definingType).toBe(superType); expect(booMethod.parentType).toBe(subType); expect(booMethod.definingType).toBe(subType); expect(booMethod.overrides).toBe(superType); }); describe('Stability', () => { test('can be read on an item', () => { const klass = typesys.findClass('jsii-calc.DocumentedClass'); expect(klass.docs.stability).toBe(spec.Stability.Stable); }); test('is inherited from class', () => { const klass = typesys.findClass('jsii-calc.DocumentedClass'); const method = klass.getMethods().greet; expect(method.docs.stability).toBe(spec.Stability.Stable); }); test('can be overridden from class', () => { const klass = typesys.findClass('jsii-calc.DocumentedClass'); const method = klass.getMethods().hola; expect(method.docs.stability).toBe(spec.Stability.Experimental); }); // ---------------------------------------------------------------------- describe('lowest stability guarantee is advertised', () => { test('when subclass is experimental', () => { const ts = (0, util_1.typeSystemFromSource)(` /** * @stable */ export class Foo { constructor() { Array.isArray(3); } public bar() { Array.isArray(3); } } /** * @experimental */ export class SubFoo extends Foo { } `); const classType = ts.findClass('testpkg.SubFoo'); const initializer = classType.initializer; const method = classType.allMethods.find((m) => m.name === 'bar'); expect(initializer.docs.stability).toEqual(spec_1.Stability.Experimental); expect(method.docs.stability).toEqual(spec_1.Stability.Experimental); }); test('when method is experimental', () => { const ts = (0, util_1.typeSystemFromSource)(` /** * @stable */ export class Foo { /** * @experimental */ constructor() { Array.isArray(3); } /** * @experimental */ public bar() { Array.isArray(3); } } /** * @stable */ export class SubFoo extends Foo { } `); const classType = ts.findClass('testpkg.SubFoo'); const initializer = classType.initializer; const method = classType.allMethods.find((m) => m.name === 'bar'); expect(initializer.docs.stability).toEqual(spec_1.Stability.Experimental); expect(method.docs.stability).toEqual(spec_1.Stability.Experimental); }); test('when method is explicitly marked stable', () => { const ts = (0, util_1.typeSystemFromSource)(` /** * @stable */ export class Foo { /** * @stable */ constructor() { Array.isArray(3); } /** * @stable */ public bar() { Array.isArray(3); } } /** * @experimental */ export class SubFoo extends Foo { } `); const classType = ts.findClass('testpkg.SubFoo'); const initializer = classType.initializer; const method = classType.allMethods.find((m) => m.name === 'bar'); expect(initializer.docs.stability).toEqual(spec_1.Stability.Experimental); expect(method.docs.stability).toEqual(spec_1.Stability.Experimental); }); test('external stability', () => { const ts = (0, util_1.typeSystemFromSource)(` /** * @stability external */ export class Foo { public bar() { Array.isArray(3); } } /** * @stable */ export class SubFoo extends Foo { } `); const classType = ts.findClass('testpkg.SubFoo'); const method = classType.allMethods.find((m) => m.name === 'bar'); expect(method.docs.stability).toEqual(spec_1.Stability.External); }); }); }); test('TypeSystem.properties', () => { const ts = (0, util_1.typeSystemFromSource)(` export namespace submodule { export class Foo { public readonly test = 'TEST'; } export interface Bar { readonly baz: number; } } `); expect(ts.properties).toHaveLength(2); }); test('TypeSystem.methods', () => { const ts = (0, util_1.typeSystemFromSource)(` export namespace submodule { export class Foo { public method(): void {} } export interface IBar { baz(): number; } } `); expect(ts.methods).toHaveLength(2); }); test('Assembly allTypes includes submodule types', () => { const asm = (0, util_1.assemblyFromSource)({ 'index.ts': 'export * as submod from "./submod";', 'submod.ts': `export class Foo {}`, }); expect(asm.allTypes.map((t) => t.fqn)).toEqual(['testpkg.submod.Foo']); }); function resolveModuleDir(name) { return path.dirname(require.resolve(`${name}/package.json`)); } //# sourceMappingURL=type-system.test.js.map