<commit_before>'use strict';

describe('Resource', function () {
  beforeEach(module('hypermedia'));

  // Setup

  var $log, $q, $rootScope, Resource, mockContext, uri, resource;

  beforeEach(inject(function (_$log_, _$q_, _$rootScope_, _Resource_) {
    $log = _$log_;
    $q = _$q_;
    $rootScope = _$rootScope_;
    Resource = _Resource_;
    mockContext = jasmine.createSpyObj('mockContext', ['get', 'httpGet', 'httpPut', 'httpDelete', 'httpPost', 'httpPatch']);
    uri = 'http://example.com';
    resource = new Resource(uri, mockContext);
  }));

  // Tests

  it('is initialized correctly', function () {
    expect(resource.$uri).toBe(uri);
    expect(resource.$context).toBe(mockContext);
    expect(resource.$links).toEqual({self: {href: uri}});
    expect(resource.$syncTime).toBeNull();
    expect(resource.$profile).toBeNull();
  });

  // Profiles

  it('adds properties by setting $profile', function () {
    Resource.registerProfile('profile', {test: {value: true}});
    resource.$profile = 'profile';
    expect(resource.test).toBe(true);
  });

  it('adds methods by setting $profile', function () {
    Resource.registerProfile('profile', {test: {value: function () {
      return 'test' + this.number;
    }}});
    resource.$profile = 'profile';
    resource.number = 1;
    expect(resource.test()).toBe('test1');
  });

  it('accepts multiple profiles', function () {
    Resource.registerProfiles({
      profile1: {test1: {value: true}},
      profile2: {test2: {value: true}}
    });
    resource.$profile = ['profile1', 'profile2'];
    expect(resource.test1).toBe(true);
    expect(resource.test2).toBe(true);
  });

  it('can switch profiles', function () {
    Resource.registerProfiles({
      profile1: {test1: {value: true}},
      profile2: {test2: {value: true}}
    });

    resource.$profile = 'profile1';
    expect(resource.test1).toBe(true);
    expect(resource.test2).toBeUndefined();

    resource.$profile = 'profile2';
    expect(resource.test1).toBeUndefined();
    expect(resource.test2).toBe(true);
  });

  // Property hrefs

  it('gets hrefs in properties', function () {
    resource.test_href = 'http://example.com/test';
    expect(resource.$propHref('test_href')).toBe('http://example.com/test');
  });

  it('resolves templated hrefs in properties', function () {
    resource.test_href = 'http://example.com/{id}';
    var result = resource.$propHref('test_href', {id: 'test'});
    expect(result).toBe('http://example.com/test');
  });

  it('resolves arrays of templated hrefs in properties', function () {
    resource.test_href = ['http://example.com/1/{id}', 'http://example.com/2/{id}'];
    var result = resource.$propHref('test_href', {id: 'test'});
    expect(result).toEqual(['http://example.com/1/test', 'http://example.com/2/test']);
  });

  it('returns undefined getting hrefs if property does not exist', function () {
    expect(resource.$propHref('test_href')).toBeUndefined();
  });

  it('follows hrefs in properties', function () {
    var resource2 = new Resource('http://example.com/test', mockContext);
    mockContext.get.and.returnValue(resource2);

    resource.test_href = 'http://example.com/test';
    expect(resource.$propRel('test_href')).toBe(resource2);
    expect(mockContext.get).toHaveBeenCalledWith('http://example.com/test', undefined);
  });

  it('follows templated hrefs in properties', function () {
    var resource2 = new Resource('http://example.com/test', mockContext);
    mockContext.get.and.returnValue(resource2);

    resource.test_href = 'http://example.com/{id}';
    expect(resource.$propRel('test_href', {id: 'test'})).toBe(resource2);
    expect(mockContext.get).toHaveBeenCalledWith('http://example.com/test', undefined);
  });

  it('follows arrays of templated hrefs in properties', function () {
    var resource1 = new Resource('http://example.com/1/test', mockContext);
    var resource2 = new Resource('http://example.com/2/test', mockContext);
    mockContext.get.and.callFake(function (uri) {
      if (uri == 'http://example.com/1/test') {
        return resource1;
      } else {
        return resource2;
      }
    });

    resource.test_href = ['http://example.com/1/{id}', 'http://example.com/2/{id}'];
    expect(resource.$propRel('test_href', {id: 'test'})).toEqual([resource1, resource2]);
    expect(mockContext.get).toHaveBeenCalledWith('http://example.com/1/test', undefined);
    expect(mockContext.get).toHaveBeenCalledWith('http://example.com/2/test', undefined);
  });

  it('returns undefined following hrefs if property does not exist', function () {
    expect(resource.$propRel('test_href')).toBeUndefined();
  });

  it('follow hrefs in properties with a factory', function () {
    var resource2 = new Resource('http://example.com/test', mockContext);
    mockContext.get.and.returnValue(resource2);

    resource.test_href = 'http://example.com/test';
    expect(resource.$propRel('test_href', Resource)).toBe(resource2);
    expect(mockContext.get).toHaveBeenCalledWith('http://example.com/test', Resource);
  });

  // Links

  it('resolves links', function () {
    resource.$links.example = {href: 'http://example.com/test'};
    expect(resource.$linkHref('example')).toBe('http://example.com/test');
  });

  it('resolves templated links', function () {
    resource.$links.example = {href: 'http://example.com/{id}', templated: true};
    var result = resource.$linkHref('example', {id: 'test'});
    expect(result).toBe('http://example.com/test');
  });

  it('warns when resolving templated links without vars', function () {
    resource.$links.example = {href: 'http://example.com/{id}', templated: true};
    resource.$linkHref('example');
    expect($log.warn.logs).toEqual([["Following templated link relation 'example' without variables"]]);
  });

  it('warns when resolving non-templated links with vars', function () {
    resource.$links.example = {href: 'http://example.com/test'};
    resource.$linkHref('example', {foo: 'bar'});
    expect($log.warn.logs).toEqual([["Following non-templated link relation 'example' with variables"]]);
  });

  it('warns when resolving a deprecated link', function () {
    resource.$links.example = {href: 'http://example.com/test', deprecation: 'http://example.com/deprecation'};
    resource.$linkHref('example');
    expect($log.warn.logs).toEqual([["Following deprecated link relation 'example': http://example.com/deprecation"]]);
  });

  it('resolves array links', function () {
    var href1 = 'http://example.com/1';
    var href2 = 'http://example.com/2';
    resource.$links.example = [{href: href1}, {href: href2}];
    expect(resource.$linkHref('example')).toEqual([href1, href2]);
  });

  it('follows links', function () {
    var resource1 = new Resource('http://example.com/1', mockContext);
    mockContext.get.and.returnValue(resource1);
    resource.$links.example = {href: resource1.$uri};
    expect(resource.$linkRel('example')).toBe(resource1);
    expect(mockContext.get).toHaveBeenCalledWith(resource1.$uri, undefined);
  });

  it('follows links with factory', function () {
    var resource1 = new Resource('http://example.com/1', mockContext);
    mockContext.get.and.returnValue(resource1);
    resource.$links.example = {href: resource1.$uri};
    expect(resource.$linkRel('example', Resource)).toBe(resource1);
    expect(mockContext.get).toHaveBeenCalledWith(resource1.$uri, Resource);
  });

  it('follows array links', function () {
    var resource1 = new Resource('http://example.com/1', mockContext);
    var resource2 = new Resource('http://example.com/2', mockContext);
    mockContext.get.and.callFake(function (uri) {
      if (uri == 'http://example.com/1') {
        return resource1;
      } else {
        return resource2;
      }
    });

    resource.$links.example = [{href: resource1.$uri}, {href: resource2.$uri}];
    expect(resource.$linkRel('example')).toEqual([resource1, resource2]);
    expect(mockContext.get).toHaveBeenCalledWith(resource1.$uri, undefined);
    expect(mockContext.get).toHaveBeenCalledWith(resource2.$uri, undefined);
  });

  // Loading

  it('loads a resource if it has not been synced yet', function () {
    resource.$load();
    expect(mockContext.httpGet).toHaveBeenCalledWith(resource);
  });

  it('does not load a resource again if it has been synced', function () {
    resource.$syncTime = Date.now();
    resource.$load();
    expect(mockContext.httpGet).not.toHaveBeenCalled();
  });

  it('loads paths of related resources', function (done) {
    var resource1 = new Resource('http://example.com/1', mockContext);
    var resource2 = new Resource('http://example.com/2', mockContext);
    var resource3 = new Resource('http://example.com/3', mockContext);
    mockContext.get.and.callFake(function (uri) {
      switch (uri) {
        case resource.$uri:  return resource;
        case resource1.$uri: return resource1;
        case resource2.$uri: return resource2;
        case resource3.$uri: return resource3;
      }
    });
    mockContext.httpGet.and.callFake(function (resource) {
      return $q.when(resource);
    });

    resource.$links.step1 = {href: resource1.$uri};
    resource1.step2 = resource2.$uri;
    resource2.step3 = resource3;

    resource.$loadPaths({step1: {step2: {}}}).then(function () {
      expect(mockContext.httpGet).toHaveBeenCalledWith(resource);
      expect(mockContext.httpGet).toHaveBeenCalledWith(resource1);
      expect(mockContext.httpGet).toHaveBeenCalledWith(resource2);
      expect(mockContext.httpGet).toHaveBeenCalledWith(resource3);
      done();
    });
    $rootScope.$digest();
  });

  // HTTP

  it('creates HTTP GET request', function () {
    expect(resource.$getRequest()).toEqual({
      method: 'get',
      url: 'http://example.com',
      headers: {Accept: 'application/json'}
    });
  });

  it('delegates HTTP GET request to the context', function () {
    resource.$get();
    expect(mockContext.httpGet).toHaveBeenCalledWith(resource);
  });

  it('creates HTTP PUT request', function () {
    expect(resource.$putRequest()).toEqual({
      method: 'put',
      url: 'http://example.com',
      data: resource,
      headers: {'Content-Type': 'application/json'}
    });
  });

  it('delegates HTTP PUT request to the context', function () {
    resource.$put();
    expect(mockContext.httpPut).toHaveBeenCalledWith(resource);
  });

  it('creates HTTP PATCH request', function () {
    var data = {};
    expect(resource.$patchRequest(data)).toEqual({
      method: 'patch',
      url: 'http://example.com',
      data: data,
      headers: {'Content-Type': 'application/merge-patch+json'}
    });
  });

  it('delegates HTTP PATCH request to the context', function () {
    resource.$patch();
    expect(mockContext.httpPatch).toHaveBeenCalledWith(resource);
  });

  it('creates HTTP DELETE request', function () {
    expect(resource.$deleteRequest()).toEqual({
      method: 'delete',
      url: 'http://example.com'
    });
  });

  it('delegates HTTP DELETE request to the context', function () {
    resource.$delete();
    expect(mockContext.httpDelete).toHaveBeenCalledWith(resource);
  });

  it('creates HTTP POST request', function () {
    function callback(config) {
      return angular.extend(config, {extra: 'value'});
    }

    expect(resource.$postRequest('data', {foo: 'bar'}, callback)).toEqual({
      method: 'post',
      url: 'http://example.com',
      data: 'data',
      headers: {foo: 'bar'},
      extra: 'value'
    });
  });

  it('delegates HTTP POST request to the context', function () {
    resource.$post('data', {foo: 'bar'}, angular.identity);
    expect(mockContext.httpPost).toHaveBeenCalledWith(resource, 'data', {foo: 'bar'}, angular.identity);
  });

  // Updates

  it('updates state, links and profile', function () {
    resource.aVar = 'test';
    resource.$update({foo: 'bar'}, {profile: {href: 'http://example.com/profile'}});
    expect(resource.aVar).toBeUndefined();
    expect(resource.foo).toBe('bar');
    expect(resource.$links).toEqual({self: {href: uri}, profile: {href: 'http://example.com/profile'}});
    expect(resource.$profile).toBe('http://example.com/profile');
  });

  it('requires the href of a link with the "self" relation to equal the resource URI', function () {
    resource.$update({foo: 'bar'}, {self: {href: 'http://example.com'}});
    expect(function () {
      resource.$update({foo: 'qux'}, {self: {href: 'http://example.com/other'}});
    }).toThrowError('Self link href differs: expected "http://example.com", was "http://example.com/other"');
  });

  // Merges

  it('merges state', function () {
    resource.aVar = 'foo';
    resource.bVar = 'joe';
    resource.$merge({aVar: null, bVar: 'john', newVar: 'bar'});

    expect(resource.aVar).toBeUndefined();
    expect(resource.bVar).toBe('john');
    expect(resource.newVar).toBe('bar');
  });

  it('merges nested state', function () {
    resource.nested = {
      aVar: 'foo',
      bVar: 'joe'
    };
    resource.$merge({nested: {
      aVar: null,
      bVar: 'john',
      newVar: 'bar'
    }});

    expect(resource.nested.aVar).toBeUndefined();
    expect(resource.nested.bVar).toBe('john');
    expect(resource.nested.newVar).toBe('bar');
  });

});
<commit_msg>Fix coverage of $merge function<commit_after>'use strict';

