Home Reference Source

src/utils.spec.js

/*eslint-env node, jasmine*/
import {
  addStaticGetterObjectMember,
  addStaticGetterArrayMember,
  addStaticGetter,
  mergeStaticGetterArray,
  mergeStaticGetterObject,
  addBehavior,
  Inject,
  Decorators,
  Annotation,
  Behavior
} from 'anglue/anglue';

describe('Utils', () => {
  describe('@Inject()', () => {
    class Foo {
      @Inject() foo;
    }
    class Bar {
      @Inject('Foo') bar;
    }

    it('should use a CamelCased property name as the injection by default', () => {
      expect(Foo.injections).toEqual({foo: 'Foo'});
    });

    it('should accept a manual injection', () => {
      expect(Bar.injections).toEqual({bar: 'Foo'});
    });
  });

  describe('@Decorators()', () => {
    @Decorators(['Foo'])
    class Bar {}

    @Decorators(['Bar'])
    class Foo {
      static get decorators() {
        return ['Foo'];
      }
    }

    it('should set a static decorators getter on the class', () => {
      expect(Bar.decorators).toEqual(['Foo']);
    });

    it('should merge decorators with the existing getter', () => {
      expect(Foo.decorators).toEqual(['Foo', 'Bar']);
    });
  });

  describe('addStaticGetterObjectMember()', () => {
    it('should add a member to a static getters object', () => {
      class TestClass {}
      addStaticGetterObjectMember(TestClass, 'test', 'foo', 'bar');
      expect(TestClass.test).toEqual({foo: 'bar'});
    });

    it('should merge with current static getters object', () => {
      class TestClass {
        static get test() {
          return {foo: 'bar'};
        }
      }
      addStaticGetterObjectMember(TestClass, 'test', 'bar', 'foo');
      expect(TestClass.test).toEqual({foo: 'bar', bar: 'foo'});
    });

    it('should allow you to choose not to override existing properties', () => {
      class TestClass {
        static get test() {
          return {foo: 'bar'};
        }
      }
      addStaticGetterObjectMember(TestClass, 'test', 'foo', 'override', false);
      expect(TestClass.test).toEqual({foo: 'bar'});
    });

    it('should merge with current static property', () => {
      class TestClass {
        static test = {foo: 'bar'};
      }
      addStaticGetterObjectMember(TestClass, 'test', 'bar', 'foo');
      expect(TestClass.test).toEqual({foo: 'bar', bar: 'foo'});
    });

    it('should be possible to use a function as key', () => {
      class TestClass {}
      addStaticGetterObjectMember(TestClass, 'test', () => 'bar', 'foo');
      expect(TestClass.test).toEqual({bar: 'foo'});
    });
  });

  describe('mergeStaticGetterObject()', () => {
    it('should apply the object to a static getters object', () => {
      class TestClass {}
      mergeStaticGetterObject(TestClass, 'test', {foo: 'bar'});
      expect(TestClass.test).toEqual({foo: 'bar'});
    });

    it('should merge with current static getters object', () => {
      class TestClass {
        static get test() {
          return {foo: 'bar'};
        }
      }
      mergeStaticGetterObject(TestClass, 'test', {bar: 'foo'});
      expect(TestClass.test).toEqual({foo: 'bar', bar: 'foo'});
    });

    it('should allow you to choose not to override existing properties', () => {
      class TestClass {
        static get test() {
          return {foo: 'bar'};
        }
      }
      mergeStaticGetterObject(TestClass, 'test', {foo: 'override'}, false);
      expect(TestClass.test).toEqual({foo: 'bar'});
    });

    it('should merge with current static property', () => {
      class TestClass {
        static test = {foo: 'bar'};
      }
      mergeStaticGetterObject(TestClass, 'test', {bar: 'foo'});
      expect(TestClass.test).toEqual({foo: 'bar', bar: 'foo'});
    });
  });

  describe('addStaticGetterArrayMember()', () => {
    it('should push the value to a static getters array', () => {
      class TestClass {}
      addStaticGetterArrayMember(TestClass, 'test', 'foo');
      expect(TestClass.test).toEqual(['foo']);
    });

    it('should merge with current static getters array', () => {
      class TestClass {
        static get test() {
          return ['foo'];
        }
      }
      addStaticGetterArrayMember(TestClass, 'test', 'bar');
      expect(TestClass.test).toEqual(['foo', 'bar']);
    });

    it('should merge with current static property', () => {
      class TestClass {
        static test = ['foo'];
      }
      addStaticGetterArrayMember(TestClass, 'test', 'bar');
      expect(TestClass.test).toEqual(['foo', 'bar']);
    });
  });

  describe('mergeStaticGetterArray()', () => {
    it('should set the value to a static getters array', () => {
      class TestClass {}
      mergeStaticGetterArray(TestClass, 'test', ['foo']);
      expect(TestClass.test).toEqual(['foo']);
    });

    it('should merge with current static getters array', () => {
      class TestClass {
        static get test() {
          return ['foo'];
        }
      }
      mergeStaticGetterArray(TestClass, 'test', ['bar']);
      expect(TestClass.test).toEqual(['foo', 'bar']);
    });

    it('should ensure uniqueness', () => {
      class TestClass {
        static get test() {
          return ['foo'];
        }
      }
      mergeStaticGetterArray(TestClass, 'test', ['foo']);
      expect(TestClass.test).toEqual(['foo']);
    });

    it('should merge with current static property', () => {
      class TestClass {
        static test = ['foo'];
      }
      mergeStaticGetterArray(TestClass, 'test', ['bar']);
      expect(TestClass.test).toEqual(['foo', 'bar']);
    });
  });

  describe('addStaticGetter()', () => {
    it('should add a getter to a static object', () => {
      class TestClass {}
      addStaticGetter(TestClass, 'test', () => 'bar');
      expect(TestClass.test).toEqual('bar');
    });
  });

  describe('addBehavior()', () => {
    let cls, TestCls, BehaviorCls, methodSpy;
    beforeEach(() => {
      methodSpy = jasmine.createSpy('method');
      class BehaviorClass extends Behavior {
        bar() {
          methodSpy();
        }

        custom() {}

        stringProp = 'bar';
        boolProp = false;
        numberProp = 1;

        get getter() {
          return 'foo';
        }
      }
      class TestClass {
        custom() {
          methodSpy();
        }
      }

      TestCls = TestClass;
      BehaviorCls = BehaviorClass;

      cls = new TestClass();
    });

    it('should set an instance of the behavior as the chosen property on target class', () => {
      addBehavior(TestCls, BehaviorCls, {property: 'test'});
      expect(cls.test).toEqual(jasmine.any(BehaviorCls));
    });

    it('should set a unique behavior instance for each instance of the class', () => {
      addBehavior(TestCls, BehaviorCls, {property: 'test'});
      const anotherCls = new TestCls();
      expect(anotherCls.test).toEqual(jasmine.any(BehaviorCls));
      expect(anotherCls.test).not.toBe(cls.test);
    });

    it('should create working proxy methods on the prototype of the target cls', () => {
      addBehavior(TestCls, BehaviorCls, {property: 'foo', proxy: ['bar']});
      cls.bar();
      expect(methodSpy.calls.count()).toEqual(1);
    });

    it('should create working proxy getters on the prototype of the target cls', () => {
      addBehavior(TestCls, BehaviorCls, {property: 'foo', proxy: ['getter']});
      expect(cls.getter).toEqual('foo');
    });

    it('should create working proxy properties on the prototype of the target cls', () => {
      addBehavior(TestCls, BehaviorCls, {
        property: 'foo',
        proxy: ['stringProp', 'boolProp', 'numberProp']
      });
      expect(cls.stringProp).toEqual('bar');
      expect(cls.boolProp).toEqual(false);
      expect(cls.numberProp).toEqual(1);
    });

    it('should allow you to create an alias for a method', () => {
      addBehavior(TestCls, BehaviorCls, {property: 'foo', proxy: ['on:bar']});
      cls.on();
      expect(methodSpy.calls.count()).toEqual(1);
    });

    it('should not override existing methods on the class', () => {
      addBehavior(TestCls, BehaviorCls, {property: 'foo', proxy: ['custom']});
      cls.custom();
      expect(methodSpy).toHaveBeenCalled();
    });

    it('should store the decorated instance on the behavior instance', () => {
      addBehavior(TestCls, BehaviorCls, {property: 'foo'});
      expect(cls.foo.instance).toBe(cls);
    });
  });

  describe('Old-style class decorators', () => {
    class Bar {
      static decorate = jasmine.createSpy('decorate');
      static decorateClass = jasmine.createSpy('decorateClass');
    }

    class Foo {
      static get decorators() {
        return [Bar];
      }
    }

    const annotation = new Annotation('foo', Foo);

    it('should call the static decorateClass method on the decorator', () => {
      expect(Bar.decorateClass).toHaveBeenCalledWith(Foo);
    });

    it('should call the static decorate method passing the instance to be decorated', () => {
      const foo = new Foo();
      annotation.applyDecorators(foo);
      expect(Bar.decorate).toHaveBeenCalledWith(foo);
    });
  });
});