@serenity-is/corelib
Version:
Serenity Core Library
996 lines (793 loc) • 30.5 kB
text/typescript
import { implementedInterfacesSymbol, isAssignableFromSymbol, isInstanceOfTypeSymbol } from "./symbols";
import { Enum, addCustomAttribute, classTypeInfo, editorTypeInfo, fieldsProxy, formatterTypeInfo, getBaseType, getCustomAttribute, getCustomAttributes, getInstanceType, getType, getTypeFullName, getTypeNameProp, getTypeShortName, hasCustomAttribute, initFormType, interfaceTypeInfo, isAssignableFrom, isEnum, isInstanceOfType, registerClass, registerEnum, registerInterface, registerType } from "./system";
import { ensureTypeInfo, peekTypeInfo } from "./system-internal";
describe("Enum.getValues", () => {
it('returns correct values', function () {
enum Test {
Some = 1,
Other = 5,
Another = -1
}
expect(Enum.getValues(Test)).toStrictEqual([1, 5, -1]);
});
it('returns correct values for registered enums', function () {
enum Test {
Some = 1,
Other = 5,
Another = -1
}
registerEnum(Test, 'EnumGetValuesRegisteredEnums.Test')
expect(Enum.getValues(Test)).toStrictEqual([1, 5, -1]);
});
});
describe("Enum.toString", () => {
enum Test {
Some = 1,
Other = 5,
Another = -1
}
it('returns empty string for null', () => {
expect(Enum.toString(Test, null)).toBe("");
expect(Enum.toString(Test, undefined)).toBe("");
});
it('returns the value as string if value is not a number', () => {
expect(Enum.toString(Test, "test" as any)).toBe("test");
expect(Enum.toString(Test, "0" as any)).toBe("0");
});
it('returns names', function () {
expect(Enum.toString(Test, 1)).toBe("Some");
expect(Enum.toString(Test, 5)).toBe("Other");
expect(Enum.toString(Test, -1)).toBe("Another");
});
it('returns number if non-existent', function () {
expect(Enum.toString(Test, 0)).toBe("0");
expect(Enum.toString(Test, -5)).toBe("-5");
});
it('works with flags', () => {
enum Test2 {
Some = 1,
Other = 2,
Another = 4
}
ensureTypeInfo(Test2 as any).enumFlags = true;
expect(Enum.toString(Test2, Test2.Some + Test2.Another)).toBe("Some | Another");
expect(Enum.toString(Test2, Test2.Some + Test2.Another + 16)).toBe("Some | Another | 16");
});
});
namespace Module1 {
export class ISome { }
registerInterface(ISome, "ISome");
}
namespace CopyModule1 {
export class ISome { }
registerInterface(ISome, "ISome");
}
class Module1Class { }
registerClass(Module1Class, "SomeClassUsingCopy1", [Module1.ISome]);
class CopyModule1Class { }
registerClass(CopyModule1Class, "SomeClassUsingCopy2", [CopyModule1.ISome]);
describe("isAssignableFrom", () => {
it("interfaces can also be matched by their registration names", function () {
expect(isAssignableFrom(Module1.ISome, Module1Class)).toBe(true);
expect(isAssignableFrom(Module1.ISome, CopyModule1Class)).toBe(true);
expect(isAssignableFrom(CopyModule1.ISome, Module1Class)).toBe(true);
expect(isAssignableFrom(CopyModule1.ISome, CopyModule1Class)).toBe(true);
});
it("interfaces are matched by their registration names even when class name is different", function () {
class ISomeMeSome { }
registerInterface(ISomeMeSome, "ISome")
expect(isAssignableFrom(ISomeMeSome, Module1Class)).toBe(true);
expect(isAssignableFrom(ISomeMeSome, CopyModule1Class)).toBe(true);
});
it("interfaces with different class names and registration names won't match", function () {
class IOther { }
registerInterface(IOther, "IOther")
expect(isAssignableFrom(IOther, Module1Class)).toBe(false);
expect(isAssignableFrom(IOther, CopyModule1Class)).toBe(false);
});
it("interfaces with same class names but different registration names won't match", function () {
class ISome { }
registerInterface(ISome, "ISomeDiff")
class X { }
registerClass(X, "X", [ISome])
expect(isAssignableFrom(Module1.ISome, X)).toBe(false);
});
it("classes that are not registered as interfaces won't match", function () {
class ISome { }
registerClass(ISome, "ISome")
class X { }
registerClass(X, "X", [ISome])
expect(isAssignableFrom(Module1.ISome, X)).toBe(false);
});
it("returns true for same class", function () {
expect(isAssignableFrom(String, String)).toBe(true);
});
it("returns true for sub class", function () {
class A { }
class B extends A { }
expect(isAssignableFrom(A, B)).toBe(true);
expect(isAssignableFrom(B, A)).toBe(false);
});
});
describe("registerClass", () => {
function expectClassDetails(klass: any, name: string, intf?: any[]) {
expect(isEnum(klass)).toBe(false);
if (intf != null) {
expect(klass[implementedInterfacesSymbol]).toStrictEqual(intf);
}
else
expect(klass[implementedInterfacesSymbol]).toBeUndefined();
expect(peekTypeInfo(klass)?.customAttributes).toBeUndefined();
expect(peekTypeInfo(klass)?.enumFlags).toBeUndefined();
if (name != null) {
expect(getTypeFullName(klass)).toBe(name);
expect(getTypeNameProp(klass)).toBe(name);
expect(getType(name)).toStrictEqual(klass);
}
else {
var fullName = getTypeFullName(klass);
expect(fullName).toBe(klass.name);
expect(getTypeNameProp(klass)).toBeUndefined();
expect(getType(fullName) == null).toBe(true);
}
expect(klass[isAssignableFromSymbol]).toBeUndefined();
}
it('works with no name', function () {
class Test {
}
const name = 'Test_Class_NoName';
registerClass(Test, null);
expectClassDetails(Test, null);
});
it('works with with name', function () {
class Test {
}
const name = 'Test_Class_NoKind';
registerClass(Test, name);
expectClassDetails(Test, name);
});
it('works with no name with interfaces', function () {
class Intf1 {
}
class Intf2 {
}
class Test {
}
registerClass(Test, null, [Intf1, Intf2]);
expectClassDetails(Test, null, [Intf1, Intf2]);
});
it('works with name and interfaces', function () {
class Intf1 {
}
class Intf2 {
}
class Test {
}
const name = 'Test_Class_With_Name_And_Interfaces';
registerClass(Test, name, [Intf1, Intf2]);
expectClassDetails(Test, name, [Intf1, Intf2]);
});
it('works with derived class with null interface list null derives interfaces as is', function () {
class Intf1 {
}
class Intf2 {
}
class Test {
}
class Derived extends Test {
}
const nameTest = 'Test_DerivedClassNullIntfDerivesInterfacesAsIs';
const nameDerived = 'Derived_DerivedClassNullIntfDerivesInterfacesAsIs';
registerClass(Test, nameTest, [Intf1, Intf2]);
expectClassDetails(Test, nameTest, [Intf1, Intf2]);
registerClass(Derived, nameDerived, null);
expectClassDetails(Derived, nameDerived, [Intf1, Intf2]);
// should be the same reference
expect((Derived as any)[implementedInterfacesSymbol] === (Test as any)[implementedInterfacesSymbol]).toBe(true);
});
it('works with derived class with empty interface list derives interfaces as a copy', function () {
class Intf1 {
}
class Intf2 {
}
class Test {
}
class Derived extends Test {
}
const nameTest = 'Test_DerivedClassEmptyIntfDerivesInterfacesAsACopy';
const nameDerived = 'Derived_DerivedEmptyIntfClassDerivesInterfacesAsACopy';
registerClass(Test, nameTest, [Intf1, Intf2]);
expectClassDetails(Test, nameTest, [Intf1, Intf2]);
registerClass(Derived, nameDerived, []);
expectClassDetails(Derived, nameDerived, [Intf1, Intf2]);
// should be the same reference
expect((Derived as any)[implementedInterfacesSymbol] === (Test as any)[implementedInterfacesSymbol]).toBe(true);
});
it('works with derived class with extra interface list derives interfaces as a copy', function () {
class Intf1 {
}
class Intf2 {
}
class Test {
}
class Intf3 {
}
class Derived extends Test {
}
const nameTest = 'Test_DerivedClassEmptyIntfDerivesInterfacesAsCopy';
const nameDerived = 'Derived_DerivedEmptyIntfClassDerivesInterfacesAsCopy';
registerClass(Test, nameTest, [Intf1, Intf2]);
expectClassDetails(Test, nameTest, [Intf1, Intf2]);
registerClass(Derived, nameDerived, [Intf3]);
expectClassDetails(Derived, nameDerived, [Intf1, Intf2, Intf3]);
// should not be the same reference
expect((Derived as any)[implementedInterfacesSymbol] !== (Test as any)[implementedInterfacesSymbol]).toBe(true);
// check base class again to make sure its interfaces are not modified
expectClassDetails(Test, nameTest, [Intf1, Intf2]);
});
it("works with types that already have a static [Symbol.typeInfo] property with typeName", () => {
class MyClass {
static [Symbol.typeInfo] = classTypeInfo("MyClassName");
}
registerClass(MyClass, null);
expect(getType("MyClassName")).toBe(MyClass);
});
});
describe("registerEnum", () => {
function expectTypeDetails(enumObj: any, name: string) {
expect(isEnum(enumObj)).toBe(true);
expect(enumObj[Symbol.typeInfo]).toBeDefined();
expect(peekTypeInfo(enumObj)?.typeKind).toBe("enum");
expect(enumObj[implementedInterfacesSymbol]).toBeUndefined();
expect(peekTypeInfo(enumObj)?.enumFlags).toBeUndefined();
if (name != null) {
expect(getTypeFullName(enumObj)).toBe(name);
expect(enumObj[fullName]).toBe(name);
expect(getType(name)).toStrictEqual(enumObj);
}
else {
var fullName = getTypeFullName(enumObj);
expect(fullName).toBe('Object');
expect(enumObj[fullName]).toBeUndefined();
expect(getType(fullName) == null).toBe(true);
}
expect(enumObj[isAssignableFromSymbol]).toBeUndefined();
}
it('works with no name', function () {
enum Test {
A = 1,
B = 2
}
registerEnum(Test, null);
expectTypeDetails(Test, null);
});
it('works with name', function () {
enum Test {
A = 1,
B = 2
}
const name = 'Test_Enum_With_Name';
registerEnum(Test, name);
});
it('works with enum key', function () {
enum Test {
A = 1,
B = 2
}
const name = 'Test_Enum_Name';
const key = 'Test_Enum_Key';
registerEnum(Test, name, key);
expect(getType(name)).toBe(Test);
expect(getType(key)).toBe(Test);
});
});
describe("registerInterface", () => {
function expectTypeDetails(klass: any, name: string, intf?: any[]) {
expect(isEnum(klass)).toBe(false);
if (intf != null) {
expect(klass[implementedInterfacesSymbol]).toStrictEqual(intf);
}
else
expect(klass[implementedInterfacesSymbol]).toBeUndefined();
expect(peekTypeInfo(klass)?.enumFlags).toBeUndefined();
expect(peekTypeInfo(klass)?.typeKind).toBe("interface");
if (name != null) {
expect(getTypeFullName(klass)).toBe(name);
expect(getTypeNameProp(klass)).toBe(name);
expect(getType(name)).toStrictEqual(klass);
}
else {
var fullName = getTypeFullName(klass);
expect(fullName).toBe(klass.name);
expect(getTypeNameProp(klass));
expect(getType(fullName) == null).toBe(true);
}
expect(typeof klass[isAssignableFromSymbol]).toBe("function");
}
it('works with no name', function () {
class ITest {
}
registerInterface(ITest, null);
expectTypeDetails(ITest, null);
});
it('works with with name', function () {
class ITest {
}
const name = 'ITest_Interface_NoKind';
registerInterface(ITest, name);
expectTypeDetails(ITest, name);
});
it('works with no name with interfaces', function () {
class Intf1 {
}
class Intf2 {
}
class ITest {
}
registerInterface(ITest, null, [Intf1, Intf2]);
expectTypeDetails(ITest, null, [Intf1, Intf2]);
});
it('works with name and interfaces', function () {
class Intf1 {
}
class Intf2 {
}
class ITest {
}
const name = 'Test_Interface_With_Name_And_Interfaces';
registerInterface(ITest, name, [Intf1, Intf2]);
expectTypeDetails(ITest, name, [Intf1, Intf2]);
});
it('works with derived class with null interface list null derives interfaces as is', function () {
// NOTE: interfaces are not supposed to be derived
class Intf1 {
}
class Intf2 {
}
class ITest {
}
class IDerived extends ITest {
}
const nameTest = 'Test_DerivedClassNullIntfDerivesInterfacesAsIs';
const nameDerived = 'Derived_DerivedClassNullIntfDerivesInterfacesAsIs';
registerInterface(ITest, nameTest, [Intf1, Intf2]);
expectTypeDetails(ITest, nameTest, [Intf1, Intf2]);
registerInterface(IDerived, nameDerived, null);
expectTypeDetails(IDerived, nameDerived, [Intf1, Intf2]);
// should be the same reference
expect((IDerived as any)[implementedInterfacesSymbol] === (ITest as any)[implementedInterfacesSymbol]).toBe(true);
});
it('works with derived class with empty interface list derives interfaces as a copy', function () {
// NOTE: interfaces are not supposed to be derived classes
// just testing copy of [implementedInterfacesSymbol] here
class Intf1 {
}
class Intf2 {
}
class ITest {
}
class IDerived extends ITest {
}
const nameTest = 'Test_DerivedClassEmptyIntfDerivesInterfacesAsACopy';
const nameDerived = 'Derived_DerivedEmptyIntfClassDerivesInterfacesAsACopy';
registerInterface(ITest, nameTest, [Intf1, Intf2]);
expectTypeDetails(ITest, nameTest, [Intf1, Intf2]);
registerInterface(IDerived, nameDerived, []);
expectTypeDetails(IDerived, nameDerived, [Intf1, Intf2]);
// should be the same reference
expect((IDerived as any)[implementedInterfacesSymbol] === (ITest as any)[implementedInterfacesSymbol]).toBe(true);
});
it('works with derived class with extra interface list derives interfaces as a copy', function () {
// NOTE: interfaces are not supposed to be derived classes
// just testing copy of [implementedInterfacesSymbol] here
class Intf1 {
}
class Intf2 {
}
class ITest {
}
class Intf3 {
}
class IDerived extends ITest {
}
const nameTest = 'Test_DerivedClassEmptyIntfDerivesInterfacesAsCopy';
const nameDerived = 'Derived_DerivedEmptyIntfClassDerivesInterfacesAsCopy';
registerInterface(ITest, nameTest, [Intf1, Intf2]);
expectTypeDetails(ITest, nameTest, [Intf1, Intf2]);
registerInterface(IDerived, nameDerived, [Intf3]);
expectTypeDetails(IDerived, nameDerived, [Intf1, Intf2, Intf3]);
// should not be the same reference
expect((IDerived as any)[implementedInterfacesSymbol] !== (ITest as any)[implementedInterfacesSymbol]).toBe(true);
// check base class again to make sure its interfaces are not modified
expectTypeDetails(ITest, nameTest, [Intf1, Intf2]);
});
});
describe("registerType", () => {
it("throws for null or undefined", () => {
expect(() => registerType(null)).toThrow();
expect(() => registerType(undefined)).toThrow();
});
it("throws if the class does not have a typeInfo property", () => {
class Test {
}
expect(() => registerType(Test as any)).toThrow();
});
it("registers type as a class for a class typeInfo", () => {
class Test {
static [Symbol.typeInfo] = classTypeInfo("MyTestName");
}
registerType(Test);
expect(getTypeFullName(Test)).toBe("MyTestName");
expect(Test[Symbol.typeInfo].typeKind).toBe("class");
});
it("registers type as interface for an interface typeInfo", () => {
class Test {
static [Symbol.typeInfo] = interfaceTypeInfo("MyTestName");
}
registerType(Test);
expect(getTypeFullName(Test)).toBe("MyTestName");
expect(Test[Symbol.typeInfo].typeKind).toBe("interface");
});
it("registers type as editor for an editor typeInfo", () => {
class Test {
static [Symbol.typeInfo] = editorTypeInfo("MyTestName");
}
registerType(Test);
expect(getTypeFullName(Test)).toBe("MyTestName");
expect(Test[Symbol.typeInfo].typeKind).toBe("editor");
});
it("registers type as formatter for a formatter typeInfo", () => {
class Test {
static [Symbol.typeInfo] = formatterTypeInfo("MyTestName");
}
registerType(Test);
expect(getTypeFullName(Test)).toBe("MyTestName");
expect(Test[Symbol.typeInfo].typeKind).toBe("formatter");
});
});
describe("isInstanceOfType", () => {
it("returns false if instance is null or undefined", () => {
expect(isInstanceOfType(null, String)).toBe(false);
expect(isInstanceOfType(undefined, Object)).toBe(false);
});
it("uses isInstanceOfTypeSymbol function if available", () => {
class Test1 {
static [isInstanceOfTypeSymbol](type: any) { return typeof type === "string" && type.startsWith("t"); }
}
class Test2 {
static [isInstanceOfTypeSymbol] = true;
}
expect(isInstanceOfType("test", Test1)).toBe(true);
expect(isInstanceOfType("vest", Test1)).toBe(false);
expect(isInstanceOfType("test", Test2)).toBe(false);
expect(isInstanceOfType("vest", Test2)).toBe(false);
});
});
describe("getInstanceType", () => {
it("throws for null or undefined", () => {
expect(() => getInstanceType(undefined)).toThrow();
expect(() => getInstanceType(null)).toThrow();
});
it("returns Object if can't read constructor", () => {
let a = {
get constructor() { throw "test"; }
}
expect(getInstanceType(a)).toBe(Object);
});
});
describe("getBaseType", () => {
it("returns null for null, Object, NaN, and interfaces", () => {
expect(getBaseType(null)).toBeNull();
expect(getBaseType(undefined)).toBeNull();
expect(getBaseType(Object)).toBeNull();
expect(getBaseType(NaN)).toBeNull();
class ITest {
}
registerInterface(ITest, "getBaseType.ITest");
expect(getBaseType(ITest)).toBeNull();
});
it("uses getPrototypeOf for others", () => {
class Test {
}
class Sub extends Test {
}
expect(getBaseType(Test)).toBe(Object);
expect(getBaseType(Sub)).toBe(Test);
});
});
describe("initFormType", () => {
it("uses w function", () => {
class TestForm {
w(name: string, widget: string) {
return name + "_" + widget;
}
}
initFormType(TestForm, ["test1", "1", "test2", "2", "test3", "3"]);
var form = new TestForm();
expect((form as any).test1).toBe("test1_1");
expect((form as any).test2).toBe("test2_2");
expect((form as any).test3).toBe("test3_3");
});
});
describe("fieldsProxy", () => {
it("returns same instance every time", () => {
class Row1Fields {
}
class Row2Fields {
}
expect(fieldsProxy<Row1Fields>()).toBe(fieldsProxy<Row2Fields>());
});
it("returns property name as is", () => {
class Fields {
declare A: string;
declare B: string;
}
var proxy = fieldsProxy<Fields>();
expect(proxy.A).toBe("A");
expect(proxy.B).toBe("B");
});
});
describe("getType", () => {
it("can return type from a root object", () => {
let root = {
A: {
B: class {
}
}
}
expect(getType("A.B", root)).toBe(root.A.B);
});
});
describe("getTypeShortName", () => {
it("returns part after last dot", () => {
class Test {
}
registerClass(Test, "NamespaceOf.C.IsD")
expect(getTypeShortName(Test)).toBe("IsD");
});
it("returns as is if no dot", () => {
class Test {
}
registerClass(Test, "Me")
expect(getTypeShortName(Test)).toBe("Me");
});
});
describe("addCustomAttribute", () => {
it("can add custom attribute", () => {
class MyAttribute {
}
registerClass(MyAttribute, "MyAttribute");
class MyClass {
}
const attr = new MyAttribute();
addCustomAttribute(MyClass, attr);
expect(getCustomAttribute(MyClass, MyAttribute)).toBe(attr);
});
it("can add multiple attribute", () => {
class MyAttribute1 {
}
class MyAttribute2 {
}
class MyClass {
}
const attr1 = new MyAttribute1();
addCustomAttribute(MyClass, attr1);
const attr2 = new MyAttribute2();
addCustomAttribute(MyClass, attr2);
expect(getCustomAttribute(MyClass, MyAttribute1)).toBe(attr1);
expect(getCustomAttribute(MyClass, MyAttribute2)).toBe(attr2);
});
it("can add multiple attribute of same type", () => {
class MyAttribute {
constructor(public value: boolean) {
}
}
class MyClass {
}
const attr1 = new MyAttribute(false);
addCustomAttribute(MyClass, attr1);
const attr2 = new MyAttribute(true);
addCustomAttribute(MyClass, attr2);
expect(getCustomAttribute(MyClass, MyAttribute)).toBe(attr2); // returns last added one
let attrs = getCustomAttributes(MyClass, MyAttribute);
expect(attrs?.length).toBe(2);
expect(attrs[0]).toBe(attr2); // returns last added one first
expect(attrs[1]).toBe(attr1);
});
});
describe("getCustomAttribute", () => {
class MyAttribute {
}
it("returns null if type is null or undefined", () => {
expect(getCustomAttribute(null, MyAttribute)).toBeNull();
expect(getCustomAttribute(undefined, MyAttribute)).toBeNull();
});
it("returns null if attribute type is null or undefined", () => {
class MyClass {
}
addCustomAttribute(MyClass, new MyAttribute());
expect(getCustomAttribute(MyClass, null)).toBeNull();
expect(getCustomAttribute(MyClass, undefined)).toBeNull();
});
it("returns empty array if no custom attribute", () => {
class MyClass {
}
expect(getCustomAttributes(MyClass, MyAttribute)).toStrictEqual([]);
});
it("returns the attribute if exists", () => {
class MyClass {
}
const attr = new MyAttribute();
addCustomAttribute(MyClass, attr);
expect(getCustomAttribute(MyClass, MyAttribute)).toBe(attr);
});
it("does not return another attribute type", () => {
class MyClass {
}
class OtherAttribute {
}
const attr = new MyAttribute();
addCustomAttribute(MyClass, attr);
expect(getCustomAttribute(MyClass, OtherAttribute)).toBeUndefined();
});
it("returns the last added attribute if multiple of same type of attribute is added", () => {
class MyClass {
}
const attr1 = new MyAttribute();
const attr2 = new MyAttribute();
addCustomAttribute(MyClass, attr1);
addCustomAttribute(MyClass, attr2);
expect(getCustomAttribute(MyClass, MyAttribute)).toBe(attr2);
});
it("returns inherited attribute types", () => {
class MyDerivedAttr extends MyAttribute {
}
class MyClass {
}
const attr = new MyDerivedAttr();
addCustomAttribute(MyClass, attr);
expect(getCustomAttribute(MyClass, MyAttribute)).toBe(attr);
});
it("returns inherited attributes from subclasses", () => {
class MyClass {
}
class SubClass extends MyClass {
}
const attr = new MyAttribute();
addCustomAttribute(MyClass, attr);
expect(getCustomAttribute(SubClass, MyAttribute)).toBe(attr);
});
it("returns the subclass attribute if both the class and the subclass has same attribute", () => {
class MyClass {
}
class SubClass extends MyClass {
}
const attr1 = new MyAttribute();
addCustomAttribute(MyClass, attr1);
const attr2 = new MyAttribute();
addCustomAttribute(SubClass, attr2);
expect(getCustomAttribute(SubClass, MyAttribute)).toBe(attr2);
});
it("does not return inherited attributes from subclasses if inherited is false", () => {
class MyClass {
}
class SubClass extends MyClass {
}
const attr = new MyAttribute();
addCustomAttribute(MyClass, attr);
expect(getCustomAttribute(SubClass, MyAttribute, false)).toBeUndefined();
});
});
describe("getCustomAttributes", () => {
class MyAttribute {
}
it("returns empty array if type is null or undefined", () => {
expect(getCustomAttributes(null, null)).toStrictEqual([]);
expect(getCustomAttributes(undefined, undefined)).toStrictEqual([]);
});
it("returns empty array if attribute type is null", () => {
class MyClass {
}
const attr = new MyAttribute();
addCustomAttribute(MyClass, attr);
expect(getCustomAttributes(MyClass, null)).toStrictEqual([]);
});
it("returns all attributes if attribute type is undefined", () => {
class MyClass {
}
const attr1 = new MyAttribute();
addCustomAttribute(MyClass, attr1);
const attr2 = new MyAttribute();
addCustomAttribute(MyClass, attr2);
expect(getCustomAttributes(MyClass, void 0)).toStrictEqual([attr1, attr2]);
});
it("returns empty array if no custom attribute", () => {
class MyClass {
}
expect(getCustomAttributes(MyClass, MyAttribute)).toStrictEqual([]);
});
it("returns array if custom attribute", () => {
class MyClass {
}
const attr = new MyAttribute();
addCustomAttribute(MyClass, attr);
expect(getCustomAttributes(MyClass, MyAttribute)).toStrictEqual([attr]);
});
it("returns inherited attribute types", () => {
class MyDerivedAttr extends MyAttribute {
}
class MyClass {
}
const attr = new MyDerivedAttr();
addCustomAttribute(MyClass, attr);
expect(getCustomAttributes(MyClass, MyAttribute)).toStrictEqual([attr]);
});
it("returns the attributes in descending order", () => {
class MyClass {
}
const attr1 = new MyAttribute();
const attr2 = new MyAttribute();
addCustomAttribute(MyClass, attr1);
addCustomAttribute(MyClass, attr2);
expect(getCustomAttributes(MyClass, MyAttribute)).toStrictEqual([attr2, attr1]);
});
it("returns inherited attributes from subclasses", () => {
class MyClass {
}
class SubClass extends MyClass {
}
const attr = new MyAttribute();
addCustomAttribute(MyClass, attr);
expect(getCustomAttributes(SubClass, MyAttribute)).toStrictEqual([attr]);
});
it("does not return inherited attributes from subclasses if inherited is false", () => {
class MyClass {
}
class SubClass extends MyClass {
}
const attr = new MyAttribute();
addCustomAttribute(MyClass, attr);
expect(getCustomAttributes(SubClass, MyAttribute, false)).toStrictEqual([]);
});
it("returns the attributes in descending order", () => {
class MyClass {
}
const attr1 = new MyAttribute();
const attr2 = new MyAttribute();
addCustomAttribute(MyClass, attr1);
addCustomAttribute(MyClass, attr2);
expect(getCustomAttributes(MyClass, MyAttribute)).toStrictEqual([attr2, attr1]);
});
it("returns the base class attributes last", () => {
class MyClass {
}
class MyDerivedClass extends MyClass {
}
const attr1 = new MyAttribute();
const attr2 = new MyAttribute();
const attr3 = new MyAttribute();
addCustomAttribute(MyDerivedClass, attr1);
addCustomAttribute(MyClass, attr2);
addCustomAttribute(MyDerivedClass, attr3);
expect(getCustomAttributes(MyDerivedClass, MyAttribute)).toStrictEqual([attr3, attr1, attr2]);
expect(getCustomAttributes(MyClass, MyAttribute)).toStrictEqual([attr2]);
});
});
describe("hasCustomAttribute", () => {
it("returns false if no custom attribute", () => {
class MyAttribute {
}
class MyClass {
}
expect(hasCustomAttribute(MyClass, MyAttribute)).toBe(false);
});
it("returns true if custom attribute", () => {
class MyAttribute {
}
class MyClass {
}
const attr = new MyAttribute();
addCustomAttribute(MyClass, attr);
expect(hasCustomAttribute(MyClass, MyAttribute)).toBe(true);
});
});