describe('Resource', function () {
  beforeEach(module('hypermedia'));

  // Setup

  var $log, $q, $rootScope, Resource, mockContext, uri, resource;

  beforeEach(inject(function (_$log_, _$q_, _$rootScope_, _Resource_) {
    $log = _$log_;
    $q = _$q_;
    $rootScope = _$rootScope_;
    Resource = _Resource_;
    mockContext = jasmine.createSpyObj('mockContext', ['get', 'httpGet', 'httpPut', 'httpDelete', 'httpPost', 'httpPatch']);
    uri = 'http://example.com';
    resource = new Resource(uri, mockContext);
  }));

  // Tests

  it('is initialized correctly', function () {
    expect(resource.$uri).toBe(uri);
    expect(resource.$context).toBe(mockContext);
    expect(resource.$links).toEqual({self: {href: uri}});
    expect(resource.$syncTime).toBeNull();
    expect(resource.$profile).toBeNull();
  });

  // Profiles

  it('adds properties by setting $profile', function () {
    Resource.registerProfile('profile', {test: {value: true}});
    resource.$profile = 'profile';
    expect(resource.test).toBe(true);
  });

  it('adds methods by setting $profile', function () {
    Resource.registerProfile('profile', {test: {value: function () {
      return 'test' + this.number;
    }}});
    resource.$profile = 'profile';
    resource.number = 1;
    expect(resource.test()).toBe('test1');
  });

  it('accepts multiple profiles', function () {
    Resource.registerProfiles({
      profile1: {test1: {value: true}},
      profile2: {test2: {value: true}}
    });
    resource.$profile = ['profile1', 'profile2'];
    expect(resource.test1).toBe(true);
    expect(resource.test2).toBe(true);
  });

  it('can switch profiles', function () {
    Resource.registerProfiles({
      profile1: {test1: {value: true}},
      profile2: {test2: {value: true}}
    });

    resource.$profile = 'profile1';
    expect(resource.test1).toBe(true);
    expect(resource.test2).toBeUndefined();

    resource.$profile = 'profile2';
    expect(resource.test1).toBeUndefined();
    expect(resource.test2).toBe(true);
  });

  // Property hrefs

  it('gets hrefs in properties', function () {
    resource.test_href = 'http://example.com/test';
    expect(resource.$propHref('test_href')).toBe('http://example.com/test');
  });

  it('resolves templated hrefs in properties', function () {
    resource.test_href = 'http://example.com/{id}';
    var result = resource.$propHref('test_href', {id: 'test'});
    expect(result).toBe('http://example.com/test');
  });

  it('resolves arrays of templated hrefs in properties', function () {
    resource.test_href = ['http://example.com/1/{id}', 'http://example.com/2/{id}'];
    var result = resource.$propHref('test_href', {id: 'test'});
    expect(result).toEqual(['http://example.com/1/test', 'http://example.com/2/test']);
  });

  it('returns undefined getting hrefs if property does not exist', function () {
    expect(resource.$propHref('test_href')).toBeUndefined();
  });

  it('follows hrefs in properties', function () {
    var resource2 = new Resource('http://example.com/test', mockContext);
    mockContext.get.and.returnValue(resource2);

    resource.test_href = 'http://example.com/test';
    expect(resource.$propRel('test_href')).toBe(resource2);
    expect(mockContext.get).toHaveBeenCalledWith('http://example.com/test', undefined);
  });

  it('follows templated hrefs in properties', function () {
    var resource2 = new Resource('http://example.com/test', mockContext);
    mockContext.get.and.returnValue(resource2);

    resource.test_href = 'http://example.com/{id}';
    expect(resource.$propRel('test_href', {id: 'test'})).toBe(resource2);
    expect(mockContext.get).toHaveBeenCalledWith('http://example.com/test', undefined);
  });

  it('follows arrays of templated hrefs in properties', function () {
    var resource1 = new Resource('http://example.com/1/test', mockContext);
    var resource2 = new Resource('http://example.com/2/test', mockContext);
    mockContext.get.and.callFake(function (uri) {
      if (uri == 'http://example.com/1/test') {
        return resource1;
      } else {
        return resource2;
      }
    });

    resource.test_href = ['http://example.com/1/{id}', 'http://example.com/2/{id}'];
    expect(resource.$propRel('test_href', {id: 'test'})).toEqual([resource1, resource2]);
    expect(mockContext.get).toHaveBeenCalledWith('http://example.com/1/test', undefined);
    expect(mockContext.get).toHaveBeenCalledWith('http://example.com/2/test', undefined);
  });

  it('returns undefined following hrefs if property does not exist', function () {
    expect(resource.$propRel('test_href')).toBeUndefined();
  });

  it('follow hrefs in properties with a factory', function () {
    var resource2 = new Resource('http://example.com/test', mockContext);
    mockContext.get.and.returnValue(resource2);

    resource.test_href = 'http://example.com/test';
    expect(resource.$propRel('test_href', Resource)).toBe(resource2);
    expect(mockContext.get).toHaveBeenCalledWith('http://example.com/test', Resource);
  });

  // Links

  it('resolves links', function () {
    resource.$links.example = {href: 'http://example.com/test'};
    expect(resource.$linkHref('example')).toBe('http://example.com/test');
  });

  it('resolves templated links', function () {
    resource.$links.example = {href: 'http://example.com/{id}', templated: true};
    var result = resource.$linkHref('example', {id: 'test'});
    expect(result).toBe('http://example.com/test');
  });

  it('warns when resolving templated links without vars', function () {
    resource.$links.example = {href: 'http://example.com/{id}', templated: true};
    resource.$linkHref('example');
    expect($log.warn.logs).toEqual([["Following templated link relation 'example' without variables"]]);
  });

  it('warns when resolving non-templated links with vars', function () {
    resource.$links.example = {href: 'http://example.com/test'};
    resource.$linkHref('example', {foo: 'bar'});
    expect($log.warn.logs).toEqual([["Following non-templated link relation 'example' with variables"]]);
  });

  it('warns when resolving a deprecated link', function () {
    resource.$links.example = {href: 'http://example.com/test', deprecation: 'http://example.com/deprecation'};
    resource.$linkHref('example');
    expect($log.warn.logs).toEqual([["Following deprecated link relation 'example': http://example.com/deprecation"]]);
  });

  it('resolves array links', function () {
    var href1 = 'http://example.com/1';
    var href2 = 'http://example.com/2';
    resource.$links.example = [{href: href1}, {href: href2}];
    expect(resource.$linkHref('example')).toEqual([href1, href2]);
  });

  it('follows links', function () {
    var resource1 = new Resource('http://example.com/1', mockContext);
    mockContext.get.and.returnValue(resource1);
    resource.$links.example = {href: resource1.$uri};
    expect(resource.$linkRel('example')).toBe(resource1);
    expect(mockContext.get).toHaveBeenCalledWith(resource1.$uri, undefined);
  });

  it('follows links with factory', function () {
    var resource1 = new Resource('http://example.com/1', mockContext);
    mockContext.get.and.returnValue(resource1);
    resource.$links.example = {href: resource1.$uri};
    expect(resource.$linkRel('example', Resource)).toBe(resource1);
    expect(mockContext.get).toHaveBeenCalledWith(resource1.$uri, Resource);
  });

  it('follows array links', function () {
    var resource1 = new Resource('http://example.com/1', mockContext);
    var resource2 = new Resource('http://example.com/2', mockContext);
    mockContext.get.and.callFake(function (uri) {
      if (uri == 'http://example.com/1') {
        return resource1;
      } else {
        return resource2;
      }
    });

    resource.$links.example = [{href: resource1.$uri}, {href: resource2.$uri}];
    expect(resource.$linkRel('example')).toEqual([resource1, resource2]);
    expect(mockContext.get).toHaveBeenCalledWith(resource1.$uri, undefined);
    expect(mockContext.get).toHaveBeenCalledWith(resource2.$uri, undefined);
  });

  // Loading

  it('loads a resource if it has not been synced yet', function () {
    resource.$load();
    expect(mockContext.httpGet).toHaveBeenCalledWith(resource);
  });

  it('does not load a resource again if it has been synced', function () {
    resource.$syncTime = Date.now();
    resource.$load();
    expect(mockContext.httpGet).not.toHaveBeenCalled();
  });

  it('loads paths of related resources', function (done) {
    var resource1 = new Resource('http://example.com/1', mockContext);
    var resource2 = new Resource('http://example.com/2', mockContext);
    var resource3 = new Resource('http://example.com/3', mockContext);
    mockContext.get.and.callFake(function (uri) {
      switch (uri) {
        case resource.$uri:  return resource;
        case resource1.$uri: return resource1;
        case resource2.$uri: return resource2;
        case resource3.$uri: return resource3;
      }
    });
    mockContext.httpGet.and.callFake(function (resource) {
      return $q.when(resource);
    });

    resource.$links.step1 = {href: resource1.$uri};
    resource1.step2 = resource2.$uri;
    resource2.step3 = resource3;

    resource.$loadPaths({step1: {step2: {}}}).then(function () {
      expect(mockContext.httpGet).toHaveBeenCalledWith(resource);
      expect(mockContext.httpGet).toHaveBeenCalledWith(resource1);
      expect(mockContext.httpGet).toHaveBeenCalledWith(resource2);
      expect(mockContext.httpGet).toHaveBeenCalledWith(resource3);
      done();
    });
    $rootScope.$digest();
  });

  // HTTP

  it('creates HTTP GET request', function () {
    expect(resource.$getRequest()).toEqual({
      method: 'get',
      url: 'http://example.com',
      headers: {Accept: 'application/json'}
    });
  });

  it('delegates HTTP GET request to the context', function () {
    resource.$get();
    expect(mockContext.httpGet).toHaveBeenCalledWith(resource);
  });

  it('creates HTTP PUT request', function () {
    expect(resource.$putRequest()).toEqual({
      method: 'put',
      url: 'http://example.com',
      data: resource,
      headers: {'Content-Type': 'application/json'}
    });
  });

  it('delegates HTTP PUT request to the context', function () {
    resource.$put();
    expect(mockContext.httpPut).toHaveBeenCalledWith(resource);
  });

  it('creates HTTP PATCH request', function () {
    var data = {};
    expect(resource.$patchRequest(data)).toEqual({
      method: 'patch',
      url: 'http://example.com',
      data: data,
      headers: {'Content-Type': 'application/merge-patch+json'}
    });
  });

  it('delegates HTTP PATCH request to the context', function () {
    resource.$patch();
    expect(mockContext.httpPatch).toHaveBeenCalledWith(resource);
  });

  it('creates HTTP DELETE request', function () {
    expect(resource.$deleteRequest()).toEqual({
      method: 'delete',
      url: 'http://example.com'
    });
  });

  it('delegates HTTP DELETE request to the context', function () {
    resource.$delete();
    expect(mockContext.httpDelete).toHaveBeenCalledWith(resource);
  });

  it('creates HTTP POST request', function () {
    function callback(config) {
      return angular.extend(config, {extra: 'value'});
    }

    expect(resource.$postRequest('data', {foo: 'bar'}, callback)).toEqual({
      method: 'post',
      url: 'http://example.com',
      data: 'data',
      headers: {foo: 'bar'},
      extra: 'value'
    });
  });

  it('delegates HTTP POST request to the context', function () {
    resource.$post('data', {foo: 'bar'}, angular.identity);
    expect(mockContext.httpPost).toHaveBeenCalledWith(resource, 'data', {foo: 'bar'}, angular.identity);
  });

  // Updates

  it('updates state, links and profile', function () {
    resource.aVar = 'test';
    resource.$update({foo: 'bar'}, {profile: {href: 'http://example.com/profile'}});
    expect(resource.aVar).toBeUndefined();
    expect(resource.foo).toBe('bar');
    expect(resource.$links).toEqual({self: {href: uri}, profile: {href: 'http://example.com/profile'}});
    expect(resource.$profile).toBe('http://example.com/profile');
  });

  it('requires the href of a link with the "self" relation to equal the resource URI', function () {
    resource.$update({foo: 'bar'}, {self: {href: 'http://example.com'}});
    expect(function () {
      resource.$update({foo: 'qux'}, {self: {href: 'http://example.com/other'}});
    }).toThrowError('Self link href differs: expected "http://example.com", was "http://example.com/other"');
  });

  // Merges

  it('merges state', function () {
    resource.aVar = 'foo';
    resource.bVar = 'joe';
    resource.$merge({aVar: null, bVar: 'john', newVar: 'bar'});

    expect(resource.aVar).toBeUndefined();
    expect(resource.bVar).toBe('john');
    expect(resource.newVar).toBe('bar');
  });

  it('merges nested state', function () {
    resource.nested = {
      aVar: 'foo',
      bVar: 'joe'
    };
    resource.$merge({
      nested: {
        aVar: null,
        bVar: 'john',
        newVar: 'bar'
      },
      newNested: {
        newVar: 'qux'
      }
    });

    expect(resource.nested.aVar).toBeUndefined();
    expect(resource.nested.bVar).toBe('john');
    expect(resource.nested.newVar).toBe('bar');
    expect(resource.newNested.newVar).toBe('qux');
  });

});
<|endoftext|>


<commit_before>import _ from 'lodash'
import dbg from 'debug'
import async from 'async'
import {params, normalize} from './query'
import filter from './odata/filter'
import * as errors from 'netiam-errors'

const debug = dbg('netiam:rest:resource')

/**
 * A main resource has a list of subresources.
 *
 * Example:
 *
 * ```javascript
 * app.get('/users/:user/projects'
 *   api()
 *    .rest({
 *      idParam: 'user',
 *      collection: User
 *      relationship: hasMany(Project, {field: 'projects'})
 *    })
 * )
 * ```
 *
 * @type {Symbol}
 */
export const ONE_TO_MANY = Symbol('one-to-many')

/**
 * A subresource has a reference to a main resource.
 *
 * Example:
 *
 * ```javascript
 * app.get('/projects/:project/campaigns'
 *   api()
 *    .rest({
 *      collection: Campaign
 *      relationship: belongsTo(Project, {field: 'project', param: 'project'})
 *    })
 * )
 * ```
 *
 * @type {Symbol}
 */
export const MANY_TO_ONE = Symbol('many-to-one')

export default function resource(spec) {
  const {collection} = spec
  const {relationshipField} = spec
  const {relationshipCollection} = spec
  const {map} = spec
  let {relationship} = spec
  let {idParam} = spec
  let {idField} = spec

  idParam = idParam || 'id'
  idField = idField || '_id'
  relationship = relationship || ONE_TO_MANY

  function listHandle(q, query, resolve, reject) {
    if (query.expand.length > 0) {
      q = q.populate(query.expand.join(' '))
    }

    q = q.sort(query.sort)

    q = q.skip(query.offset)
    if (query.limit && query.limit !== -1) {
      q = q.limit(query.limit)
    }

    q.exec((err, documents) => {
      if (err) {
        debug(err)
        return reject(
          errors.internalServerError(
            err,
            [errors.Codes.E3000]))
      }

      if (!_.isArray(documents)) {
        return resolve([])
      }

      resolve(_.map(documents, document => {
        return document.toObject()
      }))
    })
  }

  /**
   * Fetches database for a list of documents. As all public resource methods,
   * this one provides several predefined parameters someone can use to
   * manipulate the resultset.
   *
   * Example:
   * GET /users?filter=email EQ
   * 'box@mail.tld'&sort=email-&limit=10&offset=1&expand=messages
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function list(req) {
    return new Promise((resolve, reject) => {
      const query = normalize({
        req,
        idField
      })

      let queryFilter = filter(query.filter)

      if (relationship === MANY_TO_ONE &&
        relationshipField &&
        relationshipCollection &&
        map) {

        const key = Object.keys(map)[0]
        const val = req.params[map[key].substr(1)]

        collection
          .findOne({[key]: val})
          .select(relationshipField)
          .exec((err, doc) => {
            if (err) {
              debug(err)
              return reject(
                errors.internalServerError(
                  err,
                  [errors.Codes.E3000]))
            }

            if (!doc) {
              return resolve([])
            }

            let q
            try {
              q = relationshipCollection.find(queryFilter.toObject())
            } catch (err) {
              debug(err)
              reject(
                errors.badRequest(
                  err, [errors.Codes.E3000]))
            }

            q = q.where('_id').in(doc[relationshipField])

            listHandle(q, query, resolve, reject)
          })

        return
      }

      if (relationship === ONE_TO_MANY) {
        queryFilter = queryFilter.where(params({
          req,
          map
        }))

        let q
        try {
          q = collection.find(queryFilter.toObject())
        } catch (err) {
          debug(err)
          reject(
            errors.badRequest(
              err,
              [errors.Codes.E3000]))
        }

        return listHandle(q, query, resolve, reject)
      }
    })
  }

  function readHandle(q, query, resolve, reject) {
    if (query.expand.length > 0) {
      q = q.populate(query.expand.join(' '))
    }

    q.exec((err, document) => {
      if (err) {
        debug(err)
        return reject(
          errors.internalServerError(
            err,
            [errors.Codes.E3000]))
      }

      if (!document) {
        return reject(
          errors.notFound(
            'Document not found',
            [errors.Codes.E3000]))
      }

      resolve(document.toObject())
    })
  }

  /**
   * Fetches a single document from database.
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function read(req) {
    return new Promise((resolve, reject) => {
      const query = normalize({
        req,
        idField
      })

      if (relationship === MANY_TO_ONE &&
        relationshipField &&
        relationshipCollection &&
        map) {

        const key = Object.keys(map)[0]
        const val = req.params[map[key].substr(1)]

        collection
          .findOne({[key]: val})
          .select(relationshipField)
          .exec((err, doc) => {
            if (err) {
              debug(err)
              return reject(
                errors.internalServerError(
                  err,
                  [errors.Codes.E3000]))
            }

            if (!doc) {
              return reject(
                errors.notFound(
                  'Document not found',
                  [errors.Codes.E3000]))
            }

            const queryOptions = {[idField]: req.params[idParam]}

            const q = relationshipCollection.findOne(queryOptions)

            readHandle(q, query, resolve, reject)
          })

        return
      }

      if (relationship === ONE_TO_MANY) {
        const queryOptions = {[idField]: req.params[idParam]}

        const q = collection.findOne(queryOptions)

        return readHandle(q, query, resolve, reject)
      }

    })
  }

  /**
   * Creates a new document and saves it to the database.
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function create(req) {
    return new Promise((resolve, reject) => {
      const query = normalize({
        req,
        idField
      })

      collection
        .create(req.body, (err, document) => {
          if (err) {
            debug(err)
            if (err.code === 11000) {
              return reject(
                errors.badRequest(
                  err,
                  [errors.Codes.E1001]))
            }

            if (err.name === 'ValidationError') {
              const errList = []

              _.forEach(err.errors, error => {
                const modError = Object.assign({
                  path: error.path,
                  value: error.value
                }, errors.Codes.E3002)

                errList.push(modError)
              })

              return reject(
                errors.badRequest(
                  err,
                  errList))
            }

            return reject(
              errors.internalServerError(
                err,
                [errors.Codes.E3000]))
          }

          if (!document) {
            return reject(
              errors.internalServerError(
                'Document could not be created',
                [errors.Codes.E3000]))
          }

          if (query.expand.length > 0) {
            document.populate(query.expand.join(' '), err => {
              if (err) {
                debug(err)
                return reject(
                  errors.internalServerError(
                    err,
                    [errors.Codes.E3000]))
              }

              resolve(document.toJSON())
            })
          } else {
            resolve(document.toJSON())
          }
        })
    })
  }

  function updateExpandedPath(document, path, payload) {
    return new Promise((resolve, reject) => {
      const data = document[path]
      const update = payload[path]

      if (_.isArray(data)) {
        async.each(
          data,
          (node, done) => {
            const index = data.indexOf(node)
            node
              .merge(update[index])
              .save(done)
          },
          (err) => {
            if (err) {
              debug(err)
              return reject(
                errors.internalServerError(
                  err,
                  [errors.Codes.E3000]))
            }

            return resolve()
          })
        return
      }

      if (_.isObject(data)) {
        data
          .merge(update)
          .save(err => {
            if (err) {
              return reject(
                errors.internalServerError(
                  err,
                  [errors.Codes.E3000]))
            }

            resolve()
          })
      }
    })
  }

  /**
   * Modifies a single and existing document
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function update(req) {
    const query = normalize({
      req,
      idField
    })

    function updateExpanded(document) {
      if (!document) {
        throw errors.notFound('Document not found')
      }
      const ops = _.map(query.expand, path => {
        return updateExpandedPath(document, path, req.body)
      })

      return Promise
        .all(ops)
        .then(() => {
          return document
        })
    }

    return new Promise((resolve, reject) => {
      if (relationship === MANY_TO_ONE &&
        relationshipField &&
        relationshipCollection &&
        map) {

        const key = Object.keys(map)[0]
        const val = req.params[map[key].substr(1)]

        collection
          .findOne({[key]: val})
          .select(relationshipField).exec()
          .then(doc => {
            if (!doc) {
              throw errors.notFound('Document not found')
            }

            const queryOptions = {[idField]: req.params[idParam]}

            let q = relationshipCollection.findOne(queryOptions)

            if (query.expand.length > 0) {
              q = q.populate(query.expand.join(' '))
            }

            return q.exec()
          })
          .then(updateExpanded)
          .then(document => {
            return document
              .merge(req.body)
              .save()
          })
          .then(document => {
            if (req.query.expand) {
              return document
                .populate(query.expand.join(' '), err => {
                  if (err) {
                    return reject(
                      errors.internalServerError(
                        err,
                        [errors.Codes.E3000]))
                  }

                  resolve(document.toJSON())
                })
            }
            resolve(document.toJSON())
          })
          .then(null, err => {
            debug(err)

            if (err.name === 'ValidationError') {
              const errList = []

              _.forEach(err.errors, error => {
                const modError = Object.assign({
                  path: error.path,
                  value: error.value
                }, errors.Codes.E3002)

                errList.push(modError)
              })

              return reject(
                errors.badRequest(
                  err,
                  errList))
            }

            reject(
              errors.internalServerError(
                err,
                [errors.Codes.E3000])
            )
          })
        return
      }

      if (relationship === ONE_TO_MANY) {
        const queryOptions = {[idField]: req.params[idParam]}

        const q = collection.findOne(queryOptions)

        q.exec((err, document) => {
          if (err) {
            debug(err)
            return reject(
              errors.internalServerError(
                err,
                [errors.Codes.E3000]))
          }
          if (!document) {
            return reject(
              errors.notFound(
                'Document not found',
                [errors.Codes.E3000]))
          }

          document
            .merge(req.body)
            .save(err => {
              if (err) {
                debug(err)

                if (err.name === 'ValidationError') {
                  const errList = []

                  _.forEach(err.errors, error => {
                    const modError = Object.assign({
                      path: error.path,
                      value: error.value
                    }, errors.Codes.E3002)

                    errList.push(modError)
                  })

                  return reject(
                    errors.badRequest(
                      err,
                      errList))
                }

                return reject(
                  errors.internalServerError(
                    err,
                    [errors.Codes.E3000]))
              }

              if (req.query.expand) {
                document.populate(query.expand.join(' '), err => {
                  if (err) {
                    return reject(
                      errors.internalServerError(
                        err,
                        [errors.Codes.E3000]))
                  }

                  resolve(document.toJSON())
                })
              } else {
                resolve(document.toJSON())
              }
            })
        })
      }

    })
  }

  /**
   * Deletes an existing document from database
   *
   * @param {Object} req The request object
   * @returns {Promise}
   */
  function remove(req) {
    return new Promise((resolve, reject) => {
      const queryOptions = {[idField]: req.params[idParam]}

      collection
        .findOneAndRemove(queryOptions)
        .exec((err, documents) => {
          if (err) {
            debug(err)
            return reject(
              errors.internalServerError(
                err,
                [errors.Codes.E3000]))
          }
          if (!documents) {
            return reject(
              errors.notFound(
                'Document not found',
                [errors.Codes.E3000]))
          }

          resolve()
        })

    })
  }

  return Object.freeze({
    list,
    read,
    create,
    update,
    remove
  })
}
<commit_msg>refactor: REST resource methods have been extracted<commit_after>import _ from 'lodash'
import dbg from 'debug'
import async from 'async'
import {params, normalize} from './query'
import {ONE_TO_MANY, MANY_TO_ONE} from './relationships'
import filter from './odata/filter'
import * as errors from 'netiam-errors'
import list from './methods/list'
import create from './methods/create'
import read from './methods/read'
import update from './methods/update'
import remove from './methods/remove'

const debug = dbg('netiam:rest:resource')

/**
 * A main resource has a list of subresources.
 *
 * Example:
 *
 * ```javascript
 * app.get('/users/:user/projects'
 *   api()
 *    .rest({
 *      idParam: 'user',
 *      collection: User
 *      relationship: hasMany(Project, {field: 'projects', param: 'user'})
 *    })
 * )
 * ```
 *
 * @param {Object} Model
 * @param {Object} spec
 * @returns {Object}
 */
export function hasMany(Model, spec) {
  if (!Model) {
    throw errors.internalServerError(
      'A "hasMany" relationship must must define a target "Model"')
  }

  if (!spec.field) {
    throw errors.internalServerError(
      'A "hasMany" relationship must define option "field"')
  }

  return Object.assign({
    idField: '_id',
    type: ONE_TO_MANY,
    Model: Model
  }, spec)
}

/**
 * A subresource has a reference to a main resource.
 *
 * Example:
 *
 * ```javascript
 * app.get('/projects/:project/campaigns'
 *   api()
 *    .rest({
 *      collection: Campaign
 *      relationship: belongsTo(Project, {field: 'project', param: 'project'})
 *    })
 * )
 * ```
 *
 * @param {Object} Model
 * @param {Object} spec
 * @returns {Object}
 */
export function belongsTo(Model, spec) {
  if (!Model) {
    throw errors.internalServerError(
      'A "belongsTo" relationship must must define a base "Model"')
  }

  if (!spec.field) {
    throw errors.internalServerError(
      'A "belongsTo" relationship must define option "field"')
  }

  return Object.assign({
    idField: '_id',
    type: MANY_TO_ONE,
    Model: Model
  }, spec)
}

export default function resource(spec) {
  spec.idParam = spec.idParam || 'id'
  spec.idField = spec.idField || '_id'

  return Object.freeze({
    list: (req) => list(Object.assign({req}, spec)),
    create: (req) => create(Object.assign({req}, spec)),
    read: (req) => read(Object.assign({req}, spec)),
    update: (req) => update(Object.assign({req}, spec)),
    remove: (req) => remove(Object.assign({req}, spec))
  })
}
<|endoftext|>


<commit_before>import {REPLACE, buildNavigationPlan} from './navigation-plan';

export class RouteLoader {
  loadRoute(config){
    throw Error('Route loaders must implment "loadRoute(config)".');
  }
}

export class LoadRouteStep {
  static inject(){ return [RouteLoader]; }
  constructor(routeLoader){
    this.routeLoader = routeLoader;
  }

  run(navigationContext, next) {
    return loadNewRoute(this.routeLoader, navigationContext)
      .then(next)
      .catch(next.cancel);
  }
}

export function loadNewRoute(routeLoader, navigationContext) {
  var toLoad = determineWhatToLoad(navigationContext);
  var loadPromises = toLoad.map(current => loadRoute(
    routeLoader,
    current.navigationContext,
    current.viewPortPlan
    )
  );

  return Promise.all(loadPromises);
}

function determineWhatToLoad(navigationContext, toLoad) {
  var plan = navigationContext.plan;
  var next = navigationContext.nextInstruction;

  toLoad = toLoad || [];

  for (var viewPortName in plan) {
    var viewPortPlan = plan[viewPortName];

    if (viewPortPlan.strategy == REPLACE) {
      toLoad.push({
        viewPortPlan: viewPortPlan,
        navigationContext: navigationContext
      });

      if (viewPortPlan.childNavigationContext) {
        determineWhatToLoad(viewPortPlan.childNavigationContext, toLoad);
      }
    } else {
      var viewPortInstruction = next.addViewPortInstruction(
          viewPortName,
          viewPortPlan.strategy,
          viewPortPlan.prevModuleId,
          viewPortPlan.prevComponent
          );

      if (viewPortPlan.childNavigationContext) {
        viewPortInstruction.childNavigationContext = viewPortPlan.childNavigationContext;
        determineWhatToLoad(viewPortPlan.childNavigationContext, toLoad);
      }
    }
  }

  return toLoad;
}

function loadRoute(routeLoader, navigationContext, viewPortPlan) {
  var moduleId = viewPortPlan.config.moduleId;
  var next = navigationContext.nextInstruction;

  return resolveComponent(
    routeLoader,
    navigationContext.router,
    viewPortPlan
    ).then(component => {

    var viewPortInstruction = next.addViewPortInstruction(
      viewPortPlan.name,
      viewPortPlan.strategy,
      moduleId,
      component
      );

    var controller = component.executionContext;

    if (controller.router) {
      var path = next.getWildcardPath();

      return controller.router.createNavigationInstruction(path, next)
        .then(childInstruction => {
          viewPortPlan.childNavigationContext = controller.router
            .createNavigationContext(childInstruction);

          return buildNavigationPlan(viewPortPlan.childNavigationContext)
            .then(childPlan => {
              viewPortPlan.childNavigationContext.plan = childPlan;
              viewPortInstruction.childNavigationContext = viewPortPlan.childNavigationContext;

              return loadNewRoute(routeLoader, viewPortPlan.childNavigationContext);
            });
        });
    }
  });
}

function resolveComponent(routeLoader, router, viewPortPlan) {
  var possibleRouterViewPort = router.viewPorts[viewPortPlan.name];

  return routeLoader.loadRoute(viewPortPlan.config).then(type => {
    return new Promise((resolve, reject) => {
      function createChildRouter() {
        return router.createChild();
      }

      function getComponent(routerViewPort) {
        try {
          resolve(routerViewPort.getComponent(type, createChildRouter));
        } catch(error){
          reject(error);
        }
      }

      if (possibleRouterViewPort) {
        getComponent(possibleRouterViewPort);
      } else {
        router.viewPorts[viewPortPlan.name] = getComponent;
      }
    });
  });
}<commit_msg>feat(route-loading): enable async view port component creation<commit_after>import {REPLACE, buildNavigationPlan} from './navigation-plan';

export class RouteLoader {
  loadRoute(config){
    throw Error('Route loaders must implment "loadRoute(config)".');
  }
}

export class LoadRouteStep {
  static inject(){ return [RouteLoader]; }
  constructor(routeLoader){
    this.routeLoader = routeLoader;
  }

  run(navigationContext, next) {
    return loadNewRoute(this.routeLoader, navigationContext)
      .then(next)
      .catch(next.cancel);
  }
}

export function loadNewRoute(routeLoader, navigationContext) {
  var toLoad = determineWhatToLoad(navigationContext);
  var loadPromises = toLoad.map(current => loadRoute(
    routeLoader,
    current.navigationContext,
    current.viewPortPlan
    )
  );

  return Promise.all(loadPromises);
}

function determineWhatToLoad(navigationContext, toLoad) {
  var plan = navigationContext.plan;
  var next = navigationContext.nextInstruction;

  toLoad = toLoad || [];

  for (var viewPortName in plan) {
    var viewPortPlan = plan[viewPortName];

    if (viewPortPlan.strategy == REPLACE) {
      toLoad.push({
        viewPortPlan: viewPortPlan,
        navigationContext: navigationContext
      });

      if (viewPortPlan.childNavigationContext) {
        determineWhatToLoad(viewPortPlan.childNavigationContext, toLoad);
      }
    } else {
      var viewPortInstruction = next.addViewPortInstruction(
          viewPortName,
          viewPortPlan.strategy,
          viewPortPlan.prevModuleId,
          viewPortPlan.prevComponent
          );

      if (viewPortPlan.childNavigationContext) {
        viewPortInstruction.childNavigationContext = viewPortPlan.childNavigationContext;
        determineWhatToLoad(viewPortPlan.childNavigationContext, toLoad);
      }
    }
  }

  return toLoad;
}

function loadRoute(routeLoader, navigationContext, viewPortPlan) {
  var moduleId = viewPortPlan.config.moduleId;
  var next = navigationContext.nextInstruction;

  return resolveComponent(
    routeLoader,
    navigationContext.router,
    viewPortPlan
    ).then(component => {

    var viewPortInstruction = next.addViewPortInstruction(
      viewPortPlan.name,
      viewPortPlan.strategy,
      moduleId,
      component
      );

    var controller = component.executionContext;

    if (controller.router) {
      var path = next.getWildcardPath();

      return controller.router.createNavigationInstruction(path, next)
        .then(childInstruction => {
          viewPortPlan.childNavigationContext = controller.router
            .createNavigationContext(childInstruction);

          return buildNavigationPlan(viewPortPlan.childNavigationContext)
            .then(childPlan => {
              viewPortPlan.childNavigationContext.plan = childPlan;
              viewPortInstruction.childNavigationContext = viewPortPlan.childNavigationContext;

              return loadNewRoute(routeLoader, viewPortPlan.childNavigationContext);
            });
        });
    }
  });
}

function resolveComponent(routeLoader, router, viewPortPlan) {
  var possibleRouterViewPort = router.viewPorts[viewPortPlan.name];

  return routeLoader.loadRoute(viewPortPlan.config).then(type => {
    return new Promise((resolve, reject) => {
      function createChildRouter() {
        return router.createChild();
      }

      function getComponent(routerViewPort) {
        routerViewPort.getComponent(type, createChildRouter, viewPortPlan.config)
                      .then(resolve)
                      .catch(error);
      }

      if (possibleRouterViewPort) {
        getComponent(possibleRouterViewPort);
      } else {
        router.viewPorts[viewPortPlan.name] = getComponent;
      }
    });
  });
}<|endoftext|>


<commit_before>// Routes
import Landing from '@/components/Landing'
import Welcome from '@/components/Welcome'
import Login from '@/components/auth/Login'
import UpdateProfile from '@/components/auth/UpdateProfile'
import App from '@/components/App'
import NotFound from '@/components/errors/404'

const routes = [{
        path: '/',
        component: Landing,
        children: [{
                path: '/',
                component: Welcome,
                name: 'Welcome'
            },
            {
                path: '/login',
                component: Login,
                name: 'login'
            },
            {
                path: '/update-profile',
                component: UpdateProfile,
                name: 'Profile Update'
            },
            {
                path: '/app',
                component: App,
                name: 'Main app'
            }
        ]
    },
    {
        path: '*',
        components: NotFound
    }
]

export default routes;<commit_msg>update routes<commit_after>// Routes
import Landing from '@/components/Landing'
import Welcome from '@/components/Welcome'
import Login from '@/components/auth/Login'
import UpdateProfile from '@/components/auth/UpdateProfile'
import App from '@/components/App'
import NotFound from '@/components/errors/404'

const routes = [{
        path: '/',
        component: Landing,
        children: [{
                path: '/',
                component: Welcome,
                name: 'Welcome'
            },
            {
                path: '/login',
                component: Login,
                name: 'login'
            },
            {
                path: '/update-profile',
                component: UpdateProfile,
                name: 'Profile Update',
                meta: {
                    auth: true
                }
            },
            {
                path: '/app',
                component: App,
                name: 'Main app',
                meta: {
                    auth: true
                }
            }
        ]
    },
    {
        path: '*',
        components: NotFound
    }
]

export default routes;<|endoftext|>


<commit_before>import compose from 'koa-compose';
import routerAuth from './router-auth';
import routerUser from './router-user';

//We need to convert list of separated route in to the one
let routers = [
  routerAuth,
  routerUser
];

//So we extract the middelware from router
let middleware = [];
routers.forEach((router) => {
  middleware.push(router.routes())
  middleware.push(router.allowedMethods())
});

//Then put them into one router. Magic here!
export default compose(middleware);
<commit_msg>auto import new route<commit_after>import glob from 'glob';
import path from 'path';
import compose from 'koa-compose';

//We need to convert list of separated route in to the one
//The block below does dynamic import all routers inside current directory.
let routers = [];
glob.sync( path.join(__dirname,'router-*.js') ).forEach( function( file ) {
  let r = require( path.resolve( file ) ).default;
  routers.push(r);
});

//So we extract the middelware from router
let middleware = [];
routers.forEach((router) => {
  middleware.push(router.routes())
  middleware.push(router.allowedMethods())
});

//Then put them into one router. Magic here!
export default compose(middleware);
<|endoftext|>


<commit_before>/**
 * @eslint-rule
 * @author nate-wilkins <nwilkins2012@gmail.com> (https://github.com/nate-wilkins)
 * @license MIT (c) 2014
 *
 * @description
 * Rule to determine if any specs/tests are being excluded
 */
'use strict';

var Errors = require('./../errors').Exclude,
  Common = require('./../common');

var defaultConfig = require('./../default-config');

/**
 *
 * @type {exports}
 */
var rule = module.exports = function (context) {
  var config = defaultConfig(context.options[0]);

  return {
    'Identifier': function (node) {
      Common.Identifiers.Exclude.concat(config.exclude).some(function (name) {
        if (node.name !== name) { return; }
        context.report(node, Errors.ToHaveNone);
      });
    },
    'MemberExpression': function (node) {
      if (!node.object || node.object.type !== 'Identifier') { return; }
      if (!node.property || node.property.type !== 'Identifier') { return; }

      if (Common.Identifiers.Original.concat(config.original).some(function (name) {
        return node.object.name === name;
      }) && Common.Identifiers.Skip.concat(config.skip).some(function (name) {
        return node.property.name === name;
      })) { context.report(node, Errors.ToHaveNone); }
    },
    'CallExpression': function (node) {
      if (node.callee.type !== 'Identifier') { return; }
      if (node.arguments.length === 2) { return; }

      if (Common.Identifiers.Original.concat(config.original).some(function (name) {
        return node.callee.name.indexOf(name) === 0;
      })) { context.report(node, Errors.ToHaveNone); }
    }
  };
};

rule.Errors = Errors;
<commit_msg>Fix empty describe/it validation<commit_after>/**
 * @eslint-rule
 * @author nate-wilkins <nwilkins2012@gmail.com> (https://github.com/nate-wilkins)
 * @license MIT (c) 2014
 *
 * @description
 * Rule to determine if any specs/tests are being excluded
 */
'use strict';

var Errors = require('./../errors').Exclude,
  Common = require('./../common');

var defaultConfig = require('./../default-config');

/**
 *
 * @type {exports}
 */
var rule = module.exports = function (context) {
  var config = defaultConfig(context.options[0]);

  return {
    'Identifier': function (node) {
      Common.Identifiers.Exclude.concat(config.exclude).some(function (name) {
        if (node.name !== name) { return; }
        context.report(node, Errors.ToHaveNone);
      });
    },
    'MemberExpression': function (node) {
      if (!node.object || node.object.type !== 'Identifier') { return; }
      if (!node.property || node.property.type !== 'Identifier') { return; }

      if (Common.Identifiers.Original.concat(config.original).some(function (name) {
        return node.object.name === name;
      }) && Common.Identifiers.Skip.concat(config.skip).some(function (name) {
        return node.property.name === name;
      })) { context.report(node, Errors.ToHaveNone); }
    },
    'CallExpression': function (node) {
      if (node.callee.type !== 'Identifier') { return; }
      if (node.arguments.length === 2) { return; }

      if (Common.Identifiers.Original.concat(config.original).some(function (name) {
        return node.callee.name === name;
      })) { context.report(node, Errors.ToHaveNone); }
    }
  };
};

rule.Errors = Errors;
<|endoftext|>


<commit_before>var corpo = document.querySelector('.flash-run');
var flash = document.createElement('span');
let tam = corpo.offsetWidth;

//Faz o elemento sumir ou não
//1 = true, 2 = false
let vanish = 2;

flash.className = 'flash';

Object.assign(flash.style, {
    'background': 'url(https://cotts.tech/flash.svg) center no-repeat',
    'background-size': '50px 50px',
    'width': '50px',
    'height': '50px',
    'position': 'absolute'

});

corpo.appendChild(flash);
var flasharr = [];
tam = tam * 0.80;
var count;

var path = 0;
var i = 0;

function createShadow(size) {
    return new Promise((resolve, reject) => {
        for (let i = 0; i <= size; i++) flasharr.push(flash.cloneNode(true));
    });
};
function createMotion(shadow) {
    return new Promise((resolve, reject) => {
        shadow.forEach((element, index, ar) => {

            element.style.left = index * 1.4 + 'px';
            element.style.opacity = (index * 0.0001);
        });
    })
}

function moveSpeedster(speedster, position) {
    return new Promise((resolve, reject) =>{
        speedster.style.left = position * 1.4 + 'px';
    });
};

function addShadow(element) {
    corpo.appendChild(element);
}

function removeShadow(element, position, vanish){
    return new Promise((resolve, reject)=>{
        setTimeout(() => element.removeChild(element.childNodes[vanish]), position * 0.8);
    })
}

function runSpeedster(element) {
    return new Promise((resolve,reject)=>{
        element.forEach((element, index, arr) =>{
            moveSpeedster(flash, index)
                .then(addShadow(element))
                .then(removeShadow(corpo, index, vanish));
        });
    })
}

setTimeout(() => {
    createShadow(tam)
        .then(createMotion(flasharr))
        .then(runSpeedster(flasharr));
}, 3000);

<commit_msg>increased script with options and some bug fix<commit_after>let runnerShadow = [];
let size = 0;
let blurSize = 0;
let runSize = 0;
let show = -100;
let traveled = 0;
let last = false;
let textRunning;
var runtext;
function setDetails(config) {
    details = config;

    details.runner = config.runner || 'standard'
    details.howBig = config.howBig || 50;
    details.disappear = config.disappear || false;
    details.speed = config.speed || 1;

    return Promise.resolve(details);
};

var createRunner = async (racePath,runText, config) => {
    let path = document.querySelector(racePath);
    runtext = document.querySelector(runText);
    let sizeOfPath = path.offsetWidth;
    let speedster = document.createElement('span');
    textRunning = path.offsetWidth * -1;
    setDetails(config)
        .then((properties) => {
            details = properties;
            Object.assign(speedster.style, {
                'background': `url(img/${details.runner}.svg) center no-repeat`,
                'background-size': `${details.howBig}px ${details.howBig}px`,
                'width': `${details.howBig}px`,
                'height': `${details.howBig}px`,
                'position': 'absolute',
                'opacity': 0,
                'left': `${path.offsetLeft}px`
            });
            Object.assign(speedster, {
                config: {
                    speed: details.speed,
                    path: path,
                    init: path.offsetLeft,
                    sizeOfPath: sizeOfPath,
                    disappear: details.disappear,
                    howBig: details.howBig
                },

                className: 'speedster'
            });
            Object.assign(runtext.style, {
                'left' : (path.offsetWidth - details.howBig) * -1 +'px',
                'position' : 'absolute'
            });

            path.appendChild(speedster);
        });

    return await Promise.resolve(speedster);
};

let insertShadow = (el) => {
    if (size === el.config.sizeOfPath - el.config.howBig) {
        console.log('inseriu as sombras');

        return;
    }
    runnerShadow.push({
        span: el.cloneNode(true),
        config: el.config
    });
    size++;
    insertShadow(el);

    return Promise.resolve(runnerShadow);
}

let createBlur = (shadow) => {
    if (blurSize === shadow.length) {
        console.log('fez o blur');
        return;
    }
    Object.assign(shadow[blurSize].span.style, {
        'left': `${shadow[blurSize].config.init + blurSize}px`,
        'opacity': blurSize * 0.0001,
        'z-index': blurSize * -1
    });
    blurSize++;
    createBlur(shadow);
    // await sleep(200);
    return Promise.resolve(shadow);
}

let run = (el, loop) => {
    let x = loop;
    // speed of the object
    var vel = el[0].config.speed;

    //if the object will disappear in the end of the path
    var disappear = el[0].config.disappear;

    //max distance to run = iniial path + pathsize
    var max =  el[0].config.sizeOfPath;

    //initial position of the path
    var init = el[0].config.init;

    //the object that will be include the object and shadows
    var path = el[0].config.path;

    //actual position of the object
    var posicao = path.firstElementChild.getBoundingClientRect().left;

    //counter of the children objects inside the path way
    var count = path.childElementCount;

    //if get to the end, will make the next check or end
    if (x >= max) {
        console.log('correu');
        return;
    }

    //if the way is in the last loop, change the velocity to not pass trough the path
    if (posicao >= (el.length - vel - 2)) {
        vel = max - posicao - el[0].config.howBig - 1;
        textRunning = textRunning + 2;
        last = true;
    }
    for (let i = 0; i <= vel; i++) {
        if (typeof x === 'undefined') {
            x = 0;
        }
        count = path.childElementCount;

        if (posicao >= (max / 2) && count > vel && disappear) {

            if (count === 1) {
                path.removeChild(path.childNodes[1]);
                console.log('correu e sumiu');
                return;
            } else {
                path.removeChild(path.childNodes[1]);
                path.removeChild(path.childNodes[1]);
            }

        }
        if (posicao >= (max / 2) && count > vel && !disappear) {
            if (count === 2) {
                console.log('correu e ficou');
                path.removeChild(path.childNodes[2]);
                return;
            } else {

                path.removeChild(path.childNodes[2]);
                path.removeChild(path.childNodes[2]);
            }
        }
        path.firstElementChild.style.left = `${posicao + i}px`;
        path.appendChild(runnerShadow[x + i].span);
        runtext.style.left = `${textRunning + x + i +1}px`;
    }
    requestAnimationFrame(() => run(el, posicao - el[0].config.init + vel));
    return Promise.resolve(el);
};

var showRunner = async (el) => {
    if (show === 0) {
        return;
    };
    el.style.opacity = 1 + (show / 100);
    show++;
    requestAnimationFrame(() => showRunner(el));
    await sleep(2000);
    return Promise.resolve(el);
}

async function sleep(long) {
    function timeout(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }
    await timeout(long);
}

createRunner('.race-path','.run-text', { 'runner': 'standard', 'speed': 60, 'disappear': true }).then(showRunner).then(insertShadow).then(createBlur).then(run);<|endoftext|>


<commit_before>import Joi from "joi"

export default Joi.object().keys({
    name: Joi.string().min(3).max(32).label("Alert title").regex(/^[a-zA-Z0-9şŞıİçÇöÖüÜğĞ_\\s]+$/).options({ language: { string:{ regex: { base: 'must be alphanumeric'}}, any: { empty: 'length must be at least 3 characters long'}}}),
    requiredCriteria: Joi.string().required().max(140).label("Must include").regex(/^[a-zA-Z0-9şŞıİçÇöÖüÜğĞ_\\s]+$/).options({ language: { string:{ regex: { base: 'must be alphanumeric' }}}}),
    niceTohaveCriteria: Joi.string().max(140).empty("").label("Can include").regex(/^[a-zA-Z0-9şŞıİçÇöÖüÜğĞ_\\s]+$/).options({ language: { string:{ regex: { base: 'must be alphanumeric'}}}}),
    excludedCriteria: Joi.string().max(140).empty("").label("Exclude criteria").regex(/^[a-zA-Z0-9şŞıİçÇöÖüÜğĞ_\\s]+$/).options({ language: { string:{ regex: { base: 'must be alphanumeric'}}}}),
    threshold: Joi.number().required().min(1).max(1000000).label("Threshold")
}).unknown()
<commit_msg>@sinan-si add regex patterns and custom messages to alert validation<commit_after>import Joi from "joi"

export default Joi.object().keys({
    name: Joi.string().min(3).max(32).label("Alert title").regex(/^[a-zA-Z0-9şŞıİçÇöÖüÜğĞ_\\s]+$/).options({ language: { string:{ regex: { base: "must be alphanumeric"}}, any: { empty: "length must be at least 3 characters long"}}}),
    requiredCriteria: Joi.string().required().max(140).label("Must include").regex(/^[a-zA-Z0-9şŞıİçÇöÖüÜğĞ_\\s]+$/).options({ language: { string:{ regex: { base: "must be alphanumeric" }}}}),
    niceTohaveCriteria: Joi.string().max(140).empty("").label("Can include").regex(/^[a-zA-Z0-9şŞıİçÇöÖüÜğĞ_\\s]+$/).options({ language: { string:{ regex: { base: "must be alphanumeric"}}}}),
    excludedCriteria: Joi.string().max(140).empty("").label("Exclude criteria").regex(/^[a-zA-Z0-9şŞıİçÇöÖüÜğĞ_\\s]+$/).options({ language: { string:{ regex: { base: "must be alphanumeric"}}}}),
    threshold: Joi.number().required().min(1).max(1000000).label("Threshold")
}).unknown()
<|endoftext|>


<commit_before>(function (root, factory) {
  "use strict";
  if (typeof define === 'function' && define.amd) {
    define([], function () {
      return (root.BotUI = factory(root));
    });
  } else {
    root.BotUI = factory(root);
  }
}(typeof window !== 'undefined' ? window : this, function (root, undefined) {
  "use strict";

  var BotUI = (function (id, opts) {

    opts = opts || {};

    if(!id) {
      throw Error('BotUI: Container id is required as first argument.');
    }

    if(!document.getElementById(id)) {
      throw Error('BotUI: Element with id #' + id + ' does not exist.');
    }

    if(!root.Vue && !opts.vue) {
      throw Error('BotUI: Vue is required but not found.');
    }

    var _botApp, // current vue instance.
    _options = {
      debug: false,
      fontawesome: true,
      searchselect: true
    },
    _container, // the outermost Element. Needed to scroll to bottom, for now.
    _interface = {}, // methods returned by a BotUI() instance.
    _actionResolve,
    _markDownRegex = {
      icon: /!\(([^\)]+)\)/igm, // !(icon)
      image: /!\[(.*?)\]\((.*?)\)/igm, // ![aleternate text](src)
      link: /\[([^\[]+)\]\(([^\)]+)\)(\^?)/igm // [text](link) ^ can be added at end to set the target as 'blank'
    },
    _fontAwesome = 'https://use.fontawesome.com/ea731dcb6f.js',
    _esPromisePollyfill = 'https://cdn.jsdelivr.net/es6-promise/4.1.0/es6-promise.min.js', // mostly for IE
    _searchselect =  "https://unpkg.com/vue-select@2.4.0/dist/vue-select.js";

    root.Vue = root.Vue || opts.vue;

    // merge opts passed to constructor with _options
    for (var prop in _options) {
      if (opts.hasOwnProperty(prop)) {
        _options[prop] = opts[prop];
      }
    }

    if(!root.Promise && typeof Promise === "undefined" && !opts.promise) {
      loadScript(_esPromisePollyfill);
    }

    function _linkReplacer(match, $1, $2, $3) {
      var _target = $3 ? 'blank' : ''; // check if '^' sign is present with link syntax
      return "" + $1 + "";
    }

    function _parseMarkDown(text) {
      return text
                 .replace(_markDownRegex.image, "")
                 .replace(_markDownRegex.icon, "")
                 .replace(_markDownRegex.link, _linkReplacer);
    }

    function loadScript(src, cb) {
      var script = document.createElement('script');
          script.type = 'text/javascript';
          script.src = src;

          if(cb) {
            script.onload = cb;
          }

      document.body.appendChild(script);
    }

    function _handleAction(text) {
      if(_instance.action.addMessage) {
        _interface.message.human({
          delay: 100,
          content: text
        });
      }
      _instance.action.show = !_instance.action.autoHide;
    }

    var _botuiComponent = {
      template: 'BOTUI_TEMPLATE', // replaced by HTML template during build. see Gulpfile.js
      data: function () {
        return {
          action: {
            text: {
              size: 30,
              placeholder: 'Write here ..'
            },
            button: {},
            show: false,
            type: 'text',
            autoHide: true,
            addMessage: true
          },
          messages: []
        };
      },
      computed: {
        isMobile: function () {
          return root.innerWidth && root.innerWidth <= 768;
        }
      },
    	methods: {
    		handle_action_button: function (button) {
          for (var i = 0; i < this.action.button.buttons.length; i++) {
            if(this.action.button.buttons[i].value == button.value && typeof(this.action.button.buttons[i].event) == 'function') {
              this.action.button.buttons[i].event(button);
              if (this.action.button.buttons[i].actionStop) return false;
              break;
            }
          }

          _handleAction(button.text);

          var defaultActionObj = {
            type: 'button',
            text: button.text,
            value: button.value
          };

          for (var eachProperty in button) {
            if (button.hasOwnProperty(eachProperty)) {
              if (eachProperty !== 'type' && eachProperty !== 'text' && eachProperty !== 'value') {
                defaultActionObj[eachProperty] = button[eachProperty];
              }
            }
          }

          _actionResolve(defaultActionObj);
    		},
    		handle_action_text: function () {
    			if(!this.action.text.value) return;
          _handleAction(this.action.text.value);
    			_actionResolve({
            type: 'text',
            value: this.action.text.value
          });
    			this.action.text.value = '';
    		},
        handle_action_select: function () {
          if(this.action.select.searchselect && !this.action.select.multipleselect) {
            if(!this.action.select.value.value) return;
            _handleAction(this.action.select.value[this.action.select.label]);
            _actionResolve({
              type: 'text',
              value: this.action.select.value.value,
              text: this.action.select.value.text,
              obj: this.action.select.value
            });
          }
          if(this.action.select.searchselect && this.action.select.multipleselect) {
            if(!this.action.select.value) return;
            var values = new Array();
            var labels = new Array();
            for (var i = 0; i < this.action.select.value.length; i++) {
              values.push(this.action.select.value[i].value);
              labels.push(this.action.select.value[i][this.action.select.label]);
            }
            _handleAction(labels.join(', '));
            _actionResolve({
              type: 'text',
              value: values.join(', '),
              text: labels.join(', '),
              obj: this.action.select.value
            });
          }
          else {
            if(!this.action.select.value) return;
            for (var i = 0; i < this.action.select.options.length; i++) { // Find select title
              if (this.action.select.options[i].value == this.action.select.value) {
                _handleAction(this.action.select.options[i].text);
                _actionResolve({
                  type: 'text',
                  value: this.action.select.value,
                  text: this.action.select.options[i].text
                });
              }
            }
          }
        }
    	}
    };

    root.Vue.directive('botui-markdown', function (el, binding) {
      if(binding.value == 'false') return; // v-botui-markdown="false"
      el.innerHTML = _parseMarkDown(el.textContent);
    });

    root.Vue.directive('botui-scroll', {
      inserted: function (el) {
        _container.scrollTop = _container.scrollHeight;
	el.scrollIntoView(true);
      }
    });

    root.Vue.directive('focus', {
      inserted: function (el) {
        el.focus();
      }
    });

    root.Vue.directive('botui-container', {
      inserted: function (el) {
        _container = el;
      }
    });

    _botApp = new root.Vue({
      components: {
        'bot-ui': _botuiComponent
      }
    }).$mount('#' + id);

    var _instance = _botApp.$children[0]; // to access the component's data

    function _addMessage(_msg) {

      if(!_msg.loading && !_msg.content) {
        throw Error('BotUI: "content" is required in a non-loading message object.');
      }

      _msg.type = _msg.type || 'text';
      _msg.visible = (_msg.delay || _msg.loading) ? false : true;
      var _index = _instance.messages.push(_msg) - 1;

      return new Promise(function (resolve, reject) {
        setTimeout(function () {
          if(_msg.delay) {
            _msg.visible = true;

            if(_msg.loading) {
              _msg.loading = false;
            }
          }
          resolve(_index);
        }, _msg.delay || 0);
      });
    }

    function _checkOpts(_opts) {
      if(typeof _opts === 'string') {
        _opts = {
          content: _opts
        };
      }
      return _opts || {};
    }

    _interface.message =  {
      add: function (addOpts) {
        return _addMessage( _checkOpts(addOpts) );
      },
      bot: function (addOpts) {
        addOpts = _checkOpts(addOpts);
        return _addMessage(addOpts);
      },
      human: function (addOpts) {
        addOpts = _checkOpts(addOpts);
        addOpts.human = true;
        return _addMessage(addOpts);
      },
      get: function (index) {
        return Promise.resolve(_instance.messages[index]);
      },
      remove: function (index) {
        _instance.messages.splice(index, 1);
        return Promise.resolve();
      },
      update: function (index, msg) { // only content can be updated, not the message type.
        var _msg = _instance.messages[index];
        _msg.content = msg.content;
        _msg.visible = !msg.loading;
        _msg.loading = !!msg.loading;
        return Promise.resolve(msg.content);
      },
      removeAll: function () {
        _instance.messages.splice(0, _instance.messages.length);
        return Promise.resolve();
      }
    };

    function mergeAtoB(objA, objB) {
      for (var prop in objA) {
        if (!objB.hasOwnProperty(prop)) {
          objB[prop] = objA[prop];
        }
      }
    }

    function _checkAction(_opts) {
      if(!_opts.action && !_opts.actionButton  && !_opts.actionText) {
        throw Error('BotUI: "action" property is required.');
      }
    }

    function _showActions(_opts) {

      _checkAction(_opts);

      mergeAtoB({
        type: 'text',
        cssClass: '',
        attributes: {},
        autoHide: true,
        addMessage: true
      }, _opts);

      _instance.action.type = _opts.type;
      _instance.action.cssClass = _opts.cssClass;
      _instance.action.attributes = _opts.attributes;
      _instance.action.autoHide = _opts.autoHide;
      _instance.action.addMessage = _opts.addMessage;

      return new Promise(function(resolve, reject) {
        _actionResolve = resolve; // resolved when action is performed, i.e: button clicked, text submitted, etc.
        setTimeout(function () {
          _instance.action.show = true;
        }, _opts.delay || 0);
      });
    };

    _interface.action = {
      show: _showActions,
      hide: function () {
        _instance.action.show = false;
        return Promise.resolve();
      },
      text: function (_opts) {
        _checkAction(_opts);
        _instance.action.text = _opts.action;
        return _showActions(_opts);
      },
      button: function (_opts) {
        _checkAction(_opts);
        _opts.type = 'button';
        _instance.action.button.buttons = _opts.action;
        return _showActions(_opts);
      },
      select: function (_opts) {
        _checkAction(_opts);
        _opts.type = 'select';
        _opts.action.label = _opts.action.label || 'text';
        _opts.action.value = _opts.action.value || '';
        _opts.action.searchselect = typeof _opts.action.searchselect !== 'undefined' ? _opts.action.searchselect : _options.searchselect;
        _opts.action.multipleselect = _opts.action.multipleselect || false;
        if (_opts.action.searchselect && typeof(_opts.action.value) == 'string') {
          if (!_opts.action.multipleselect) {
            for (var i = 0; i < _opts.action.options.length; i++) { // Find object
              if (_opts.action.options[i].value == _opts.action.value) {
                _opts.action.value = _opts.action.options[i]
              }
            }
          }
          else {
            var vals = _opts.action.value.split(',');
            _opts.action.value = new Array();
            for (var i = 0; i < _opts.action.options.length; i++) { // Find object
              for (var j = 0; j < vals.length; j++) { // Search values
                if (_opts.action.options[i].value == vals[j]) {
                  _opts.action.value.push(_opts.action.options[i]);
                }
              }
            }
          }
        }
        if (!_opts.action.searchselect) { _opts.action.options.unshift({value:'',text : _opts.action.placeholder}); }
        _instance.action.button = _opts.action.button;
        _instance.action.select = _opts.action;
        return _showActions(_opts);
      },
      buttontext: function (_opts) {
        _checkAction(_opts);
        _opts.type = 'buttontext';
        _instance.action.button.buttons = _opts.actionButton;
        _instance.action.text = _opts.actionText;
        return _showActions(_opts);
      }
    };

    if(_options.fontawesome) {
      loadScript(_fontAwesome);
    }

    if(_options.searchselect) {
      loadScript(_searchselect, function() {
        Vue.component('v-select', VueSelect.VueSelect);
      });
    }

    if(_options.debug) {
      _interface._botApp = _botApp; // current Vue instance
    }

    return _interface;
  });

  return BotUI;

}));
<commit_msg>clean slate<commit_after>(function (root, factory) {
  "use strict";
  if (typeof define === 'function' && define.amd) {
    define([], function () {
      return (root.BotUI = factory(root));
    });
  } else {
    root.BotUI = factory(root);
  }
}(typeof window !== 'undefined' ? window : this, function (root, undefined) {
  "use strict";

  var BotUI = (function (id, opts) {

    opts = opts || {};

    if(!id) {
      throw Error('BotUI: Container id is required as first argument.');
    }

    if(!document.getElementById(id)) {
      throw Error('BotUI: Element with id #' + id + ' does not exist.');
    }

    var botApp, // current vue instance.
    botInterface = {} // methods returned by a BotUI() instance.

    return botInterface;
  });

  return BotUI;

}));
<|endoftext|>


<commit_before>import { weel as $ } from "./libs/Weel"
import { wave, select } from "./libs/ui/common"
import { swapLanguages, config_to_render, option_to_config } from "./libs/ui/translation"
import { translate_from } from "./libs/actions"
import { log, do_action, add_action, i18n } from "./libs/functions"
import { settings } from "./libs/config"
import {
  container, toolbar, mask, drawer,
  closeDrawer, setTitle, inquiry, closeInquiry, toast,
} from "./libs/ui/popup"
import {
  PROPAGATION_OUTERMOST,
  MASK_MANUAL_HIDDEN,
  PAGE_IS_SWITCHING,

  CONNECT_WITH_TRANSLATING,
  TRANSLATE_IN_POPUP,
  TRANSLATE_QUERY_NONE,

  MESSAGE_IN_POPUP,
  RESPOND_TRANSLATING,

  SETTINGS_SET_SUCCESS,

  SET_LANGUAGES_FROM_TO,
} from "./libs/actions/types"

import translate, { apiPick } from "./libs/services/translation"
import synth from "./libs/services/synth"
import "./libs/actions/popup"

const scope = 'popup'
let port = {}

try {
  port = browser.runtime.connect({ name: CONNECT_WITH_TRANSLATING })
  port.onMessage.addListener(data => do_action(MESSAGE_IN_POPUP, data))
} catch (e) {}

/**
 * Application Container
 * @type {Closure}
 */
;(() => {
  $('body').delegate('click',
    ev => do_action(PROPAGATION_OUTERMOST, ev),
    wave,
    select
  )

  add_action(PROPAGATION_OUTERMOST, ev => {
    $('.select.-js._on', ev.currentTarget).off()

    closeDrawer()
  })

  $('.drawer-menu.-js', toolbar).register('click', ev => {
    $(container).data('actived-ui').add('drawer')
    $(mask).on()
  })

  $('.treat-paragraph.-js', toolbar).register('click', ev => {
    inquiry('暂未实现的“段落翻译”', '这种功能会占用太多翻译资源（免费），所以目前没有继续开发👹。', {
      ok: ev => void 0,
    })
  })

  $(mask).register('click', ev => {
    const $target = $(ev.currentTarget)

    closeInquiry()

    if (!$target.isOn()) return 0

    closeDrawer()

    do_action(MASK_MANUAL_HIDDEN, ev)
  })

  $(drawer).localizeHTML()

  $('nav.link-list > a.item', drawer)
  .localizeHTML()
  .register('click', ev => {
    ev.preventDefault()

    const target = ev.currentTarget

    $('main.content .page', container).pageSwitcher(target)
  })
})()

/**
 * Entry Page
 * @type {Closure}
 */
;(page => {
  setTitle('TRANSLATION')
  _initLanguagesBar()

  // Render Settings Page
  // add_action(`${PAGE_IS_SWITCHING}_ENTRY`, name => {
  //   setTitle('TRANSLATION')
  // })

  add_action(`CHANGED_SETTING_${'api_src'.toUpperCase()}`, _initLanguagesBar)

  const inputStream = page.querySelector('.input-stream')
  const streamBehavior = page.querySelector('.stream-behavior')
  const outputStream = page.querySelector('.output-stream')

  $(inputStream).localizeHTML()
  $(streamBehavior).localizeHTML()

  $('.language .-swap.-js', inputStream).ready(({ elem }) => {
    const {
      previousElementSibling,
      nextElementSibling,
    } = elem

    // $('.-opt', previousElementSibling).register('click', ({ target }) => {
    //   console.log(target)
    // })

    try {
      settings(['lang_from', 'lang_to']).get(({ lang_from, lang_to }) => {
        // if (!lang_from.text || !lang_to.text) return void 0

        do_action(
          SET_LANGUAGES_FROM_TO,
          [previousElementSibling, lang_from],
          [nextElementSibling, lang_to]
        )
      })
    } catch (e) {}
  }).register('click', swapLanguages)

  const $inputText = $('textarea', inputStream)

  $('.clear.-js', streamBehavior).register('click', $inputText.textArea().clear)

  // TODO: Test input, keep in mind that remove this
  // $inputText.textArea().in('全文翻译')

  const doTransalte = () => do_action(TRANSLATE_IN_POPUP, port)

  try {
    /** Auto Translate Selection That Selected From Content */
    settings('auto_translate_selection').get(({ auto_translate_selection }) => {
      if (!auto_translate_selection) return void 0

      browser.tabs.executeScript({
        code: 'window.getSelection().toString().trim();',
      }, (selection = []) => {
        if (!selection[0]) return void 0

        $inputText.textArea().in(selection[0])
        doTransalte()
      })
    })
  } catch (e) {}

  $inputText.register('keydown', ev => {
    const { keyCode, ctrlKey } = ev

    // Ctrl + Enter
    if (ctrlKey && keyCode === 13) {
      doTransalte()
    }
  })

  $('.translate.-js', streamBehavior).register('click', ev => doTransalte())

  $('.switch-translator.-js', streamBehavior).register('click', ev => {
    const apis = apiPick()

    settings('api_src').get(({ api_src }) => {
      apis.forEach((api, i) => {
        if (api_src === api.slug) {
          const src = apis[((i < apis.length - 1) ? (i + 1) : 0)]['slug']

          settings().set({ api_src: src })
          _initLanguagesBar(src)

          toast(`已切换到 ${api.name}`)
        }
      })
    })
  })

  $('.voice.-js', outputStream).register('click', ev => {
    settings().get(cfg => synth($inputText.textArea().out(), cfg))
  })

  $('.copy.-js', outputStream).register('click', ev => {
    const target = ev.currentTarget.nextElementSibling
    const textarea = outputStream.querySelector('textarea.-fake.-js')

    textarea.value = target.innerText
    textarea.select()

    document.execCommand('copy')
  })

  const result = outputStream.querySelector('.result')
  const reference = outputStream.querySelector('.reference')

  add_action(`${RESPOND_TRANSLATING}_${scope.toUpperCase()}`, data => {
    const { explains, phonetic, translation } = data

    if (!translation.length) return void 0

    $(result).on()

    $('.-phonetic', result).on()
    .children('.-plain').text(`[ ${phonetic[0] || '...'} ]`)

    $('.-explain', result).on()
    .children('.-plain').text(`${translation.join(' ')}`)

    $('.-explain', result).on()
    .children('.-detail')[explains[0] ? 'on' : 'off']()
    .html(`${explains.join('<br>')}`)
  })

  add_action(SET_LANGUAGES_FROM_TO, () => {
    $(result).off()
    // $inputText.textArea().clear()
  })

  function _initLanguagesBar(src) {
    try {
      browser.storage.local.get('api_src')
      .then(({ api_src }) => $('.language', inputStream).initLanguages(apiPick(api_src), src))
    } catch (e) {
      $('.language', inputStream).initLanguages(apiPick('youdao'), src)
    }
  }
})(document.querySelector('.page.-entry'))

/**
 * Preferences Page
 * @type {Closure}
 */
;(page => {
  add_action(`${PAGE_IS_SWITCHING}_PREFERENCES`, name => {
    config_to_render(page)
  })

  $(page.querySelectorAll('input, textarea'))
  .filter(target => target.name.length)
  .register('change', ({ target }) => {
    const { name, value } = target

    try {
      option_to_config(target)
      do_action(SETTINGS_SET_SUCCESS, name, value)
    } catch (e) {}
  })
})(document.querySelector('.page.-preferences'))

/**
 * Settings Page
 * @type {Closure}
 */
;(page => {
  // Render Settings Page
  add_action(`${PAGE_IS_SWITCHING}_SETTINGS`, name => {
    config_to_render(page)
  })

  $(page.querySelectorAll('input, textarea'))
  .filter(target => target.name.length)
  .register('change', ({ target }) => {
    const { name, value } = target

    if (name === 'custom_api') {
      if (!value) return void 0

      try {
        JSON.parse(value)
      } catch (e) {
        return toast('请确保 JSON 格式正确！')
      }
    }

    try {
      option_to_config(target)
      do_action(SETTINGS_SET_SUCCESS, name, value)
    } catch (e) {}
  })

  $('.reset-settings.-js', page).register('click', ev => {
    inquiry('确定要清除扩展的数据？', '操作会让扩展恢复到初次安装的状态。', {
      ok: ev => {
        settings().reset()
        browser.runtime.reload()
      },
      cancel: ev => void 0,
    })
  })

  $('.uninstall.-js', page).register('click', ev => {
    inquiry('确定要卸载 ...(｡•ˇ‸ˇ•｡) ... ？', i18n.get('UNINSTALL_DIALOG_MESSAGE'), {
      ok: ev => {
        browser.management.uninstallSelf()
      },
      cancel: ev => void 0,
    })
  })
})(document.querySelector('.page.-settings'))
<commit_msg>Fixed translation switching toast<commit_after>import { weel as $ } from "./libs/Weel"
import { wave, select } from "./libs/ui/common"
import { swapLanguages, config_to_render, option_to_config } from "./libs/ui/translation"
import { translate_from } from "./libs/actions"
import { log, do_action, add_action, i18n } from "./libs/functions"
import { settings } from "./libs/config"
import {
  container, toolbar, mask, drawer,
  closeDrawer, setTitle, inquiry, closeInquiry, toast,
} from "./libs/ui/popup"
import {
  PROPAGATION_OUTERMOST,
  MASK_MANUAL_HIDDEN,
  PAGE_IS_SWITCHING,

  CONNECT_WITH_TRANSLATING,
  TRANSLATE_IN_POPUP,
  TRANSLATE_QUERY_NONE,

  MESSAGE_IN_POPUP,
  RESPOND_TRANSLATING,

  SETTINGS_SET_SUCCESS,

  SET_LANGUAGES_FROM_TO,
} from "./libs/actions/types"

import translate, { apiPick } from "./libs/services/translation"
import synth from "./libs/services/synth"
import "./libs/actions/popup"

const scope = 'popup'
let port = {}

try {
  port = browser.runtime.connect({ name: CONNECT_WITH_TRANSLATING })
  port.onMessage.addListener(data => do_action(MESSAGE_IN_POPUP, data))
} catch (e) {}

/**
 * Application Container
 * @type {Closure}
 */
;(() => {
  $('body').delegate('click',
    ev => do_action(PROPAGATION_OUTERMOST, ev),
    wave,
    select
  )

  add_action(PROPAGATION_OUTERMOST, ev => {
    $('.select.-js._on', ev.currentTarget).off()

    closeDrawer()
  })

  $('.drawer-menu.-js', toolbar).register('click', ev => {
    $(container).data('actived-ui').add('drawer')
    $(mask).on()
  })

  $('.treat-paragraph.-js', toolbar).register('click', ev => {
    inquiry('暂未实现的“段落翻译”', '这种功能会占用太多翻译资源（免费），所以目前没有继续开发👹。', {
      ok: ev => void 0,
    })
  })

  $(mask).register('click', ev => {
    const $target = $(ev.currentTarget)

    closeInquiry()

    if (!$target.isOn()) return 0

    closeDrawer()

    do_action(MASK_MANUAL_HIDDEN, ev)
  })

  $(drawer).localizeHTML()

  $('nav.link-list > a.item', drawer)
  .localizeHTML()
  .register('click', ev => {
    ev.preventDefault()

    const target = ev.currentTarget

    $('main.content .page', container).pageSwitcher(target)
  })
})()

/**
 * Entry Page
 * @type {Closure}
 */
;(page => {
  setTitle('TRANSLATION')
  _initLanguagesBar()

  // Render Settings Page
  // add_action(`${PAGE_IS_SWITCHING}_ENTRY`, name => {
  //   setTitle('TRANSLATION')
  // })

  add_action(`CHANGED_SETTING_${'api_src'.toUpperCase()}`, _initLanguagesBar)

  const inputStream = page.querySelector('.input-stream')
  const streamBehavior = page.querySelector('.stream-behavior')
  const outputStream = page.querySelector('.output-stream')

  $(inputStream).localizeHTML()
  $(streamBehavior).localizeHTML()

  $('.language .-swap.-js', inputStream).ready(({ elem }) => {
    const {
      previousElementSibling,
      nextElementSibling,
    } = elem

    // $('.-opt', previousElementSibling).register('click', ({ target }) => {
    //   console.log(target)
    // })

    try {
      settings(['lang_from', 'lang_to']).get(({ lang_from, lang_to }) => {
        // if (!lang_from.text || !lang_to.text) return void 0

        do_action(
          SET_LANGUAGES_FROM_TO,
          [previousElementSibling, lang_from],
          [nextElementSibling, lang_to]
        )
      })
    } catch (e) {}
  }).register('click', swapLanguages)

  const $inputText = $('textarea', inputStream)

  $('.clear.-js', streamBehavior).register('click', $inputText.textArea().clear)

  // TODO: Test input, keep in mind that remove this
  // $inputText.textArea().in('全文翻译')

  const doTransalte = () => do_action(TRANSLATE_IN_POPUP, port)

  try {
    /** Auto Translate Selection That Selected From Content */
    settings('auto_translate_selection').get(({ auto_translate_selection }) => {
      if (!auto_translate_selection) return void 0

      browser.tabs.executeScript({
        code: 'window.getSelection().toString().trim();',
      }, (selection = []) => {
        if (!selection[0]) return void 0

        $inputText.textArea().in(selection[0])
        doTransalte()
      })
    })
  } catch (e) {}

  $inputText.register('keydown', ev => {
    const { keyCode, ctrlKey } = ev

    // Ctrl + Enter
    if (ctrlKey && keyCode === 13) {
      doTransalte()
    }
  })

  $('.translate.-js', streamBehavior).register('click', ev => doTransalte())

  $('.switch-translator.-js', streamBehavior).register('click', ev => {
    const apis = apiPick()

    settings('api_src').get(({ api_src }) => {
      apis.forEach((api, i) => {
        if (api_src === api.slug) {
          const next_api = apis[((i < apis.length - 1) ? (i + 1) : 0)]
          const src = next_api['slug']

          settings().set({ api_src: src })
          _initLanguagesBar(src)

          toast(`已切换到 ${next_api['name']}`)
        }
      })
    })
  })

  $('.voice.-js', outputStream).register('click', ev => {
    settings().get(cfg => synth($inputText.textArea().out(), cfg))
  })

  $('.copy.-js', outputStream).register('click', ev => {
    const target = ev.currentTarget.nextElementSibling
    const textarea = outputStream.querySelector('textarea.-fake.-js')

    textarea.value = target.innerText
    textarea.select()

    document.execCommand('copy')
  })

  const result = outputStream.querySelector('.result')
  const reference = outputStream.querySelector('.reference')

  add_action(`${RESPOND_TRANSLATING}_${scope.toUpperCase()}`, data => {
    const { explains, phonetic, translation } = data

    if (!translation.length) return void 0

    $(result).on()

    $('.-phonetic', result).on()
    .children('.-plain').text(`[ ${phonetic[0] || '...'} ]`)

    $('.-explain', result).on()
    .children('.-plain').text(`${translation.join(' ')}`)

    $('.-explain', result).on()
    .children('.-detail')[explains[0] ? 'on' : 'off']()
    .html(`${explains.join('<br>')}`)
  })

  add_action(SET_LANGUAGES_FROM_TO, () => {
    $(result).off()
    // $inputText.textArea().clear()
  })

  function _initLanguagesBar(src) {
    try {
      browser.storage.local.get('api_src')
      .then(({ api_src }) => $('.language', inputStream).initLanguages(apiPick(api_src), src))
    } catch (e) {
      $('.language', inputStream).initLanguages(apiPick('youdao'), src)
    }
  }
})(document.querySelector('.page.-entry'))

/**
 * Preferences Page
 * @type {Closure}
 */
;(page => {
  add_action(`${PAGE_IS_SWITCHING}_PREFERENCES`, name => {
    config_to_render(page)
  })

  $(page.querySelectorAll('input, textarea'))
  .filter(target => target.name.length)
  .register('change', ({ target }) => {
    const { name, value } = target

    try {
      option_to_config(target)
      do_action(SETTINGS_SET_SUCCESS, name, value)
    } catch (e) {}
  })
})(document.querySelector('.page.-preferences'))

/**
 * Settings Page
 * @type {Closure}
 */
;(page => {
  // Render Settings Page
  add_action(`${PAGE_IS_SWITCHING}_SETTINGS`, name => {
    config_to_render(page)
  })

  $(page.querySelectorAll('input, textarea'))
  .filter(target => target.name.length)
  .register('change', ({ target }) => {
    const { name, value } = target

    if (name === 'custom_api') {
      if (!value) return void 0

      try {
        JSON.parse(value)
      } catch (e) {
        return toast('请确保 JSON 格式正确！')
      }
    }

    try {
      option_to_config(target)
      do_action(SETTINGS_SET_SUCCESS, name, value)
    } catch (e) {}
  })

  $('.reset-settings.-js', page).register('click', ev => {
    inquiry('确定要清除扩展的数据？', '操作会让扩展恢复到初次安装的状态。', {
      ok: ev => {
        settings().reset()
        browser.runtime.reload()
      },
      cancel: ev => void 0,
    })
  })

  $('.uninstall.-js', page).register('click', ev => {
    inquiry('确定要卸载 ...(｡•ˇ‸ˇ•｡) ... ？', i18n.get('UNINSTALL_DIALOG_MESSAGE'), {
      ok: ev => {
        browser.management.uninstallSelf()
      },
      cancel: ev => void 0,
    })
  })
})(document.querySelector('.page.-settings'))
<|endoftext|>