//angular-components/app_test.js
// TODO(hanuszczak): Figure out why this file cannot be named `tests.js`.
goog.provide('grrUi.tests');
goog.provide('grrUi.tests.browserTriggerEvent');
goog.provide('grrUi.tests.browserTriggerKeyDown');
goog.provide('grrUi.tests.stubDirective');
goog.provide('grrUi.tests.stubTranscludeDirective');
goog.provide('grrUi.tests.stubUiTrait');
goog.provide('grrUi.tests.testsModule');


/**
 * Module required to run GRR javascript tests in Karma.
 */
grrUi.tests.testsModule = angular.module('grrUi.tests', ['ng', 'ui.bootstrap']);

var $animate;
beforeEach(module('ngAnimateMock'));

grrUi.tests.testsModule.config(function($interpolateProvider, $qProvider,
                                   $uibModalProvider) {
  $interpolateProvider.startSymbol('{$');
  $interpolateProvider.endSymbol('$}');

  $qProvider.errorOnUnhandledRejections(false);

  $uibModalProvider.options.animation = false;
}).run(function($injector) {
  $animate = $injector.get('$animate');
});


beforeEach(function() {
  module('grrUi.tests');

  // We provide a general mock for grrRoutingService here. This mock can be
  // injected in any test via $inject. We do this since we want to test
  // directives in isolation without routing. Furthermore, the grrUi.routing
  // module runs init routines during configuration. We do not want them to
  // interfere with directive tests.
  var grrRoutingServiceMock = {
    go: function(state, params) {},
    href: function(state, params) { return '#test/href'; },
    uiOnParamsChanged: function(scope, paramNames, callback) {},
    onStateChange: function(scope, callback) {}
  };
  module(function($provide) {
    $provide.factory('grrRoutingService', function() {
      return grrRoutingServiceMock;
    });
  });
});

/**
 * Trigger a browser event on the given element.
 * The triggered event will be the simplest possible - e.g. for mouse events,
 * the coordinates will be 0, 0 and the related target element is null.
 * @param {!angular.jQuery} element
 * @param {string} eventType
 * @export
 */
grrUi.tests.browserTriggerEvent = function(element, eventType) {
  if (element.injector) {
    element = element[0];
  } else if (element.prevObject) {
    element = element[0];
  }

  if (eventType === 'change') {
    $(element).triggerHandler('change');
    if (element.type === 'checkbox') {
      // Angular v1.6 doesn't react on checkboxes' 'change' event and
      // apparently requires clicking, while Angular v1.7 expects a proper
      // 'change'.
      $(element).triggerHandler('click');
    }
  } else {
    if (document.createEvent) {
      var event = document.createEvent('MouseEvents');
      // mouseenter and mouseleave must be edited because jqLite doesn't actually
      // listen on them - it listens on mouseover and mouseout and performs its
      // own logic to ignore the event if the related target is contained by the
      // target.
      if (eventType === 'mouseenter') {
        eventType = 'mouseover';
      }
      if (eventType === 'mouseleave') {
        eventType = 'mouseout';
      }
      event.initMouseEvent(eventType, true, true, window, 0, 0, 0, 0, 0, false,
                           false, false, false, 0, null);
      element.dispatchEvent(event);
    } else {
      element.fireEvent('on' + eventType);
    }
  }

  // True is for 'hideErrors' for cases when no animations are pending.
  $animate.flush(true);
};

/**
 * Triggers a key down event on the given element.
 * @param {!angular.jQuery} element
 * @param {number} keyCode
 * @export
 */
grrUi.tests.browserTriggerKeyDown = function(element, keyCode) {
  var event = jQuery.Event("keypress");
  event.which = keyCode;
  element.trigger(event);
};

var directiveStubCounter = 0;

/**
 * Stub out a directive.
 *
 * This function creates a temporary test module and registers a stub
 * directive there with a high priority and terminal=true - this directive
 * will effectively block all other directives with a same name.
 *
 * Module with a fake directive has a unique name, so it won't get loaded
 * in other tests and therefore won't affect them.
 *
 * @param {string} directiveName
 * @export
 */
grrUi.tests.stubDirective = function(directiveName) {
  var moduleName = 'test.directives.stubs.' + directiveStubCounter;
  directiveStubCounter += 1;

  angular.module(moduleName, []).directive(
      directiveName,
      function() {
        return {
          priority: 100000 + directiveStubCounter,
          terminal: true
        };
      });

  beforeEach(module(moduleName));
};


/**
 * Stub out a transclude directive.
 *
 * This function stubs the directive exactly as stubDirective does, but
 * it declares the stub as a 'transclude' directive, thus rendering
 * everything between the the stubbed directive tags. Useful when
 * we need to stub directive "foo", but we care about the transcluded
 * directive "bar":
 * <foo>
 *  <bar></bar>
 * </foo>
 *
 * @param {string} directiveName
 * @export
 */
grrUi.tests.stubTranscludeDirective = function(directiveName) {
  var moduleName = 'test.directives.stubs.' + directiveStubCounter;
  directiveStubCounter += 1;

  angular.module(moduleName, []).directive(
      directiveName,
      function() {
        return {
          restrict: 'E',
          scope: {},
          transclude: true,
          priority: 100000 + directiveStubCounter,
          terminal: true,
          link: function($scope, $element, $attrs, controller, $transclude) {
            function ngTranscludeCloneAttachFn(clone) {
              if (clone.length) {
                $element.empty();
                $element.append(clone);
              }
            }
            $transclude(ngTranscludeCloneAttachFn, null, null);
          }
        };
      });

  beforeEach(module(moduleName));
};


/**
 * Stub out a GRR UI trait (see grr-disable-if-no-trait directive).
 *
 * This function stubs out the trait, so that UI pieces that depend on this
 * trait treat it as "enabled" in the test.
 *
 * @param {string} traitName
 * @export
 */
grrUi.tests.stubUiTrait = function(traitName) {
  beforeEach(inject(function($injector) {
    const $q = $injector.get('$q');
    const grrApiService = $injector.get('grrApiService');

    var deferred = $q.defer();
    var response = {
      data: {
        value: {
          interface_traits: {
            value: {}
          }
        }
      }
    };
    response['data']['value']['interface_traits']['value'][traitName] = {
      value: true
    };
    deferred.resolve(response);

    var currentImpl = grrApiService.getCached;
    spyOn(grrApiService, 'getCached').and.callFake(function(url, params) {
      if (url == 'users/me') {
        return deferred.promise;
      } else {
        return currentImpl(url, params);
      }
    });
  }));
};

//angular-components/acl/request-approval-dialog-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.acl.requestApprovalDialogDirectiveTest');
goog.setTestOnly();

const {aclModule} = goog.require('grrUi.acl.acl');
const {browserTriggerEvent, testsModule, stubDirective} = goog.require('grrUi.tests');

describe('request approval dialog', () => {
  let $compile;
  let $q;
  let $rootScope;
  let closeSpy;
  let dismissSpy;
  let grrApiService;

  beforeEach(module('/static/angular-components/acl/' +
      'request-approval-dialog.html'));
  beforeEach(module('/static/angular-components/core/' +
      'confirmation-dialog.html'));

  beforeEach(module(aclModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrApproverInput');

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    grrApiService = $injector.get('grrApiService');

    closeSpy = jasmine.createSpy('close');
    dismissSpy = jasmine.createSpy('dismiss');
  }));

  const renderTestTemplate =
      ((approvalType, createUrl, createArgs, description, reason) => {
        $rootScope.approvalType = approvalType;
        $rootScope.createUrl = createUrl;
        $rootScope.createArgs = createArgs;
        $rootScope.description = description;
        $rootScope.reason = reason;

        $rootScope.$close = closeSpy;
        $rootScope.$dismiss = dismissSpy;

        const template = '<grr-request-approval-dialog ' +
            'approval-type="approvalType" ' +
            'create-request-url="createUrl" ' +
            'create-request-args="createArgs" ' +
            'access-error-description="description" ' +
            'reason="reason" />';

        const element = $compile(template)($rootScope);
        $rootScope.$apply();

        return element;
      });

  const setApproverInput = (element, value) => {
    const valueElement = element.find('grr-approver-input');
    const valueAttr = valueElement.attr('ng-model');
    const expression = `${valueAttr} = "${value}"`;
    valueElement.scope().$eval(expression);
  };

  let approvals;
  let clientApprovalRequest;
  let configEntry;

  beforeEach(() => {
    clientApprovalRequest = {
      client_id: 'C:123456',
      approval: {},
    };

    approvals = {
      data: {
        items: [
          {
            type: 'ApiClientApproval',
            value: {
              reason: {
                type: 'RDFString',
                value: 'reason1',
              },
            },
          },
          {
            type: 'ApiClientApproval',
            value: {
              reason: {
                type: 'RDFString',
                value: 'reason2',
              },
            },
          },
        ],
      },
    };

    configEntry = {
      data: {
        value: {
          type: 'RDFString',
          value: 'foo@bar.com, xyz@example.com',
        },
      },
    };

    let deferred = $q.defer();
    deferred.resolve(configEntry);
    spyOn(grrApiService, 'getCached').and.returnValue(deferred.promise);

    deferred = $q.defer();
    deferred.resolve(approvals);
    spyOn(grrApiService, 'get').and.returnValue(deferred.promise);
  });

  it('shows a list of previous client reasons', () => {
    const element = renderTestTemplate('client');

    expect($('select option', element).length).toBe(3);
    expect($('select option:nth(0)', element).val()).toEqual('');
    expect($('select option:nth(1)', element).val()).toEqual('reason1');
    expect($('select option:nth(2)', element).val()).toEqual('reason2');
  });

  it('doesn\'t show a list of CC addresses when not available', () => {
    configEntry['data']['value']['value'] = undefined;
    const element = renderTestTemplate('client');

    expect(element.text()).not.toContain('CC');
    expect($('input[name=cc_approval]', element).length).toBe(0);
  });

  it('shows a list of CC addresses when they\'re available', () => {
    const element = renderTestTemplate('client');

    expect(element.text()).toContain('CC foo@bar.com, xyz@example.com');
    expect($('input[name=cc_approval]', element).length).toBe(1);
  });

  it('disables reason field when dropdown reason is selected', () => {
    const element = renderTestTemplate('client');

    $('select', element).val('reason1');
    browserTriggerEvent($('select', element), 'change');

    expect($('input[name=acl_reason]', element).attr('disabled')).toBeTruthy();
  });

  it('doesn\'t show keep-alive checkbox for "hunt"approval type', () => {
    const element = renderTestTemplate('hunt');

    expect($('input[name=keepalive]', element).length).toBe(0);
  });

  it('shows keep-alive checkbox for "client" approval type', () => {
    const element = renderTestTemplate('client');

    expect($('input[name=keepalive]', element).length).toBe(1);
  });

  it('includes approvers into request if CC-checbox is selected', () => {
    spyOn(grrApiService, 'post').and.returnValue($q.defer().promise);

    const element =
        renderTestTemplate('client', 'foo/bar', clientApprovalRequest);

    setApproverInput(element, 'foo');

    $('input[name=acl_reason]', element).val('bar');
    browserTriggerEvent($('input[name=acl_reason]', element), 'change');

    $('input[name=cc_approval]', element).prop('checked', true);
    browserTriggerEvent($('input[name=cc_approval]', element), 'change');

    browserTriggerEvent($('button[name=Proceed]', element), 'click');

    expect(grrApiService.post).toHaveBeenCalledWith('foo/bar', {
      client_id: 'C:123456',
      approval: {
        reason: 'bar',
        notified_users: ['foo'],
        email_cc_addresses: ['foo@bar.com', 'xyz@example.com'],
      },
      keep_client_alive: true,
    });
  });

  it('includes keep_client_alive into request if checkbox is selected', () => {
    spyOn(grrApiService, 'post').and.returnValue($q.defer().promise);

    const element =
        renderTestTemplate('client', 'foo/bar', clientApprovalRequest);

    setApproverInput(element, 'foo');

    $('input[name=acl_reason]', element).val('bar');
    browserTriggerEvent($('input[name=acl_reason]', element), 'change');

    browserTriggerEvent(
        $('input[name=cc_approval]', element).prop('checked', false),
        'change');
    browserTriggerEvent(
        $('input[name=keepalive]', element).prop('checked', true),
        'change');

    browserTriggerEvent($('button[name=Proceed]', element), 'click');

    expect(grrApiService.post).toHaveBeenCalledWith('foo/bar', {
      client_id: 'C:123456',
      approval: {
        reason: 'bar',
        notified_users: ['foo'],
      },
      keep_client_alive: true,
    });
  });

  it('includes dropdown reason into request', () => {
    spyOn(grrApiService, 'post').and.returnValue($q.defer().promise);

    const element =
        renderTestTemplate('client', 'foo/bar', clientApprovalRequest);

    setApproverInput(element, 'foo');

    $('input[name=acl_reason]', element).val('bar');
    browserTriggerEvent($('input[name=acl_reason]', element), 'change');

    browserTriggerEvent(
        $('input[name=cc_approval]', element).prop('checked', false),
        'change');

    $('select', element).val('reason2');
    browserTriggerEvent($('select', element), 'change');

    browserTriggerEvent($('button[name=Proceed]', element), 'click');

    expect(grrApiService.post).toHaveBeenCalledWith('foo/bar', {
      client_id: 'C:123456',
      approval: {
        reason: 'reason2',
        notified_users: ['foo'],
      },
      keep_client_alive: true,
    });
  });

  it('uses empty reason when reason is not supplied', () => {
    const element = renderTestTemplate('client');
    expect($('input[name=acl_reason]', element).val()).toEqual('');
  });

  it('suggests a reason from the scope', () => {
    const element = renderTestTemplate('client', '', '', '', 'foobar');
    expect($('input[name=acl_reason]', element).val()).toEqual('foobar');
  });

});


exports = {};

;return exports;});

//angular-components/artifact/artifact-descriptor-service_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.artifact.artifactDescriptorServiceTest');
goog.setTestOnly();

const {ArtifactDescriptorsService} = goog.require('grrUi.artifact.artifactDescriptorsService');
const {artifactModule} = goog.require('grrUi.artifact.artifact');
const {testsModule} = goog.require('grrUi.tests');


describe('grrArtifactDescriptorsService service', () => {
  let $q;
  let $rootScope;
  let grrApiServiceMock;
  let grrArtifactDescriptorsService;


  beforeEach(module(artifactModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');

    grrApiServiceMock = {get: function() {}};
    grrArtifactDescriptorsService =
        $injector.instantiate(ArtifactDescriptorsService, {
          'grrApiService': grrApiServiceMock,
        });
  }));

  const successResponse = {
    data: {
      items: [
        {
          type: 'ArtifactDescriptor',
          value: {
            artifact: {
              value: {
                name: {
                  value: 'foo',
                },
              },
            },
          },
        },
        {
          type: 'ArtifactDescriptor',
          value: {
            artifact: {
              value: {
                name: {
                  value: 'bar',
                },
              },
            },
          },
        },
      ],
    },
  };

  const failureResponse = {
    data: {
      message: 'Oh no!',
    },
  };

  describe('listDescriptors()', () => {
    it('resolves to a dictionary of descriptors on success', (done) => {
      const deferred = $q.defer();
      deferred.resolve(successResponse);
      spyOn(grrApiServiceMock, 'get').and.returnValue(deferred.promise);

      grrArtifactDescriptorsService.listDescriptors().then((descriptors) => {
        expect(Object.keys(descriptors)).toEqual(['foo', 'bar']);
        done();
      });
      $rootScope.$apply();
    });

    it('resolves to an error message on error', (done) => {
      const deferred = $q.defer();
      deferred.reject(failureResponse);
      spyOn(grrApiServiceMock, 'get').and.returnValue(deferred.promise);

      grrArtifactDescriptorsService.listDescriptors().then(
          () => {}, (message) => {
            expect(message).toBe('Oh no!');
            done();
          });
      $rootScope.$apply();
    });

    it('does not start more than one API requests', () => {
      const deferred1 = $q.defer();
      const deferred2 = $q.defer();
      spyOn(grrApiServiceMock, 'get').and.returnValues(deferred1.promise,
                                                       deferred2.promise);

      grrArtifactDescriptorsService.listDescriptors();
      grrArtifactDescriptorsService.listDescriptors();

      expect(grrApiServiceMock.get.calls.count()).toBe(1);
    });
  });

  describe('getDescriptorByName()', () => {
    it('resolves to a descriptor', (done) => {
      const deferred = $q.defer();
      deferred.resolve(successResponse);
      spyOn(grrApiServiceMock, 'get').and.returnValue(deferred.promise);

      grrArtifactDescriptorsService.getDescriptorByName('foo').then(
          (descriptor) => {
            expect(descriptor).toEqual(successResponse.data.items[0]);
            done();
          });
      $rootScope.$apply();
    });

    it('resolves to undefined if descriptor not found', (done) => {
      const deferred = $q.defer();
      deferred.resolve(successResponse);
      spyOn(grrApiServiceMock, 'get').and.returnValue(deferred.promise);

      grrArtifactDescriptorsService.getDescriptorByName('something')
          .then((descriptor) => {
            expect(descriptor).toBeUndefined();
            done();
          });
      $rootScope.$apply();
    });

    it('resolve to an error message in case of error', (done) => {
      const deferred = $q.defer();
      deferred.reject(failureResponse);
      spyOn(grrApiServiceMock, 'get').and.returnValue(deferred.promise);

      grrArtifactDescriptorsService.getDescriptorByName('something')
          .then(() => {}, (message) => {
            expect(message).toBe('Oh no!');
            done();
          });
      $rootScope.$apply();
    });
  });

  describe('clearCache()', () => {
    it('forces next listDescriptors call to do an API request', () => {
      const deferred1 = $q.defer();
      deferred1.resolve(successResponse);

      const deferred2 = $q.defer();
      deferred2.resolve(successResponse);

      spyOn(grrApiServiceMock, 'get').and.returnValues(deferred1.promise,
                                                       deferred2.promise);

      grrArtifactDescriptorsService.listDescriptors();
      $rootScope.$apply();

      grrArtifactDescriptorsService.clearCache();
      grrArtifactDescriptorsService.listDescriptors();
      $rootScope.$apply();

      expect(grrApiServiceMock.get.calls.count()).toBe(2);
    });
  });
});


exports = {};

;return exports;});

//angular-components/artifact/artifact-name-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.artifact.artifactNameDirectiveTest');
goog.setTestOnly();

const {artifactModule} = goog.require('grrUi.artifact.artifact');
const {testsModule} = goog.require('grrUi.tests');


describe('grr-artifact-name directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrArtifactDescriptorsService;


  beforeEach(module('/static/angular-components/artifact/artifact-name.html'));
  beforeEach(module(artifactModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrArtifactDescriptorsService = $injector.get('grrArtifactDescriptorsService');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-artifact-name value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  const systemDescriptor = {
    type: 'ArtifactDescriptor',
    value: {
      artifact: {
        value: {
          name: {
            value: 'foo',
          },
        },
      },
      is_custom: {
        value: false,
      },
    },
  };

  const userDescriptor = {
    type: 'ArtifactDescriptor',
    value: {
      artifact: {
        value: {
          name: {
            value: 'foo',
          },
        },
      },
      is_custom: {
        value: true,
      },
    },
  };

  it('shows artifact name as a string before it\'s resolved', () => {
    const deferred = $q.defer();
    spyOn(grrArtifactDescriptorsService, 'getDescriptorByName')
        .and.returnValue(deferred.promise);

    const element = renderTestTemplate({
      value: 'foo',
    });
    expect($('span.system', element).length).toBe(0);
    expect($('span.user', element).length).toBe(0);
    expect($('span.icon', element).length).toBe(0);
    expect(element.text()).toContain('foo');
  });

  it('marks system artifacts with .system class and no icon', () => {
    const deferred = $q.defer();
    deferred.resolve(systemDescriptor);
    spyOn(grrArtifactDescriptorsService, 'getDescriptorByName')
        .and.returnValue(deferred.promise);

    const element = renderTestTemplate({
      value: 'foo',
    });
    expect($('span.system', element).length).toBe(1);
    expect($('span.user', element).length).toBe(0);
    expect($('span.icon', element).length).toBe(0);
    expect(element.text()).toContain('foo');
  });

  it('marks user artifacts with .user class and an icon', () => {
    const deferred = $q.defer();
    deferred.resolve(userDescriptor);
    spyOn(grrArtifactDescriptorsService, 'getDescriptorByName')
        .and.returnValue(deferred.promise);

    const element = renderTestTemplate({
      value: 'foo',
    });
    expect($('span.system', element).length).toBe(0);
    expect($('span.user', element).length).toBe(1);
    expect(element.text()).toContain('foo');
  });

  it('does not mark unknown artifacts', () => {
    const deferred = $q.defer();
    deferred.resolve(undefined);
    spyOn(grrArtifactDescriptorsService, 'getDescriptorByName')
        .and.returnValue(deferred.promise);

    const element = renderTestTemplate({
      value: 'foo',
    });
    expect($('span.system', element).length).toBe(0);
    expect($('span.user', element).length).toBe(0);
    expect($('span.icon', element).length).toBe(0);
    expect(element.text()).toContain('foo');
  });
});


exports = {};

;return exports;});

//angular-components/artifact/artifacts-list-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.artifact.artifactsListFormDirectiveTest');
goog.setTestOnly();

const {artifactModule} = goog.require('grrUi.artifact.artifact');
const {browserTriggerEvent, stubDirective, testsModule} = goog.require('grrUi.tests');


describe('artifacts list form directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrArtifactDescriptorsService;

  let descriptorDarwinWindows;
  let descriptorLinux;


  beforeEach(module('/static/angular-components/artifact/artifacts-list-form.html'));
  beforeEach(module(artifactModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrSemanticValue');

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    grrArtifactDescriptorsService = $injector.get('grrArtifactDescriptorsService');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;
    $rootScope.descriptor = {
      default: {
        type: 'ArtifactName',
        value: '',
      },
    };

    const template = '<grr-artifacts-list-form descriptor="descriptor" ' +
        'value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows "Loading artifacts..." while artifacts are being loaded', () => {
    const deferred = $q.defer();
    spyOn(grrArtifactDescriptorsService, 'listDescriptors').and
        .returnValue(deferred.promise);

    const element = renderTestTemplate([]);
    expect(element.text()).toContain('Loading artifacts...');
  });

  describe('when descriptors listing fails', () => {
    beforeEach(() => {
      spyOn(grrArtifactDescriptorsService, 'listDescriptors')
          .and.callFake(() => {
            const deferred = $q.defer();
            deferred.reject('Oh no!');
            return deferred.promise;
          });
    });

    it('hides "Loading artifacts..." message', () => {
      const element = renderTestTemplate([]);

      expect(element.text()).not.toContain('Loading artifacts...');
    });

    it('shows a failure message on artifacts fetch failure', () => {
      const element = renderTestTemplate([]);

      expect(element.text()).toContain('Oh no!');
    });
  });

  describe('when descriptors listing succeeds', () => {
    beforeEach(() => {
      descriptorLinux = {
        type: 'ArtifactDescriptor',
        value: {
          artifact: {
            type: 'Artifact',
            value: {
              name: {type: 'ArtifactName', value: 'FooLinux'},
              supported_os: [
                {type: 'RDFString', value: 'Linux'},
              ],
            },
          },
        },
      };

      descriptorDarwinWindows = {
        type: 'ArtifactDescriptor',
        value: {
          artifact: {
            type: 'Artifact',
            value: {
              name: {type: 'ArtifactName', value: 'BarDarwinWindows'},
              supported_os: [
                {type: 'RDFString', value: 'Darwin'},
                {type: 'RDFString', value: 'Windows'},
              ],
            },
          },
        },
      };

      spyOn(grrArtifactDescriptorsService, 'listDescriptors')
          .and.callFake(() => {
            const deferred = $q.defer();
            deferred.resolve({
              'FooLinux': descriptorLinux,
              'BarDarwinWindows': descriptorDarwinWindows,
            });
            return deferred.promise;
          });
    });

    it('hides "Loading artifacts..." message', () => {
      const element = renderTestTemplate([]);

      expect(element.text()).not.toContain('Loading artifacts...');
    });

    it('shows all artifacts for selection by default', () => {
      const element = renderTestTemplate([]);

      expect(element.text()).toContain('FooLinux');
      expect(element.text()).toContain('BarDarwinWindows');
    });

    it('prefills selection list from model', () => {
      const element =
          renderTestTemplate([{type: 'ArtifactName', value: 'FooLinux'}]);

      expect(element.find('table[name=SelectedArtifacts] ' +
          'tr:contains("FooLinux")').length).toBe(1);
    });

    it('filters artifacts by platform', () => {
      const element = renderTestTemplate([]);

      browserTriggerEvent(element.find('a:contains("Darwin")'), 'click');
      expect(element.text()).not.toContain('FooLinux');
      expect(element.text()).toContain('BarDarwinWindows');

      browserTriggerEvent(element.find('a:contains("Windows")'), 'click');
      expect(element.text()).not.toContain('FooLinux');
      expect(element.text()).toContain('BarDarwinWindows');

      browserTriggerEvent(element.find('a:contains("Linux")'), 'click');
      expect(element.text()).toContain('FooLinux');
      expect(element.text()).not.toContain('BarDarwinWindows');
    });

    it('checks sources platform when filtering by platform', () => {
      descriptorLinux = {
        type: 'ArtifactDescriptor',
        value: {
          artifact: {
            type: 'Artifact',
            value: {
              name: {type: 'ArtifactName', value: 'FooLinux'},
              sources: [
                {
                  type: 'ArtifactSource',
                  value: {
                    supported_os: [
                      {type: 'RDFString', value: 'Linux'},
                    ],
                  },
                },
              ],
            },
          },
        },
      };

      descriptorDarwinWindows = {
        type: 'ArtifactDescriptor',
        value: {
          artifact: {
            type: 'Artifact',
            value: {
              name: {type: 'ArtifactName', value: 'BarDarwinWindows'},
              sources: [
                {
                  type: 'ArtifactSource',
                  value: {
                    supported_os: [
                      {type: 'RDFString', value: 'Darwin'},
                      {type: 'RDFString', value: 'Windows'},
                    ],
                  },
                },
              ],
            },
          },
        },
      };

      const element = renderTestTemplate([]);
      browserTriggerEvent(element.find('a:contains("Darwin")'), 'click');
      expect(element.text()).not.toContain('FooLinux');
      expect(element.text()).toContain('BarDarwinWindows');

      browserTriggerEvent(element.find('a:contains("Windows")'), 'click');
      expect(element.text()).not.toContain('FooLinux');
      expect(element.text()).toContain('BarDarwinWindows');

      browserTriggerEvent(element.find('a:contains("Linux")'), 'click');
      expect(element.text()).toContain('FooLinux');
      expect(element.text()).not.toContain('BarDarwinWindows');
    });

    it('filters artifacts by name', () => {
      const element = renderTestTemplate([]);

      element.find('input[name=Search]').val('bar');
      browserTriggerEvent(element.find('input[name=Search]'), 'change');
      $rootScope.$apply();

      expect(element.text()).not.toContain('FooLinux');
      expect(element.text()).toContain('BarDarwinWindows');
    });

    it('shows artifact descriptor info for selected artifact', () => {
      const element = renderTestTemplate([]);

      let infoDirective;
      browserTriggerEvent(element.find('td:contains("FooLinux")'), 'click');
      infoDirective = element.find('grr-semantic-value');
      expect(infoDirective.scope().$eval(infoDirective.attr('value'))).toEqual(
          descriptorLinux);

      browserTriggerEvent(element.find('td:contains("BarDarwinWindows")'), 'click');
      infoDirective = element.find('grr-semantic-value');
      expect(infoDirective.scope().$eval(infoDirective.attr('value'))).toEqual(
          descriptorDarwinWindows);
    });

    it('picks the artifact when Add is pressed', () => {
      const element = renderTestTemplate([]);

      browserTriggerEvent(element.find('table[name=Artifacts] ' +
          'td:contains("FooLinux")'), 'click');
      browserTriggerEvent(element.find('button:contains("Add")'), 'click');

      expect(element.find('table[name=Artifacts] ' +
          'td:contains("FooLinux")').length).toBe(0);
      expect(element.find('table[name=SelectedArtifacts] ' +
          'td:contains("FooLinux")').length).toBe(1);
    });

    it('picks the artifact on double click', () => {
      const element = renderTestTemplate([]);

      browserTriggerEvent(element.find('table[name=Artifacts] ' +
          'td:contains("FooLinux")'), 'dblclick');

      expect(element.find('table[name=Artifacts] ' +
          'td:contains("FooLinux")').length).toBe(0);
      expect(element.find('table[name=SelectedArtifacts] ' +
          'td:contains("FooLinux")').length).toBe(1);
    });

    it('updates the model when artifact is picked', () => {
      const element = renderTestTemplate([]);

      browserTriggerEvent(element.find('table[name=Artifacts] ' +
          'td:contains("FooLinux")'), 'dblclick');

      expect(angular.equals($rootScope.value,
                            [{type: 'ArtifactName', value: 'FooLinux'}]));
    });

    it('unpicks the artifact when Remove is pressed', () => {
      const element =
          renderTestTemplate([{type: 'ArtifactName', value: 'FooLinux'}]);

      browserTriggerEvent(element.find('table[name=SelectedArtifacts] ' +
          'td:contains("FooLinux")'), 'click');
      browserTriggerEvent(element.find('button:contains("Remove")'), 'click');

      expect(element.find('table[name=Artifacts] ' +
          'td:contains("FooLinux")').length).toBe(1);
      expect(element.find('table[name=SelectedArtifacts] ' +
          'td:contains("FooLinux")').length).toBe(0);
    });

    it('unpicks the artifact on double click', () => {
      const element =
          renderTestTemplate([{type: 'ArtifactName', value: 'FooLinux'}]);

      browserTriggerEvent(element.find('table[name=SelectedArtifacts] ' +
          'td:contains("FooLinux")'), 'dblclick');

      expect(element.find('table[name=Artifacts] ' +
          'td:contains("FooLinux")').length).toBe(1);
      expect(element.find('table[name=SelectedArtifacts] ' +
          'td:contains("FooLinux")').length).toBe(0);
    });

    it('updates the model when artifact is unpicked', () => {
      const element =
          renderTestTemplate([{type: 'ArtifactName', value: 'FooLinux'}]);

      browserTriggerEvent(element.find('table[name=SelectedArtifacts] ' +
          'td:contains("FooLinux")'), 'dblclick');

      expect(angular.equals($rootScope.value, []));
    });

    it('clears list of picked artifacts when Clear is pressed', () => {
      const element = renderTestTemplate([
        {type: 'ArtifactName', value: 'FooLinux'},
        {type: 'ArtifactName', value: 'BarDarwinWindows'}
      ]);

      browserTriggerEvent(element.find('button:contains("Clear")'), 'click');

      expect(element.find('table[name=SelectedArtifacts] ' +
          'td:contains("FooLinux")').length).toBe(0);
      expect(element.find('table[name=SelectedArtifacts] ' +
          'td:contains("BarDarwinWindows")').length).toBe(0);
    });

    it('updates the model when selection list is cleared', () => {
      const element = renderTestTemplate([
        {type: 'ArtifactName', value: 'FooLinux'},
        {type: 'ArtifactName', value: 'BarDarwinWindows'}
      ]);

      browserTriggerEvent(element.find('button:contains("Clear")'), 'click');

      expect(angular.equals($rootScope.value, []));
    });
  });
});


exports = {};

;return exports;});

//angular-components/client/add-clients-labels-dialog-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.client.addClientsLabelsDialogDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {clientModule} = goog.require('grrUi.client.client');


describe('add clients labels dialog', () => {
  let $compile;
  let $q;
  let $rootScope;
  let closeSpy;
  let dismissSpy;
  let grrApiService;


  beforeEach(module('/static/angular-components/client/' +
      'add-clients-labels-dialog.html'));
  beforeEach(module('/static/angular-components/core/' +
      'confirmation-dialog.html'));
  // TODO(user): get rid of references to nested directives
  // templates in tests that do not test these nested directives. I.e. here
  // grr-client-urn directive is used and its template has to be
  // explicitly references in the test, although the test itself
  // is written for add-clients-labels-dialog directive.
  beforeEach(module('/static/angular-components/semantic/' +
      'client-urn.html'));
  beforeEach(module(clientModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    grrApiService = $injector.get('grrApiService');

    closeSpy = jasmine.createSpy('close');
    dismissSpy = jasmine.createSpy('dismiss');
  }));

  const clients = [
    {
      type: 'ApiClient',
      value: {
        urn: {
          value: 'aff4:/C.0000111122223333',
          type: 'RDFURN',
        },
        client_id: {
          value: 'C.0000111122223333',
          type: 'ApiClientId',
        },
      },
    },
    {
      type: 'ApiClient',
      value: {
        urn: {
          value: 'aff4:/C.1111222233334444',
          type: 'RDFURN',
        },
        client_id: {
          value: 'C.1111222233334444',
          type: 'ApiClientId',
        },
      },
    },
  ];

  const renderTestTemplate = (clients) => {
    $rootScope.clients = clients;
    $rootScope.$close = closeSpy;
    $rootScope.$dismiss = dismissSpy;

    const template = '<grr-add-clients-labels-dialog ' +
        'clients="clients" />';

    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows list of affected clients', () => {
    const element = renderTestTemplate(clients);
    expect(element.text()).toContain('C.0000111122223333');
    expect(element.text()).toContain('C.1111222233334444');
  });

  it('calls dismiss callback when "Cancel" is pressed', () => {
    const element = renderTestTemplate(clients);
    browserTriggerEvent($('button[name=Cancel]', element), 'click');

    expect(dismissSpy).toHaveBeenCalled();
  });

  it('disables Proceed if no label name is entered', () => {
    const element = renderTestTemplate(clients);
    expect($('input[name=labelBox]', element).val()).toEqual('');
    expect($('button[name=Proceed][disabled]', element).length).toBe(1);
  });

  it('enables Proceed if label name is entered', () => {
    const element = renderTestTemplate(clients);

    $('input[name=labelBox]', element).val('foobar');
    browserTriggerEvent($('input[name=labelBox]', element), 'change');
    $rootScope.$apply();

    expect($('button[name=Proceed][disabled]', element).length).toBe(0);
  });

  it('sends request when Proceed is clicked', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'post').and.returnValue(deferred.promise);

    const element = renderTestTemplate(clients);

    $('input[name=labelBox]', element).val('foobar');
    browserTriggerEvent($('input[name=labelBox]', element), 'change');
    $rootScope.$apply();

    browserTriggerEvent($('button[name=Proceed]', element), 'click');

    expect(grrApiService.post).toHaveBeenCalledWith('/clients/labels/add', {
      client_ids: ['C.0000111122223333', 'C.1111222233334444'],
      labels: ['foobar'],
    });
  });

  it('shows failure warning on failure', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'post').and.returnValue(deferred.promise);

    const element = renderTestTemplate(clients);
    $('input[name=labelBox]', element).val('foobar');
    browserTriggerEvent($('input[name=labelBox]', element), 'change');
    $rootScope.$apply();
    browserTriggerEvent($('button[name=Proceed]', element), 'click');

    deferred.reject({data: {message: 'NOT OK'}});
    $rootScope.$apply();

    expect(element.text()).toContain('NOT OK');
  });

  it('shows success message on success', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'post').and.returnValue(deferred.promise);

    const element = renderTestTemplate(clients);
    $('input[name=labelBox]', element).val('foobar');
    browserTriggerEvent($('input[name=labelBox]', element), 'change');
    $rootScope.$apply();
    browserTriggerEvent($('button[name=Proceed]', element), 'click');

    deferred.resolve('OK');
    $rootScope.$apply();

    expect(element.text()).toContain('Label was successfully added');
  });

  it('calls on-close callback when closed after success', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'post').and.returnValue(deferred.promise);

    const element = renderTestTemplate(clients);
    $('input[name=labelBox]', element).val('foobar');
    browserTriggerEvent($('input[name=labelBox]', element), 'change');
    $rootScope.$apply();
    browserTriggerEvent($('button[name=Proceed]', element), 'click');

    deferred.resolve('OK');
    $rootScope.$apply();

    browserTriggerEvent($('button[name=Close]', element), 'click');

    expect(closeSpy).toHaveBeenCalled();
  });
});


exports = {};

;return exports;});

//angular-components/client/check-client-access-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.client.checkClientAccessDirectiveTest');
goog.setTestOnly();

const {clientModule} = goog.require('grrUi.client.client');
const {testsModule} = goog.require('grrUi.tests');


describe('grr-check-client-access directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let $timeout;
  let grrApiService;
  let grrRoutingService;


  beforeEach(module('/static/angular-components/client/' +
      'check-client-access.html'));
  beforeEach(module(clientModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $timeout = $injector.get('$timeout');

    grrApiService = $injector.get('grrApiService');
    grrRoutingService = $injector.get('grrRoutingService');
  }));

  const renderTestTemplate = (noRedirect, omitClientId) => {
    $rootScope.noRedirect = noRedirect;
    if (!omitClientId) {
      $rootScope.clientId = 'C.0001000200030004';
    }
    $rootScope.outHasAccess = undefined;

    const template = '<grr-check-client-access no-redirect="noRedirect" ' +
        'client-id="clientId" out-has-access="outHasAccess">foo-bar' +
        '</grr-check-client-access>';

    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('doesn\'t assign .access-disabled class when clientId is undefined',
     () => {
       spyOn(grrApiService, 'head').and.returnValue($q.defer().promise);

       const element = renderTestTemplate(false, true);
       expect(element.find('div.access-disabled').length).toBe(0);
     });

  it('shows transcluded content', () => {
    spyOn(grrApiService, 'head').and.returnValue($q.defer().promise);

    const element = renderTestTemplate();
    expect(element.text()).toContain('foo-bar');
  });

  it('sends HEAD request to check access', () => {
    spyOn(grrApiService, 'head').and.returnValue($q.defer().promise);

    renderTestTemplate();
    expect(grrApiService.head).toHaveBeenCalledWith(
        'clients/C.0001000200030004/flows');
  });

  it('updates the "outHasAccess" binding when access is permitted', () => {
    const deferred = $q.defer();
    deferred.resolve();
    spyOn(grrApiService, 'head').and.returnValue(deferred.promise);

    renderTestTemplate();
    expect($rootScope.outHasAccess).toBe(true);
  });

  it('doesn\'t assign .access-disabled class to container div when access is permitted',
     () => {
       const deferred = $q.defer();
       deferred.resolve();
       spyOn(grrApiService, 'head').and.returnValue(deferred.promise);

       const element = renderTestTemplate();
       expect(element.find('div.access-disabled').length).toBe(0);
     });

  it('updates the "outHasAccess" binding when access is rejected', () => {
    const deferred = $q.defer();
    deferred.reject();
    spyOn(grrApiService, 'head').and.returnValue(deferred.promise);

    renderTestTemplate();
    expect($rootScope.outHasAccess).toBe(false);
  });

  it('assigns .access-disabled class to container div when access is rejected',
     () => {
       const deferred = $q.defer();
       deferred.reject();
       spyOn(grrApiService, 'head').and.returnValue(deferred.promise);

       const element = renderTestTemplate();
       expect(element.find('div.access-disabled').length).toBe(1);
     });

  it('redirects to "client" state after 1s if noRedirect!=true and access is rejected',
     () => {
       const deferred = $q.defer();
       deferred.reject();
       spyOn(grrApiService, 'head').and.returnValue(deferred.promise);

       renderTestTemplate();

       spyOn(grrRoutingService, 'go');
       $timeout.flush();
       expect(grrRoutingService.go).toHaveBeenCalledWith('client', {
         clientId: 'C.0001000200030004'
       });
     });

  it('does not redirect after 1s if noRedirect=true and access is rejected',
     () => {
       const deferred = $q.defer();
       deferred.resolve();
       spyOn(grrApiService, 'head').and.returnValue(deferred.promise);

       renderTestTemplate();

       spyOn(grrRoutingService, 'go');
       $timeout.flush();
       expect(grrRoutingService.go).not.toHaveBeenCalled();
     });
});


exports = {};

;return exports;});

//angular-components/client/client-status-icons-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.client.clientStatusIconsDirectiveTest');
goog.setTestOnly();

const {clientModule} = goog.require('grrUi.client.client');
const {testsModule} = goog.require('grrUi.tests');


describe('client status icons', () => {
  let $compile;
  let $rootScope;
  let grrTimeService;


  beforeEach(module('/static/angular-components/client/client-status-icons.html'));
  beforeEach(module(clientModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    grrTimeService = $injector.get('grrTimeService');
  }));

  const render = (client) => {
    $rootScope.client = client;

    const template = '<grr-client-status-icons client="client" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows online icon when last ping is 1 minute ago', () => {
    const client = {
      type: 'ApiClient',
      value: {
        'last_seen_at': {
          value: 42 * 1000000,
        },
      },
    };
    grrTimeService.getCurrentTimeMs = (() => (42 + 60) * 1000);

    const element = render(client);
    const iconElement = $('img[name=clientStatusIcon]', element);

    expect(iconElement.length).toBe(1);
    expect(iconElement[0].src).toContain('online.png');
    expect(iconElement[0].title).toBe('1 minutes ago');
  });

  it('shows online-1day icon when last ping is 23 hours ago', () => {
    const client = {
      type: 'ApiClient',
      value: {
        'last_seen_at': {
          value: 42 * 1000000,
        },
      },
    };
    grrTimeService.getCurrentTimeMs = (() => (42 + 60 * 60 * 23) * 1000);

    const element = render(client);
    const iconElement = $('img[name=clientStatusIcon]', element);

    expect(iconElement.length).toBe(1);
    expect(iconElement[0].src).toContain('online-1d.png');
    expect(iconElement[0].title).toBe('23 hours ago');
  });

  it('shows offline icon when last ping is is 3 days ago', () => {
    const client = {
      type: 'ApiClient',
      value: {
        'last_seen_at': {
          value: 42 * 1000000,
        },
      },
    };
    grrTimeService.getCurrentTimeMs = (() => (42 + 60 * 60 * 24 * 3) * 1000);

    const element = render(client);
    const iconElement = $('img[name=clientStatusIcon]', element);

    expect(iconElement.length).toBe(1);
    expect(iconElement[0].src).toContain('offline.png');
    expect(iconElement[0].title).toBe('3 days ago');
  });

  it('does not show crash icon if no crash happened', () => {
    const client = {
      type: 'ApiClient',
      value: {},
    };

    const element = render(client);
    const iconElement = $('img[name=clientCrashIcon]', element);

    expect(iconElement.length).toBe(0);
  });

  it('does not show crash icon if crash happened 1 week ago', () => {
    const client = {
      type: 'ApiClient',
      value: {
        'last_crash_at': {
          value: 42 * 1000000,
        },
      },
    };
    grrTimeService.getCurrentTimeMs = (() => (42 + 60 * 60 * 24 * 7) * 1000);

    const element = render(client);
    const iconElement = $('img[name=clientCrashIcon]', element);
    expect(iconElement.length).toBe(0);
  });

  it('shows crash icon if crash happened 1 hour ago', () => {
    const client = {
      type: 'ApiClient',
      value: {
        'last_crash_at': {
          value: 42 * 1000000,
        },
      },
    };
    grrTimeService.getCurrentTimeMs = (() => (42 + 60 * 60) * 1000);

    const element = render(client);
    const iconElement = $('img[name=clientCrashIcon]', element);

    expect(iconElement.length).toBe(1);
    expect(iconElement[0].src).toContain('skull-icon.png');
    expect(iconElement[0].title).toBe('1 hours ago');
  });

  it('shows no disk warning icon if none are present', () => {
    const element = render({
      type: 'ApiClient',
      value: {},
    });
    const warningElement = $('img[name=clientDiskWarnings]', element);
    expect(warningElement.length).toBe(0);
  });

  it('shows disk warning icon if some are present', () => {
    const volume1 = {
      name: {
        value: '/Volume/A',
      },
      total_allocation_units: {
        value: 100,
      },
      actual_available_allocation_units: {
        value: 3,
      },
    };
    const volume2 = {
      name: {
        value: 'C:',
      },
      total_allocation_units: {
        value: 100,
      },
      actual_available_allocation_units: {
        value: 4,
      },
    };
    const client = {
      type: 'ApiClient',
      value: {
        volumes: [
          {
            type: 'Volume',
            value: volume1,
          },
          {
            type: 'Volume',
            value: volume2,
          },
        ],
      },
    };

    const element = render(client);
    const warningElement = $('img[name=clientDiskWarnings]', element);

    expect(warningElement.length).toBe(1);
    // TODO(hanuszczak): Write tests for the dialog with warning details.
  });
});


exports = {};

;return exports;});

//angular-components/client/client-usernames-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.client.clientUsernamesDirectiveTest');
goog.setTestOnly();

const {clientModule} = goog.require('grrUi.client.client');
const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('client usernames', () => {
  let $compile;
  let $rootScope;

  beforeEach(module(clientModule.name));
  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrSemanticValue');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const render = (value) => {
    $rootScope.value = value;

    const template = '<grr-client-usernames value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('List client usernames with grr-semantic-value', () => {
    const usernames = {value: 'test_1 test_2 test_3'};
    const element = render(usernames);
    const directive = element.find('grr-semantic-value');
    expect(directive.scope().$eval('usernames').length).toEqual(3);
    expect(directive.scope().$eval('usernames')[0].value).toEqual('test_1');
    expect(directive.scope().$eval('usernames')[1].value).toEqual('test_2');
    expect(directive.scope().$eval('usernames')[2].value).toEqual('test_3');

    expect(directive.scope().$eval('usernames')[0].type).toEqual('RDFString');
    expect(directive.scope().$eval('usernames')[1].type).toEqual('RDFString');
    expect(directive.scope().$eval('usernames')[2].type).toEqual('RDFString');
  });
});


exports = {};

;return exports;});

//angular-components/client/clients-list-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.client.clientsListDirectiveTest');
goog.setTestOnly();

const {clientModule} = goog.require('grrUi.client.client');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('clients list', () => {
  let $compile;
  let $interval;
  let $q;
  let $rootScope;
  let grrApiService;
  let grrRoutingService;

  let grrReflectionService;

  beforeEach(module('/static/angular-components/client/clients-list.html'));
  beforeEach(module(clientModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrClientStatusIcons');
  stubDirective('grrSemanticValue');
  stubDirective('grrDisableIfNoTrait');

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $interval = $injector.get('$interval');
    grrApiService = $injector.get('grrApiService');
    grrReflectionService = $injector.get('grrReflectionService');
    grrRoutingService = $injector.get('grrRoutingService');

    grrReflectionService.getRDFValueDescriptor = ((valueType) => {
      const deferred = $q.defer();
      deferred.resolve({
        name: valueType,
        mro: [valueType],
      });
      return deferred.promise;
    });
  }));

  afterEach(() => {
    // We have to clean document's body to remove tables we add there.
    $(document.body).html('');
  });

  const render = (query) => {
    const template = '<grr-clients-list />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    $('body').append(element);
    $interval.flush(1000);

    return element;
  };

  const mockApiService = (value) => {
    if (value) {
      // Be able to handle 2 requests: second request will be made if less
      // then 1 page of items is returned.
      spyOn(grrApiService, 'get').and.returnValues($q.when({ data: value }),
                                                   $q.defer().promise);
    } else {
      spyOn(grrApiService, 'get').and.returnValue($q.defer().promise);
    }
  };

  const mockRoutingService = (query) => {
    spyOn(grrRoutingService, 'uiOnParamsChanged').and.callFake((s, p, cb) => {
      cb(query);
    });
  };

  it('sends request with a query to the server', () => {
    mockApiService();
    mockRoutingService('.');

    render();
    expect(grrApiService.get).toHaveBeenCalledWith('/clients', {
      query: '.',
      offset: 0,
      count: 50,
    });
  });

  it('renders list with one client correctly', () => {
    const clientsResponse = {
      items: [
        {
          type: 'VFSGRRClient',
          value: {
            client_id: {
              value: 'C.0000000000000001',
              type: 'ApiClientId',
            },
            first_seen_at: {
              value: 1358346544915179,
              type: 'RDFDatetime',
            },
            last_clock: {
              value: 1427750098770803,
              type: 'RDFDatetime',
            },
            os_info: {
              value: {
                fqdn: {
                  value: 'localhost.com',
                  type: 'RDFString',
                },
                version: {
                  value: '10.9.5',
                  type: 'VersionString',
                },
                install_date: {
                  value: 1385377629000000,
                  type: 'RDFDatetime',
                },
              },
              type: 'ClientInformation',
            },
            labels: [
              {
                'value': {
                  'owner': {
                    'value': 'GRR',
                    'type': 'unicode',
                    'age': 0,
                  },
                  'name': {
                    'value': 'foobar-label',
                    'type': 'unicode',
                    'age': 0,
                  },
                },
              },
            ],
            interfaces: [
              {
                type: 'Interface',
                value: {
                  mac_address: '<mac address>',
                },
              },
            ],
            users: [
              {
                type: 'User',
                value: {
                  username: {
                    type: 'RDFString',
                    value: 'user_foo',
                  },
                },
              },
              {
                type: 'User',
                value: {
                  type: 'RDFString',
                  value: 'user_bar',
                },
              },
            ],
          },
        },
      ],
    };

    mockApiService(clientsResponse);
    mockRoutingService('.');

    const element = render();
    // Check that grrClientStatusIcons directive is rendered. It means
    // that the row with a client info got rendered correctly.
    expect($('grr-client-status-icons', element).length).toBe(1);
  });

  it('ignores interfaces without mac addresses', () => {
    const clientsResponse = {
      items: [
        {
          type: 'VFSGRRClient',
          value: {
            client_id: {
              value: 'C.0000000000000001',
              type: 'ApiClientId',
            },
            interfaces: [
              {
                type: 'Interface1',
                value: {
                  mac_address: '<mac address 1>',
                },
              },
              {
                type: 'Interface Without Mac Address',
                value: {},
              },
              {
                type: 'Interface2',
                value: {
                  mac_address: '<mac address 2>',
                },
              },
            ],
          },
        },
      ],
    };

    mockApiService(clientsResponse);
    mockRoutingService('.');

    const element = render();
    const macTableColumn = $('th:contains(MAC)', element).index();
    const macCell = $('tr td', element)[macTableColumn];
    const macDirective = $('grr-semantic-value', macCell);
    const macDirectiveScope = macDirective.scope();

    const addresses = macDirectiveScope.$eval(macDirective.attr('value'));
    expect(addresses).toEqual(['<mac address 1>', '<mac address 2>']);
  });
});


exports = {};

;return exports;});

//angular-components/client/host-history-dialog-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.client.hostHistoryDialogDirectiveTest');
goog.setTestOnly();

const {clientModule} = goog.require('grrUi.client.client');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('semantic versioned proto directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;
  let grrTimeService;


  beforeEach(module('/static/angular-components/client/' +
      'host-history-dialog.html'));
  beforeEach(module(clientModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrSemanticValue');

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    grrApiService = $injector.get('grrApiService');
    grrTimeService = $injector.get('grrTimeService');
  }));

  const renderTestTemplate = (fieldPath) => {
    $rootScope.clientId = 'C.00000000000000001';
    $rootScope.fieldPath = fieldPath;
    $rootScope.close = (() => {});

    const template = '<grr-host-history-dialog ' +
        'client-id="clientId" field-path="fieldPath" ' +
        'close="close()" />';

    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('fetches data for the last year', () => {
    // One year + 1 millisecond.
    spyOn(grrTimeService, 'getCurrentTimeMs').and.returnValue(31536000001);
    spyOn(grrApiService, 'get').and.returnValue($q.defer().promise);

    renderTestTemplate('foo');
    expect(grrApiService.get)
        .toHaveBeenCalledWith('/clients/C.00000000000000001/versions', {
          mode: 'DIFF',
          start: 1000,
          end: 31536000001000,
        });
  });

  it('correctly shows data for field path with 1 component', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'get').and.returnValue(deferred.promise);
    deferred.resolve({
      data: {
        items: [
          {
            type: 'Foo',
            value: {
              field: {
                type: 'RDFString',
                value: 'bar1',
              },
              age: {
                type: 'RDFDatetime',
                value: 1,
              },
            },
          },
          {
            type: 'Foo',
            value: {
              age: {
                type: 'RDFDatetime',
                value: 2,
              },
            },
          },
          {
            type: 'Foo',
            value: {
              field: {
                type: 'RDFString',
                value: 'bar2',
              },
              age: {
                type: 'RDFDatetime',
                value: 3,
              },
            },
          },
        ],
      },
    });

    const element = renderTestTemplate('field');
    // 2nd item shouldn't be shown since it doesn't have the needed field.
    expect(element.find('td.version grr-semantic-value').length).toBe(2);

    // Values should be listed in their timestamps order (with timtestamps
    // descending).
    let directive = element.find('td.version:nth(0) grr-semantic-value');
    let value = directive.scope().$eval(directive.attr('value'));
    expect(value).toEqual({type: 'RDFString', value: 'bar2'});

    directive = element.find('td.version:nth(1) grr-semantic-value');
    value = directive.scope().$eval(directive.attr('value'));
    expect(value).toEqual({type: 'RDFString', value: 'bar1'});
  });

  it('correctly shows data for field path with 2 components', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'get').and.returnValue(deferred.promise);
    deferred.resolve({
      data: {
        items: [
          {
            type: 'Foo',
            value: {
              field1: {
                type: 'Bar',
                value: {
                  field2: {
                    type: 'RDFString',
                    value: 'oh1',
                  },
                },
              },
              age: {
                type: 'RDFDatetime',
                value: 1,
              },
            },
          },
          {
            type: 'Foo',
            value: {
              age: {
                type: 'RDFDatetime',
                value: 2,
              },
            },
          },
          {
            type: 'Foo',
            value: {
              field1: {
                type: 'Bar',
                value: {
                  field2: {
                    type: 'RDFString',
                    value: 'oh2',
                  },
                },
              },
              age: {
                type: 'RDFDatetime',
                value: 3,
              },
            },
          },
        ],
      },
    });

    const element = renderTestTemplate('field1.field2');
    // 2nd item shouldn't be shown since it doesn't have the needed field.
    expect(element.find('td.version grr-semantic-value').length).toBe(2);

    // Values should be listed in their timestamps order (with timtestamps
    // descending).
    let directive = element.find('td.version:nth(0) grr-semantic-value');
    let value = directive.scope().$eval(directive.attr('value'));
    expect(value).toEqual({type: 'RDFString', value: 'oh2'});

    directive = element.find('td.version:nth(1) grr-semantic-value');
    value = directive.scope().$eval(directive.attr('value'));
    expect(value).toEqual({type: 'RDFString', value: 'oh1'});
  });
});


exports = {};

;return exports;});

//angular-components/client/remove-clients-labels-dialog-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.client.removeClientsLabelsDialogDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {clientModule} = goog.require('grrUi.client.client');


describe('remove clients labels dialog', () => {
  let $compile;
  let $q;
  let $rootScope;
  let closeSpy;
  let dismissSpy;
  let grrApiService;


  beforeEach(module('/static/angular-components/client/' +
      'remove-clients-labels-dialog.html'));
  beforeEach(module('/static/angular-components/core/' +
      'confirmation-dialog.html'));
  // TODO(user): get rid of references to nested directives
  // templates in tests that do not test these nested directives. I.e. here
  // grr-client-urn directive is used and its template has to be
  // explicitly references in the test, although the test itself
  // is written for remove-clients-labels-dialog directive.
  beforeEach(module('/static/angular-components/semantic/' +
      'client-urn.html'));
  beforeEach(module(clientModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    grrApiService = $injector.get('grrApiService');

    closeSpy = jasmine.createSpy('close');
    dismissSpy = jasmine.createSpy('dismiss');
  }));

  const renderTestTemplate = (clients) => {
    $rootScope.clients = clients;
    $rootScope.$close = closeSpy;
    $rootScope.$dismiss = dismissSpy;

    const template = '<grr-remove-clients-labels-dialog ' +
        'clients="clients" />';

    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows list of affected clients', () => {
    const clients = [
      {
        type: 'ApiClient',
        value: {
          urn: {
            value: 'aff4:/C.0000111122223333',
            type: 'RDFURN',
          },
          client_id: {
            value: 'C.0000111122223333',
            type: 'ApiClientId',
          },
        },
      },
      {
        type: 'ApiClient',
        value: {
          urn: {
            value: 'aff4:/C.1111222233334444',
            type: 'RDFURN',
          },
          client_id: {
            value: 'C.1111222233334444',
            type: 'ApiClientId',
          },
        },
      },
    ];
    const element = renderTestTemplate(clients);
    expect(element.text()).toContain('C.0000111122223333');
    expect(element.text()).toContain('C.1111222233334444');
  });

  const clientsWithTwoUserLabels = [
    {
      type: 'ApiClient',
      value: {
        urn: {
          value: 'aff4:/C.0000111122223333',
          type: 'RDFURN',
        },
        client_id: {
          value: 'C.0000111122223333',
          type: 'ApiClientId',
        },
        labels: [
          {
            value: {
              name: {
                value: 'foo',
              },
              owner: {
                value: 'test2',
              },
            },
          },
        ],
      },
    },
    {
      type: 'ApiClient',
      value: {
        urn: {
          value: 'aff4:/C.1111222233334444',
          type: 'RDFURN',
        },
        client_id: {
          value: 'C.1111222233334444',
          type: 'ApiClientId',
        },
        labels: [
          {
            value: {
              name: {
                value: 'bar',
              },
              owner: {
                value: 'test2',
              },
            },
          },
        ],
      },
    },
  ];

  const clientsWithOneUserLabelAndOneSystemLabel = [
    {
      type: 'ApiClient',
      value: {
        urn: {
          value: 'aff4:/C.0000111122223333',
          type: 'RDFURN',
        },
        client_id: {
          value: 'C.0000111122223333',
          type: 'ApiClientId',
        },
        labels: [
          {
            value: {
              name: {
                value: 'foo',
              },
              owner: {
                value: 'GRR',
              },
            },
          },
        ],
      },
    },
    {
      type: 'ApiClient',
      value: {
        urn: {
          value: 'aff4:/C.1111222233334444',
          type: 'RDFURN',
        },
        client_id: {
          value: 'C.1111222233334444',
          type: 'ApiClientId',
        },
        labels: [
          {
            value: {
              name: {
                value: 'bar',
              },
              owner: {
                value: 'test2',
              },
            },
          },
        ],
      },
    },
  ];

  it('shows dropdown with a union of labels from all clients', () => {
    const element = renderTestTemplate(clientsWithTwoUserLabels);
    expect($('select option', element).length).toBe(2);
    expect($('select option[label=foo]', element).length).toBe(1);
    expect($('select option[label=bar]', element).length).toBe(1);
  });

  it('does not show system labels in the list', () => {
    const element =
        renderTestTemplate(clientsWithOneUserLabelAndOneSystemLabel);
    expect($('select option', element).length).toBe(1);
    expect($('select option[label=foo]', element).length).toBe(0);
    expect($('select option[label=bar]', element).length).toBe(1);
  });

  it('has a label selected by default', () => {
    const element = renderTestTemplate(clientsWithTwoUserLabels);
    const selected = $(':selected', element).text();
    expect(selected).toBeDefined();
  });

  it('calls dismiss callback when "Cancel" is pressed', () => {
    const element = renderTestTemplate(clientsWithTwoUserLabels);
    browserTriggerEvent($('button[name=Cancel]', element), 'click');

    expect(dismissSpy).toHaveBeenCalled();
  });

  it('sends request when proceed is clicked', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'post').and.returnValue(deferred.promise);

    const element = renderTestTemplate(clientsWithTwoUserLabels);
    browserTriggerEvent($('button[name=Proceed]', element), 'click');

    expect(grrApiService.post).toHaveBeenCalledWith('/clients/labels/remove', {
      client_ids: ['C.0000111122223333', 'C.1111222233334444'],
      labels: ['foo'],
    });
  });

  it('shows failure warning on failure', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'post').and.returnValue(deferred.promise);

    const element = renderTestTemplate(clientsWithTwoUserLabels);
    browserTriggerEvent($('button[name=Proceed]', element), 'click');

    deferred.reject({data: {message: 'NOT OK'}});
    $rootScope.$apply();

    expect(element.text()).toContain('NOT OK');
  });

  it('shows success message on success', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'post').and.returnValue(deferred.promise);

    const element = renderTestTemplate(clientsWithTwoUserLabels);
    browserTriggerEvent($('button[name=Proceed]', element), 'click');

    deferred.resolve('OK');
    $rootScope.$apply();

    expect(element.text()).toContain('Label was successfully removed');
  });

  it('calls on-close callback when closed after success', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'post').and.returnValue(deferred.promise);

    const element = renderTestTemplate(clientsWithTwoUserLabels);
    browserTriggerEvent($('button[name=Proceed]', element), 'click');

    deferred.resolve('OK');
    $rootScope.$apply();

    browserTriggerEvent($('button[name=Close]', element), 'click');

    expect(closeSpy).toHaveBeenCalled();
  });
});


exports = {};

;return exports;});

//angular-components/config/binaries-list-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.config.binariesListDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {configModule} = goog.require('grrUi.config.config');
const {sortBinaries} = goog.require('grrUi.config.binariesListDirective');


describe('grr-binaries-list directive', () => {
  describe('sortBinaries()', () => {

    it('adds correct metadata to binaries without slashes in path', () => {
      const binaries = sortBinaries([{
        value: {
          path: {
            value: 'foo',
          },
        },
      }]);
      expect(binaries[0]['pathLen']).toBe(1);
      expect(binaries[0]['dirName']).toBe('');
      expect(binaries[0]['baseName']).toBe('foo');
    });

    it('adds correct metadata to binaries with slashes in path', () => {
      const binaries = sortBinaries([{
        value: {
          path: {
            value: 'foo/bar/42',
          },
        },
      }]);
      expect(binaries[0]['pathLen']).toBe(3);
      expect(binaries[0]['dirName']).toBe('foo/bar');
      expect(binaries[0]['baseName']).toBe('42');
    });

    it('puts paths with more slashes first', () => {
      const binaries = sortBinaries([
        {
          value: {
            path: {
              value: 'foo',
            },
          },
        },
        {
          value: {
            path: {
              value: 'foo/bar',
            },
          },
        },
        {
          value: {
            path: {
              value: 'foo/bar/42',
            },
          },
        },
      ]);

      expect(binaries[0]['value']['path']['value']).toBe('foo/bar/42');
      expect(binaries[1]['value']['path']['value']).toBe('foo/bar');
      expect(binaries[2]['value']['path']['value']).toBe('foo');
    });

    it('sorts paths with same number of slashes alphabetically', () => {
      const binaries = sortBinaries([
        {
          value: {
            path: {
              value: 'foo/b',
            },
          },
        },
        {
          value: {
            path: {
              value: 'foo/c',
            },
          },
        },
        {
          value: {
            path: {
              value: 'foo/a',
            },
          },
        },
      ]);

      expect(binaries[0]['value']['path']['value']).toBe('foo/a');
      expect(binaries[1]['value']['path']['value']).toBe('foo/b');
      expect(binaries[2]['value']['path']['value']).toBe('foo/c');
    });
  });

  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;

  beforeEach(module('/static/angular-components/config/binaries-list.html'));
  beforeEach(module(configModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrApiService = $injector.get('grrApiService');
  }));

  const render = (binaries, typeFilter) => {
    $rootScope.binaries = binaries;
    $rootScope.typeFilter = typeFilter;

    const template = '<grr-binaries-list binaries="binaries" ' +
        'type-filter="{$ typeFilter $}" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('triggers download when row is clicked', () => {
    const element = render(
        [{
          value: {
            path: {
              value: 'foo/bar',
            },
            type: {
              value: 'PYTHON_HACK',
            },
          },
        }],
        'PYTHON_HACK');

    const deferred = $q.defer();
    spyOn(grrApiService, 'downloadFile').and.returnValue(deferred.promise);

    browserTriggerEvent(element.find('tr:contains("foo")'), 'click');

    expect(grrApiService.downloadFile).toHaveBeenCalledWith(
        '/config/binaries-blobs/PYTHON_HACK/foo/bar');
  });
});


exports = {};

;return exports;});

//angular-components/core/api-items-provider-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.apiItemsProviderDirectiveTest');
goog.setTestOnly();

const {ApiItemsProviderController} = goog.require('grrUi.core.apiItemsProviderDirective');
const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('API items provider directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiServiceMock;


  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');

    grrApiServiceMock = {get: function() {}};
  }));

  const getController = (url, queryParams, transformItems, testResponse) => {
    let controller;

    $rootScope.testUrl = url;
    $rootScope.testQueryParams = queryParams;
    $rootScope.testTransformItems = transformItems;

    inject(($injector) => {
      controller = $injector.instantiate(ApiItemsProviderController, {
        '$scope': $rootScope,
        '$attrs': {
          'url': 'testUrl',
          'queryParams': 'testQueryParams',
          'transformItems': transformItems ? 'testTransformItems(items)' :
                                             undefined,
        },
        'grrApiService': grrApiServiceMock,
      });
    });

    const deferred = $q.defer();
    deferred.resolve(testResponse);
    spyOn(grrApiServiceMock, 'get').and.returnValue(deferred.promise);

    $rootScope.$apply();

    return controller;
  };

  it('fetches ranges of items according to offset and count', () => {
    const controller = getController('some/api/path', undefined, undefined);

    controller.fetchItems(0, 10);
    expect(grrApiServiceMock.get).toHaveBeenCalledWith(
        'some/api/path', {offset: 0, count: 10});
  });

  it('does not fetch total count when opt_withTotalCount is true', () => {
    const controller = getController('some/api/path', undefined, undefined);

    controller.fetchItems(0, 10, true);
    expect(grrApiServiceMock.get).toHaveBeenCalledWith(
        'some/api/path', {offset: 0, count: 10});
  });

  it('adds "filter" to query when fetching filtered items', () => {
    const controller = getController('some/api/path', undefined, undefined);

    controller.fetchFilteredItems('some', 0, 10);
    expect(grrApiServiceMock.get).toHaveBeenCalledWith(
        'some/api/path', {offset: 0, count: 10, filter: 'some'});
  });
});


exports = {};

;return exports;});

//angular-components/core/api-service_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.apiServiceTest');
goog.setTestOnly();

const {coreModule} = goog.require('grrUi.core.core');
const {encodeUrlPath, stripTypeInfo, UNAUTHORIZED_API_RESPONSE_EVENT} = goog.require('grrUi.core.apiService');


describe('API service', () => {
  let $httpBackend;
  let $interval;
  let $q;
  let $rootScope;
  let grrApiService;


  beforeEach(module(coreModule.name));

  beforeEach(inject(($injector) => {
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    $httpBackend = $injector.get('$httpBackend');
    $interval = $injector.get('$interval');
    grrApiService = $injector.get('grrApiService');

    grrApiService.markAuthDone();
  }));

  afterEach(() => {
    $httpBackend.verifyNoOutstandingExpectation();
    $httpBackend.verifyNoOutstandingRequest();
  });

  describe('encodeUrlPath() function', () => {

    it('does not touch slashes and normal characters', () => {
      expect(encodeUrlPath('////')).toBe('////');
      expect(encodeUrlPath('/a/b/c/d/')).toBe('/a/b/c/d/');
    });

    it('encodes "?", "&" and "+" characters', () => {
      expect(encodeUrlPath('/foo?bar=a+b')).toBe('/foo%3Fbar%3Da%2Bb');
    });
  });

  describe('stripTypeInfo() function', () => {

    it('converts richly typed primitive into a primitive value', () => {
      const richData = {
        'age': 0,
        'mro': [
          'RDFString',
          'object',
        ],
        'type': 'unicode',
        'value': 'label2',
      };

      expect(stripTypeInfo(richData)).toEqual('label2');
    });

    it('converts typed structure into a primitive dictionary', () => {
      const richData = {
        'age': 0,
        'mro': [
          'ObjectLabel',
          'RDFProtoStruct',
          'RDFStruct',
          'RDFValue',
          'object',
        ],
        'type': 'ObjectLabel',
        'value': {
          'name': {
            'age': 0,
            'mro': [
              'unicode',
              'basestring',
              'object',
            ],
            'type': 'unicode',
            'value': 'label2',
          },
        },
      };

      expect(stripTypeInfo(richData)).toEqual({'name': 'label2'});
    });

    it('converts richly typed list into list of primitives', () => {
      const richData = [
        {
          'age': 0,
          'mro': [
            'RDFString',
            'object',
          ],
          'type': 'unicode',
          'value': 'label2',
        },
        {
          'age': 0,
          'mro': [
            'RDFString',
            'object',
          ],
          'type': 'unicode',
          'value': 'label3',
        },
      ];


      expect(stripTypeInfo(richData)).toEqual(
        ['label2', 'label3']);
    });

    it('converts list structure field into list of primitives', () => {
      const richData = {
        'age': 0,
        'mro': [
          'ObjectLabel',
          'RDFProtoStruct',
          'RDFStruct',
          'RDFValue',
          'object',
        ],
        'type': 'ObjectLabel',
        'value': {
          'name': [
            {
              'age': 0,
              'mro': [
                'unicode',
                'basestring',
                'object',
              ],
              'type': 'unicode',
              'value': 'label2',
            },
            {
              'age': 0,
              'mro': [
                'unicode',
                'basestring',
                'object',
              ],
              'type': 'unicode',
              'value': 'label3',
            },
          ],
        },
      };

      expect(stripTypeInfo(richData)).toEqual({
        'name': ['label2', 'label3'],
      });
    });
  });

  describe('head() method', () => {
    it('adds "/api/" to a given url', () => {
      $httpBackend.whenHEAD('/api/some/path').respond(200);
      grrApiService.head('some/path');
      $httpBackend.flush();
    });

    it('adds "/api/" to a given url starting with "/"', () => {
      $httpBackend.whenHEAD('/api/some/path').respond(200);
      grrApiService.head('/some/path');
      $httpBackend.flush();
    });

    it('passes user-provided headers in the request', () => {
      $httpBackend.whenHEAD('/api/some/path?key1=value1&key2=value2').
          respond(200);
      grrApiService.head('some/path', {key1: 'value1', key2: 'value2'});
      $httpBackend.flush();
    });

    it('passes user-provided headers in the request', () => {
      $httpBackend.whenHEAD('/api/some/path?' +
          'key1=value1&key2=value2').respond(200);
      grrApiService.head('some/path', {key1: 'value1', key2: 'value2'});
      $httpBackend.flush();
    });

    it('url-escapes the path', () => {
      $httpBackend.whenHEAD(
          '/api/some/path%3Ffoo%26bar?key1=value1&key2=value2').respond(200);
      grrApiService.head('some/path?foo&bar', {key1: 'value1', key2: 'value2'});
      $httpBackend.flush();
    });
  });

  describe('get() method', () => {
    it('adds "/api/" to a given url', () => {
      $httpBackend.whenGET('/api/some/path').respond(200);
      grrApiService.get('some/path');
      $httpBackend.flush();
    });

    it('adds "/api/" to a given url starting with "/"', () => {
      $httpBackend.whenGET('/api/some/path').respond(200);
      grrApiService.get('/some/path');
      $httpBackend.flush();
    });

    it('passes user-provided headers in the request', () => {
      $httpBackend.whenGET('/api/some/path?key1=value1&key2=value2').
          respond(200);
      grrApiService.get('some/path', {key1: 'value1', key2: 'value2'});
      $httpBackend.flush();
    });

    it('passes user-provided headers in the request', () => {
      $httpBackend.whenGET('/api/some/path?' +
          'key1=value1&key2=value2').respond(200);
      grrApiService.get('some/path', {key1: 'value1', key2: 'value2'});
      $httpBackend.flush();
    });

    it('url-escapes the path', () => {
      $httpBackend.whenGET(
          '/api/some/path%3Ffoo%26bar?key1=value1&key2=value2').respond(200);
      grrApiService.get('some/path?foo&bar', {key1: 'value1', key2: 'value2'});
      $httpBackend.flush();
    });
  });

  describe('poll() method', () => {
    it('triggers url once if condition is immediately satisfied', () => {
      $httpBackend.expectGET('/api/some/path').respond(200, {
        'state': 'FINISHED',
      });

      grrApiService.poll('some/path', 1000);

      $httpBackend.flush();
      $interval.flush(2000);
      // No requests should be outstanding by this point.
    });

    it('does not call callbacks when cancelled via cancelPoll()', () => {
      $httpBackend.expectGET('/api/some/path').respond(200, {});

      let successHandlerCalled = false;
      let failureHandlerCalled = false;
      let finallyHandlerCalled = false;
      const pollPromise = grrApiService.poll('some/path', 1000);

      pollPromise
          .then(
              () => {
                successHandlerCalled = true;
              },
              () => {
                failureHandlerCalled = true;
              })
          .finally(() => {
            finallyHandlerCalled = true;
          });
      grrApiService.cancelPoll(pollPromise);

      $httpBackend.flush();
      expect(successHandlerCalled).toBe(false);
      expect(failureHandlerCalled).toBe(false);
      expect(finallyHandlerCalled).toBe(false);
    });

    it('succeeds and returns response if first try succeeds', () => {
      $httpBackend.expectGET('/api/some/path').respond(200, {
        'foo': 'bar',
        'state': 'FINISHED',
      });

      let successHandlerCalled = false;
      grrApiService.poll('some/path', 1000).then((response) => {
        expect(response['data']).toEqual({
          'foo': 'bar',
          'state': 'FINISHED',
        });
        successHandlerCalled = true;
      });

      $httpBackend.flush();
      expect(successHandlerCalled).toBe(true);
    });

    it('triggers url multiple times if condition is not satisfied', () => {
      $httpBackend.expectGET('/api/some/path').respond(200, {});

      grrApiService.poll('some/path', 1000);

      $httpBackend.flush();

      $httpBackend.expectGET('/api/some/path').respond(200, {});
      $interval.flush(2000);
      $httpBackend.flush();
    });

    it('succeeds and returns response if second try succeeds', () => {
      $httpBackend.expectGET('/api/some/path').respond(200, {});

      let successHandlerCalled = false;
      grrApiService.poll('some/path', 1000).then((response) => {
        expect(response['data']).toEqual({
          'foo': 'bar',
          'state': 'FINISHED',
        });
        successHandlerCalled = true;
      });

      $httpBackend.flush();

      $httpBackend.expectGET('/api/some/path').respond(200, {
        'foo': 'bar',
        'state': 'FINISHED',
      });
      $interval.flush(2000);
      $httpBackend.flush();

      expect(successHandlerCalled).toBe(true);
    });

    it('fails if first try fails', () => {
      $httpBackend.expectGET('/api/some/path').respond(500);

      let failureHandleCalled = false;
      grrApiService.poll('some/path', 1000).then(() => {}, () => {
        failureHandleCalled = true;
      });

      $httpBackend.flush();
      expect(failureHandleCalled).toBe(true);
    });

    it('fails if first try is correct, but second one fails', () => {
      $httpBackend.expectGET('/api/some/path').respond(200, {});

      let failureHandleCalled = false;
      grrApiService.poll('some/path', 1000).then(() => {}, () => {
        failureHandleCalled = true;
      });

      $httpBackend.flush();

      $httpBackend.expectGET('/api/some/path').respond(500);
      $interval.flush(2000);
      $httpBackend.flush();

      expect(failureHandleCalled).toBe(true);
    });

    it('returns response payload on failure', () => {
      $httpBackend.expectGET('/api/some/path').respond(500, {'foo': 'bar'});

      grrApiService.poll('some/path', 1000).then(() => {}, (response) => {
        expect(response['data']).toEqual({'foo': 'bar'});
      });

      $httpBackend.flush();
    });

    it('notifies on every intermediate poll result', () => {
      $httpBackend.expectGET('/api/some/path').respond(200, {});

      let notificationCount = 0;
      grrApiService.poll('some/path', 1000)
          .then(undefined, undefined, (data) => {
            notificationCount += 1;
          });
      expect(notificationCount).toBe(0);

      $httpBackend.flush();
      expect(notificationCount).toBe(1);

      $interval.flush(500);
      expect(notificationCount).toBe(1);

      $httpBackend.expectGET('/api/some/path').respond(200, {});
      $interval.flush(500);
      $httpBackend.flush();
      expect(notificationCount).toBe(2);
    });

    it('does not allow API requests to overlap', () => {
      const deferred = $q.defer();
      spyOn(grrApiService, 'get').and.returnValue(deferred.promise);

      grrApiService.poll('some/path', 1000);
      expect(grrApiService.get).toHaveBeenCalledTimes(1);

      $interval.flush(2000);
      expect(grrApiService.get).toHaveBeenCalledTimes(1);
    });

    it('does not resolve the promise after cancelPoll() call', () => {
      $httpBackend.expectGET('/api/some/path').respond(200, {
        'foo': 'bar',
        'state': 'FINISHED',
      });

      let successHandlerCalled = false;
      const promise = grrApiService.poll('some/path', 1000).then((response) => {
        successHandlerCalled = true;
      });

      grrApiService.cancelPoll(promise);
      $httpBackend.flush();
      expect(successHandlerCalled).toBe(false);
    });

    it('works correctly on a chained promise', () => {
      $httpBackend.expectGET('/api/some/path').respond(200, {
        'foo': 'bar',
        'state': 'FINISHED',
      });

      let successHandlerCalled = false;
      let finallyHandlerCalled = false;
      grrApiService.poll('some/path', 1000)
          .then(() => {
            successHandlerCalled = true;
          })
          .catch(() => {})
          .finally(() => {
            finallyHandlerCalled = true;
          });

      $httpBackend.flush();
      expect(finallyHandlerCalled).toBe(true);
      expect(successHandlerCalled).toBe(true);
    });

    it('allows cancelPoll to be called on a chained promise', () => {
      $httpBackend.expectGET('/api/some/path').respond(200, {
        'foo': 'bar',
        'state': 'FINISHED',
      });

      let successHandlerCalled = false;
      let finallyHandlerCalled = false;
      const promise = grrApiService.poll('some/path', 1000)
                          .then(() => {
                            successHandlerCalled = true;
                          })
                          .catch(() => {})
                          .finally(() => {
                            finallyHandlerCalled = true;
                          });

      grrApiService.cancelPoll(promise);
      $httpBackend.flush();
      expect(finallyHandlerCalled).toBe(false);
      expect(successHandlerCalled).toBe(false);
    });
  });

  describe('cancelPoll() method', () => {
    it('raises if the promise does not have "cancel" attribute', () => {
      const deferred = $q.defer();
      expect(() => {
        grrApiService.cancelPoll(deferred.promise);
      }).toThrow(new Error('Invalid promise to cancel: not cancelable.'));
    });
  });

  describe('delete() method', () => {
    it('adds "/api/" to a given url', () => {
      $httpBackend.expectDELETE('/api/some/path').respond(200);
      grrApiService.delete('some/path');
      $httpBackend.flush();
    });

    it('adds "/api/" to a given url starting with "/"', () => {
      $httpBackend.expectDELETE('/api/some/path').respond(200);
      grrApiService.delete('/some/path');
      $httpBackend.flush();
    });

    it('passes user-provided data in the request', () => {
      $httpBackend.expect(
          'DELETE', '/api/some/path', {key1: 'value1', key2: 'value2'})
              .respond(200);
      grrApiService.delete('some/path', {key1: 'value1', key2: 'value2'});
      $httpBackend.flush();
    });


    it('url-escapes the path', () => {
      $httpBackend.expectDELETE(
          '/api/some/path%3Ffoo%26bar').respond(200);
      grrApiService.delete('some/path?foo&bar');
      $httpBackend.flush();
    });

    it('doesn\'t send request body if no payload provided', () => {
      $httpBackend
          .expect(
              'DELETE', '/api/some/path', (data) => angular.isUndefined(data))
          .respond(200);
      grrApiService.delete('some/path');
      $httpBackend.flush();
    });
  });

  describe('patch() method', () => {
    it('adds "/api/" to a given url', () => {
      $httpBackend.expectPATCH('/api/some/path').respond(200);
      grrApiService.patch('some/path');
      $httpBackend.flush();
    });

    it('adds "/api/" to a given url starting with "/"', () => {
      $httpBackend.expectPATCH('/api/some/path').respond(200);
      grrApiService.patch('/some/path');
      $httpBackend.flush();
    });

    it('passes user-provided data in the request', () => {
      $httpBackend.expect(
          'PATCH', '/api/some/path', {key1: 'value1', key2: 'value2'})
              .respond(200);
      grrApiService.patch('some/path', {key1: 'value1', key2: 'value2'});
      $httpBackend.flush();
    });


    it('url-escapes the path', () => {
      $httpBackend.expectPATCH(
          '/api/some/path%3Ffoo%26bar').respond(200);
      grrApiService.patch('some/path?foo&bar');
      $httpBackend.flush();
    });
  });

  describe('post() method', () => {
    it('adds "/api/" to a given url', () => {
      $httpBackend.whenPOST('/api/some/path').respond(200);
      grrApiService.post('some/path');
      $httpBackend.flush();
    });

    it('adds "/api/" to a given url starting with "/"', () => {
      $httpBackend.whenPOST('/api/some/path').respond(200);
      grrApiService.post('/some/path', {});
      $httpBackend.flush();
    });

    it('strips type info from params if opt_stripTypeInfo is true', () => {
      const richData = {
        'age': 0,
        'mro': [
          'ObjectLabel',
          'RDFProtoStruct',
          'RDFStruct',
          'RDFValue',
          'object',
        ],
        'type': 'ObjectLabel',
        'value': {
          'name': {
            'age': 0,
            'mro': [
              'unicode',
              'basestring',
              'object',
            ],
            'type': 'unicode',
            'value': 'label2',
          },
        },
      };

      $httpBackend.whenPOST('/api/some/path', {name: 'label2'}).respond(200);
      grrApiService.post('some/path', richData, true);
      $httpBackend.flush();
    });

    it('passes user-provided headers in the request', () => {
      $httpBackend.expectPOST(
          '/api/some/path', {key1: 'value1', key2: 'value2'}).respond(200);

      grrApiService.post('some/path', {key1: 'value1', key2: 'value2'});
      $httpBackend.flush();
    });

    it('url-escapes the path', () => {
      $httpBackend.whenPOST(
          '/api/some/path%3Ffoo%26bar').respond(200);
      grrApiService.post('some/path?foo&bar', {});
      $httpBackend.flush();
    });

    it('url-escapes the path when files are uploaded', () => {
      $httpBackend.whenPOST('/api/some/path%3Ffoo%26bar').respond(200);
      grrApiService.post('some/path?foo&bar', {}, false, {'file1': 'blah'});
      $httpBackend.flush();
    });
  });

  describe('downloadFile() method', () => {
    afterEach(() => {
      // We have to clean document's body to remove an iframe generated
      // by the downloadFile call.
      $(document.body).html('');
    });

    it('sends HEAD request first to check if URL is accessible', () => {
      $httpBackend.expectHEAD('/api/some/path').respond(200);

      grrApiService.downloadFile('some/path');

      $httpBackend.flush();
    });

    it('sends query parameters in the HEAD request', () => {
      $httpBackend.expectHEAD('/api/some/path?abra=cadabra&foo=bar').respond(
          200);

      grrApiService.downloadFile('some/path', {'foo': 'bar',
                                               'abra': 'cadabra'});

      $httpBackend.flush();
    });

    it('url-escapes the path', () => {
      $httpBackend.expectHEAD(
          '/api/some/path%3Ffoo%26bar?key1=value1&key2=value2').respond(200);
      grrApiService.downloadFile('some/path?foo&bar',
                                 {key1: 'value1', key2: 'value2'});
      $httpBackend.flush();
    });

    it('rejects promise if HEAD request fails', () => {
      $httpBackend.whenHEAD('/api/some/path').respond(500);

      let promiseRejected = false;
      const promise = grrApiService.downloadFile('some/path');
      promise.then(() => {}, () => {
        promiseRejected = true;
      });

      $httpBackend.flush();

      expect(promiseRejected).toBe(true);
    });

    it('broadcasts subject/reason from UnauthorizedAccess HEAD response',
       () => {
         $httpBackend.whenHEAD('/api/some/path').respond(403, {}, {
           'x-grr-unauthorized-access-subject': 'some subject',
           'x-grr-unauthorized-access-reason': 'some reason'
         });

         spyOn($rootScope, '$broadcast');
         grrApiService.downloadFile('some/path');
         $httpBackend.flush();

         expect($rootScope.$broadcast)
             .toHaveBeenCalledWith(UNAUTHORIZED_API_RESPONSE_EVENT, {
               subject: 'some subject',
               reason: 'some reason',
             });
       });

    it('creates an iframe request if HEAD succeeds', () => {
      $httpBackend.whenHEAD('/api/some/path').respond(200);

      grrApiService.downloadFile('some/path');
      $httpBackend.flush();

      expect($('iframe').attr('src')).toBe('/api/some/path');
    });

    it('propagates query option to iframe "src" attribute', () => {
      $httpBackend.whenHEAD('/api/some/path?abra=cadabra&foo=bar').respond(200);

      grrApiService.downloadFile('some/path',
                                 {'foo': 'bar', 'abra': 'cadabra'});
      $httpBackend.flush();

      expect($('iframe').attr('src')).toBe('/api/some/path?abra=cadabra&foo=bar');
    });

    it('fails if same-origin-policy error is thrown when accessing iframe',
       () => {
         $httpBackend.whenHEAD('/api/some/path').respond(200);

         let promiseRejected = false;
         const promise = grrApiService.downloadFile('some/path');
         promise.then(() => {}, () => {
           promiseRejected = true;
         });
         $httpBackend.flush();

         // If iframe request fails, iframe will sho a standard error page
         // which will have a different origin and raise on access.
         Object.defineProperty(
             $('iframe')[0].contentWindow.document, 'readyState', {
               __proto__: null,
               get: function() {
                 throw new Error('Same origin policy error');
               },
             });
         $interval.flush(1000);

         expect(promiseRejected).toBe(true);
       });

    it('cancels the interval timer if iframe request fails', () => {
      $httpBackend.whenHEAD('/api/some/path').respond(200);

      spyOn($interval, 'cancel').and.returnValue();
      grrApiService.downloadFile('some/path');
      $httpBackend.flush();

      Object.defineProperty(
          $('iframe')[0].contentWindow.document, 'readyState', {
            __proto__: null,
            get: function() {
              throw new Error('Same origin policy error');
            },
          });
      $interval.flush(1000);

      expect($interval.cancel).toHaveBeenCalled();
    });

    it('succeeds if iframe request succeeds', () => {
      $httpBackend.whenHEAD('/api/some/path').respond(200);

      let promiseSucceeded = false;
      const promise = grrApiService.downloadFile('some/path');
      promise.then(() => {
        promiseSucceeded = true;
      });
      $httpBackend.flush();

      Object.defineProperty(
          $('iframe')[0].contentWindow.document, 'readyState', {
            __proto__: null,
            get: function() {
              return 'complete';
            },
          });
      $interval.flush(1000);

      expect(promiseSucceeded).toBe(true);
    });

    it('cancels the interval timer if iframe request succeeds', () => {
      $httpBackend.whenHEAD('/api/some/path').respond(200);

      spyOn($interval, 'cancel').and.returnValue();
      grrApiService.downloadFile('some/path');
      $httpBackend.flush();

      Object.defineProperty(
          $('iframe')[0].contentWindow.document, 'readyState', {
            __proto__: null,
            get: function() {
              return 'complete';
            },
          });
      $interval.flush(1000);

      expect($interval.cancel).toHaveBeenCalled();
    });
  });
});


exports = {};

;return exports;});

//angular-components/core/bind-key-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.bindKeyDirectiveTest');
goog.setTestOnly();

const {browserTriggerKeyDown, testsModule} = goog.require('grrUi.tests');
const {coreModule} = goog.require('grrUi.core.core');


describe('bind key directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector, _$interval_) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const render = (callback, key) => {
    $rootScope.callback = callback;

    const template =
        '<input type="text" grr-bind-key="callback()" key="' + key + '" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('calls the specified function on keydown', (done) => {
    const element = render(done, 13);     // ENTER
    browserTriggerKeyDown(element, 13);
  });

  it('calls the specified function only on specified keydown', () => {
    const callback = (() => {
      fail('Callback should not be called');
    });
    const element = render(callback, 13); // ENTER
    browserTriggerKeyDown(element, 15);   // Raise some other key.
  });
});


exports = {};

;return exports;});

//angular-components/core/bytes-to-hex-filter_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.bytesToHexFilterTest');
goog.setTestOnly();

const {BytesToHexFilter} = goog.require('grrUi.core.bytesToHexFilter');
const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('grrBytesToHex filter', () => {
  let grrBytesToHexFilter;

  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    grrBytesToHexFilter = $injector.instantiate(BytesToHexFilter);
  }));

  it('returns the correct hex for different input bytes', () => {
    let result = grrBytesToHexFilter('some text');
    expect(result).toBe('736f6d652074657874');

    result = grrBytesToHexFilter('123abc');
    expect(result).toBe('313233616263');
  });
});


exports = {};

;return exports;});

//angular-components/core/canary-only-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.canaryOnlyDirectiveTest');
goog.setTestOnly();

const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('The canaryOnlyDirective package', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;


  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrApiService = $injector.get('grrApiService');
  }));

  const renderTestTemplate = () => {
    const template = '<grr-canary-only>' +
        'This text is being canaried.' +
        '</grr-canary-only>' +
        '<grr-non-canary-only>' +
        'This text is being deprecated.' +
        '</grr-non-canary-only>';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  const setMockedCanaryModeValue = (newValue) => {
    const deferred = $q.defer();
    deferred.resolve(
        {data: {value: {settings: {value: {canary_mode: {value: newValue}}}}}});
    spyOn(grrApiService, 'getCached').and.returnValue(deferred.promise);
  };

  it('renders the grr-canary-only element in canary mode', () => {
    setMockedCanaryModeValue(true);
    const element = renderTestTemplate();
    expect(element.text()).toContain('This text is being canaried.');
  });

  it('doesn\'t render the grr-canary-only element in non-canary mode', () => {
    setMockedCanaryModeValue(false);
    const element = renderTestTemplate();
    expect(element.text()).not.toContain('This text is being canaried.');
  });

  it('renders the grr-non-canary-only element in non-canary mode', () => {
    setMockedCanaryModeValue(false);
    const element = renderTestTemplate();
    expect(element.text()).toContain('This text is being deprecated.');
  });

  it('doesn\'t render the grr-non-canary-only element in canary mode', () => {
    setMockedCanaryModeValue(true);
    const element = renderTestTemplate();
    expect(element.text()).not.toContain('This text is being deprecated.');
  });
});


exports = {};

;return exports;});

//angular-components/core/clock-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.clockDirectiveTest');
goog.setTestOnly();

const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('clock directive', () => {
  let $compile;
  let $interval;
  let $rootScope;
  let grrTimeService;


  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector, _$interval_) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $interval = _$interval_;
    grrTimeService = $injector.get('grrTimeService');
  }));

  afterEach(inject(($injector) => {
    grrTimeService = $injector.get('grrTimeService');
  }));

  const renderTestTemplate = () => {
    const template = '<grr-live-clock />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows the current time in a clock', () => {
    let time = '2015-01-02 03:04:05';

    grrTimeService.formatAsUTC = (() => `${time} UTC`);

    const newElement = renderTestTemplate();

    // Live clock must use UTC time and label it.
    expect(newElement.text()).toContain('2015-01-02 03:04:05 UTC');

    // Make sure time changes are effected in the live clock.
    time = '2015-01-02 03:04:06';

    $interval.flush(60000);

    expect(newElement.text()).toContain('03:04:06');
  });
});


exports = {};

;return exports;});

//angular-components/core/confirmation-dialog-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.confirmationDialogDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {coreModule} = goog.require('grrUi.core.core');


describe('confirmation dialog directive', () => {
  let $compile;
  let $q;
  let $rootScope;


  beforeEach(module('/static/angular-components/core/confirmation-dialog.html'));
  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
  }));

  const render = (title, message, proceedCallback, buttonClass) => {
    $rootScope.title = title;
    $rootScope.message = message;
    $rootScope.proceed = proceedCallback;
    $rootScope.buttonClass = buttonClass;

    const template = '<grr-confirmation-dialog ' +
        '    title="title" ' +
        '    proceed="proceed()"' +
        '    proceed-class="buttonClass">' +
        '  {$ message $}' +
        '</grr-confirmation-dialog>';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows title and message', () => {
    const title = 'Test Title';
    const message = 'Test Content Message';
    const element = render(title, message);
    expect(element.find('.modal-header h3').text().trim()).toBe(title);
    expect(element.find('.modal-body').text().trim()).toContain(message);
  });

  it('applies given style to "proceed" button', () => {
    const element = render('title', 'message', undefined, 'btn-warning');
    expect(element.find('button.btn-warning')).not.toBe(undefined);
  });

  it('shows success on proceed promise resolve', () => {
    const successMessage = 'Some action just happened successfully';
    const proceed = (() => $q.when(successMessage));
    const element = render('', '', proceed);
    spyOn($rootScope, 'proceed').and.callThrough();

    // check proceed() was clicked and shows the success message
    browserTriggerEvent(element.find('button[name="Proceed"]'), 'click');
    $rootScope.$apply();

    expect($rootScope.proceed).toHaveBeenCalled();
    expect(element.find('.modal-footer .text-success').text().trim()).toBe(successMessage);
    expect(element.find('button[name="Close"]').length).toBe(1);
  });

  it('shows error on proceed promise reject', () => {
    const errorMessage = 'Some action could not be performed';
    const proceed = (() => $q.reject(errorMessage));
    const element = render('', '', proceed);

    spyOn($rootScope, 'proceed').and.callThrough();
    browserTriggerEvent(element.find('button[name="Proceed"]'), 'click');
    $rootScope.$apply();

    expect($rootScope.proceed).toHaveBeenCalled();
    expect(element.find('.modal-footer .text-danger').text().trim()).toBe(errorMessage);
  });

  it('shows a disabled proceed button when canProceed is false', () => {
    let buttonEnabled = false;
    $rootScope.canProceed = (() => buttonEnabled);
    const template = '<grr-confirmation-dialog can-proceed="canProceed()">' +
        '</grr-confirmation-dialog>';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    expect($('button[name=Proceed][disabled]', element).length).toBe(1);
    buttonEnabled = true;
    $rootScope.$apply();
    expect($('button[name=Proceed][disabled]', element).length).toBe(0);
  });
});


exports = {};

;return exports;});

//angular-components/core/disable-if-no-trait-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.disableIfNoTraitDirectiveTest');
goog.setTestOnly();

const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('grr-disable-if-no-trait directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;


  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrApiService = $injector.get('grrApiService');
  }));

  const renderTestTemplate = () => {
    const template = '<span grr-disable-if-no-trait="trait_foo"></span>';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('applied "disabled" attribute if trait is missing', () => {
    const deferred = $q.defer();
    deferred.resolve({
      data: {
        value: {
          interface_traits: {
            value: {},
          },
        },
      },
    });
    spyOn(grrApiService, 'getCached').and.returnValue(deferred.promise);

    const element = renderTestTemplate();
    expect(element.attr('disabled')).toBe('disabled');
  });

  it('applied "disabled" attribute if trait is false', () => {
    const deferred = $q.defer();
    deferred.resolve({
      data: {
        value: {
          interface_traits: {
            value: {
              trait_foo: {
                value: false,
              },
            },
          },
        },
      },
    });
    spyOn(grrApiService, 'getCached').and.returnValue(deferred.promise);

    const element = renderTestTemplate();
    expect(element.attr('disabled')).toBe('disabled');
  });

  it('does nothing if trait is true', () => {
    const deferred = $q.defer();
    deferred.resolve({
      data: {
        value: {
          interface_traits: {
            value: {
              trait_foo: {
                value: true,
              },
            },
          },
        },
      },
    });
    spyOn(grrApiService, 'getCached').and.returnValue(deferred.promise);

    const element = renderTestTemplate();
    expect(element.attr('disabled')).toBe(undefined);
  });
});


exports = {};

;return exports;});

//angular-components/core/download-collection-as-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.downloadCollectionAsDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {coreModule} = goog.require('grrUi.core.core');


describe('"download collection as" panel', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;

  beforeEach(module('/static/angular-components/core/download-collection-as.html'));
  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    grrApiService = $injector.get('grrApiService');
  }));

  const renderTestTemplate = (baseUrl) => {
    $rootScope.baseUrl = baseUrl || 'foo/bar';

    const template = '<grr-download-collection-as ' +
        'base-url="baseUrl" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  const testDownloadAsType =
      ((plugin) => (() => {
         const deferred = $q.defer();
         spyOn(grrApiService, 'downloadFile').and.returnValue(deferred.promise);

         const element = renderTestTemplate();
         element.find('#plugin-select').val(`string:${plugin}`).change();
         browserTriggerEvent(element.find('button[name="download-as"]'), 'click');

         expect(grrApiService.downloadFile)
             .toHaveBeenCalledWith(`foo/bar/${plugin}`);
       }));

  it('sends correct request for CSV download', testDownloadAsType('csv-zip'));

  it('sends correct request for flattened YAML download',
     testDownloadAsType('flattened-yaml-zip'));

  it('sends correct request for sqlite download',
      testDownloadAsType('sqlite-zip'));
});


exports = {};

;return exports;});

//angular-components/core/download-collection-files-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.downloadCollectionFilesDirectiveTest');
goog.setTestOnly();

const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('download collection files directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;


  const $window = {
    navigator: {
      appVersion: ''
    },
  };
  beforeEach(module(($provide) => {
    $provide.value('$window', $window);
  }));

  beforeEach(module('/static/angular-components/core/' +
      'download-collection-files.html'));
  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $q = $injector.get('$q');
    $rootScope = $injector.get('$rootScope');
    grrApiService = $injector.get('grrApiService');
  }));

  const renderTestTemplate = (opt_withExportCommand) => {
    $rootScope.downloadUrl = 'some/download/url';
    if (opt_withExportCommand) {
      $rootScope.exportCommandUrl = 'some/export-command/url';
    }

    const template = '<grr-download-collection-files ' +
        'download-url="downloadUrl" export-command-url="exportCommandUrl" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows TAR.GZ as default option on Mac', () => {
    $window.navigator.appVersion = 'Mac';

    const element = renderTestTemplate();
    expect(element.find('button').text()).toContain('Generate TAR.GZ');
    expect(element.find('ul.dropdown-menu li').text()).toContain(
        'Generate ZIP');
  });

  it('shows ZIP as default option on Linux', () => {
    $window.navigator.appVersion = 'Linux';

    const element = renderTestTemplate();
    expect(element.find('button').text()).toContain('Generate ZIP');
    expect(element.find('ul.dropdown-menu li').text()).toContain(
        'Generate TAR.GZ');
  });

  it('sends TAR.GZ generation request when button clicked on Mac', () => {
    $window.navigator.appVersion = 'Mac';

    const deferred = $q.defer();
    spyOn(grrApiService, 'downloadFile').and.returnValue(deferred.promise);

    const element = renderTestTemplate();
    element.find('button').click();

    expect(grrApiService.downloadFile).toHaveBeenCalledWith(
        'some/download/url', {archive_format: 'TAR_GZ'});
  });

  it('sends ZIP generation request when dropdownclicked on Mac', () => {
    $window.navigator.appVersion = 'Mac';

    const deferred = $q.defer();
    spyOn(grrApiService, 'downloadFile').and.returnValue(deferred.promise);

    const element = renderTestTemplate();
    element.find('ul.dropdown-menu li a').click();

    expect(grrApiService.downloadFile).toHaveBeenCalledWith(
        'some/download/url', {archive_format: 'ZIP'});
  });

  it('sends ZIP generation request when button is clicked on Linux', () => {
    $window.navigator.appVersion = 'Linux';

    const deferred = $q.defer();
    spyOn(grrApiService, 'downloadFile').and.returnValue(deferred.promise);

    const element = renderTestTemplate();
    element.find('button').click();

    expect(grrApiService.downloadFile).toHaveBeenCalledWith(
        'some/download/url', {archive_format: 'ZIP'});
  });

  it('sends TAR.GZ generation request when dropdown clicked on Linux', () => {
    $window.navigator.appVersion = 'Linux';

    const deferred = $q.defer();
    spyOn(grrApiService, 'downloadFile').and.returnValue(deferred.promise);

    const element = renderTestTemplate();
    element.find('ul.dropdown-menu li a').click();

    expect(grrApiService.downloadFile).toHaveBeenCalledWith(
        'some/download/url', {archive_format: 'TAR_GZ'});
  });

  it('disables the button after request is sent', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'downloadFile').and.returnValue(deferred.promise);

    const element = renderTestTemplate();
    expect(element.find('button[disabled]').length).toBe(0);

    element.find('ul.dropdown-menu li a').click();

    expect(element.find('button[disabled]').length).not.toBe(0);
  });

  it('shows success message if request succeeds', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'downloadFile').and.returnValue(deferred.promise);

    const element = renderTestTemplate();
    element.find('button').click();

    deferred.resolve({status: 'OK'});
    $rootScope.$apply();

    expect(element.text()).toContain('Generation has started.');
  });

  it('shows failure message if request fails', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'downloadFile').and.returnValue(deferred.promise);

    const element = renderTestTemplate();
    element.find('button').click();

    deferred.reject({data: {message: 'FAIL'}});
    $rootScope.$apply();

    expect(element.text()).toContain('Can\'t generate archive: FAIL');
  });

  describe('with export command url provided', () => {
    let exportCommandDeferred;

    beforeEach(() => {
      $window.navigator.appVersion = 'Mac';

      exportCommandDeferred = $q.defer();
      spyOn(grrApiService, 'get').and.returnValue(
          exportCommandDeferred.promise);
    });

    it('fetches export command', () => {
      renderTestTemplate(true);
      expect(grrApiService.get).toHaveBeenCalledWith('some/export-command/url');
    });

    it('shows "Show export command" link', () => {
      exportCommandDeferred.resolve({
        data: {
          command: 'blah --foo',
        },
      });
      const element = renderTestTemplate(true);
      expect($('a:contains("Show export command")', element).length)
          .toBe(1);
    });

    it('renders export command', () => {
      exportCommandDeferred.resolve({
        data: {
          command: 'blah --foo',
        },
      });
      const element = renderTestTemplate(true);

      expect($('pre:contains("blah --foo")', element).length).toBe(1);
    });
  });
});


exports = {};

;return exports;});

//angular-components/core/file-download-utils_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.fileDownloadUtilsTest');
goog.setTestOnly();

const {getPathSpecFromValue, makeValueDownloadable, pathSpecToAff4Path} = goog.require('grrUi.core.fileDownloadUtils');


describe('statEntryDirective.buildAff4Path', () => {

  it('converts os+tsk pathspec correctly', () => {
    const pathspec = {
      type: 'PathSpec',
      value: {
        path: {value: '\\\\.\\Volume{1234}\\', type: 'RDFString'},
        pathtype: {value: 'OS', type: 'EnumNamedValue'},
        mount_point: '/c:/',
        nested_path: {
          type: 'PathSpec',
          value: {
            path: {value: '/windows', type: 'RDFString'},
            pathtype: {value: 'TSK', type: 'EnumNamedValue'},
          },
        },
      },
    };

    expect(pathSpecToAff4Path(pathspec, 'C.1234567812345678'))
        .toBe('aff4:/C.1234567812345678/fs/tsk/\\\\.\\Volume{1234}\\/windows');
  });

  it('converts os+tsk pathspec correctly when the device starts with /', () => {
    const pathspec = {
      type: 'PathSpec',
      value: {
        path: {value: '/\\\\.\\Volume{1234}\\', type: 'RDFString'},
        pathtype: {value: 'OS', type: 'EnumNamedValue'},
        mount_point: '/c:/',
        nested_path: {
          type: 'PathSpec',
          value: {
            path: {value: '/windows', type: 'RDFString'},
            pathtype: {value: 'TSK', type: 'EnumNamedValue'},
          },
        },
      },
    };

    expect(pathSpecToAff4Path(pathspec, 'C.1234567812345678'))
        .toBe('aff4:/C.1234567812345678/fs/tsk/\\\\.\\Volume{1234}\\/windows');
  });

  it('converts os+tsk ADS pathspec correctly', () => {
    const pathspec = {
      type: 'PathSpec',
      value: {
        path: {value: '\\\\.\\Volume{1234}\\', type: 'RDFString'},
        pathtype: {value: 'OS', type: 'EnumNamedValue'},
        mount_point: '/c:/',
        nested_path: {
          type: 'PathSpec',
          value: {
            path: {value: '/Test Directory/notes.txt:ads', type: 'RDFString'},
            pathtype: {value: 'TSK', type: 'EnumNamedValue'},
            inode: {value: 66, type: 'int'},
            ntfs_type: {value: 128, type: 'int'},
            ntfs_id: {value: 2, type: 'int'},
          },
        },
      },
    };

    expect(pathSpecToAff4Path(pathspec, 'C.1234567812345678'))
        .toBe('aff4:/C.1234567812345678/fs/tsk/\\\\.\\Volume{1234}\\/Test Directory/notes.txt:ads');
  });

  it('converts os+ntfs pathspec correctly', () => {
    const pathspec = {
      type: 'PathSpec',
      value: {
        path: {value: '\\\\.\\Volume{1234}\\', type: 'RDFString'},
        pathtype: {value: 'OS', type: 'EnumNamedValue'},
        mount_point: '/c:/',
        nested_path: {
          type: 'PathSpec',
          value: {
            path: {value: '/windows', type: 'RDFString'},
            pathtype: {value: 'NTFS', type: 'EnumNamedValue'},
          },
        },
      },
    };

    expect(pathSpecToAff4Path(pathspec, 'C.1234567812345678'))
        .toBe('aff4:/C.1234567812345678/fs/ntfs/\\\\.\\Volume{1234}\\/windows');
  });

});


describe('fileDownloadUtils', () => {
  let artifactFilesDownloaderResult;
  let fileFinderResult;
  let pathspec;
  let rdfstring;
  let statEntry;


  beforeEach(() => {
    pathspec = {
      value: {
        path: {
          type: 'RDFString',
          value: 'foo/bar',
        },
      },
      type: 'Pathspec',
    };

    statEntry = {
      value: {
        pathspec: pathspec,
      },
      type: 'StatEntry',
    };

    fileFinderResult = {
      value: {
        stat_entry: angular.copy(statEntry),
      },
      type: 'FileFinderResult',
    };

    artifactFilesDownloaderResult = {
      value: {
        downloaded_file: angular.copy(statEntry),
      },
      type: 'ArtifactFilesDownloaderResult',
    };

    rdfstring = {
      value: 'blah',
      type: 'RDFString',
    };
  });

  describe('getPathSpecFromValue', () => {

    it('returns null if argument is null or undefined', () => {
      expect(getPathSpecFromValue(null)).toBe(null);
      expect(getPathSpecFromValue(undefined)).toBe(null);
    });

    it('extracts pathspec from StatEntry', () => {
      expect(getPathSpecFromValue(statEntry)).toEqual(pathspec);
    });

    it('extracts pathspec from FileFinderResult', () => {
      expect(getPathSpecFromValue(fileFinderResult)).toEqual(pathspec);
    });

    it('extracts pathspec from ArtifactFilesDownloaderResult', () => {
      expect(getPathSpecFromValue(artifactFilesDownloaderResult)).toEqual(
          pathspec);
    });

    it('extracts pathspec recursively from ApiFlowResult', () => {
      let apiFlowResult = {
        value: {
          payload: angular.copy(statEntry),
        },
        type: 'ApiFlowResult',
      };
      expect(getPathSpecFromValue(apiFlowResult)).toEqual(pathspec);

      apiFlowResult = {
        value: {
          payload: angular.copy(fileFinderResult),
        },
        type: 'ApiFlowResult',
      };
      expect(getPathSpecFromValue(apiFlowResult)).toEqual(pathspec);

      apiFlowResult = {
        value: {
          payload: angular.copy(artifactFilesDownloaderResult),
        },
        type: 'ApiFlowResult',
      };
      expect(getPathSpecFromValue(apiFlowResult)).toEqual(pathspec);
    });

    it('extracts pathspec recursively from ApiHuntResult', () => {
      let apiHuntResult = {
        value: {
          payload: angular.copy(statEntry),
        },
        type: 'ApiHuntResult',
      };
      expect(getPathSpecFromValue(apiHuntResult)).toEqual(pathspec);

      apiHuntResult = {
        value: {
          payload: angular.copy(fileFinderResult),
        },
        type: 'ApiHuntResult',
      };
      expect(getPathSpecFromValue(apiHuntResult)).toEqual(pathspec);

      apiHuntResult = {
        value: {
          payload: angular.copy(artifactFilesDownloaderResult),
        },
        type: 'ApiHuntResult',
      };
      expect(getPathSpecFromValue(apiHuntResult)).toEqual(pathspec);
    });

    it('returns null for other aff4 types', () => {
      expect(getPathSpecFromValue(rdfstring)).toBe(null);
    });

    it('returns null for other types wrapped in ApiFlowResult', () => {
      const apiFlowResult = {
        value: {
          payload: angular.copy(rdfstring),
        },
        type: 'ApiFlowResult',
      };
      expect(getPathSpecFromValue(apiFlowResult)).toBe(null);
    });

    it('returns null for other types wrapped in ApiHuntResult', () => {
      const apiHuntResult = {
        value: {
          payload: angular.copy(rdfstring),
        },
        type: 'ApiHuntResult',
      };
      expect(getPathSpecFromValue(apiHuntResult)).toBe(null);
    });
  });


  describe('makeValueDownloadable', () => {
    const downloadUrl = 'download/foo/bar';
    const downloadParams = {
      foo: 'bar',
      blah: 'blah',
    };

    it('does nothing if argument is null or undefned', () => {
      expect(makeValueDownloadable(null, downloadUrl, downloadParams))
          .toBe(false);
      expect(makeValueDownloadable(undefined, downloadUrl, downloadParams))
          .toBe(false);
    });

    it('replaces aff4path in StatEntry', () => {
      const originalStatEntry = angular.copy(statEntry);

      expect(makeValueDownloadable(statEntry, downloadUrl, downloadParams))
          .toBe(true);
      expect(statEntry).toEqual({
        downloadUrl: downloadUrl,
        downloadParams: downloadParams,
        originalValue: originalStatEntry,
        type: '__DownloadableStatEntry',
      });
    });

    it('replaces aff4path in FileFinderResult', () => {
      const originalFileFinderResult = angular.copy(fileFinderResult);

      expect(makeValueDownloadable(
          fileFinderResult, downloadUrl, downloadParams))
          .toBe(true);
      expect(fileFinderResult.value.stat_entry).toEqual({
        downloadUrl: downloadUrl,
        downloadParams: downloadParams,
        originalValue: originalFileFinderResult.value.stat_entry,
        type: '__DownloadableStatEntry',
      });
    });

    it('replaces aff4path in ArtifactFilesDownloaderResult', () => {
      const original = angular.copy(artifactFilesDownloaderResult);

      expect(makeValueDownloadable(
          artifactFilesDownloaderResult, downloadUrl, downloadParams))
          .toBe(true);
      expect(artifactFilesDownloaderResult.value.downloaded_file).toEqual({
        downloadUrl: downloadUrl,
        downloadParams: downloadParams,
        originalValue: original.value.downloaded_file,
        type: '__DownloadableStatEntry',
      });
    });

    it('replaces aff4path recursively in ApiFlowResult', () => {
      const apiFlowResult = {
        value: {
          payload: angular.copy(statEntry),
        },
        type: 'ApiFlowResult',
      };
      expect(makeValueDownloadable(apiFlowResult, downloadUrl, downloadParams))
          .toBe(true);
      expect(apiFlowResult.value.payload).toEqual({
        downloadUrl: downloadUrl,
        downloadParams: downloadParams,
        originalValue: statEntry,
        type: '__DownloadableStatEntry',
      });
    });

    it('replaces aff4path recursively in ApiHuntResult', () => {
      const apiHuntResult = {
        value: {
          payload: angular.copy(statEntry),
        },
        type: 'ApiHuntResult',
      };
      expect(makeValueDownloadable(apiHuntResult, downloadUrl, downloadParams))
          .toBe(true);
      expect(apiHuntResult.value.payload).toEqual({
        downloadUrl: downloadUrl,
        downloadParams: downloadParams,
        originalValue: statEntry,
        type: '__DownloadableStatEntry',
      });
    });

    it('does nothing in other aff4 types', () => {
      const original = angular.copy(rdfstring);
      expect(makeValueDownloadable(original, downloadUrl, downloadParams))
          .toBe(false);
    });

    it('does nothing for other types wrapped in ApiFlowResult', () => {
      const apiFlowResult = {
        value: {
          payload: angular.copy(rdfstring),
        },
        type: 'ApiFlowResult',
      };
      expect(makeValueDownloadable(apiFlowResult, downloadUrl, downloadParams))
          .toBe(false);
    });

    it('does nothing for other types wrapped in ApiHuntResult', () => {
      const apiHuntResult = {
        value: {
          payload: angular.copy(rdfstring),
        },
        type: 'ApiHuntResult',
      };
      expect(makeValueDownloadable(apiHuntResult, downloadUrl, downloadParams))
          .toBe(false);
    });
  });
});


exports = {};

;return exports;});

//angular-components/core/firebase-service_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.firebaseServiceTest');
goog.setTestOnly();

const {coreModule} = goog.require('grrUi.core.core');

window.firebase = window.firebase || {};


describe('API service', () => {
  let $http;
  let $q;
  let $rootScope;
  let grrApiService;
  let grrFirebaseService;

  let fbAuthResult;
  let redirectDeferred;


  beforeEach(module(coreModule.name));

  beforeEach(inject(($injector) => {
    $rootScope = $injector.get('$rootScope');
    $http = $injector.get('$http');
    $q = $injector.get('$q');
    grrApiService = $injector.get('grrApiService');
    grrFirebaseService = $injector.get('grrFirebaseService');

    redirectDeferred = $q.defer();

    fbAuthResult = {
      getRedirectResult: jasmine.createSpy('getRedirectResult')
                             .and.returnValue(redirectDeferred.promise),
      onAuthStateChanged: jasmine.createSpy('onAuthStateChanged'),
      signInWithRedirect: jasmine.createSpy('signInWithRedirect'),
    };
    firebase = {
      auth: jasmine.createSpy('auth').and.returnValue(fbAuthResult),
      apps: [{
        options: {
          authProvider: 'GoogleAuthProvider',
        },
      }],
    };
    firebase.auth.GoogleAuthProvider = jasmine.createSpy('GoogleAuthProvider');
  }));

  it('does nothing and marks auth done on no firebase apps', () => {
    firebase.apps = [];

    spyOn(grrApiService, 'markAuthDone');

    grrFirebaseService.setupIfNeeded();
    $rootScope.$apply();

    expect(firebase.auth).not.toHaveBeenCalled();
    expect(grrApiService.markAuthDone).toHaveBeenCalled();
  });

  it('adjusts headers and marks auth done when user authenticates', () => {
    const tokenDeferred = $q.defer();
    tokenDeferred.resolve('blah');
    const user = {
      getToken:
          jasmine.createSpy('getToken').and.returnValue(tokenDeferred.promise),
    };
    fbAuthResult.onAuthStateChanged.and.callFake((fn) => {
      fn(user);
    });
    spyOn(grrApiService, 'markAuthDone');

    grrFirebaseService.setupIfNeeded();
    $rootScope.$apply();

    expect(fbAuthResult.onAuthStateChanged).toHaveBeenCalled();
    expect(fbAuthResult.signInWithRedirect).not.toHaveBeenCalled();
    expect(grrApiService.markAuthDone).toHaveBeenCalled();

    expect($http.defaults.headers['common']['Authorization'])
        .toBe('Bearer blah');
  });

  it('redirects to sign-in flow if the user is not authenticated', () => {
    fbAuthResult.onAuthStateChanged.and.callFake((fn) => {
      fn(undefined);
    });
    spyOn(grrApiService, 'markAuthDone');

    grrFirebaseService.setupIfNeeded();
    $rootScope.$apply();

    expect(fbAuthResult.onAuthStateChanged).toHaveBeenCalled();
    expect(fbAuthResult.signInWithRedirect).toHaveBeenCalled();
    expect(grrApiService.markAuthDone).not.toHaveBeenCalled();
  });

  it('marks auth done and does not redirect again on auth error', () => {
    const redirectDeferred = $q.defer();
    redirectDeferred.reject('blah');
    fbAuthResult.getRedirectResult.and.returnValue(redirectDeferred.promise);

    spyOn(grrApiService, 'markAuthDone');

    grrFirebaseService.setupIfNeeded();
    $rootScope.$apply();

    expect(grrApiService.markAuthDone).toHaveBeenCalled();
    expect(fbAuthResult.signInWithRedirect).not.toHaveBeenCalled();
  });
});


exports = {};

;return exports;});

//angular-components/core/force-refresh-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.forceRefreshDirectiveTest');
goog.setTestOnly();

const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('grr-force-refresh directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');

    $rootScope.value = 42;
  }));

  const render = (objectEquality) => {
    $rootScope.objectEquality = objectEquality;

    const template = '<grr-force-refresh object-equality="objectEquality" ' +
        'refresh-trigger="value">' +
        '{$ ::value $}</grr-force-refresh>';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows transcluded template immediately', () => {
    const element = render();
    expect(element.text()).toContain('42');
  });

  it('reloads children elements effectively updating one-time bindings', () => {
    const element = render();
    expect(element.text()).toContain('42');

    $rootScope.value = 43;
    $rootScope.$apply();
    expect(element.text()).toContain('43');
  });

  it('reloads on object-level changes', () => {
    $rootScope.value = {
      a: 'a',
    };
    const element = render(true);
    expect(element.text()).toContain('{"a":"a"}');

    $rootScope.value['a'] = 'b';
    $rootScope.$apply();
    expect(element.text()).toContain('{"a":"b"}');
  });
});


exports = {};

;return exports;});

//angular-components/core/hex-number-filter_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.hexNumberFilterTest');
goog.setTestOnly();

const {HexNumberFilter} = goog.require('grrUi.core.hexNumberFilter');
const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('grrHexNumber filter', () => {
  let grrHexNumberFilter;

  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    grrHexNumberFilter = $injector.instantiate(HexNumberFilter);
  }));

  it('returns the correct hex for different input numbers', () => {
    let result = grrHexNumberFilter(0);
    expect(result).toBe('0x00000000');

    result = grrHexNumberFilter(255);
    expect(result).toBe('0x000000ff');

    result = grrHexNumberFilter(1010101);
    expect(result).toBe('0x000f69b5');

    result = grrHexNumberFilter(4294967296 - 1);
    expect(result).toBe('0xffffffff');
  });

  it('inserts leading 0s to always return a multiple of eight places', () => {
    let result = grrHexNumberFilter(1);
    expect(result).toBe('0x00000001');

    result = grrHexNumberFilter(4294967296);
    expect(result).toBe('0x0000000100000000');
  });
});


exports = {};

;return exports;});

//angular-components/core/infinite-table-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.infiniteTableDirectiveTest');
goog.setTestOnly();

const {InfiniteTableController} = goog.require('grrUi.core.infiniteTableDirective');
const {MemoryItemsProviderController} = goog.require('grrUi.core.memoryItemsProviderDirective');
const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('infinite table', () => {
  let $compile;
  let $interval;
  let $q;
  let $rootScope;


  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $interval = $injector.get('$interval');
    $q = $injector.get('$q');
  }));

  afterEach(() => {
    // We have to clean document's body to remove tables we add there.
    $(document.body).html('');
  });

  const render = (items, noDomAppend, filterValue, withAutoRefresh) => {
    $rootScope.testItems = items;
    if (filterValue) {
      $rootScope.filterValue = filterValue;
    }

    // We render the infinite table with grr-memory-items-provider.
    // While it means that this unit test actually depends on the working
    // code of grr-memory-items-provider (which is tested separately),
    // this seems to be the easiest/most reasonable way to test that
    // grr-infinite-table is working correctly. Mocking out
    // items providers would require writing code that's almost
    // equal to grr-memory-items-provider code.
    const template = '<div>' +
        '<table>' +
        '<tbody>' +
        '<tr grr-infinite-table grr-memory-items-provider ' +
        '    items="testItems" page-size="5"' +
        (withAutoRefresh ? '    auto-refresh-interval="1" ' : '') +
        '    filter-value="filterValue"' +
        '    trigger-update="triggerUpdate">' +
        '  <td>{$ ::item.timestamp $}</td>' +
        '  <td>{$ ::item.message $}</td>' +
        '</tr>' +
        '</tbody' +
        '</table>' +
        '</div>';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    if (!noDomAppend) {
      $('body').append(element);
      $interval.flush(1000);
    }

    return element;
  };

  it('throws if items provider is not specified', () => {
    const template = '<table><tbody><tr grr-infinite-table />' +
        '</tbody></table>';
    const compiledTemplate = $compile(template);
    expect(() => {
      compiledTemplate($rootScope);
    }).toThrow(Error('Data provider not specified.'));
  });

  it('shows empty table when there are no elements', () => {
    const element = render([]);

    expect($('table', element).length).toBe(1);
    expect($('table tr', element).length).toBe(0);
  });

  it('shows 2 rows for 2 items', () => {
    const element = render(
        [{timestamp: 42, message: 'foo'}, {timestamp: 43, message: 'bar'}]);

    expect($('table', element).length).toBe(1);
    expect($('table tr', element).length).toBe(2);

    expect($('table tr:eq(0) td:eq(0):contains(42)', element).length).
        toBe(1);
    expect($('table tr:eq(0) td:eq(1):contains(foo)', element).length).
        toBe(1);
    expect($('table tr:eq(1) td:eq(0):contains(43)', element).length).
        toBe(1);
    expect($('table tr:eq(1) td:eq(1):contains(bar)', element).length).
        toBe(1);
  });

  it('does nothing when "Loading..." row is not seen', () => {
    const element = render(
        [{timestamp: 42, message: 'foo'}, {timestamp: 43, message: 'bar'}],
        true);

    expect($('table', element).length).toBe(1);
    expect($('table tr', element).length).toBe(1);
    expect($('table tr:contains("Loading...")', element).length).toBe(1);

    $interval.flush(1000);
    expect($('table tr', element).length).toBe(1);
    expect($('table tr:contains("Loading...")', element).length).toBe(1);

    $('body').append(element);
    $interval.flush(1000);

    expect($('table tr', element).length).toBe(2);
    expect($('table tr:contains("Loading...")', element).length).toBe(0);
    expect($('table tr:eq(0) td:eq(0):contains(42)', element).length).
        toBe(1);
    expect($('table tr:eq(0) td:eq(1):contains(foo)', element).length).
        toBe(1);
    expect($('table tr:eq(1) td:eq(0):contains(43)', element).length).
        toBe(1);
    expect($('table tr:eq(1) td:eq(1):contains(bar)', element).length).
        toBe(1);
  });

  it('applies the filter when a filter value is set', () => {
    const element = render(
        [{timestamp: 42, message: 'foo'}, {timestamp: 43, message: 'bar'}],
        false, 'foo');

    expect($('table', element).length).toBe(1);
    expect($('table tr', element).length).toBe(1);

    expect($('table tr:eq(0) td:eq(0):contains(42)', element).length).
        toBe(1);
    expect($('table tr:eq(0) td:eq(1):contains(foo)', element).length).
        toBe(1);
  });

  it('shows an empty table when the filter removes all items', () => {
    const element = render(
        [{timestamp: 42, message: 'foo'}, {timestamp: 43, message: 'bar'}],
        false, 'xxx');

    expect($('table', element).length).toBe(1);
    expect($('table tr', element).length).toBe(0);
  });

  it('cancels an in-flight request when trigger-update is called', () => {
    const deferred1 = $q.defer();
    const deferred2 = $q.defer();
    spyOn(MemoryItemsProviderController.prototype, 'fetchItems').and
        .returnValues(deferred1.promise, deferred2.promise);

    const element = render([]);
    // Only the 'Loading...' row should be displayed.
    expect($('table tr', element).length).toBe(1);

    // This should trigger yet another call.
    $rootScope.triggerUpdate();
    // Run the timer forward, so that newly displayed 'Loading...' element
    // can be detected.
    $interval.flush(1000);

    deferred2.resolve({
      offset: 0,
      items: [
        {timestamp: 44, message: 'foo2'},
        {timestamp: 45, message: 'bar2'},
      ],
    });
    $rootScope.$apply();

    deferred1.resolve({
      offset: 0,
      items: [
        {timestamp: 42, message: 'foo1'},
        {timestamp: 43, message: 'bar1'},
      ],
    });
    $rootScope.$apply();

    // Check that deferred1's result gets discarded as it
    // returns after the triggerUpdate is called.
    expect($('td:contains("foo2")', element).length).toBe(1);
    expect($('td:contains("bar2")', element).length).toBe(1);
    expect($('td:contains("bar1")', element).length).toBe(0);
    expect($('td:contains("bar1")', element).length).toBe(0);
  });

  describe('with auto refresh turned on', () => {
    const TABLE_KEY = InfiniteTableController.UNIQUE_KEY_NAME;
    const ROW_HASH = InfiniteTableController.ROW_HASH_NAME;

    const transformItems = ((items) => {
      for (let i = 0; i < items.length; ++i) {
        const item = items[i];
        item[TABLE_KEY] = item['message'];
        item[ROW_HASH] = item['timestamp'];
      }
      return items;
    });

    it('adds new element to the beginning of the list', () => {
      const element = render(
          transformItems([
            {timestamp: 42, message: 'foo'}, {timestamp: 43, message: 'bar'}
          ]),
          undefined, undefined, true);

      expect($('table', element).length).toBe(1);
      expect($('table tr', element).length).toBe(2);

      // Update the memory items provider elements and push the clock.
      $rootScope.testItems.push(
          transformItems([{timestamp: 44, message: 'blah'}])[0]);
      $interval.flush(1000);
      expect($('table tr', element).length).toBe(3);

      // New element should be inserted in the beginning of the table.
      expect($('table tr:eq(0) td:eq(0):contains(44)', element).length).
        toBe(1);
      expect($('table tr:eq(0) td:eq(1):contains(blah)', element).length).
          toBe(1);

      // Check that the behavior is stable, i.e. element is added only once.
      $interval.flush(2000);
      expect($('table tr', element).length).toBe(3);
      expect($('table tr:eq(0) td:eq(0):contains(44)', element).length).
        toBe(1);
      expect($('table tr:eq(0) td:eq(1):contains(blah)', element).length).
          toBe(1);

      expect($('table tr:eq(1) td:eq(0):contains(42)', element).length).
        toBe(1);
      expect($('table tr:eq(1) td:eq(1):contains(foo)', element).length).
          toBe(1);

      expect($('table tr:eq(2) td:eq(0):contains(43)', element).length).
        toBe(1);
      expect($('table tr:eq(2) td:eq(1):contains(bar)', element).length).
          toBe(1);
    });

    it('adds multiple new elements in the right order', () => {
      const element = render([], undefined, undefined, true);

      expect($('table tr', element).length).toBe(0);

      // Update the memory items provider elements and push the clock.
      Array.prototype.push.apply(
          $rootScope.testItems,
          transformItems([{timestamp: 42, message: 'foo'},
                          {timestamp: 43, message: 'bar'}]));
      $interval.flush(1000);
      expect($('table tr', element).length).toBe(2);

      // New element should be inserted in the beginning of the table.
      expect($('table tr:eq(0) td:eq(1):contains(foo)', element).length).
        toBe(1);
      expect($('table tr:eq(1) td:eq(1):contains(bar)', element).length).
          toBe(1);
    });

    it('does nothing with the row if row hash has not changed', () => {
      const element = render(
          transformItems([
            {timestamp: 42, message: 'foo'}, {timestamp: 43, message: 'bar'}
          ]),
          undefined, undefined, true);

      expect($('table tr:eq(0) td:eq(0):contains(42)', element).length).
        toBe(1);

      // Change the message, but don't touch the hash.
      $rootScope.testItems[0]["timestamp"] = 88;
      $interval.flush(2000);

      // Result shouldn't be updated, since the hash hasn't changed.
      //
      // (Note that one-time-bindings are used in the template, meaning
      // that each row can only be updated by grr-infinite-table and not
      // via standard Angular bindings mechanism).
      expect($('table tr:eq(0) td:eq(0):contains(42)', element).length).
          toBe(1);
    });

    it('updates the row if row hash has changed', () => {
      const element = render(
          transformItems([
            {timestamp: 42, message: 'foo'}, {timestamp: 43, message: 'bar'}
          ]),
          undefined, undefined, true);

      expect($('table tr:eq(0) td:eq(0):contains(42)', element).length).
        toBe(1);

      // Change the message, but don't touch the hash.
      $rootScope.testItems[0]["timestamp"] = 88;
      transformItems($rootScope.testItems);
      $interval.flush(2000);

      // Result should be updated, since the hash has changed.
      //
      // (Note that one-time-bindings are used in the template, meaning
      // that each row can only be updated by grr-infinite-table and not
      // via standard Angular bindings mechanism).
      expect($('table tr:eq(0) td:eq(0):contains(88)', element).length).
          toBe(1);
    });
  });
});


exports = {};

;return exports;});

//angular-components/core/loading-indicator-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.loadingIndicatorDirectiveTest');
goog.setTestOnly();

const {LoadingIndicatorDirective} = goog.require('grrUi.core.loadingIndicatorDirective');
const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('loading indicator directive', () => {

  const LOADING_STARTED_EVENT_NAME =
      LoadingIndicatorDirective.loading_started_event_name;

  const LOADING_FINISHED_EVENT_NAME =
      LoadingIndicatorDirective.loading_finished_event_name;

  let $compile;
  let $rootScope;
  let $scope;


  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $scope = $rootScope.$new();
  }));

  const render = () => {
    const template = '<grr-loading-indicator />';
    const element = $compile(template)($scope);
    $scope.$apply();
    return element;
  };

  const isVisible =
      ((element) => !element.find('.ajax_spinner').hasClass('ng-hide'));

  const broadcast = (event, key) => {
    $rootScope.$apply(() => {
      $rootScope.$broadcast(event, key);
    });
  };

  it('should be hidden by default', () => {
    const element = render();
    expect(isVisible(element)).toBe(false);
  });

  it('should turn visible once a loading started event is fired', () => {
    const element = render();
    expect(isVisible(element)).toBe(false);
    broadcast(LOADING_STARTED_EVENT_NAME, 'some key');
    expect(isVisible(element)).toBe(true);
  });

  it('should turn invisible once a corresponding loading finished event is fired',
     () => {
       const element = render();
       expect(isVisible(element)).toBe(false);

       broadcast(LOADING_STARTED_EVENT_NAME, 'some key');
       expect(isVisible(element)).toBe(true);

       broadcast(LOADING_FINISHED_EVENT_NAME, 'some key');
       expect(isVisible(element)).toBe(false);
     });

  it('should ignore unrelated loading finished events', () => {
    const element = render();
    expect(isVisible(element)).toBe(false);

    broadcast(LOADING_STARTED_EVENT_NAME, 'some key');
    expect(isVisible(element)).toBe(true);

    broadcast(LOADING_FINISHED_EVENT_NAME, 'some other key');
    expect(isVisible(element)).toBe(true);

    // TODO(user): once all requests go through angular, we can throw error on
    // unrelated events. This code can be used to test this behavior:
    //    expect(function(){
    //      broadcast(LOADING_FINISHED_EVENT_NAME, 'some other key');
    //    }).toThrowError("Key not found: some other key");
    //    expect(isVisible(element)).toBe(true);
  });

  it('should turn invisible once all loading finished events occurred', () => {
    const element = render();
    expect(isVisible(element)).toBe(false);

    broadcast(LOADING_STARTED_EVENT_NAME, 'some key');
    broadcast(LOADING_STARTED_EVENT_NAME, 'some other key');
    broadcast(LOADING_STARTED_EVENT_NAME, 'one more key');
    expect(isVisible(element)).toBe(true);

    // finish events in arbitrary order
    broadcast(LOADING_FINISHED_EVENT_NAME, 'one more key');
    expect(isVisible(element)).toBe(true);

    broadcast(LOADING_FINISHED_EVENT_NAME, 'some key');
    expect(isVisible(element)).toBe(true);

    broadcast(LOADING_FINISHED_EVENT_NAME, 'some other key');
    expect(isVisible(element)).toBe(false); // all finished events occurred
  });
});

exports = {};

;return exports;});

//angular-components/core/markdown-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.markdownDirectiveTest');
goog.setTestOnly();

const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('markdown directive', () => {
  let $compile;
  let $rootScope;

  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (source) => {
    $rootScope.source = source;

    const template = '<grr-markdown source="source"></grr-markdown>';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('renders markdown text', () => {
    const element = renderTestTemplate('*blah*');
    expect(element.html().trim()).toBe('<p><em>blah</em></p>');
  });
});


exports = {};

;return exports;});

//angular-components/core/memory-items-provider-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.memoryItemsProviderDirectiveTest');
goog.setTestOnly();

const {MemoryItemsProviderController} = goog.require('grrUi.core.memoryItemsProviderDirective');


describe('memory items provider directive', () => {
  let $rootScope;

  beforeEach(inject(($injector) => {
    $rootScope = $injector.get('$rootScope');
  }));

  const getController = (testItems) => {
    $rootScope.testItems = testItems;

    let controller;
    inject(($injector) => {
      controller = $injector.instantiate(MemoryItemsProviderController, {
        '$scope': $rootScope,
        '$attrs': {
          'items': 'testItems',
        },
      });
    });
    $rootScope.$apply();

    return controller;
  };

  it('fetches ranges of elements according to offset and count', () => {
    const controller = getController([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
    let items;

    controller.fetchItems(0, 10).then((resultItems) => {
      items = resultItems;
    });
    $rootScope.$apply();  // process promises
    expect(items).toEqual({items: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], offset: 0});

    controller.fetchItems(9, 1).then((resultItems) => {
      items = resultItems;
    });
    $rootScope.$apply();  // process promises
    expect(items).toEqual({items: [9], offset: 9});

    controller.fetchItems(9, 2).then((resultItems) => {
      items = resultItems;
    });
    $rootScope.$apply();  // process promises
    expect(items).toEqual({items: [9], offset: 9});
  });

  it('does not return total count when !opt_withTotalCount', () => {
    const controller = getController([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
    let items;

    controller.fetchItems(9, 1).then((resultItems) => {
      items = resultItems;
    });
    $rootScope.$apply();  // process promises
    expect(items.totalCount).toBeUndefined();
  });

  it('returns total count when opt_withTotalCount is true', () => {
    const controller = getController([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
    let items;

    controller.fetchItems(9, 1, true).then((resultItems) => {
      items = resultItems;
    });
    $rootScope.$apply();  // process promises
    expect(items.totalCount).toEqual(10);
  });

  it('fetches ranges of filtered strings', () => {
    const controller = getController(['foo', 'bar', 'foobar', 'barfoo']);
    let items;

    controller.fetchFilteredItems('foo', 0, 10).then((resultItems) => {
      items = resultItems;
    });
    $rootScope.$apply();  // process promises
    expect(items).toEqual({items: ['foo', 'foobar', 'barfoo'], offset: 0});

    controller.fetchFilteredItems('foo', 0, 1).then((resultItems) => {
      items = resultItems;
    });
    $rootScope.$apply();  // process promises
    expect(items).toEqual({items: ['foo'], offset: 0});

    controller.fetchFilteredItems('foo', 2, 1).then((resultItems) => {
      items = resultItems;
    });
    $rootScope.$apply();  // process promises
    expect(items).toEqual({items: ['barfoo'], offset: 2});
  });

  it('ignores case when filtering strings', () => {
    const controller = getController(['FOO', 'Bar', 'fooBar', 'barfoo']);
    let items;

    controller.fetchFilteredItems('foo', 0, 10).then((resultItems) => {
      items = resultItems;
    });
    $rootScope.$apply();  // process promises
    expect(items).toEqual({items: ['FOO', 'fooBar', 'barfoo'], offset: 0});

    controller.fetchFilteredItems('BAR', 0, 10).then((resultItems) => {
      items = resultItems;
    });
    $rootScope.$apply();  // process promises
    expect(items).toEqual({items: ['Bar', 'fooBar', 'barfoo'], offset: 0});
  });

  it('fetches ranges of filtered dictionaries', () => {
    const controller = getController([
      {message: 'foo'}, {message: 'bar'}, {message: 'foobar'},
      {message: 'barfoo'}
    ]);
    let items;

    controller.fetchFilteredItems('foo', 0, 10).then((resultItems) => {
      items = resultItems;
    });
    $rootScope.$apply();  // process promises
    expect(items).toEqual({
      items: [{message: 'foo'}, {message: 'foobar'}, {message: 'barfoo'}],
      offset: 0,
    });

    controller.fetchFilteredItems('foo', 0, 1).then((resultItems) => {
      items = resultItems;
    });
    $rootScope.$apply();  // process promises
    expect(items).toEqual({items: [{message: 'foo'}], offset: 0});

    controller.fetchFilteredItems('foo', 2, 1).then((resultItems) => {
      items = resultItems;
    });
    $rootScope.$apply();  // process promises
    expect(items).toEqual({items: [{message: 'barfoo'}], offset: 2});
  });
});


exports = {};

;return exports;});

//angular-components/core/paged-filtered-table-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.pagedFilteredTableDirectiveTest');
goog.setTestOnly();

const {MemoryItemsProviderController} = goog.require('grrUi.core.memoryItemsProviderDirective');
const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {coreModule} = goog.require('grrUi.core.core');


describe('paged filtered table', () => {
  let $compile;
  let $interval;
  let $q;
  let $rootScope;


  beforeEach(module('/static/angular-components/core/paged-filtered-table-top.html'));
  beforeEach(module('/static/angular-components/core/paged-filtered-table-bottom.html'));
  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    $interval = $injector.get('$interval');
  }));

  const render = (items, withAutoRefresh) => {
    $rootScope.testItems = items;
    if (withAutoRefresh) {
      $rootScope.autoRefreshInterval = 1000;
    }

    // We render the paged filtered table with grr-memory-items-provider.
    // While it means that this unit test actually depends on the working
    // code of grr-memory-items-provider (which is tested separately),
    // this seems to be the easiest/most reasonable way to test that
    // grr-paged-filtered-table is working correctly. Mocking out
    // items providers would require writing code that's almost
    // equal to grr-memory-items-provider code.
    const template = '<div>' +
        '<table>' +
        '<tbody>' +
        '<tr grr-paged-filtered-table grr-memory-items-provider ' +
        'items="testItems" page-size="5" ' +
        'auto-refresh-interval="autoRefreshInterval">' +
        '<td>{$ item.timestamp $}</td>' +
        '<td>{$ item.message $}</td>' +
        '</tr>' +
        '</tbody' +
        '</table>' +
        '</div>';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('throws if items provider is not specified', () => {
    // We need outer container, as grr-paged-filtered-table inserts directives
    // before and after containing table.
    const template = '<div><table><tbody><tr grr-paged-filtered-table />' +
        '</tbody></table></div>';
    const compiledTemplate = $compile(template);
    expect(() => {
      compiledTemplate($rootScope);
    }).toThrow(Error('Data provider not specified.'));
  });

  it('shows empty table when there are no elements', () => {
    const element = render([]);

    expect($('table', element).length).toBe(1);
    expect($('table tr', element).length).toBe(0);
  });

  it('shows 2 rows for 2 items', () => {
    const element = render(
        [{timestamp: 42, message: 'foo'}, {timestamp: 43, message: 'bar'}]);

    expect($('table', element).length).toBe(1);
    expect($('table tr', element).length).toBe(2);
    expect(element.text()).toContain('2 entries');

    expect($('table tr:eq(0) td:eq(0):contains(42)', element).length).
        toBe(1);
    expect($('table tr:eq(0) td:eq(1):contains(foo)', element).length).
        toBe(1);
    expect($('table tr:eq(1) td:eq(0):contains(43)', element).length).
        toBe(1);
    expect($('table tr:eq(1) td:eq(1):contains(bar)', element).length).
        toBe(1);
  });


  const checkItemsAreShown = (itemsToCheck, element) => {
    angular.forEach(itemsToCheck, (item) => {
      const foundElems = $('td:contains(' + item.message + ')', element);
      expect(foundElems.length).toBe(1);
    });
  };

  const checkItemsAreNotShown = (itemsToCheck, element) => {
    angular.forEach(itemsToCheck, (item) => {
      const foundElems = $('td:contains(' + item.message + ')', element);
      expect(foundElems.length).toBe(0);
    });
  };

  it('switches between pages correctly when there are 2 pages', () => {
    const items = [];
    for (let i = 0; i < 10; ++i) {
      items.push({timestamp: i,
        message: 'message_' + i.toString()});
    }

    const element = render(items);

    checkItemsAreShown(items.slice(0, 5), element);
    checkItemsAreNotShown(items.slice(5), element);

    browserTriggerEvent($('a:contains(Next)', element), 'click');
    checkItemsAreNotShown(items.slice(0, 5), element);
    checkItemsAreShown(items.slice(5), element);

    browserTriggerEvent($('a:contains(Previous)', element), 'click');
    checkItemsAreShown(items.slice(0, 5), element);
    checkItemsAreNotShown(items.slice(5), element);
  });

  it('switches between pages correctly when there are 15 pages', () => {
    const items = [];
    for (let i = 0; i < 5 * 15; ++i) {
      items.push({timestamp: i,
        message: 'message_' + i.toString()});
    }

    const element = render(items);

    for (let i = 0; i < 15; ++i) {
      const pageLink = $('a', element).filter(function() {
        return $(this).text() == (i + 1).toString();
      });
      browserTriggerEvent(pageLink, 'click');

      checkItemsAreShown(items.slice(i * 5, i * 5 + 5), element);
      if (i > 0) {
        checkItemsAreNotShown(items.slice((i - 1) * 5, i * 5), element);
      }
      if (i < 14) {
        checkItemsAreNotShown(items.slice((i + 1) * 5, items.length), element);
      }
    }
  });

  it('filters collection of 5 elements correctly', () => {
    const someItems = [
      {message: 'some1'},
      {message: 'some2'},
    ];
    const otherItems = [
      {message: 'other1'},
      {message: 'other2'},
      {message: 'other3'},
    ];

    const element = render(someItems.concat(otherItems));
    checkItemsAreShown(someItems, element);
    checkItemsAreShown(otherItems, element);

    $('input.search-query', element).val('some');
    browserTriggerEvent($('input.search-query', element), 'input');
    browserTriggerEvent($('button:contains(Filter)', element), 'click');

    expect(element.text()).toContain('Filtered by: some');
    checkItemsAreShown(someItems, element);
    checkItemsAreNotShown(otherItems, element);

    $('input.search-query', element).val('');
    browserTriggerEvent($('input.search-query', element), 'input');
    browserTriggerEvent($('button:contains(Filter)', element), 'click');

    expect(element.text()).not.toContain('Filtered by');
    checkItemsAreShown(someItems, element);
    checkItemsAreShown(otherItems, element);
  });

  it('loads more filtered results when "Fetch More" is clicked', () => {
    const someItems = [
      {message: 'some1'},
      {message: 'some2'},
      {message: 'some3'},
      {message: 'some4'},
      {message: 'some5'},
      {message: 'some6'},
      {message: 'some7'},
    ];
    const otherItems = [
      {message: 'other1'},
      {message: 'other2'},
      {message: 'other3'},
    ];

    const element = render(someItems.concat(otherItems));

    $('input.search-query', element).val('some');
    browserTriggerEvent($('input.search-query', element), 'input');
    browserTriggerEvent($('button:contains(Filter)', element), 'click');

    checkItemsAreShown(someItems.slice(0, 5), element);
    checkItemsAreNotShown(someItems.slice(5), element);
    checkItemsAreNotShown(otherItems, element);

    browserTriggerEvent($('button:contains("Fetch More")', element), 'click');
    checkItemsAreShown(someItems, element);
    checkItemsAreNotShown(otherItems, element);
  });

  it('fetches 5 pages of filtered results when "Fetch 5" is clicked', () => {
    const someItems = [];
    for (let i = 0; i < 35; ++i) {
      someItems.push({message: 'some_' + i.toString(36)});
    }

    const otherItems = [];
    for (let i = 0; i < 20; ++i) {
      otherItems.push({message: 'other' + i.toString(36)});
    }

    const element = render(someItems.concat(otherItems));

    $('input.search-query', element).val('some');
    browserTriggerEvent($('input.search-query', element), 'input');
    browserTriggerEvent($('button:contains(Filter)', element), 'click');

    browserTriggerEvent($('a:contains("Fetch 25")', element), 'click');
    // 5 items were shown initially, we fetched 25 more, so 30 should be
    // shown.
    checkItemsAreShown(someItems.slice(0, 30), element);
    // 5 items (30..35) were left unshown.
    checkItemsAreNotShown(someItems.slice(30, 35), element);
    // Elements that do not match the filter shouldn't be shown at all.
    checkItemsAreNotShown(otherItems, element);
  });

  describe('with auto-refresh-interval set', () => {
    it('adds new element to the end of the list', () => {
      const someItems = [
        {message: 'some1'},
        {message: 'some2'},
      ];

      const element = render(someItems, true);
      checkItemsAreShown(someItems, element);

      someItems.push({message: 'some3'});
      $interval.flush(1000);
      checkItemsAreShown(someItems, element);
    });

    it('updates total count', () => {
      const someItems = [
        {message: 'some1'},
        {message: 'some2'},
      ];

      const element = render(someItems, true);
      expect(element.text().indexOf('2 entries') != -1 ).toBe(true);

      for (let i = 0; i < 5; ++i) {
        someItems.push({message: 'somethingelse'});
      }
      $interval.flush(1000);
      expect(element.text().indexOf('7 entries') != -1 ).toBe(true);
    });

    it('updates paging', () => {
      const someItems = [
        {message: 'some1'},
        {message: 'some2'},
      ];

      const element = render(someItems, true);
      expect(element.find('ul.pagination li:contains("2")').length).toBe(0);

      for (let i = 0; i < 5; ++i) {
        someItems.push({message: 'somethingelse'});
      }
      $interval.flush(1000);
      expect(element.find('ul.pagination li:contains("2")').length).toBe(1);
    });

    it('does not auto-update in filtered mode', () => {
      const someItems = [
        {message: 'some1'},
        {message: 'some2'},
      ];

      const element = render(someItems, true);
      $('input.search-query', element).val('some');
      browserTriggerEvent($('input.search-query', element), 'input');
      browserTriggerEvent($('button:contains(Filter)', element), 'click');

      checkItemsAreShown(someItems, element);

      someItems.push({message: 'some3'});
      $interval.flush(1000);
      checkItemsAreShown(someItems.slice(0, 2), element);
      checkItemsAreNotShown(someItems.slice(2, 3), element);
    });

    it('does not auto-update if full page is shown', () => {
      // Page size is 5.
      const someItems = [
        {message: 'some1'},
        {message: 'some2'},
        {message: 'some3'},
        {message: 'some4'},
        {message: 'some5'},
      ];

      const element = render(someItems, true);
      checkItemsAreShown(someItems, element);

      someItems[0] = {message: 'someother'};
      someItems.push({message: 'some6'});
      $interval.flush(1000);
      checkItemsAreShown(someItems.slice(1, 5), element);
      checkItemsAreNotShown(someItems.slice(0, 1), element);
      checkItemsAreNotShown(someItems.slice(5, 6), element);
    });

    it('skips results of the obsolete request if page is changed', () => {
      // Page size is 5.
      const someItems = [
        {message: 'some1'},
        {message: 'some2'},
        {message: 'some3'},
        {message: 'some4'},
        {message: 'some5'},
        {message: 'some6'},
      ];

      const element = render(someItems, true);
      browserTriggerEvent($('a:contains(Next)', element), 'click');
      checkItemsAreShown(someItems.slice(5), element);

      const deferred1 = $q.defer();
      const deferred2 = $q.defer();
      spyOn(MemoryItemsProviderController.prototype, 'fetchItems')
          .and.returnValue(deferred1.promise)
          .and.returnValue(deferred2.promise);

      // First let the auto-update trigger.
      $interval.flush(1000);

      // Second let's go back to the previous page.
      browserTriggerEvent($('a:contains(Previous)', element), 'click');

      // Resolve the non-auto-update deferred.
      deferred2.resolve({items: someItems.slice(0, 5)});
      $rootScope.$apply();
      checkItemsAreShown(someItems.slice(0, 5), element);

      // Resolve the now-obsolete deferred corresponding to the auto-update
      // action. Nothing should happen.
      deferred1.resolve({items: someItems.slice(5, 6)});
      $rootScope.$apply();
      checkItemsAreShown(someItems.slice(0, 5), element);
      checkItemsAreNotShown(someItems.slice(5, 6), element);
    });

    it('does not start auto-update request if one is in progress', () => {
      // Page size is 5.
      const someItems = [
        {message: 'some1'},
      ];

      render(someItems, true);

      const deferred = $q.defer();
      spyOn(MemoryItemsProviderController.prototype, 'fetchItems')
          .and.returnValue(deferred.promise);

      // Let the auto-update trigger.
      $interval.flush(1000);

      // And once again.
      $interval.flush(1000);

      // Check that fetchItems() was called just once.
      expect(MemoryItemsProviderController.prototype.fetchItems)
          .toHaveBeenCalledTimes(1);
    });
  });
});


exports = {};

;return exports;});

//angular-components/core/periodic-refresh-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.periodicRefreshDirectiveTest');
goog.setTestOnly();

const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('grr-periodic-refresh directive', () => {
  let $compile;
  let $interval;
  let $rootScope;


  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $interval = $injector.get('$interval');
    $rootScope = $injector.get('$rootScope');
  }));

  it('reloads children elements effectively updating one-time bindings', () => {
    $rootScope.value = 42;

    const template = '<grr-periodic-refresh interval="1000">' +
        '{$ ::value $}</grr-periodic-refresh>';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    expect(element.text()).toContain('42');

    $rootScope.value = 43;
    $rootScope.$apply();

    // The value was updated but DOM stays the same, as one-time binding
    // is used in the template.
    expect(element.text()).toContain('42');

    // Transcluded template gets re-rendered when the timer hits.
    $interval.flush(1001);
    expect(element.text()).toContain('43');
  });

  it('calls a callback on timer', () => {
    $rootScope.callback = jasmine.createSpy('callback');

    const template = '<grr-periodic-refresh interval="1000" ' +
        'on-refresh="callback()"></grr-periodic-refresh>';
    $compile(template)($rootScope);
    $rootScope.$apply();

    expect($rootScope.callback).not.toHaveBeenCalled();

    $interval.flush(1001);
    expect($rootScope.callback).toHaveBeenCalled();
  });
});


exports = {};

;return exports;});

//angular-components/core/reflection-service_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.reflectionServiceTest');
goog.setTestOnly();

const {ReflectionService} = goog.require('grrUi.core.reflectionService');
const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('AFF4 items provider directive', () => {
  let $q;
  let $rootScope;
  let grrApiServiceMock;
  let grrReflectionService;


  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');

    grrApiServiceMock = {get: function() {}};
    grrReflectionService = $injector.instantiate(ReflectionService, {
      'grrApiService': grrApiServiceMock,
    });
  }));

  it('fetches data from the server only once', () => {
    const deferred = $q.defer();
    spyOn(grrApiServiceMock, 'get').and.returnValue(deferred.promise);

    grrReflectionService.getRDFValueDescriptor('DurationSeconds');
    grrReflectionService.getRDFValueDescriptor('RDFDatetime');
    // Check that only 1 call to API service was made.
    expect(grrApiServiceMock.get.calls.count()).toBe(1);
  });

  it('queues requests until the data are fetched', () => {
    const deferred = $q.defer();
    spyOn(grrApiServiceMock, 'get').and.returnValue(deferred.promise);

    const responses = [];
    grrReflectionService.getRDFValueDescriptor('DurationSeconds')
        .then((response) => {
          responses.push(response);
        });

    grrReflectionService.getRDFValueDescriptor('RDFDatetime')
        .then((response) => {
          responses.push(response);
        });

    expect(responses.length).toBe(0);

    deferred.resolve({
      data: {
        items: [
          {
            'doc': 'Duration value stored in seconds internally.',
            'kind': 'primitive',
            'name': 'DurationSeconds',
          },
          {
            'doc': 'Date and time.',
            'kind': 'primitive',
            'name': 'RDFDatetime',
          },
        ],
      },
    });
    $rootScope.$apply();

    expect(responses.length).toBe(2);
    expect(responses[0]).toEqual({
      'doc': 'Duration value stored in seconds internally.',
      'kind': 'primitive',
      'name': 'DurationSeconds',
    });
    expect(responses[1]).toEqual({
      'doc': 'Date and time.',
      'kind': 'primitive',
      'name': 'RDFDatetime',
    });
  });

  it('returns data with dependencies if opt_withDeps is true', () => {
    const deferred = $q.defer();
    deferred.resolve({
      data: {
        items: [
          {
            'doc': 'Sample struct.',
            'kind': 'struct',
            'name': 'Struct',
            'fields': [
              {'type': 'RDFInteger'},
            ],
          },
          {
            'doc': 'Sample integer.',
            'kind': 'primitive',
            'name': 'RDFInteger',
          },
        ],
      },
    });
    spyOn(grrApiServiceMock, 'get').and.returnValue(deferred.promise);

    let descriptors;
    grrReflectionService.getRDFValueDescriptor('Struct', true)
        .then((response) => {
          descriptors = response;
        });
    $rootScope.$apply();

    expect(descriptors['Struct']).toBeDefined();
    expect(descriptors['RDFInteger']).toBeDefined();
  });
});


exports = {};

;return exports;});

//angular-components/core/search-box-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.searchBoxDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, stubUiTrait, testsModule} = goog.require('grrUi.tests');
const {coreModule} = goog.require('grrUi.core.core');


describe('search box directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let $scope;
  let grrApiService;
  let grrRoutingService;


  beforeEach(module('/static/angular-components/core/search-box.html'));
  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  stubUiTrait('search_clients_action_enabled');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $scope = $rootScope.$new();
    $q = $injector.get('$q');
    grrApiService = $injector.get('grrApiService');
    grrRoutingService = $injector.get('grrRoutingService');
  }));

  const render = () => {
    const template = '<grr-search-box />';
    const element = $compile(template)($scope);
    $scope.$apply();
    return element;
  };

  /**
   * Stubs out any calls to api service. By passing a dictionary of url-result pairs,
   * different server calls can be simulated. If no result for a given URL is found,
   * a failed server call will be simulated.
   */
  const mockApiServiceResponse = (results) => {
    results = results || {};
    spyOn(grrApiService, 'get').and.callFake((apiPath, params) => {
      const value = results[apiPath];
      if (value) {
        return $q.resolve({ data: value });
      } else {
        return $q.reject('Invalid');
      }
    });
  };

  const triggerSearch = (element, query) => {
    $('input', element).val(query).trigger('input');
    browserTriggerEvent($('input', element), 'change');
    browserTriggerEvent($('button', element), 'click');
  };

  const triggerSearchByKeyboard = (element, query) => {
    element.find('input').val(query).trigger('input');
    $scope.$apply();

    // TODO(user): Use browserTriggerKeyDown when available.
    const event = jQuery.Event('keypress');
    event.which = 13;
    element.find('input').trigger(event);
  };

  it('should invoke client search on arbitrary input', () => {
    mockApiServiceResponse();
    spyOn(grrRoutingService, 'go');

    const element = render();
    triggerSearch(element, 'test query');

    expect(grrApiService.get).toHaveBeenCalledWith('/clients/labels');
    expect(grrRoutingService.go).toHaveBeenCalledWith('search', {q: 'test query'});
  });

  it('should invoke client search on ENTER in input', () => {
    mockApiServiceResponse();
    spyOn(grrRoutingService, 'go');

    const element = render();
    triggerSearchByKeyboard(element, 'test query');

    expect(grrApiService.get).toHaveBeenCalledWith('/clients/labels');
    expect(grrRoutingService.go).toHaveBeenCalledWith('search', {q: 'test query'});
  });

  it('should request hunt details if a hunt id is detected', () => {
    mockApiServiceResponse();

    const element = render();
    triggerSearch(element, 'H:12345678');
    expect(grrApiService.get).toHaveBeenCalledWith('hunts/H:12345678');
  });

  it('should forward to the hunt details if a hunt was found', () => {
    mockApiServiceResponse({
      'hunts/H:12345678': {
        value: {
          hunt_id: {
            value: 'H:12345678',
          },
        },
      },
    });
    spyOn(grrRoutingService, 'go');

    const element = render();
    triggerSearch(element, 'H:12345678');

    expect(grrApiService.get).toHaveBeenCalledWith('hunts/H:12345678');
    expect(grrRoutingService.go).toHaveBeenCalledWith('hunts', {huntId: 'H:12345678'});
  });

  it('should fall back to regular client search if no hunt was found', () => {
    mockApiServiceResponse(/* No param for HUNT url, so service call will be rejected. */);
    spyOn(grrRoutingService, 'go');

    const element = render();
    triggerSearch(element, 'H:12345678');

    expect(grrApiService.get).toHaveBeenCalledWith('hunts/H:12345678');
    expect(grrRoutingService.go).toHaveBeenCalledWith('search', {q: 'H:12345678'});
  });

  it('should check that potential hunt ids cannot start with search keywords',
     () => {
       mockApiServiceResponse();
       spyOn(grrRoutingService, 'go');

       const element = render();
       triggerSearch(element, 'HOST:12345678');
       triggerSearch(element, 'FQDN:12345678');
       triggerSearch(element, 'MAC:12345678');
       triggerSearch(element, 'IP:12345678');
       triggerSearch(element, 'USER:12345678');
       triggerSearch(element, 'LABEL:12345678');

       // None of the above calls should have triggered a hunt details call,
       // since they are all search keywords.
       expect(grrRoutingService.go).not.toHaveBeenCalledWith('hunts', {
         huntId: 'HOST:12345678'
       });
       expect(grrRoutingService.go).not.toHaveBeenCalledWith('hunts', {
         huntId: 'FQDN:12345678'
       });
       expect(grrRoutingService.go).not.toHaveBeenCalledWith('hunts', {
         huntId: 'MAC:12345678'
       });
       expect(grrRoutingService.go).not.toHaveBeenCalledWith('hunts', {
         huntId: 'IP:12345678'
       });
       expect(grrRoutingService.go).not.toHaveBeenCalledWith('hunts', {
         huntId: 'USER:12345678'
       });
       expect(grrRoutingService.go).not.toHaveBeenCalledWith('hunts', {
         huntId: 'LABEL:12345678'
       });

       // Instead, only client searches should have been issued.
       expect(grrRoutingService.go).toHaveBeenCalledWith('search', {
         q: 'HOST:12345678'
       });
       expect(grrRoutingService.go).toHaveBeenCalledWith('search', {
         q: 'FQDN:12345678'
       });
       expect(grrRoutingService.go).toHaveBeenCalledWith('search', {
         q: 'MAC:12345678'
       });
       expect(grrRoutingService.go).toHaveBeenCalledWith('search', {
         q: 'IP:12345678'
       });
       expect(grrRoutingService.go).toHaveBeenCalledWith('search', {
         q: 'USER:12345678'
       });
       expect(grrRoutingService.go).toHaveBeenCalledWith('search', {
         q: 'LABEL:12345678'
       });
     });
});


exports = {};

;return exports;});

//angular-components/core/semantic-registry-service_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.semanticRegistryServiceTest');
goog.setTestOnly();

const {SemanticRegistryService} = goog.require('grrUi.core.semanticRegistryService');
const {coreModule} = goog.require('grrUi.core.core');


describe('Semantic registry', () => {
  let $q;
  let $rootScope;
  let grrReflectionService;
  let testRegistry;


  beforeEach(module(coreModule.name));
  beforeEach(inject(($injector) => {
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrReflectionService = $injector.get('grrReflectionService');

    testRegistry = $injector.instantiate(SemanticRegistryService, {});
  }));

  describe('findDirectiveForMro', () => {
    it('finds previously registered directive', () => {
      testRegistry.registerDirective('SomeType', Object);
      const foundDirective = testRegistry.findDirectiveForMro(['SomeType']);
      expect(foundDirective).toBe(Object);
    });

    it('returns undefined when searching for not registered directive', () => {
      const foundDirective = testRegistry.findDirectiveForMro(['SomeType']);
      expect(foundDirective).toBeUndefined();
    });

    it('returns more specific directive when multiple directives match', () => {
      const directive1 = Object();
      const directive2 = Object();

      testRegistry.registerDirective('SomeChildType', directive1);
      testRegistry.registerDirective('SomeParentType', directive2);

      const foundDirective =
          testRegistry.findDirectiveForMro(['SomeChildType', 'SomeParentType']);
      expect(foundDirective).toBe(directive1);
    });

    it('respects override for a type without directive', () => {
      const someDirective = Object();
      const foundDirective = testRegistry.findDirectiveForMro(
          ['SomeType'], {'SomeType': someDirective});
      expect(foundDirective).toBe(someDirective);
    });

    it('respects override for a single and only MRO type', () => {
      const someDirective = Object();
      const directiveOverride = Object();

      testRegistry.registerDirective('SomeType', someDirective);
      const foundDirective = testRegistry.findDirectiveForMro(
          ['SomeType'], {'SomeType': directiveOverride});
      expect(foundDirective).toBe(directiveOverride);
    });

    it('respects override for a non-leaf MRO type', () => {
      const someDirective = Object();
      const directiveOverride = Object();

      testRegistry.registerDirective('SomeParentType', someDirective);

      const foundDirective = testRegistry.findDirectiveForMro(
          ['SomeChildType', 'SomeParentType'],
          {'SomeParentType': directiveOverride});
      expect(foundDirective).toBe(directiveOverride);
    });

    it('respects override for a leaf MRO type', () => {
      const directive1 = Object();
      const directive2 = Object();

      testRegistry.registerDirective('SomeChildType', directive1);
      testRegistry.registerDirective('SomeParentType', directive2);

      const directiveOverride = Object();
      const foundDirective = testRegistry.findDirectiveForMro(
          ['SomeChildType', 'SomeParentType'],
          {'SomeChildType': directiveOverride});
      expect(foundDirective).toBe(directiveOverride);
    });
  });

  describe('findDirectiveByType', () => {
    it('returns registered directive without using reflection', (done) => {
      testRegistry.registerDirective('SomeType', Object);
      const promise = testRegistry.findDirectiveForType('SomeType');
      promise.then((value) => {
        expect(value).toBe(Object);
        done();
      });
      $rootScope.$apply();
    });

    it('returns overridden directive without using reflection', (done) => {
      const someDirective = Object();
      const directiveOverride = Object();
      testRegistry.registerDirective('SomeType', someDirective);

      const promise = testRegistry.findDirectiveForType(
          'SomeType', {'SomeType': directiveOverride});
      promise.then((value) => {
        expect(value).toBe(directiveOverride);
        done();
      });
      $rootScope.$apply();
    });

    it('queries reflection service for MRO if type unregistered', (done) => {
      testRegistry.registerDirective('SomeParentType', Object);

      const deferred = $q.defer();
      deferred.resolve({
        mro: ['SomeChildType', 'SomeParentType'],
      });
      grrReflectionService.getRDFValueDescriptor =
          jasmine.createSpy('getRDFValueDescriptor')
              .and.returnValue(deferred.promise);

      const promise = testRegistry.findDirectiveForType('SomeChildType');
      promise.then((value) => {
        expect(grrReflectionService.getRDFValueDescriptor).toHaveBeenCalled();
        expect(value).toBe(Object);
        done();
      });
      $rootScope.$apply();
    });

    it('respects overrides for parent types', () => {
      const someDirective = Object();
      const directiveOverride = Object();
      testRegistry.registerDirective('SomeParentType', someDirective);


      const deferred = $q.defer();
      deferred.resolve({
        mro: ['SomeChildType', 'SomeParentType'],
      });
      grrReflectionService.getRDFValueDescriptor =
          jasmine.createSpy('getRDFValueDescriptor')
              .and.returnValue(deferred.promise);

      const promise = testRegistry.findDirectiveForType(
          'SomeChildType', {'SomeParentType': directiveOverride});
      promise.then((value) => {
        expect(grrReflectionService.getRDFValueDescriptor).toHaveBeenCalled();
        expect(value).toBe(directiveOverride);
        done();
      });
      $rootScope.$apply();
    });
  });
});


exports = {};

;return exports;});

//angular-components/core/server-error-button-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.serverErrorButtonDirectiveTest');
goog.setTestOnly();

const {ServerErrorButtonDirective} = goog.require('grrUi.core.serverErrorButtonDirective');
const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('server error button directive', () => {
  const ERROR_EVENT_NAME = ServerErrorButtonDirective.error_event_name;
  let $compile;
  let $rootScope;
  let $scope;


  beforeEach(module('/static/angular-components/core/server-error-button.html'));
  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $scope = $rootScope.$new();
  }));

  const render = () => {
    const template = '<grr-server-error-button />';
    const element = $compile(template)($scope);
    $scope.$apply();
    return element;
  };

  const isVisible = (element) => !element.hasClass('ng-hide');

  it('should be hidden by default', () => {
    const element = render();
    expect(isVisible(element)).toBe(false);
  });

  it('should turn visible once a non-empty server error event is fired', () => {
    const element = render();
    expect(isVisible(element)).toBe(false);
    $scope.$apply(() => {
      $rootScope.$broadcast(ERROR_EVENT_NAME, {message: 'some event value'});
    });
    expect(isVisible(element)).toBe(true);
  });

  it('should ignore empty server error events', () => {
    const element = render();
    expect(isVisible(element)).toBe(false);
    $scope.$apply(() => {
      $rootScope.$broadcast(ERROR_EVENT_NAME);
    });
    expect(isVisible(element)).toBe(false);
  });
});


exports = {};

;return exports;});

//angular-components/core/server-error-dialog-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.serverErrorDialogDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {coreModule} = goog.require('grrUi.core.core');


describe('server error dialog directive', () => {
  let $compile;
  let $rootScope;
  let $scope;


  beforeEach(module('/static/angular-components/core/server-error-dialog.html'));
  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $scope = $rootScope.$new();
  }));

  const render = (message, traceBack) => {
    $scope.close = (() => {});
    $scope.message = message;
    $scope.traceBack = traceBack;

    const template =
        '<grr-server-error-dialog close="close()" message="message" trace-back="traceBack" />';
    const element = $compile(template)($scope);
    $scope.$apply();
    return element;
  };

  it('should show a basic error message and traceback', () => {
    const message = 'Some error message';
    const traceBack = 'Some trace back';
    const element = render(message, traceBack);

    expect(element.find('.modal-header h3').text()).toBe(message);
    expect(element.find('.modal-body pre').text()).toBe(traceBack);
  });

  it('should operate on empty input', () => {
    const message = '';
    const traceBack = '';
    const element = render(message, traceBack);

    expect(element.find('.modal-header h3').text()).toBe(message);
    expect(element.find('.modal-body pre').text()).toBe(traceBack);
  });

  it('should call scope.close when clicking the X', () => {
    const message = '...';
    const traceBack = '...';
    const element = render(message, traceBack);

    spyOn($scope, 'close');
    browserTriggerEvent(element.find('.modal-header button'), 'click');
    expect($scope.close).toHaveBeenCalled();
  });


  it('should call scope.close when clicking the close button', () => {
    const message = '...';
    const traceBack = '...';
    const element = render(message, traceBack);

    spyOn($scope, 'close');
    browserTriggerEvent(element.find('.modal-footer button'), 'click');
    expect($scope.close).toHaveBeenCalled();
  });
});


exports = {};

;return exports;});

//angular-components/core/server-error-preview-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.serverErrorPreviewDirectiveTest');
goog.setTestOnly();

const {ServerErrorButtonDirective} = goog.require('grrUi.core.serverErrorButtonDirective');
const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('server error preview directive', () => {
  const ERROR_EVENT_NAME = ServerErrorButtonDirective.error_event_name;

  let $compile;
  let $rootScope;
  let $scope;


  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $scope = $rootScope.$new();
  }));

  const render = () => {
    const template = '<grr-server-error-preview />';
    const element = $compile(template)($scope);
    $scope.$apply();
    return element;
  };

  const isVisible = (element) => !element.hasClass('ng-hide');

  it('should be hidden by default', () => {
    const element = render();
    expect(isVisible(element)).toBe(false);
  });

  it('should show error message once a non-empty server error event is fired',
     () => {
       const errorMessage = 'some event value';
       const element = render();
       expect(isVisible(element)).toBe(false);
       $scope.$apply(() => {
         $rootScope.$broadcast(ERROR_EVENT_NAME, {message: errorMessage});
       });
       expect(isVisible(element)).toBe(true);
       expect(element.text().trim()).toBe(errorMessage);
     });

  it('should ignore empty server error events', () => {
    const element = render();
    expect(isVisible(element)).toBe(false);
    $scope.$apply(() => {
      $rootScope.$broadcast(ERROR_EVENT_NAME);
    });
    expect(isVisible(element)).toBe(false);
  });
});


exports = {};

;return exports;});

//angular-components/core/splitter-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.splitterDirectiveTest');
goog.setTestOnly();

const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('grr-splitter directive', () => {
  let $compile;
  let $rootScope;
  let $interval;
  const splitSpy = jasmine.createSpy('Split');
  let prevSplit;

  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $interval = $injector.get('$interval');
  }));

  beforeEach(() => {
    prevSplit = window.Split;
    window.Split = splitSpy;
  });

  afterEach(() => {
    window.Split = prevSplit;
  });

  const renderTestTemplate = (template) => {
    const element = $compile(template)($rootScope);

    // Ensure that grr-splitter element has proper width and height.
    element.css({'width': '300px', height: '300px'});
    // Move the timer forward to make sure grr-splitter sees that its
    // element has a proper size now.
    $interval.flush(101);

    $rootScope.$apply();
    return element;
  };

  it('calls Split() correctly for a simple horizontal splitter', () => {
    const element = renderTestTemplate(`
<div grr-splitter orientation="horizontal">
  <div grr-splitter-pane></div>
  <div grr-splitter-pane></div>
</div>
`);
    expect(splitSpy).toHaveBeenCalled();

    const lastArgs = splitSpy.calls.mostRecent().args;
    expect(lastArgs).toEqual([
      element.find('div[grr-splitter-pane]').toArray(),
      {gutterSize: 4, sizes: [50, 50], direction: 'vertical'},
    ]);
  });

  it('calls Split() correctly for a simple vertical splitter', () => {
    const element = renderTestTemplate(`
<div grr-splitter orientation="vertical">
  <div grr-splitter-pane></div>
  <div grr-splitter-pane></div>
</div>
`);
    expect(splitSpy).toHaveBeenCalled();

    const lastArgs = splitSpy.calls.mostRecent().args;
    expect(lastArgs).toEqual([
      element.find('div[grr-splitter-pane]').toArray(),
      {gutterSize: 4, sizes: [50, 50], direction: 'horizontal'},
    ]);
  });

  it('calls Split() correctly for a vertical splitter with sizes set', () => {
    const element = renderTestTemplate(`
<div grr-splitter orientation="vertical">
  <div grr-splitter-pane size="25"></div>
  <div grr-splitter-pane></div>
</div>
`);
    expect(splitSpy).toHaveBeenCalled();

    const lastArgs = splitSpy.calls.mostRecent().args;
    expect(lastArgs).toEqual([
      element.find('div[grr-splitter-pane]').toArray(),
      {gutterSize: 4, sizes: [25, 75], direction: 'horizontal'},
    ]);
  });

  it('correctly fills-in missing sizes if some are not specified', () => {
  });
});

;return exports;});

//angular-components/core/time-since-filter_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.timeSinceFilterTest');
goog.setTestOnly();

const {TimeSinceFilter} = goog.require('grrUi.core.timeSinceFilter');
const {clientModule} = goog.require('grrUi.client.client');
const {testsModule} = goog.require('grrUi.tests');


describe('grrTimeSince filter', () => {
  let grrTimeSinceFilter;
  const referenceTime = 5 * 60 * 60 * 24 * 1000000;

  beforeEach(module(clientModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    grrTimeSinceFilter = $injector.instantiate(TimeSinceFilter, {
      'grrTimeService': {
        'getCurrentTimeMs': function() {
          return referenceTime / 1000;
        },
      },
    });
  }));

  it('returns seconds when value is 42 seconds ago', () => {
    const result = grrTimeSinceFilter(referenceTime - 42 * 1000000);
    expect(result).toBe('42 seconds ago');
  });

  it('returns hours when value is 3 hours ago', () => {
    const result =
        grrTimeSinceFilter(referenceTime - (60 * 60 * 3 + 42) * 1000000);
    expect(result).toBe('3 hours ago');
  });

  it('returns days when value is 3 days ago', () => {
    const result =
        grrTimeSinceFilter(referenceTime - (60 * 60 * 24 * 3 + 42) * 1000000);
    expect(result).toBe('3 days ago');
  });

  it('returns seconds when value is 42 seconds in the future', () => {
    const result = grrTimeSinceFilter(referenceTime + 42 * 1000000);
    expect(result).toBe('in 42 seconds');
  });

  it('returns hours when value is 3 hours in the future', () => {
    const result =
        grrTimeSinceFilter(referenceTime + (60 * 60 * 3 + 42) * 1000000);
    expect(result).toBe('in 3 hours');
  });

  it('returns days when value is 3 days in the future', () => {
    const result =
        grrTimeSinceFilter(referenceTime + (60 * 60 * 24 * 3 + 42) * 1000000);
    expect(result).toBe('in 3 days');
  });

  it('returns error message when value is 0', () => {
    const result = grrTimeSinceFilter(0);
    expect(result).toBe('<invalid time value>');
  });
});


exports = {};

;return exports;});

//angular-components/core/timestamp-filter_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.timestampFilterTest');
goog.setTestOnly();

const {TimestampFilter} = goog.require('grrUi.core.timestampFilter');
const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('grrTimestamp filter', () => {
  const MICRO_IN_MILLI = 1000;
  const MILLI_IN_UNIT = 1000;

  const SECONDS = MICRO_IN_MILLI * MILLI_IN_UNIT;
  const MINUTES = 60 * SECONDS;
  let grrTimestampFilter;

  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    grrTimestampFilter = $injector.instantiate(TimestampFilter);
  }));

  it('returns reference date on 0', () => {
    const result = grrTimestampFilter(0);
    expect(result).toBe('1970-01-01 00:00:00 UTC');
  });

  it('returns correct value for seconds', () => {
    const result = grrTimestampFilter(42 * SECONDS);
    expect(result).toBe('1970-01-01 00:00:42 UTC');
  });

  it('returns correct value for minutes', () => {
    const result = grrTimestampFilter(10 * MINUTES + 42 * SECONDS);
    expect(result).toBe('1970-01-01 00:10:42 UTC');
  });
});


exports = {};

;return exports;});

//angular-components/core/troggle-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.troggleDirectiveTest');
goog.setTestOnly();

const {TroggleDirective, TroggleState} = goog.require('grrUi.core.troggleDirective');
const {coreModule} = goog.require('grrUi.core.core');


describe('Troggle', () => {
  let $compile;
  let $rootScope;

  beforeEach(module(TroggleDirective().templateUrl));
  beforeEach(module(coreModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const render = (state) => {
    const template = `<grr-troggle ng-model="state" />`;
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('correctly renders `VOID` state', () => {
    $rootScope.state = TroggleState.VOID;

    let element = render();
    expect(element.text().trim()).toBe('_');
  });

  it('correctly renders `SET` state', () => {
    $rootScope.state = TroggleState.SET;

    let element = render();
    expect(element.text().trim()).toBe('✓');
  });

  it('correctly renders `UNSET` state', () => {
    $rootScope.state = TroggleState.UNSET;

    let element = render();
    expect(element.text().trim()).toBe('✕');
  });

  it('changes states when clicked', () => {
    $rootScope.state = TroggleState.VOID;
    let element = render();

    element.find(':first-child').click();
    expect(element.text().trim()).toBe('✓');
    expect($rootScope.state).toBe(TroggleState.SET);

    element.find(':first-child').click();
    expect(element.text().trim()).toBe('✕');
    expect($rootScope.state).toBe(TroggleState.UNSET);

    element.find(':first-child').click();
    expect(element.text().trim()).toBe('_');
    expect($rootScope.state).toBe(TroggleState.VOID);
  });
});

;return exports;});

//angular-components/core/utils_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.utilsTest');
goog.setTestOnly();

const {camelCaseToDashDelimited, getLastPathComponent, stringToList} = goog.require('grrUi.core.utils');


describe('core utils', () => {
  describe('camelCaseToDashDelimited', () => {

    it('returns a dash delimited string on camel case input', () => {
      const result = camelCaseToDashDelimited('someTestInput');
      expect(result).toBe('some-test-input');
    });

    it('replaces spaces with dashes', () => {
      const result = camelCaseToDashDelimited('some string with spaces');
      expect(result).toBe('some-string-with-spaces');
    });

    it('handles non-word characters by substitution with dash', () => {
      const result = camelCaseToDashDelimited('some string with $ symbols');
      expect(result).toBe('some-string-with-symbols');
    });

    it('handles uppercase abbreviations correctly', () => {
      let result = camelCaseToDashDelimited('someDDirectiveName');
      expect(result).toBe('some-d-directive-name');

      result = camelCaseToDashDelimited('someDDDirectiveName');
      expect(result).toBe('some-d-d-directive-name');
    });

    it('handles string beginning with uppercase characters correctly', () => {
      const result = camelCaseToDashDelimited('SOMEUppercaseString');
      expect(result).toBe('s-o-m-e-uppercase-string');
    });
  });


  describe('stringToList', () => {

    it('returns empty list for empty string', () => {
      const result = stringToList('');
      expect(result).toEqual([]);
    });

    it('splits 3 items correctly', () => {
      const result = stringToList('a, b, c');
      expect(result).toEqual(['a', 'b', 'c']);
    });

    it('trims spaces from elements', () => {
      const result = stringToList('a  , b  ,c ');
      expect(result).toEqual(['a', 'b', 'c']);
    });
  });

  describe('getLastPathComponent', () => {

    it('returns empty string for an empty string', () => {
      expect(getLastPathComponent('')).toBe('');
    });

    it('returns correct last component', () => {
      expect(getLastPathComponent('foo')).toBe('foo');
      expect(getLastPathComponent('foo/bar')).toBe('bar');
      expect(getLastPathComponent('foo/bar/blah')).toBe('blah');
    });
  });
});


exports = {};

;return exports;});

//angular-components/core/version-dropdown-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.versionDropdownDirectiveTest');
goog.setTestOnly();

const {VersionDropdownDirective} = goog.require('grrUi.core.versionDropdownDirective');
const {coreModule} = goog.require('grrUi.core.core');
const {testsModule} = goog.require('grrUi.tests');


describe('version dropdown directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let $scope;
  let grrApiService;


  const REFRESH_VERSIONS_EVENT =
      VersionDropdownDirective.REFRESH_VERSIONS_EVENT;

  beforeEach(module('/static/angular-components/core/version-dropdown.html'));
  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrApiService = $injector.get('grrApiService');
    $scope = $rootScope.$new();
  }));

  const render = (url, version) => {
    $scope.url = url;
    $scope.version = {
      // We need to pass an object to see changes.
      data: version,
    };

    const template =
        '<grr-version-dropdown url="url" version="version.data" />';
    const element = $compile(template)($scope);
    $scope.$apply();

    return element;
  };

  const mockApiService = (responses) => {
    spyOn(grrApiService, 'get').and.callFake((path) => {
      const response = {
        times: responses[path]
      };  // Wrap return value in type structure.
      return $q.when({ data: response });
    });
  };

  it('shows HEAD as the first element in the versions list', () => {
    mockApiService({
      'some/url': [{value: 10}],
    });

    const element = render('some/url');
    expect(element.find('option').length).toBe(2);
    expect(element.find('option:nth(0)').val()).toBe('HEAD');
  });

  it('selects HEAD if not version specified', () => {
    mockApiService({
      'some/url': [{value: 10}],
    });

    const element = render('some/url');
    expect(element.find('option[selected]').val()).toBe('HEAD');
  });

  it('should show all versions from server and select the passed one', () => {
    mockApiService({
      'some/url': [{value: 10}, {value: 42}, {value: 50}],
    });

    const element = render('some/url', 42);
    expect(element.find('option').length).toBe(4);  // 3 versions + HEAD
    expect(element.find('option[selected]').val()).toBe('42');
    expect($scope.version.data).toBe(42);
  });

  it('should show add current version to the server versions list', () => {
    mockApiService({
      'some/url': [{value: 10}, {value: 41}, {value: 50}],
    });

    const element = render('some/url', 42);

    // 3 versions from server + selected version + HEAD
    expect(element.find('option').length).toBe(5);

    expect(element.find('option[selected]').val()).toBe('42');
    expect($scope.version.data).toBe(42);
  });

  it('shows hint when a version other than the latest is shown', () => {
    mockApiService({
      'some/url': [{value: 10}, {value: 42}, {value: 50}],
    });

    const element = render('some/url', 42);
    expect(element.find('.newer-version-hint').length).toBe(1);
  });

  it('does not show a hint when a newest version  is shown', () => {
    mockApiService({
      'some/url': [{value: 10}, {value: 42}, {value: 50}],
    });

    const element = render('some/url', 50);
    expect(element.find('.newer-version-hint').length).toBe(0);
  });

  it('should update the selected option on scope value change', () => {
    mockApiService({
      'some/url': [{value: 10}, {value: 42}, {value: 50}],
    });

    const element = render('some/url', 42);
    expect(element.find('option').length).toBe(4);
    expect(element.find('option[selected]').val()).toBe('42');
    expect($scope.version.data).toBe(42);

    $scope.version.data = 50;
    $scope.$apply();
    expect(element.find('option').length).toBe(4);
    expect(element.find('option[selected]').val()).toBe('50');

    $scope.version.data = 99;
    $scope.$apply();
    // This version is not in the server-provided list, so the list should be
    // extended.
    expect(element.find('option').length).toBe(5);
    expect(element.find('option[selected]').val()).toBe('99');
  });

  it('should be disabled when no options are available', () => {
    mockApiService({
      'some/url': [],
    });

    const element = render('some/url', 42);
    expect(element.find('select[disabled]').length).toBe(1);
    expect(element.find('option[selected]').text().trim()).toBe('No versions available.');
    expect($scope.version.data).toBe(42); // It does not change the model.
  });

  it('should fetch versions again when a REFRESH_VERSIONS_EVENT is broadcasted',
     () => {
       const items = [];
       mockApiService({
         'some/url': items,
       });

       const element = render('some/url');
       expect(element.find('select[disabled]').length).toBe(1);
       expect(element.find('option[selected]').text().trim())
           .toBe('No versions available.');

       // Broadcast REFRESH_VERSIONS_EVENT and check that there are options now.
       items.push({value: 10});
       items.push({value: 42});
       $rootScope.$broadcast(REFRESH_VERSIONS_EVENT, {});
       $rootScope.$apply();

       expect(element.find('select[disabled]').length).toBe(0);
       expect(element.find('option').length).toBe(3);
       expect(element.find('option[selected]').val()).toBe('HEAD');
       expect($scope.version.data)
           .toBeUndefined();  // It does not change the model.
     });
});


exports = {};

;return exports;});

//angular-components/core/wizard-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.wizardFormDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {coreModule} = goog.require('grrUi.core.core');


describe('grr-wizard-form directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module('/static/angular-components/core/wizard-form.html'));
  beforeEach(module('/static/angular-components/core/wizard-form-page.html'));
  beforeEach(module(coreModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (template) => {
    if (angular.isUndefined(template)) {
      template = '<grr-wizard-form title="Test wizard form" ' +
          'on-resolve="resolved = true" on-reject="rejected = true">' +

          '<grr-wizard-form-page title="Page 1" next-button-label="go on!">' +
          'foo</grr-wizard-form-page>' +

          '<grr-wizard-form-page title="Page 2" prev-button-label="go back!">' +
          'bar</grr-wizard-form-page>' +

          '</grr-wizard-form>';
    }

    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows first page by default', () => {
    const element = renderTestTemplate();

    expect(element.text()).toContain('Page 1');
    expect(element.text()).not.toContain('Page 2');
  });

  it('clicking on "Next" button shows next page', () => {
    const element = renderTestTemplate();

    browserTriggerEvent(element.find('button.Next'), 'click');

    expect(element.text()).toContain('Page 2');
    expect(element.text()).not.toContain('Page 1');
  });

  it('clicking on "Back" button shows previous page', () => {
    const element = renderTestTemplate();

    browserTriggerEvent(element.find('button.Next'), 'click');
    browserTriggerEvent(element.find('button.Back'), 'click');

    expect(element.text()).toContain('Page 1');
    expect(element.text()).not.toContain('Page 2');
  });

  it('hides "Back" button on the first page', () => {
    const element = renderTestTemplate();

    expect(element.find('button.Back[disabled]').length).toBe(0);
  });

  it('shows "Back" button on the second page', () => {
    const element = renderTestTemplate();

    browserTriggerEvent(element.find('button.Next'), 'click');

    expect(element.find('button.Back[disabled]').length).toBe(0);
  });

  it('hides "Back" button if page\'s noBackButton is true', () => {
    const element = renderTestTemplate(
        '<grr-wizard-form>' +
        '<grr-wizard-form-page title="Page 1">foo</grr-wizard-form-page>' +
        '<grr-wizard-form-page title="Page 2" no-back-button="flag">bar' +
        '</grr-wizard-form-page>' +
        '</grr-wizard-form>');

    browserTriggerEvent(element.find('button.Next'), 'click');
    expect(element.find('button.Back').length).toBe(1);

    $rootScope.flag = true;
    $rootScope.$apply();

    expect(element.find('button.Back').length).toBe(0);
  });

  it('disables "Next" button if current page reports as invalid', () => {
    const element = renderTestTemplate(
        '<grr-wizard-form>' +
        '<grr-wizard-form-page title="Page 1" is-valid="flag">foo' +
        '</grr-wizard-form-page>' +
        '</grr-wizard-form>');

    expect(element.find('button.Next[disabled]').length).toBe(1);

    $rootScope.flag = true;
    $rootScope.$apply();

    expect(element.find('button.Next:not([disabled])').length).toBe(1);
  });

  it('takes "Back" button label from page settings', () => {
    const element = renderTestTemplate();

    browserTriggerEvent(element.find('button.Next'), 'click');

    expect(element.find('button.Back').text().trim()).toBe('go back!');
  });

  it('takes "Next" button label from page settings', () => {
    const element = renderTestTemplate();

    expect(element.find('button.Next').text().trim()).toBe('go on!');
  });

  it('calls "on-resolve" when "Next" is clicked on last page', () => {
    const element = renderTestTemplate();

    expect($rootScope.resolved).toBeUndefined();

    browserTriggerEvent(element.find('button.Next'), 'click');
    expect($rootScope.resolved).toBeUndefined();

    browserTriggerEvent(element.find('button.Next'), 'click');
    expect($rootScope.resolved).toBe(true);
  });

  it('calls "on-reject" when "x" is clicked', () => {
    const element = renderTestTemplate();

    expect($rootScope.rejected).toBeUndefined();

    browserTriggerEvent(element.find('button.close'), 'click');
    expect($rootScope.rejected).toBe(true);
  });
});


exports = {};

;return exports;});

//angular-components/docs/api-helper-curl-service_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.docs.apiHelperCurlServiceTest');
goog.setTestOnly();

const {docsModule} = goog.require('grrUi.docs.docs');


describe('ApiHelperCurlService', () => {
  let $rootScope;
  let grrApiHelperCurlService;


  beforeEach(module(docsModule.name));

  beforeEach(module(($provide) => {
    $provide.value('$window', {
      location: {
        origin: 'http://localhost:42',
      },
    });
  }));

  beforeEach(inject(($injector) => {
    $rootScope = $injector.get('$rootScope');
    grrApiHelperCurlService = $injector.get('grrApiHelperCurlService');
  }));

  const startFlowRequest =
      `CSRFTOKEN=\`curl http://localhost:42 -o /dev/null -s -c - | grep csrftoken  | cut -f 7\`; \\
\tcurl -X POST -H "Content-Type: application/json" -H "X-CSRFToken: $CSRFTOKEN" \\
\thttp://localhost:42/api/v2/clients/C.1111222233334444/flows -d @- << EOF
{
  "foo": "bar"
}
EOF`;

  it('builds start flow request', function(done) {
    grrApiHelperCurlService.buildStartFlow('C.1111222233334444', {foo: 'bar'})
        .then(((cmd) => {
                expect(cmd).toBe(startFlowRequest);
                done();
              }).bind(this));

    $rootScope.$apply();
  });
});


exports = {};

;return exports;});

//angular-components/docs/api-helper-service_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.docs.apiHelperServiceTest');
goog.setTestOnly();

const {docsModule} = goog.require('grrUi.docs.docs');


describe('ApiHelperService', () => {
  let $q;
  let $rootScope;
  let grrApiHelperService;
  let grrApiService;


  beforeEach(module(docsModule.name));

  beforeEach(inject(($injector) => {
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrApiService = $injector.get('grrApiService');
    grrApiHelperService = $injector.get('grrApiHelperService');

    const deferred = $q.defer();
    deferred.resolve({
      data: {
        value: {
          type: 'RDFString',
          value: 'FooBarAuthManager',
        },
      },
    });
    spyOn(grrApiService, 'getCached').and.returnValue(deferred.promise);

    grrApiHelperService.clear();
  }));

  const fooHelper = {
    buildStartFlow: function(clientId, createFlowJson) {
      const deferred = $q.defer();
      deferred.resolve(`foo start ${clientId}`);
      return deferred.promise;
    },
  };

  const barHelper = {
    buildStartFlow: function(clientId, createFlowJson) {
      const deferred = $q.defer();
      deferred.resolve(`bar start ${clientId}`);
      return deferred.promise;
    },
  };

  it('builds start flow commands with helpers of all types', (done) => {
    grrApiHelperService.registerHelper('Foo', null, fooHelper);
    grrApiHelperService.registerHelper('Bar', null, barHelper);

    grrApiHelperService
        .buildStartFlow('C.1111222233334444', {
          foo: 'bar',
        })
        .then((result) => {
          expect(result).toEqual({
            'Foo': {
              webAuthType: null,
              data: 'foo start C.1111222233334444',
            },
            'Bar': {
              webAuthType: null,
              data: 'bar start C.1111222233334444',
            },
          });
          done();
        });

    $rootScope.$apply();
  });

  it('uses helper with a matching webAuthType, if available', (done) => {
    grrApiHelperService.registerHelper('Foo', null, fooHelper);
    grrApiHelperService.registerHelper('Foo', 'FooBarAuthManager', barHelper);

    grrApiHelperService
        .buildStartFlow('C.1111222233334444', {
          foo: 'bar',
        })
        .then((result) => {
          expect(result).toEqual({
            'Foo': {
              webAuthType: 'FooBarAuthManager',
              data: 'bar start C.1111222233334444',
            },
          });
          done();
        });

    $rootScope.$apply();
  });

  it('uses helper with null webAuthType if no matches', (done) => {
    grrApiHelperService.registerHelper('Foo', null, fooHelper);
    grrApiHelperService.registerHelper('Foo', 'SomeOtherAuthManager',
                                       barHelper);

    grrApiHelperService
        .buildStartFlow('C.1111222233334444', {
          foo: 'bar',
        })
        .then((result) => {
          expect(result).toEqual({
            'Foo': {
              webAuthType: null,
              data: 'foo start C.1111222233334444',
            },
          });
          done();
        });

    $rootScope.$apply();
  });

  it('ignores helpers if no matches and no helper with null webAuthType',
     (done) => {
       grrApiHelperService.registerHelper(
           'Foo', 'YetAnotherAuthManager', fooHelper);
       grrApiHelperService.registerHelper(
           'Foo', 'SomeOtherAuthManager', barHelper);

       grrApiHelperService
           .buildStartFlow('C.1111222233334444', {
             foo: 'bar',
           })
           .then((result) => {
             expect(result).toEqual({});
             done();
           });

       $rootScope.$apply();
     });
});


exports = {};

;return exports;});

//angular-components/flow/copy-flow-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.flow.copyFlowFormDirectiveTest');
goog.setTestOnly();

const {flowModule} = goog.require('grrUi.flow.flow');
const {stubDirective, stubTranscludeDirective, testsModule} = goog.require('grrUi.tests');


describe('copy flow form directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;

  let flowObject;

  beforeEach(module('/static/angular-components/flow/copy-flow-form.html'));
  beforeEach(module(flowModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrFlowForm');
  stubTranscludeDirective('grrConfirmationDialog');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrApiService = $injector.get('grrApiService');

    flowObject = {
      type: 'ApiFlow',
      value: {
        args: {
          type: 'FooFlowArgs',
          value: {
            aFoo: 'aBar',
          },
        },
        runner_args: {
          type: 'FlowRunnerArgs',
          value: {
            rFoo: 'rBar',
          },
        },
      },
    };

    const deferred = $q.defer();
    deferred.resolve({data: flowObject});
    spyOn(grrApiService, 'get').and.returnValue(deferred.promise);
  }));

  const renderTestTemplate = () => {
    $rootScope.clientId = 'C.0000111122223333';
    $rootScope.flowId = 'F:123456';

    const template = '<grr-copy-flow-form ' +
        'client-id="clientId" flow-id="flowId" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('fetches existing flow with correct client id and flow id', () => {
    renderTestTemplate();

    expect(grrApiService.get).toHaveBeenCalledWith(
        'clients/C.0000111122223333/flows/F:123456');
  });

  it('sends correct request when grr-confirmation-dialog triggers proceed',
     () => {
       const element = renderTestTemplate();
       const directive = element.find('grr-confirmation-dialog');

       const deferred = $q.defer();
       spyOn(grrApiService, 'post').and.returnValue(deferred.promise);

       directive.scope().$eval(directive.attr('proceed'));

       expect(grrApiService.post)
           .toHaveBeenCalledWith('clients/C.0000111122223333/flows', {
             flow: {
               args: {
                 aFoo: 'aBar',
               },
               runner_args: {
                 rFoo: 'rBar',
               },
             },
             original_flow: {
               flow_id: 'F:123456',
               client_id: 'C.0000111122223333',
             },
           });
     });
});


exports = {};

;return exports;});

//angular-components/flow/flow-descriptors-tree-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.flow.flowDescriptorsTreeDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {flowModule} = goog.require('grrUi.flow.flow');


describe('flow descriptors tree directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;

  let emptySettingsDeferred;

  beforeEach(module(flowModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrApiService = $injector.get('grrApiService');

    // If user settings are empty, flows tree should use 'BASIC' mode.
    emptySettingsDeferred = $q.defer();
    emptySettingsDeferred.resolve({
      data: {
        value: {
          settings: {
            value: {},
          },
        },
      },
    });
  }));

  afterEach(() => {
    // We have to clean document's body to remove tables we add there.
    $(document.body).html('');
  });

  const renderTestTemplate = () => {
    const template = '<grr-flow-descriptors-tree ' +
        'selected-descriptor="selectedDescriptor.value" />';
    const element = $compile(template)($rootScope);
    $rootScope.selectedDescriptor = {
      value: undefined,
    };
    $rootScope.$apply();

    // We have to add element to the body, because jsTree implementation
    // depends on element being part of the page's DOM tree.
    $(document.body).html('');
    $(document.body).append(element);

    $(element.children('div.tree')[0]).on('loaded.jstree', function(e, data) {
      $(this).jstree('open_all');
    });

    return element;
  };

  it('fetches descriptors from the server', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'get').and.returnValue(deferred.promise);

    renderTestTemplate();

    expect(grrApiService.get).toHaveBeenCalledWith('/flows/descriptors');
  });

  it('fetches user settings from the server', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'get').and.returnValue(deferred.promise);

    renderTestTemplate();

    expect(grrApiService.get).toHaveBeenCalledWith('/users/me');
  });

  it('creates node per category', (done) => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'get').and.callFake((url) => {
      if (url == '/users/me') {
        return emptySettingsDeferred.promise;
      } else {
        return deferred.promise;
      }
    });

    deferred.resolve({
      data: {
        items: [
          {
            type: 'ApiFlowDescriptor',
            value: {
              category: {
                type: 'RDFString',
                value: 'Category foo',
              },
              name: {
                type: 'RDFString',
                value: 'foo',
              },
              friendly_name: {
                type: 'RDFString',
                value: 'friendly foo',
              },
              behaviours: [{
                type: 'RDFString',
                value: 'BASIC',
              }],
            },
          },
          {
            type: 'ApiFlowDescriptor',
            value: {
              category: {
                type: 'RDFString',
                value: 'Category bar',
              },
              name: {
                type: 'RDFString',
                value: 'bar',
              },
              friendly_name: {
                type: 'RDFString',
                value: 'friendly bar',
              },
              behaviours: [{
                type: 'RDFString',
                value: 'BASIC',
              }],
            },
          },
        ],
      },
    });

    const element = renderTestTemplate();
    new MutationObserver(() => {
      if (element.text().indexOf('Category foo') != -1 &&
          element.text().indexOf('Category bar') != -1) {
        done();
      }
    }).observe(element[0], {childList: true, subtree: true});
  });

  it('uses friendly name if available', (done) => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'get').and.callFake((url) => {
      if (url == '/users/me') {
        return emptySettingsDeferred.promise;
      } else {
        return deferred.promise;
      }
    });

    deferred.resolve({
      data: {
        items: [
          {
            type: 'ApiFlowDescriptor',
            value: {
              category: {
                type: 'RDFString',
                value: 'Category foo',
              },
              name: {
                type: 'RDFString',
                value: 'foo',
              },
              friendly_name: {
                type: 'RDFString',
                value: 'friendly foo',
              },
              behaviours: [{
                type: 'RDFString',
                value: 'BASIC',
              }],
            },
          },
        ],
      },
    });

    const element = renderTestTemplate();
    new MutationObserver(() => {
      if (element.text().indexOf('friendly foo') != -1) {
        done();
      }
    }).observe(element[0], {childList: true, subtree: true});
  });

  it('hides flows without specified behavior', (done) => {
    const advancedSettingsDeferred = $q.defer();
    advancedSettingsDeferred.resolve({
      data: {
        value: {
          settings: {
            value: {
              mode: {
                value: 'ADVANCED',
              },
            },
          },
        },
      },
    });

    const deferred = $q.defer();
    deferred.resolve({
      data: {
        items: [
          {
            type: 'ApiFlowDescriptor',
            value: {
              category: {
                type: 'RDFString',
                value: 'Category foo',
              },
              name: {
                type: 'RDFString',
                value: 'foo',
              },
              friendly_name: {
                type: 'RDFString',
                value: 'friendly foo',
              },
              behaviours: [{
                type: 'RDFString',
                value: 'BASIC',
              }],
            },
          },
          {
            type: 'ApiFlowDescriptor',
            value: {
              category: {
                type: 'RDFString',
                value: 'Category bar',
              },
              name: {
                type: 'RDFString',
                value: 'bar',
              },
              friendly_name: {
                type: 'RDFString',
                value: 'friendly bar',
              },
              behaviours: [{
                type: 'RDFString',
                value: 'ADVANCED',
              }],
            },
          },
        ],
      },
    });

    spyOn(grrApiService, 'get').and.callFake((url) => {
      if (url == '/users/me') {
        return advancedSettingsDeferred.promise;
      } else {
        return deferred.promise;
      }
    });

    const element = renderTestTemplate();
    new MutationObserver(() => {
      if (element.text().indexOf('friendly bar') != -1 &&
          element.text().indexOf('friendly foo') == -1) {
        done();
      }
    }).observe(element[0], {childList: true, subtree: true});
  });

  describe('when clicked', () => {
    let element;

    beforeEach((done) => {
      const deferred = $q.defer();
      spyOn(grrApiService, 'get').and.callFake((url) => {
        if (url == '/users/me') {
          return emptySettingsDeferred.promise;
        } else {
          return deferred.promise;
        }
      });

      deferred.resolve({
        data: {
          items: [
            {
              type: 'ApiFlowDescriptor',
              value: {
                category: {
                  type: 'RDFString',
                  value: 'Category 1',
                },
                name: {
                  type: 'RDFString',
                  value: 'foo',
                },
                friendly_name: {
                  type: 'RDFString',
                  value: 'friendly foo',
                },
                behaviours: [{
                  type: 'RDFString',
                  value: 'BASIC',
                }],
              },
            },
          ],
        },
      });

      element = renderTestTemplate();
      new MutationObserver(() => {
        if (element.text().indexOf('friendly foo') != -1) {
          done();
        }
      }).observe(element[0], {childList: true, subtree: true});
    });

    it('updates selectedDescriptor binding', () => {
      expect($rootScope.selectedDescriptor.value).toBeUndefined();

      browserTriggerEvent(element.find('a:contains("Category 1")'), 'click');
      browserTriggerEvent(element.find('a:contains("friendly foo")'), 'click');
      $rootScope.$apply();

      expect($rootScope.selectedDescriptor.value).toEqual({
        type: 'ApiFlowDescriptor',
        value: {
          category: {
            type: 'RDFString',
            value: 'Category 1',
          },
          name: {
            type: 'RDFString',
            value: 'foo',
          },
          friendly_name: {
            type: 'RDFString',
            value: 'friendly foo',
          },
          behaviours: [{
            type: 'RDFString',
            value: 'BASIC',
          }],
        },
      });
    });
  });
});


exports = {};

;return exports;});

//angular-components/flow/flow-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.flow.flowFormDirectiveTest');
goog.setTestOnly();

const {flowModule} = goog.require('grrUi.flow.flow');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('flow form directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrReflectionService;


  beforeEach(module('/static/angular-components/flow/flow-form.html'));
  beforeEach(module(flowModule.name));
  beforeEach(module(testsModule.name));

  // Stub out grr-form-value and grr-form-proto-repeated-field directives,
  // as all rendering is going to be delegated to them.
  angular.forEach(
      ['grrFormValue', 'grrFormProtoRepeatedField'], (directiveName) => {
        stubDirective(directiveName);
      });

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrReflectionService = $injector.get('grrReflectionService');

    spyOn(grrReflectionService, 'getRDFValueDescriptor')
        .and.callFake((valueType) => {
          const deferred = $q.defer();

          if (valueType == 'FlowRunnerArgs') {
            deferred.resolve({
              default: {
                type: 'FlowRunnerArgs',
                value: {},
              },
              fields: [
                {
                  name: 'output_plugins',
                  default: [],
                  foo: 'bar',
                },
              ],
            });
          } else if (valueType == 'OutputPluginDescriptor') {
            deferred.resolve({
              default: {
                type: 'OutputPluginDescriptor',
                value: 'OutputPluginDescriptor-default',
                foo: 'bar',
              },
            });
          }

          return deferred.promise;
        });
  }));

  const renderTestTemplate = (args, runnerArgs, withOutputPlugins) => {
    $rootScope.clientId = 'C.0000111122223333';
    $rootScope.args = args || {
      type: 'FooFlowArgs',
      value: {
        foo: 'bar',
      },
    };

    $rootScope.runnerArgs = runnerArgs || {
      type: 'FlowRunnerArgs',
      value: {
        flow_name: {
          type: 'RDFString',
          value: 'FooFlow',
        },
      },
    };

    if (angular.isUndefined(withOutputPlugins)) {
      withOutputPlugins = true;
    }
    $rootScope.withOutputPlugins = withOutputPlugins;

    const template = '<grr-flow-form ' +
        'flow-args="args" flow-runner-args="runnerArgs" ' +
        'with-output-plugins="withOutputPlugins" ' +
        'has-errors="hasErrors" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows flow arguments form', () => {
    const element = renderTestTemplate();
    const directive = element.find('grr-form-value:nth(0)');

    expect(directive.scope().$eval(directive.attr('value'))).toEqual(
        $rootScope.args);
  });

  it('shows flow runner arguments form', () => {
    const element = renderTestTemplate();
    const directive = element.find('grr-form-value:nth(1)');

    // This should be equal to flow runner arguments default + initialized
    // empty output plugins list.
    const expected = angular.copy($rootScope.runnerArgs);
    expected['value']['output_plugins'] = [];
    expect(directive.scope().$eval(directive.attr('value'))).toEqual(expected);
  });

  it('preserves existing output plugins list', () => {
    const runnerArgs = {
      type: 'FlowRunnerArgs',
      value: {
        flow_name: {
          type: 'RDFString',
          value: 'FooFlow',
        },
        output_plugins: [
          {
            type: 'FooPluginArgs',
            value: {},
          },
        ],
      },
    };
    const element = renderTestTemplate(undefined, angular.copy(runnerArgs));
    const directive = element.find('grr-form-value:nth(1)');
    expect(directive.scope().$eval(directive.attr('value'))).toEqual(runnerArgs);
  });

  it('does not show output plugins if with-output-plugns=false', () => {
    const element = renderTestTemplate(undefined, undefined, false);
    const directive = element.find('grr-form-value:nth(1)');

    // This should be equal to flow runner arguments default. output_plugins
    // list is not initialized as no output plugins form controls are present.
    expect(directive.scope().$eval(directive.attr('value'))).toEqual(
        angular.copy($rootScope.runnerArgs));

    const pluginsDirective = element.find('grr-form-proto-repeated-field');
    expect(pluginsDirective.length).toBe(0);
  });

  it('shows output plugins list form', () => {
    const element = renderTestTemplate();
    const directive = element.find('grr-form-proto-repeated-field');

    // This should be equal to output plugin descriptor.
    expect(directive.scope().$eval(directive.attr('descriptor'))).toEqual({
      default: {
        type: 'OutputPluginDescriptor',
        value: 'OutputPluginDescriptor-default',
        foo: 'bar',
      },
    });

    // This should be equal to output plugins field from flor runner arguments
    // descriptor.
    expect(directive.scope().$eval(directive.attr('field'))).toEqual({
      name: 'output_plugins',
      default: [],
      foo: 'bar',
    });

    // Output plugins list is empty by default.
    expect(directive.scope().$eval(directive.attr('value'))).toEqual([]);
  });

  it('updates has-errors binding if flow arguments are invalid', () => {
    renderTestTemplate();

    expect($rootScope.hasErrors).toBe(false);

    $rootScope.args['validationError'] = 'Oh no!';
    $rootScope.$apply();

    expect($rootScope.hasErrors).toBe(true);
  });

  it('updates has-errors binding if runner arguments are invalid', () => {
    renderTestTemplate();

    expect($rootScope.hasErrors).toBe(false);

    $rootScope.runnerArgs['validationError'] = 'Oh no!';
    $rootScope.$apply();

    expect($rootScope.hasErrors).toBe(true);
  });
});


exports = {};

;return exports;});

//angular-components/flow/flow-results-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.flow.flowResultsDirectiveTest');
goog.setTestOnly();

const {flowModule} = goog.require('grrUi.flow.flow');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('flow results directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module('/static/angular-components/flow/flow-results.html'));
  beforeEach(module(flowModule.name));
  beforeEach(module(testsModule.name));

  // Stub out grrResultsCollection directive, as all rendering is going
  // to be delegated to it.
  stubDirective('grrResultsCollection');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (flowId) => {
    $rootScope.flowId = flowId;
    $rootScope.apiBasePath = 'foo/bar';

    const template = '<grr-flow-results flow-id="flowId" ' +
        'api-base-path="apiBasePath" />';

    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('delegates rendering to grr-results-collection directive', () => {
    const element = renderTestTemplate('F:1234');
    const directive = element.find('grr-results-collection:nth(0)');
    expect(directive.length).toBe(1);
  });

  it('builds grr-result-collection urls correctly', () => {
    const element = renderTestTemplate('F:1234');
    const directive = element.find('grr-results-collection:nth(0)');
    expect(directive.scope().$eval(directive.attr('results-url'))).toEqual(
        'foo/bar/F:1234/results');
    expect(
        directive.scope().$eval(directive.attr('output-plugins-url'))).toEqual(
            'foo/bar/F:1234/output-plugins');
    expect(
        directive.scope().$eval(directive.attr('download-files-url'))).toEqual(
            'foo/bar/F:1234/results/files-archive');
  });
});


exports = {};

;return exports;});

//angular-components/flow/flow-status-icon-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.flow.flowStatusIconDirectiveTest');
goog.setTestOnly();

const {flowModule} = goog.require('grrUi.flow.flow');
const {testsModule} = goog.require('grrUi.tests');


describe('grr-flow-status-icon directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module('/static/angular-components/flow/flow-status-icon.html'));
  beforeEach(module(flowModule.name));
  beforeEach(module(testsModule.name));


  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));


  const renderTestTemplate = (state) => {
    $rootScope.flow = {
      type: 'ApiFlow',
      value: {
        state: {
          type: 'EnumNamedValue',
          value: state,
        },
      },
    };

    const template = '<grr-flow-status-icon flow="flow" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows an image for 4 possible flow states', () => {
    const states = ['TERMINATED', 'RUNNING', 'ERROR', 'CLIENT_CRASHED'];

    angular.forEach(states, (state) => {
      const element = renderTestTemplate(state);
      expect($('img', element).length).toBe(1);
    });
  });
});


exports = {};

;return exports;});

//angular-components/flow/flows-list-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.flow.flowsListDirectiveTest');
goog.setTestOnly();

const {flattenFlowsList, toggleFlowExpansion} = goog.require('grrUi.flow.flowsListDirective');

describe('grr-flows-list directive', () => {
  describe('flattenFlowsList()', () => {

    it('assigns zero depth when flows have no nested_flows', () => {
      const source = [
        {value: {foo: 1}},
        {value: {foo: 2}},
        {value: {foo: 3}},
      ];
      expect(flattenFlowsList(source)).toEqual([
        {value: {foo: 1}, depth: 0},
        {value: {foo: 2}, depth: 0},
        {value: {foo: 3}, depth: 0},
      ]);
    });

    it('assigns depths correctly for flows with nested_flows', () => {
      const source = [
        {
          value: {
            foo: 1,
          },
        },
        {
          value: {
            foo: 2,
            nested_flows: [
              {
                value: {
                  foo: 21,
                },
              },
              {
                value: {
                  foo: 22,
                  nested_flows: [
                    {
                      value: {
                        foo: 223,
                      },
                    },
                    {
                      value: {
                        foo: 224,
                      },
                    },
                  ],
                },
              },
            ],
          },
        },
        {
          foo: 3,
        },
      ];

      expect(flattenFlowsList(source)).toEqual([
        {value: {foo: 1}, depth: 0},
        {value: {foo: 2}, depth: 0},
        {value: {foo: 21}, depth: 1},
        {value: {foo: 22}, depth: 1},
        {value: {foo: 223}, depth: 2},
        {value: {foo: 224}, depth: 2},
        {foo: 3, depth: 0},
      ]);
    });
  });

  describe('toggleFlowExpansion()', () => {

    it('expands node with 2 children correctly', () => {
      const source = [
        {value: {foo: 2}, depth: 0, shown: true},
        {value: {foo: 21}, depth: 1, shown: false},
        {value: {foo: 22}, depth: 1, shown: false},
      ];
      expect(toggleFlowExpansion(source, 0)).toEqual([
        {value: {foo: 2}, depth: 0, shown: true, expanded: true},
        {value: {foo: 21}, depth: 1, shown: true},
        {value: {foo: 22}, depth: 1, shown: true},
      ]);
    });

    it('does not show adjacent items', () => {
      const source = [
        {value: {foo: 2}, depth: 0, shown: true},
        {value: {foo: 21}, depth: 1, shown: false},
        {value: {foo: 3}, depth: 0, shown: false},
      ];
      expect(toggleFlowExpansion(source, 0)).toEqual([
        {value: {foo: 2}, depth: 0, shown: true, expanded: true},
        {value: {foo: 21}, depth: 1, shown: true},
        {value: {foo: 3}, depth: 0, shown: false},
      ]);
    });

    it('collapses node with 2 children correctly', () => {
      const source = [
        {value: {foo: 2}, depth: 0, shown: true, expanded: true},
        {value: {foo: 21}, depth: 1, shown: true},
        {value: {foo: 22}, depth: 1, shown: true},
      ];
      expect(toggleFlowExpansion(source, 0)).toEqual([
        {value: {foo: 2}, depth: 0, shown: true, expanded: false},
        {value: {foo: 21}, depth: 1, shown: false},
        {value: {foo: 22}, depth: 1, shown: false},
      ]);
    });

    it('recursively shows expanded children', () => {
      const source = [
        {value: {foo: 2}, depth: 0, shown: true, expanded: false},
        {value: {foo: 21}, depth: 1, shown: false},
        {value: {foo: 22}, depth: 1, shown: false, expanded: true},
        {value: {foo: 223}, depth: 2, shown: false},
        {value: {foo: 224}, depth: 2, shown: false},
      ];
      expect(toggleFlowExpansion(source, 0)).toEqual([
        {value: {foo: 2}, depth: 0, shown: true, expanded: true},
        {value: {foo: 21}, depth: 1, shown: true},
        {value: {foo: 22}, depth: 1, shown: true, expanded: true},
        {value: {foo: 223}, depth: 2, shown: true},
        {value: {foo: 224}, depth: 2, shown: true},
      ]);
    });

    it('does not recursively show collapsed children', () => {
      const source = [
        {value: {foo: 2}, depth: 0, shown: true, expanded: false},
        {value: {foo: 21}, depth: 1, shown: false},
        {value: {foo: 22}, depth: 1, shown: false, expanded: false},
        {value: {foo: 223}, depth: 2, shown: false},
        {value: {foo: 224}, depth: 2, shown: false},
      ];
      expect(toggleFlowExpansion(source, 0)).toEqual([
        {value: {foo: 2}, depth: 0, shown: true, expanded: true},
        {value: {foo: 21}, depth: 1, shown: true},
        {value: {foo: 22}, depth: 1, shown: true, expanded: false},
        {value: {foo: 223}, depth: 2, shown: false},
        {value: {foo: 224}, depth: 2, shown: false},
      ]);
    });
  });

  // TODO(user): implement better way of testing directives that use
  // grr-infinite-table and grr-api-items-provider and test the directive
  // itself, not just the basic logic functions.
});


exports = {};

;return exports;});

//angular-components/flow/start-flow-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.flow.startFlowFormDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, stubDirective, testsModule} = goog.require('grrUi.tests');
const {flowModule} = goog.require('grrUi.flow.flow');


describe('start flow form directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;
  let grrReflectionService;

  let flowRunnerArgsDefault;

  beforeEach(module('/static/angular-components/flow/start-flow-form.html'));
  beforeEach(module(flowModule.name));
  beforeEach(module(testsModule.name));

  // Stub out grr-semantic-value and grr-flow-form directives, as all
  // rendering is going to be delegated to them.
  angular.forEach(['grrFlowForm', 'grrSemanticValue'], (directiveName) => {
    stubDirective(directiveName);
  });

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrApiService = $injector.get('grrApiService');
    grrReflectionService = $injector.get('grrReflectionService');

    flowRunnerArgsDefault = {
      type: 'FlowRunnerArgs',
      value: {
        flow_name: {
          type: 'RDFString',
          value: 'FooFlow',
        },
        output_plugins: [
          {
            foo: 'bar',
          },
        ],
        foo: 'bar',
      },
    };

    const deferred = $q.defer();
    deferred.resolve({
      default: flowRunnerArgsDefault,
    });
    spyOn(grrReflectionService, 'getRDFValueDescriptor').and.returnValue(
        deferred.promise);
  }));

  const renderTestTemplate = () => {
    $rootScope.clientId = 'C.0000111122223333';
    $rootScope.descriptor = {
      type: 'ApiFlowDescriptor',
      value: {
        name: {
          type: 'RDFString',
          value: 'FooFlow',
        },
        default_args: {
          type: 'FooFlowArgs',
          value: {
            foo: 'bar',
          },
        },
      },
    };

    const template = '<grr-start-flow-form ' +
        'client-id="clientId" descriptor="descriptor" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows flow arguments form', () => {
    const element = renderTestTemplate();
    const directive = element.find('grr-flow-form');

    expect(directive.scope().$eval(directive.attr('flow-args'))).toEqual(
        $rootScope.descriptor['value']['default_args']);
    expect(directive.scope().$eval(directive.attr('flow-runner-args'))).toEqual(
        flowRunnerArgsDefault);
  });

  it('sends request when Launch button is clicked', () => {
    const element = renderTestTemplate();

    const deferred = $q.defer();
    spyOn(grrApiService, 'post').and.returnValue(deferred.promise);

    browserTriggerEvent(element.find('button.Launch'), 'click');

    expect(grrApiService.post)
        .toHaveBeenCalledWith('/clients/C.0000111122223333/flows', {
          flow: {
            runner_args: {
              flow_name: 'FooFlow',
              output_plugins: [{foo: 'bar'}],
              foo: 'bar',
            },
            args: {
              foo: 'bar',
            },
          },
        });
  });

  it('respects changes in form data when sending request', () => {
    const element = renderTestTemplate();

    const directive = element.find('grr-flow-form:nth(0)');
    // Change flow args.
    directive.scope().$eval(directive.attr('flow-args'))['value']['changed'] = true;
    // Change flow runner args.
    directive.scope().$eval(directive.attr('flow-runner-args'))['value']['changed'] = true;
    // Change output plugins value.
    directive.scope().$eval(directive.attr('flow-runner-args'))['value']['output_plugins'].push(42);

    // Apply the changes.
    $rootScope.$apply();

    // Now click the Launch button.
    const deferred = $q.defer();
    spyOn(grrApiService, 'post').and.returnValue(deferred.promise);
    browserTriggerEvent(element.find('button.Launch'), 'click');

    expect(grrApiService.post)
        .toHaveBeenCalledWith('/clients/C.0000111122223333/flows', {
          flow: {
            runner_args: {
              flow_name: 'FooFlow',
              output_plugins: [{foo: 'bar'}, 42],
              changed: true,
              foo: 'bar',
            },
            args: {
              foo: 'bar',
              changed: true,
            },
          },
        });
  });

  it('shows progress message when request is processed', () => {
    const element = renderTestTemplate();

    const deferred = $q.defer();
    spyOn(grrApiService, 'post').and.returnValue(deferred.promise);

    browserTriggerEvent(element.find('button.Launch'), 'click');

    expect(element.text()).toContain('Launching flow FooFlow...');
  });

  it('shows flow summary when launch succeeds', () => {
    const element = renderTestTemplate();

    const deferred = $q.defer();
    spyOn(grrApiService, 'post').and.returnValue(deferred.promise);

    browserTriggerEvent(element.find('button.Launch'), 'click');

    const flow = {
      args: {'foo1': 'bar1'},
      runner_args: {'foo2': 'bar2'},
    };
    deferred.resolve({
      data: flow,
    });
    $rootScope.$apply();

    const directive = element.find('grr-semantic-value:nth(0)');
    expect(directive.scope().$eval(directive.attr('value'))).toEqual(flow);
  });

  it('shows failure message when launch fails', () => {
    const element = renderTestTemplate();

    const deferred = $q.defer();
    spyOn(grrApiService, 'post').and.returnValue(deferred.promise);

    browserTriggerEvent(element.find('button.Launch'), 'click');

    deferred.reject({
      data: {
        message: 'Something is wrong',
      },
    });
    $rootScope.$apply();

    expect(element.text()).toContain('Something is wrong');
  });
});


exports = {};

;return exports;});

//angular-components/forms/auto-generated-aes128-key-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.autoGeneratedAes128KeyFormDirectiveTest');
goog.setTestOnly();

const {formsModule} = goog.require('grrUi.forms.forms');
const {generateRandomBytes} = goog.require('grrUi.forms.autoGeneratedAes128KeyFormDirective');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('grr-form-auto-generated-aes128key form directive', () => {
  beforeEach(() => {
    let i = 0;
    const step = 1.0 / 16;

    spyOn(Math, 'random').and.callFake(() => {
      const result = step * i + step / 2;

      if (++i >= 16) {
        i = 0;
      }

      return result;
    });
  });

  describe('generateRandomBytes()', function() {

    it('correctly generates 4-bytes string', () => {
      expect(generateRandomBytes(4)).toBe('01234567');
    });

    it('correctly generates 16 bytes string', () => {
      expect(generateRandomBytes(16)).toBe(
          '0123456789abcdef0123456789abcdef');
    });
  });

  let $compile;
  let $rootScope;

  beforeEach(module('/static/angular-components/forms/' +
      'auto-generated-aes128-key-form.html'));
  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrFormPrimitive');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-form-auto-generated-aes128-key value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('delegates rendering to grr-form-primitive', () => {
    const element = renderTestTemplate({
      type: 'AutoGeneratedAES128Key',
      value: '',
    });

    const directive = element.find('grr-form-primitive');
    expect(directive.length).toBe(1);
  });

  it('prefills auto-generated key if value is empty', () => {
    renderTestTemplate({
      type: 'AutoGeneratedAES128Key',
      value: '',
    });
    expect($rootScope.value.value).toBe('0123456789abcdef0123456789abcdef');
  });

  it('preserves value if it\'s set', () => {
    renderTestTemplate({
      type: 'AutoGeneratedAES128Key',
      value: 'blah',
    });
    expect($rootScope.value.value).toBe('blah');
  });
});


exports = {};

;return exports;});

//angular-components/forms/bytes-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.bytesFormDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {bytesToHexEncodedString, hexEncodedStringToBytes, isByteString} = goog.require('grrUi.forms.bytesFormDirective');
const {formsModule} = goog.require('grrUi.forms.forms');


const hexChars = String.fromCharCode(13) + String.fromCharCode(200);
const encodedHexChars = '\\x0d\\xc8';
const nonHexChars = 'foo';

describe('bytesToHexEncodedString()', () => {
  it('does nothing with an empty string', () => {
    expect(bytesToHexEncodedString('')).toBe('');
  });

  it('doesn\'t encode characters with codes from 32 to 126', () => {
    let s = '';
    for (let i = 32; i <= 126; ++i) {
      s += String.fromCharCode(i);
    }

    expect(bytesToHexEncodedString(s)).toBe(s);
  });

  it('encodes characters with codes from 0 to 31 and from 127 to 255', () => {
    for (let i = 0; i <= 31; ++i) {
      const s = String.fromCharCode(i);
      expect(bytesToHexEncodedString(s)).toMatch(/\\x[0-9A-Fa-f]{2}/);
    }
  });


  it('encodes hex chars in the beginning of the string', () => {
    expect(bytesToHexEncodedString(hexChars + nonHexChars))
        .toBe(encodedHexChars + nonHexChars);
  });

  it('encodes hex chars in the middle of the string', () => {
    expect(bytesToHexEncodedString(nonHexChars + hexChars + nonHexChars))
        .toBe(nonHexChars + encodedHexChars + nonHexChars);
  });

  it('encodes hex chars in the end of the string', () => {
    expect(bytesToHexEncodedString(nonHexChars + hexChars))
        .toBe(nonHexChars + encodedHexChars);
  });

  it('encodes hex chars in the beginning and end of the string', () => {
    expect(bytesToHexEncodedString(hexChars + nonHexChars + hexChars))
        .toBe(encodedHexChars + nonHexChars + encodedHexChars);
  });
});

describe('hexEncodedStringToBytes()', () => {
  it('does nothing with an empty string', () => {
    expect(hexEncodedStringToBytes('')).toBe('');
  });

  it('decodes all possible characters', () => {
    for (let i = 0; i < 256; ++i) {
      let s = i.toString(16);
      if (s.length == 1) {
        s = `0${s}`;
      }
      s = `\\x${s}`;

      expect(hexEncodedStringToBytes(s)).toBe(String.fromCharCode(i));
    }
  });

  it('decodes hex chars in the beginning of the string', () => {
    expect(hexEncodedStringToBytes(encodedHexChars + nonHexChars))
        .toBe(hexChars + nonHexChars);
  });

  it('decodes hex chars in the middle of the string', () => {
    expect(hexEncodedStringToBytes(nonHexChars + encodedHexChars + nonHexChars))
        .toBe(nonHexChars + hexChars + nonHexChars);
  });

  it('decodes hex chars in the end of the string', () => {
    expect(hexEncodedStringToBytes(nonHexChars + encodedHexChars))
        .toBe(nonHexChars + hexChars);
  });

  it('decodes hex chars in the beginning and end of the string', () => {
    expect(hexEncodedStringToBytes(
        encodedHexChars + nonHexChars + encodedHexChars))
            .toBe(hexChars + nonHexChars + hexChars);
  });
});

describe('isByteString()', () => {
  it('returns true if a string had only characters with car code < 256', () => {
    let s = '';
    for (let i = 0; i < 256; ++i) {
      s += String.fromCharCode(i);
    }

    expect(isByteString(s)).toBe(true);
  });

  it('returns false if a string has a character with a char code >= 256',
     () => {
       expect(isByteString(String.fromCharCode(256))).toBe(false);
     });
});

describe('bytes form directive', () => {
  let $compile;
  let $rootScope;

  beforeEach(module('/static/angular-components/forms/bytes-form.html'));
  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-form-bytes value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows nothing if value is null', () => {
    const element = renderTestTemplate({
      type: 'RDFBytes',
      value: null,
    });
    expect(element.find('input').val()).toBe('');
  });

  it('shows base64-decoded value for plain latin characters', () => {
    const element = renderTestTemplate({
      type: 'RDFBytes',
      value: window.btoa('foo'),
    });
    expect(element.find('input').val()).toBe('foo');
  });

  it('shows nothing for incorrectly base64-encoded value', () => {
    const element = renderTestTemplate({
      type: 'RDFBytes',
      value: '--',
    });
    expect(element.find('input').val()).toBe('');
  });

  it('updates value to a base64 version on input', () => {
    const value = {
      type: 'RDFBytes',
      value: '',
    };
    const element = renderTestTemplate(value);

    element.find('input').val('a');
    browserTriggerEvent(element.find('input'), 'change');
    expect(value.value).toBe('YQ==');
  });

  it('updates value coorectly on hex-encoded input', () => {
    const value = {
      type: 'RDFBytes',
      value: '',
    };
    const element = renderTestTemplate(value);

    // Simulate user gradually typing \\x0d
    element.find('input').val('\\');
    browserTriggerEvent(element.find('input'), 'change');
    expect(value.value).toBe('XA==');

    element.find('input').val('\\x');
    browserTriggerEvent(element.find('input'), 'change');
    expect(value.value).toBe('XHg=');

    element.find('input').val('\\x0');
    browserTriggerEvent(element.find('input'), 'change');
    expect(value.value).toBe('XHgw');

    element.find('input').val('\\x0d');
    browserTriggerEvent(element.find('input'), 'change');
    expect(value.value).toBe('DQ==');
  });

  it('shows a validation message on unicode input', () => {
    const value = {
      type: 'RDFBytes',
      value: '',
    };
    const element = renderTestTemplate(value);
    element.find('input').val('昨');
    browserTriggerEvent(element.find('input'), 'change');

    expect(element.text()).toContain(
        'Unicode characters are not allowed in a byte string');
  });

  it('updates value.validationError on unicode input', () => {
    const value = {
      type: 'RDFBytes',
      value: '',
    };
    const element = renderTestTemplate(value);
    element.find('input').val('昨');
    browserTriggerEvent(element.find('input'), 'change');

    expect(value.validationError).toContain(
        'Unicode characters are not allowed in a byte string');
  });
});


exports = {};

;return exports;});

//angular-components/forms/client-label-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.clientLabelFormDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {formsModule} = goog.require('grrUi.forms.forms');


describe('client label form directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;

  const defaultOption = '-- All clients --';

  beforeEach(module('/static/angular-components/forms/client-label-form.html'));
  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrApiService = $injector.get('grrApiService');

    spyOn(grrApiService, 'get').and.callFake((url) => {
      const deferred = $q.defer();

      if (url === '/clients/labels') {
        deferred.resolve({
          data: {
            items: [
              {
                type: 'ClientLabel',
                value: {
                  name: {
                    type: 'unicode',
                    value: 'ClientLabelFoo',
                  },
                },
              },
              {
                type: 'ClientLabel',
                value: {
                  name: {
                    type: 'unicode',
                    value: 'ClientLabelBar',
                  },
                },
              },
            ],
          },
        });
      } else {
        throw new Error(`Unexpected url: ${url}`);
      }

      return deferred.promise;
    });
  }));

  const renderTestTemplate =
      ((clientLabel, formLabel, hideEmptyOption, emptyOptionLabel) => {
        $rootScope.clientLabel = clientLabel;
        $rootScope.formLabel = formLabel;
        $rootScope.hideEmptyOption = hideEmptyOption;
        $rootScope.emptyOptionLabel = emptyOptionLabel;

        const template = '<grr-form-client-label ' +
            'client-label="clientLabel" ' +
            'form-label="formLabel" ' +
            'hide-empty-option="hideEmptyOption" ' +
            'empty-option-label="emptyOptionLabel" ' +
            '></grr-form-client-label>';
        const element = $compile(template)($rootScope);
        $rootScope.$apply();

        return element;
      });

  it('shows the list of available client labels', () => {
    const element = renderTestTemplate('');

    const select = element.find('select');
    const children = select.children();
    const options = children.map((index) => children[index].innerText);

    expect(options).toContain(defaultOption);
    expect(options).toContain('ClientLabelFoo');
    expect(options).toContain('ClientLabelBar');
  });

  it('selects the label given through scope params initially', () => {
    const initialSelection = 'ClientLabelFoo';
    const element = renderTestTemplate(initialSelection);

    const select = element.find('select');
    const children = select.children();

    let found = false;
    for (let i = 0; i < children.length; ++i) {
      expect(children[i].selected).toBe(
          children[i].innerText === initialSelection);

      if (children[i].selected) {
        found = true;
      }
    }

    // Ensure the selected element exists in children.
    expect(found).toBe(true);
  });

  it('shows default <label> text by default', () => {
    const element = renderTestTemplate('');

    const labelTag = element.find('label');

    expect(labelTag.text()).toBe('Client label');
  });

  it('shows custom <label> text if given', () => {
    const element = renderTestTemplate('', 'Custom label text');

    const labelTag = element.find('label');

    expect(labelTag.text()).toBe('Custom label text');
  });

  it('forwards value changes to parent scope', () => {
    const element = renderTestTemplate('');

    const select = element.find('select');
    const newSelection = 'ClientLabelBar';
    select.val(`string:${newSelection}`);

    browserTriggerEvent(select, 'change');
    $rootScope.$apply();

    expect(element.scope().$eval(element.attr('client-label'))).toEqual(
        newSelection);
  });

  it('hides the empty option if requested', () => {
    const element =
        renderTestTemplate('', undefined, /* hideEmptyOption */ true);

    const select = element.find('select');
    const children = select.children();
    const options = children.map((index) => children[index].innerText);

    expect(options).not.toContain(defaultOption);
  });

  it('displays a given string describing the empty option if requested', () => {
    const customDefaultOption = 'Custom empty option description';
    const element = renderTestTemplate(
        '', undefined, undefined, /* emptyOptionLabel */ customDefaultOption);

    const select = element.find('select');
    const children = select.children();
    const options = children.map((index) => children[index].innerText);

    expect(options).not.toContain(defaultOption);
    expect(options).toContain(customDefaultOption);
  });
});


exports = {};

;return exports;});

//angular-components/forms/datetime-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.datetimeFormDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {formsModule} = goog.require('grrUi.forms.forms');


describe('datetime form directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module('/static/angular-components/forms/datetime-form.html'));
  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-form-datetime value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows nothing if time value is null', () => {
    const element = renderTestTemplate({
      type: 'RDFDatetime',
      value: null,
    });
    expect(element.find('input').val()).toBe('');
  });

  it('shows correct date if time value is 0', () => {
    const element = renderTestTemplate({
      type: 'RDFDatetime',
      value: 0,
    });
    expect(element.find('input').val()).toBe('1970-01-01 00:00');
  });

  it('shows nothing if time value is too big', () => {
    const element = renderTestTemplate({
      type: 'RDFDatetime',
      value: 9223372036854776000,
    });
    expect(element.find('input').val()).toBe('');
  });

  it('sets value to null on incorrect input', () => {
    const value = {
      type: 'RDFDatetime',
      value: 0,
    };
    const element = renderTestTemplate(value);
    element.find('input').val('a');
    browserTriggerEvent(element.find('input'), 'change');

    expect(value.value).toBe(null);
  });

  it('shows warning on incorrect input', () => {
    const value = {
      type: 'RDFDatetime',
      value: 0,
    };
    const element = renderTestTemplate(value);
    element.find('input').val('a');
    browserTriggerEvent(element.find('input'), 'change');

    expect(element.text()).toContain('Expected format is');
  });

  it('correctly updates the value on correct input', () => {
    const value = {
      type: 'RDFDatetime',
      value: 0,
    };
    const element = renderTestTemplate(value);
    element.find('input').val('1989-04-20 13:42');
    browserTriggerEvent(element.find('input'), 'change');

    expect(value.value).toBe(609082920000000);
  });

  it('sets current date when "today" button is pressed', () => {
    const value = {
      type: 'RDFDatetime',
      value: 0,
    };
    const baseTime = new Date(Date.UTC(1989, 4, 20));
    jasmine.clock().mockDate(baseTime);

    const element = renderTestTemplate(value);
    browserTriggerEvent(element.find('button[name=Today]'), 'click');

    expect(value.value).toBe(611625600000000);
  });
});


exports = {};

;return exports;});

//angular-components/forms/dict-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.dictFormDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, stubDirective, testsModule} = goog.require('grrUi.tests');
const {formsModule} = goog.require('grrUi.forms.forms');


describe('dict form directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrReflectionService;

  beforeEach(module('/static/angular-components/forms/dict-form.html'));
  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrFormValue');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrReflectionService = $injector.get('grrReflectionService');

    grrReflectionService.getRDFValueDescriptor = ((valueType) => {
      if (valueType != 'RDFString') {
        throw new Error('This stub accepts only RDFString value type.');
      }

      const deferred = $q.defer();
      deferred.resolve({
        name: 'RDFString',
        default: {
          type: 'RDFString',
          value: '',
        },
      });
      return deferred.promise;
    });
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-form-dict value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  const setFormValue = (element, value) => {
    const valueElement = element.find('grr-form-value');
    valueElement.scope().$eval(valueElement.attr('value') + '.value = "' +
        value + '"');
    $rootScope.$apply();
  };

  it('add empty key and value when "+" button is clicked', () => {
    const model = {
      type: 'Dict',
      value: {},
    };
    const element = renderTestTemplate(model);

    browserTriggerEvent(element.find('button[name=Add]'), 'click');

    expect(model).toEqual({
      type: 'Dict',
      value: {'': {type: 'RDFString', value: ''}},
    });
  });

  it('updates the model when key is changed', () => {
    const model = {
      type: 'Dict',
      value: {},
    };
    const element = renderTestTemplate(model);

    browserTriggerEvent(element.find('button[name=Add]'), 'click');

    element.find('input.key').val('foo');
    browserTriggerEvent(element.find('input.key'), 'change');

    expect(model).toEqual({
      type: 'Dict',
      value: {'foo': {type: 'RDFString', value: ''}},
    });
  });

  it('updates the model when value is changed', () => {
    const model = {
      type: 'Dict',
      value: {},
    };
    const element = renderTestTemplate(model);

    browserTriggerEvent(element.find('button[name=Add]'), 'click');
    setFormValue(element, 'foo');

    expect(model).toEqual({
      type: 'Dict',
      value: {'': {type: 'RDFString', value: 'foo'}},
    });
  });

  it('prefills the UI with values from model', () => {
    const model = {
      type: 'Dict',
      value: {'foo': {type: 'RDFString', value: 'bar'}},
    };

    const element = renderTestTemplate(model);
    expect(element.find('input.key').val()).toBe('foo');

    const valueElement = element.find('grr-form-value');
    expect(valueElement.scope().$eval(valueElement.attr('value'))).toEqual(
        {type: 'RDFString', value: 'bar'});
  });

  it('treats digits-only string as integers', () => {
    const model = {
      type: 'Dict',
      value: {},
    };
    const element = renderTestTemplate(model);

    browserTriggerEvent(element.find('button[name=Add]'), 'click');
    setFormValue(element, '42');

    expect(model).toEqual({
      type: 'Dict',
      value: {'': {type: 'RDFInteger', value: 42}},
    });
  });

  it('dynamically changes value type from str to int and back', () => {
    const model = {
      type: 'Dict',
      value: {},
    };
    const element = renderTestTemplate(model);

    browserTriggerEvent(element.find('button[name=Add]'), 'click');

    expect(model).toEqual({
      type: 'Dict',
      value: {'': {type: 'RDFString', value: ''}},
    });

    setFormValue(element, '1');
    expect(model).toEqual({
      type: 'Dict',
      value: {'': {type: 'RDFInteger', value: 1}},
    });

    setFormValue(element, '1a');
    expect(model).toEqual({
      type: 'Dict',
      value: {'': {type: 'RDFString', value: '1a'}},
    });
  });

  it('treats 0x.* strings as hex integers', () => {
    const model = {
      type: 'Dict',
      value: {},
    };
    const element = renderTestTemplate(model);

    browserTriggerEvent(element.find('button[name=Add]'), 'click');
    setFormValue(element, '0x2f');

    expect(model).toEqual({
      type: 'Dict',
      value: {'': {type: 'RDFInteger', value: 47}},
    });
  });

  it('dynamically changes value type from hex int to str and back', () => {
    const model = {
      type: 'Dict',
      value: {},
    };
    const element = renderTestTemplate(model);

    browserTriggerEvent(element.find('button[name=Add]'), 'click');

    expect(model).toEqual({
      type: 'Dict',
      value: {'': {type: 'RDFString', value: ''}},
    });

    setFormValue(element, '0x');
    expect(model).toEqual({
      type: 'Dict',
      value: {'': {type: 'RDFString', value: '0x'}},
    });

    setFormValue(element, '0x2f');
    expect(model).toEqual({
      type: 'Dict',
      value: {'': {type: 'RDFInteger', value: 47}},
    });

    setFormValue(element, '0x2fz');
    expect(model).toEqual({
      type: 'Dict',
      value: {'': {type: 'RDFString', value: '0x2fz'}},
    });
  });

  it('treats "true" string as a boolean', () => {
    const model = {
      type: 'Dict',
      value: {},
    };
    const element = renderTestTemplate(model);

    browserTriggerEvent(element.find('button[name=Add]'), 'click');
    setFormValue(element, 'true');

    expect(model).toEqual({
      type: 'Dict',
      value: {'': {type: 'bool', value: true}},
    });
  });

  it('treats "false" string as a boolean', () => {
    const model = {
      type: 'Dict',
      value: {},
    };
    const element = renderTestTemplate(model);

    browserTriggerEvent(element.find('button[name=Add]'), 'click');
    setFormValue(element, 'false');

    expect(model).toEqual({
      type: 'Dict',
      value: {'': {type: 'bool', value: false}},
    });
  });

  it('dynamically changes valye type from text to bool and back', () => {
    const model = {
      type: 'Dict',
      value: {},
    };
    const element = renderTestTemplate(model);

    browserTriggerEvent(element.find('button[name=Add]'), 'click');

    expect(model).toEqual({
      type: 'Dict',
      value: {'': {type: 'RDFString', value: ''}},
    });

    setFormValue(element, 'true');
    expect(model).toEqual({
      type: 'Dict',
      value: {'': {type: 'bool', value: true}},
    });

    setFormValue(element, 'truea');
    expect(model).toEqual({
      type: 'Dict',
      value: {'': {type: 'RDFString', value: 'truea'}},
    });
  });
});


exports = {};

;return exports;});

//angular-components/forms/duration-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.durationFormDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {formsModule} = goog.require('grrUi.forms.forms');


describe('duration form directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module('/static/angular-components/forms/duration-form.html'));
  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-form-duration value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows nothing if duration value is null', () => {
    const element = renderTestTemplate({
      type: 'DurationSeconds',
      value: null,
    });
    expect(element.find('input').val()).toBe('');
  });

  it('shows 0 if duration value is 0', () => {
    const element = renderTestTemplate({
      type: 'DurationSeconds',
      value: 0,
    });
    expect(element.find('input').val()).toBe('0');
  });

  it('shows correct duration for large numbers', () => {
    const element = renderTestTemplate({
      type: 'DurationSeconds',
      value: 1040688000000,
    });
    expect(element.find('input').val()).toBe('12045000d');
  });

  it('shows duration in seconds if it\'s not divisible by 60', () => {
    const element = renderTestTemplate({
      type: 'DurationSeconds',
      value: 122,
    });
    expect(element.find('input').val()).toBe('122s');
  });

  it('shows duration in minutes if possible', () => {
    const element = renderTestTemplate({
      type: 'DurationSeconds',
      value: 120,
    });
    expect(element.find('input').val()).toBe('2m');
  });

  it('shows duration in hours if possible', () => {
    const element = renderTestTemplate({
      type: 'DurationSeconds',
      value: 7200,
    });
    expect(element.find('input').val()).toBe('2h');
  });

  it('shows duration in days if possible', () => {
    const element = renderTestTemplate({
      type: 'DurationSeconds',
      value: 172800,
    });
    expect(element.find('input').val()).toBe('2d');
  });

  it('shows duration in weeks if possible', () => {
    const element = renderTestTemplate({
      type: 'DurationSeconds',
      value: 1209600,
    });
    expect(element.find('input').val()).toBe('2w');
  });

  it('sets value to null on incorrect input', () => {
    const value = {
      type: 'DurationSeconds',
      value: 0,
    };
    const element = renderTestTemplate(value);
    element.find('input').val('a');
    browserTriggerEvent(element.find('input'), 'change');

    expect(value.value).toBe(null);
  });

  it('shows warning on incorrect input', () => {
    const value = {
      type: 'DurationSeconds',
      value: 0,
    };
    const element = renderTestTemplate(value);
    element.find('input').val('a');
    browserTriggerEvent(element.find('input'), 'change');

    expect(element.text()).toContain('Expected format is');
  });

  it('correctly updates the value when input is in weeks', () => {
    const value = {
      type: 'DurationSeconds',
      value: 0,
    };
    const element = renderTestTemplate(value);
    element.find('input').val('2w');
    browserTriggerEvent(element.find('input'), 'change');

    expect(value.value).toBe(1209600);
  });

  it('correctly updates the value when input is in days', () => {
    const value = {
      type: 'DurationSeconds',
      value: 0,
    };
    const element = renderTestTemplate(value);
    element.find('input').val('2d');
    browserTriggerEvent(element.find('input'), 'change');

    expect(value.value).toBe(172800);
  });

  it('correctly updates the value when input is in hours', () => {
    const value = {
      type: 'DurationSeconds',
      value: 0,
    };
    const element = renderTestTemplate(value);
    element.find('input').val('2h');
    browserTriggerEvent(element.find('input'), 'change');

    expect(value.value).toBe(7200);
  });

  it('correctly updates the value when input is in minutes', () => {
    const value = {
      type: 'DurationSeconds',
      value: 0,
    };
    const element = renderTestTemplate(value);
    element.find('input').val('2m');
    browserTriggerEvent(element.find('input'), 'change');

    expect(value.value).toBe(120);
  });

  it('correctly updates the value when input is in seconds', () => {
    const value = {
      type: 'DurationSeconds',
      value: 0,
    };
    const element = renderTestTemplate(value);
    element.find('input').val('2s');
    browserTriggerEvent(element.find('input'), 'change');

    expect(value.value).toBe(2);
  });

  it('treats values without unit as seconds', () => {
    const value = {
      type: 'DurationSeconds',
      value: 0,
    };
    const element = renderTestTemplate(value);
    element.find('input').val('2');
    browserTriggerEvent(element.find('input'), 'change');

    expect(value.value).toBe(2);
  });
});


exports = {};

;return exports;});

//angular-components/forms/ext-flags-linux-picker-long-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.extFlagsLinuxPickerLongDirectiveTest');
goog.setTestOnly();

const {ExtFlagsLinuxPickerLongDirective} = goog.require('grrUi.forms.extFlagsLinuxPickerLongDirective');
const {LINUX_FLAGS, getLinuxFlagMaskByNames} = goog.require('grrUi.client.extFlags');
const {formsModule} = goog.require('grrUi.forms.forms');
const {testsModule} = goog.require('grrUi.tests');


describe('Extended flags picker for Linux (long)', () => {
  let $compile;
  let $rootScope;

  beforeEach(module(ExtFlagsLinuxPickerLongDirective().templateUrl));

  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const render = (bits) => {
    $rootScope.bitsSet = bits.set;
    $rootScope.bitsUnset = bits.unset;

    const template = `<grr-ext-flags-linux-picker-long
      bits-set="bitsSet"
      bits-unset="bitsUnset" />`;

    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  const parse = (element) => {
    const result = {};
    for (const flag of LINUX_FLAGS) {
      const option = $(element.find(`#${flag.name}`)).find('option[selected]');
      result[flag.name] = option.text();
    }
    return result;
  };

  it('correctly renders a table if set and unset bits are empty', () => {
    const table = parse(render({
      set: {
        value: 0,
      },
      unset: {
        value: 0,
      },
    }));

    for (const flag of LINUX_FLAGS) {
      expect(table[flag.name]).toBe('ignored');
    }
  });

  it('correctly renders a table if some bits are set', () => {
    const table = parse(render({
      set: {
        value: getLinuxFlagMaskByNames(['FS_SYNC_FL', 'FS_UNRM_FL']),
      },
      unset: {
        value: 0,
      },
    }));

    expect(table['FS_SYNC_FL']).toBe('required set');
    expect(table['FS_UNRM_FL']).toBe('required set');
    expect(table['FS_IMMUTABLE_FL']).toBe('ignored');
  });

  it('correctly renders a table if some bits are unset', () => {
    const table = parse(render({
      set: {
        value: 0,
      },
      unset: {
        value: getLinuxFlagMaskByNames(['FS_IMMUTABLE_FL', 'FS_COMPR_FL']),
      },
    }));

    expect(table['FS_IMMUTABLE_FL']).toBe('required unset');
    expect(table['FS_COMPR_FL']).toBe('required unset');
    expect(table['FS_SYNC_FL']).toBe('ignored');
  });

  it('correctly renders a table if there are bits set and bits unset', () => {
    const table = parse(render({
      set: {
        value: getLinuxFlagMaskByNames(['FS_NOATIME_FL']),
      },
      unset: {
        value: getLinuxFlagMaskByNames(['FS_DIRTY_FL', 'FS_INDEX_FL']),
      },
    }));

    expect(table['FS_NOATIME_FL']).toBe('required set');
    expect(table['FS_DIRTY_FL']).toBe('required unset');
    expect(table['FS_INDEX_FL']).toBe('required unset');
    expect(table['FS_TOPDIR_FL']).toBe('ignored');
  });
});

;return exports;});

//angular-components/forms/ext-flags-linux-picker-short-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.extFlagsLinuxPickerShortDirectiveTest');
goog.setTestOnly();

const {ExtFlagsLinuxPickerShortDirective} = goog.require('grrUi.forms.extFlagsLinuxPickerShortDirective');
const {LINUX_FLAGS, getLinuxFlagMaskByNames} = goog.require('grrUi.client.extFlags');
const {TroggleDirective} = goog.require('grrUi.core.troggleDirective');
const {coreModule} = goog.require('grrUi.core.core');
const {formsModule} = goog.require('grrUi.forms.forms');
const {testsModule} = goog.require('grrUi.tests');


describe('Extended flags picker for Linux (short)', () => {
  let $compile;
  let $rootScope;

  beforeEach(module(TroggleDirective().templateUrl));
  beforeEach(module(ExtFlagsLinuxPickerShortDirective().templateUrl));

  beforeEach(module(coreModule.name));
  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const render = (bits) => {
    $rootScope.bitsSet = bits.set;
    $rootScope.bitsUnset = bits.unset;

    const template = `<grr-ext-flags-linux-picker-short
      bits-set="bitsSet"
      bits-unset="bitsUnset" />`;

    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  const parse = (element) => {
    const ths = element.find('th');
    const tds = element.find('td');
    expect(ths.length).toBe(tds.length);

    const result = {};
    for (let i = 0; i < ths.length; i++) {
      result[$(ths[i]).text().trim()] = $(tds[i]).text().trim();
    }
    return result;
  };

  it('correctly renders a table if set and unset bits are empty', () => {
    const table = parse(render({
      set: {
        value: 0,
      },
      unset: {
        value: 0,
      },
    }));

    for (const flag of LINUX_FLAGS) {
      expect(table[flag.identifier]).toBe('_');
    }
  });

  it('correctly renders a table if some bits are set', () => {
    const table = parse(render({
      set: {
        value: getLinuxFlagMaskByNames(['FS_IMMUTABLE_FL', 'FS_APPEND_FL']),
      },
      unset: {
        value: 0,
      },
    }));

    expect(table['a']).toBe('✓');
    expect(table['i']).toBe('✓');
    expect(table['c']).toBe('_');
    expect(table['s']).toBe('_');
  });

  it('correctly renders a table if some bits are unset', () => {
    const table = parse(render({
      set: {
        value: 0,
      },
      unset: {
        value: getLinuxFlagMaskByNames(['FS_NOCOMP_FL', 'FS_NODUMP_FL']),
      },
    }));

    expect(table['X']).toBe('✕');
    expect(table['d']).toBe('✕');
    expect(table['a']).toBe('_');
    expect(table['B']).toBe('_');
  });

  it('correctly renders a table if there are bits set and bits unset', () => {
    const table = parse(render({
      set: {
        value: getLinuxFlagMaskByNames(['FS_EXTENT_FL']),
      },
      unset: {
        value: getLinuxFlagMaskByNames(['FS_TOPDIR_FL', 'FS_NOCOW_FL']),
      },
    }));

    expect(table['e']).toBe('✓');
    expect(table['T']).toBe('✕');
    expect(table['C']).toBe('✕');
    expect(table['a']).toBe('_');
  });
});

;return exports;});

//angular-components/forms/ext-flags-osx-picker-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.extFlagsOsxPickerDirectiveTest');
goog.setTestOnly();

const {ExtFlagsOsxPickerDirective} = goog.require('grrUi.forms.extFlagsOsxPickerDirective');
const {OSX_FLAGS, getOsxFlagMaskByNames} = goog.require('grrUi.client.extFlags');
const {TroggleDirective} = goog.require('grrUi.core.troggleDirective');
const {coreModule} = goog.require('grrUi.core.core');
const {formsModule} = goog.require('grrUi.forms.forms');
const {testsModule} = goog.require('grrUi.tests');


describe('Extended flags picker for macOS', () => {
  let $compile;
  let $rootScope;

  beforeEach(module(TroggleDirective().templateUrl));
  beforeEach(module(ExtFlagsOsxPickerDirective().templateUrl));

  beforeEach(module(coreModule.name));
  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const render = (bits) => {
    $rootScope.bitsSet = bits.set;
    $rootScope.bitsUnset = bits.unset;

    const template = `<grr-ext-flags-osx-picker
      bits-set="bitsSet"
      bits-unset="bitsUnset" />`;

    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  const parse = (element) => {
    const ths = element.find('th');
    const tds = element.find('td');
    expect(ths.length).toBe(tds.length);

    const result = {};
    for (let i = 0; i < ths.length; i++) {
      result[$(ths[i]).text().trim()] = $(tds[i]).text().trim();
    }
    return result;
  };

  it('correctly renders a table if set and unset bits are empty', () => {
    const table = parse(render({
      set: {
        value: 0,
      },
      unset: {
        value: 0,
      },
    }));

    for (const flag of OSX_FLAGS) {
      expect(table[flag.identifier]).toBe('_');
    }
  });

  it('correctly renders a table if some bits are set', () => {
    const table = parse(render({
      set: {
        value: getOsxFlagMaskByNames(['UF_NODUMP', 'UF_IMMUTABLE']),
      },
      unset: {
        value: 0,
      },
    }));

    expect(table['nodump']).toBe('✓');
    expect(table['uimmutable']).toBe('✓');
    expect(table['archived']).toBe('_');
  });

  it('correctly renders a table if some bits are unset', () => {
    const table = parse(render({
      set: {
        value: 0,
      },
      unset: {
        value: getOsxFlagMaskByNames(['SF_APPEND', 'SF_NOUNLINK']),
      },
    }));

    expect(table['sappend']).toBe('✕');
    expect(table['sunlnk']).toBe('✕');
    expect(table['opaque']).toBe('_');
  });

  it('correctly renders a table if there are bits set and bits unset', () => {
    const table = parse(render({
      set: {
        value: getOsxFlagMaskByNames(['UF_APPEND']),
      },
      unset: {
        value: getOsxFlagMaskByNames(['SF_APPEND']),
      },
    }));

    expect(table['uappend']).toBe('✓');
    expect(table['sappend']).toBe('✕');
    expect(table['uimmutable']).toBe('_');
    expect(table['simmutable']).toBe('_');
  });
});

;return exports;});

//angular-components/forms/ext-flags-troggling_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.extFlagsTrogglingTest');
goog.module.declareLegacyNamespace();
goog.setTestOnly();


const {TroggableFlags} = goog.require('grrUi.forms.extFlagsTroggling');
const {TroggleState} = goog.require('grrUi.core.troggleDirective');


describe('TroggableFlags', () => {

  let flags;

  beforeEach(() => {
    flags = new TroggableFlags([
      {
        name: 'FOO',
        identifier: 'foo',
        mask: 0b001,
        description: 'foo',
      },
      {
        name: 'BAR',
        identifier: 'bar',
        mask: 0b010,
        description: 'bar',
      },
      {
        name: 'BAZ',
        identifier: 'baz',
        mask: 0b100,
        description: 'baz',
      },
    ]);
  });

  it('should update itself if children state changes', () => {
    flags.children[1].state = TroggleState.SET;
    expect(flags.bitsSet).toBe(0b010);

    flags.children[0].state = TroggleState.SET;
    expect(flags.bitsSet).toBe(0b011);

    flags.children[2].state = TroggleState.UNSET;
    expect(flags.bitsUnset).toBe(0b100);
  });

  it('should update children on bits change', () => {
    flags.bitsSet = 0b000;
    flags.bitsUnset = 0b000;

    for (const flag of flags.children) {
      expect(flag.state).toBe(TroggleState.VOID);
    }

    flags.bitsSet = 0b100;
    expect(flags.children[2].state).toBe(TroggleState.SET);
    expect(flags.children[0].state).toBe(TroggleState.VOID);

    flags.bitsSet = 0b101;
    expect(flags.children[2].state).toBe(TroggleState.SET);
    expect(flags.children[2].state).toBe(TroggleState.SET);

    flags.bitsUnset = 0b010;
    expect(flags.children[1].state).toBe(TroggleState.UNSET);
  });

  it('should throw if conflicting masks are set', () => {
    flags.bitsSet = 0b110;
    flags.bitsUnset = 0b010;

    expect(() => flags.children[1].state).toThrow();
  });

});

;return exports;});

//angular-components/forms/glob-expression-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.globExpressionFormDirectiveTest');
goog.setTestOnly();

const {getSuggestions} = goog.require('grrUi.forms.globExpressionFormDirective');

describe('grr-glob-expression-form directive', () => {
  it('shows no autocomplete without delimiter', () => {
    expect(getSuggestions('fooba', ['foobar', 'baz'])).toEqual([]);
  });

  it('shows no autocompletion with closed delimiter', () => {
    expect(getSuggestions('%%foobar%%/foobar', ['foobar', 'baz'])).toEqual([]);
  });

  it('shows no autocompletion for closed group', () => {
    expect(getSuggestions('%%foobar%%foo', ['foobar', 'baz'])).toEqual([]);
  });

  it('shows autocompletion for matching prefix', () => {
    expect(getSuggestions('%%fooba', ['foobar', 'baz'])).toEqual([
      {suggestion: '%%foobar%%', expressionWithSuggestion: '%%foobar%%'}
    ]);
  });

  it('shows autocompletion for partial matching', () => {
    expect(getSuggestions('%%oba', ['foobar', 'baz'])).toEqual([
      {suggestion: '%%foobar%%', expressionWithSuggestion: '%%foobar%%'}
    ]);
  });

  it('shows autocompletion while closing term', () => {
    expect(getSuggestions('%%foobar%', ['foobar', 'baz'])).toEqual([
      {suggestion: '%%foobar%%', expressionWithSuggestion: '%%foobar%%'}
    ]);
  });

  it('shows autocompletion with unrelated query prefix', () => {
    expect(getSuggestions('/bar/%%foo', ['foobar', 'baz'])).toEqual([
      {suggestion: '%%foobar%%', expressionWithSuggestion: '/bar/%%foobar%%'}
    ]);
  });

  it('hides autocompletion for closed term', () => {
    expect(getSuggestions('%%foo%%', ['foobar', 'foo'])).toEqual([]);
  });

  it('shows multiple autocomplete suggestions', () => {
    const sug = getSuggestions('%%ba', ['foobar', 'baz']);
    expect(sug.length).toBe(2);
    expect(sug).toContain(
        {suggestion: '%%foobar%%', expressionWithSuggestion: '%%foobar%%'});
    expect(sug).toContain(
        {suggestion: '%%baz%%', expressionWithSuggestion: '%%baz%%'});
  });
});


exports = {};

;return exports;});

//angular-components/forms/output-plugin-descriptor-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.outputPluginDescriptorFormDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, stubDirective, testsModule} = goog.require('grrUi.tests');
const {formsModule} = goog.require('grrUi.forms.forms');


describe('grr-output-plugin-descriptor-form directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;
  let grrReflectionService;


  beforeEach(module('/static/angular-components/forms/' +
      'output-plugin-descriptor-form.html'));
  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  // Stub out grr-form-value directive, as arguments rendering is going
  // to be delegated to it.
  stubDirective('grrFormValue');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrApiService = $injector.get('grrApiService');
    grrReflectionService = $injector.get('grrReflectionService');

    const apiServiceResponse = $q.defer();
    apiServiceResponse.resolve({
      data: {
        items: [
          {
            args_type: 'FooOutputPluginArgs',
            name: 'FooOutputPlugin',
            plugin_type: 'LEGACY',
          },
          {
            args_type: 'BarOutputPluginArgs',
            name: 'BarOutputPlugin',
            plugin_type: 'LEGACY',
          },
          {
            name: 'foo-bar',
            friendly_name: 'FooBar plugin',
            plugin_type: 'INSTANT',
          },
        ],
      },
    });
    spyOn(grrApiService, 'get').and.returnValue(apiServiceResponse.promise);

    spyOn(grrReflectionService, 'getRDFValueDescriptor')
        .and.callFake((name) => {
          const deferred = $q.defer();

          if (name == 'FooOutputPluginArgs') {
            deferred.resolve({
              default: {
                type: 'FooOutputPluginArgs',
                value: 'FooValue',
              },
            });
          } else if (name == 'BarOutputPluginArgs') {
            deferred.resolve({
              default: {
                type: 'BarOutputPluginArgs',
                value: 'BarValue',
              },
            });
          } else {
            throw new Error(`Unexpected name: ${name}`);
          }

          return deferred.promise;
        });
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = angular.isDefined(value) ? value : {
      type: 'OutputPluginDescriptor',
      value: {},
    };

    const template = '<grr-output-plugin-descriptor-form value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('selects alphabetically first plugin if none is specified', () => {
    const element = renderTestTemplate();

    expect($rootScope.value.value.plugin_name.value).toBe('BarOutputPlugin');
    expect(element.find('select').val()).toBe('string:BarOutputPlugin');
  });

  it('keeps current output plugin type if one is specified', () => {
    const element = renderTestTemplate({
      type: 'OutputPluginDescriptor',
      value: {
        plugin_name: {
          type: 'RDFString',
          value: 'FooOutputPlugin',
        },
      },
    });

    expect($rootScope.value.value.plugin_name.value).toBe('FooOutputPlugin');
    expect(element.find('select').val()).toBe('string:FooOutputPlugin');
  });

  it('sets plugin args to default when plugin type is changed', () => {
    const element = renderTestTemplate();

    expect($rootScope.value.value.plugin_args.type).toBe('BarOutputPluginArgs');

    element.find('select').val(
        element.find('select option[label="FooOutputPlugin"]').val());
    browserTriggerEvent(element.find('select'), 'change');
    expect($rootScope.value.value.plugin_args.type).toBe('FooOutputPluginArgs');
  });

  it('delegates current plugin args rendering to grr-form-value', () => {
    const element = renderTestTemplate();

    let argsValue =
        $rootScope.$eval(element.find('grr-form-value').attr('value'));
    expect(argsValue.type).toBe('BarOutputPluginArgs');

    element.find('select').val(
        element.find('select option[label="FooOutputPlugin"]').val());
    browserTriggerEvent(element.find('select'), 'change');

    argsValue = $rootScope.$eval(element.find('grr-form-value').attr('value'));
    expect(argsValue.type).toBe('FooOutputPluginArgs');
  });
});


exports = {};

;return exports;});

//angular-components/forms/semantic-enum-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.semanticEnumFormDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {formsModule} = goog.require('grrUi.forms.forms');


describe('semantic enum form directive', () => {
  let $compile;
  let $rootScope;
  let value;


  beforeEach(module('/static/angular-components/forms/semantic-enum-form.html'));
  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');

    value = {
      age: 0,
      type: 'EnumNamedValue',
      value: 'NONE',
      mro: [
        'EnumNamedValue', 'RDFInteger', 'RDFString', 'RDFBytes', 'RDFValue',
        'object'
      ],
    };
  }));

  const renderTestTemplate = (metadata) => {
    $rootScope.value = value;
    $rootScope.metadata = metadata;

    const template = '<grr-form-enum metadata="metadata" value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows list of options from the metadata', () => {
    const element = renderTestTemplate({
      allowed_values: [
        {name: 'NONE', value: 0},
        {name: 'CHOICE 1', value: 1},
        {name: 'CHOICE 2', value: 2},
      ],
    });

    expect(element.find('option').length).toBe(3);
    expect(element.find('option:nth(0)').attr('label')).toBe('NONE');
    expect(element.find('option:nth(1)').attr('label')).toBe('CHOICE 1');
    expect(element.find('option:nth(2)').attr('label')).toBe('CHOICE 2');
  });

  it('marks the default value with "(default)"', () => {
    const element = renderTestTemplate({
      allowed_values: [
        {name: 'NONE', value: 0},
        {name: 'CHOICE 1', value: 1},
        {name: 'CHOICE 2', value: 2},
      ],
      default: {
        'age': 0,
        'type': 'EnumNamedValue',
        'value': 'CHOICE 1',
        'mro': [
          'EnumNamedValue', 'RDFInteger', 'RDFString', 'RDFBytes', 'RDFValue',
          'object'
        ]
      },
    });

    expect(element.find('option').length).toBe(3);
    expect(element.find('option:nth(0)').attr('label')).toBe('NONE');
    expect(element.find('option:nth(1)').attr('label')).toBe(
        'CHOICE 1 (default)');
    expect(element.find('option:nth(2)').attr('label')).toBe('CHOICE 2');
  });

  it('updates the value when user selects an option', () => {
    const element = renderTestTemplate({
      allowed_values: [
        {name: 'NONE', value: 0},
        {name: 'CHOICE 1', value: 1},
        {name: 'CHOICE 2', value: 2},
      ],
    });

    expect(value.value).toBe('NONE');

    element.find('select').val(
        element.find('select option[label="CHOICE 2"]').val());
    browserTriggerEvent(element.find('select'), 'change');
    $rootScope.$apply();

    expect(value.value).toBe('CHOICE 2');
  });
});


exports = {};

;return exports;});

//angular-components/forms/semantic-primitive-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.semanticPrimitiveFormDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {formsModule} = goog.require('grrUi.forms.forms');


describe('semantic primitive form directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrReflectionService;

  let boolValue;
  let intValue;
  let stringValue;


  beforeEach(module('/static/angular-components/forms/semantic-primitive-form.html'));
  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrReflectionService = $injector.get('grrReflectionService');

    grrReflectionService.getRDFValueDescriptor = ((valueType) => {
      const deferred = $q.defer();
      deferred.resolve({
        name: valueType,
        mro: [valueType],
      });
      return deferred.promise;
    });

    stringValue = {
      type: 'RDFString',
      value: 'foo',
    };

    intValue = {
      type: 'RDFInteger',
      value: 42,
    };

    boolValue = {
      type: 'bool',
      value: true,
    };
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-form-primitive value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('prefills RDFString value from a model', () => {
    const element = renderTestTemplate(stringValue);
    expect(element.find('input').val()).toBe('foo');
  });

  it('updates RDFString value when model is updated', () => {
    const element = renderTestTemplate(stringValue);
    expect(element.find('input').val()).toBe('foo');

    stringValue.value = 'bar';
    $rootScope.$apply();

    expect(element.find('input').val()).toBe('bar');
  });

  it('updates RDFString model when user changes the input', () => {
    const element = renderTestTemplate(stringValue);

    element.find('input').val('bar');
    browserTriggerEvent(element.find('input'), 'change');
    $rootScope.$apply();

    expect(stringValue.value).toBe('bar');
  });

  it('prefills RDFInteger value from a model', () => {
    const element = renderTestTemplate(intValue);
    expect(element.find('input').val()).toBe('42');
  });

  it('updates RDFInteger value when model is updated', () => {
    const element = renderTestTemplate(intValue);
    expect(element.find('input').val()).toBe('42');

    intValue.value = 84;
    $rootScope.$apply();

    expect(element.find('input').val()).toBe('84');
  });

  it('updates RDFInteger model when user changes the input', () => {
    const element = renderTestTemplate(intValue);

    element.find('input').val('84');
    browserTriggerEvent(element.find('input'), 'change');
    $rootScope.$apply();

    expect(intValue.value).toBe(84);
  });

  it('prefills bool value from a model', () => {
    const element = renderTestTemplate(boolValue);
    expect(element.find('input').prop('checked')).toBe(true);
  });

  it('updates bool value when model is updated', () => {
    const element = renderTestTemplate(boolValue);
    expect(element.find('input').prop('checked')).toBe(true);

    boolValue.value = false;
    $rootScope.$apply();

    expect(element.find('input').prop('checked')).toBe(false);
  });

  it('updates bool model when user changes the input', () => {
    const element = renderTestTemplate(boolValue);

    browserTriggerEvent(element.find('input').prop('checked', false), 'change');
    $rootScope.$apply();

    expect(boolValue.value).toBe(false);
  });
});


exports = {};

;return exports;});

//angular-components/forms/semantic-proto-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.semanticProtoFormDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, stubDirective, testsModule} = goog.require('grrUi.tests');
const {clearCaches} = goog.require('grrUi.forms.semanticValueFormDirective');
const {formsModule} = goog.require('grrUi.forms.forms');


describe('semantic proto form directive', () => {
  let $compile;
  let $q;
  let $rootScope;

  let grrReflectionServiceMock;

  beforeEach(module('/static/angular-components/forms/semantic-proto-form.html'));
  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  angular.forEach(
      [
        'grrFormProtoSingleField', 'grrFormProtoRepeatedField',
        'grrFormProtoUnion'
      ],
      (directiveName) => {
        stubDirective(directiveName);
      });

  beforeEach(module(($provide) => {
    grrReflectionServiceMock = {
      getRDFValueDescriptor: function() {},
    };

    $provide.factory('grrReflectionService', () => grrReflectionServiceMock);
  }));

  beforeEach(inject(($injector) => {
    clearCaches();

    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
  }));

  const renderTestTemplate = (value, metadata, hiddenFields) => {
    $rootScope.value = value;
    $rootScope.metadata = metadata;
    $rootScope.hiddenFields = hiddenFields;

    const template = '<grr-form-proto value="value" metadata="metadata" ' +
        'hidden-fields="hiddenFields" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  describe('form for structure with 3 primitive fields', () => {
    let defaultFooStructValue;

    beforeEach(() => {
      defaultFooStructValue = {
        type: 'Foo',
        mro: ['Foo', 'RDFProtoStruct'],
        value: {},
      };

      // Reflection service is a mock. Stub out the getRDFValueDescriptor method
      // and return a promise with the reflection data.
      const data = {
        'Foo': {
          'default': {
            'type': 'Foo',
            'value': {},
          },
          'doc': 'This is a structure Foo.',
          'fields': [
            {
              'doc': 'Field 1 description.',
              'dynamic': false,
              'friendly_name': 'Field 1',
              'index': 1,
              'name': 'field_1',
              'repeated': false,
              'type': 'PrimitiveType',
            },
            {
              'default': {
                'type': 'PrimitiveType',
                'value': 'a foo bar',
              },
              'doc': 'Field 2 description.',
              'dynamic': false,
              'friendly_name': 'Field 2',
              'index': 1,
              'name': 'field_2',
              'repeated': false,
              'type': 'PrimitiveType',
            },
            {
              'default': {
                'type': 'PrimitiveType',
                'value': '',
              },
              'doc': 'Field 3 description.',
              'dynamic': false,
              'friendly_name': 'Field 3',
              'index': 1,
              'name': 'field_3',
              'repeated': false,
              'type': 'PrimitiveType',
            },
          ],
          'kind': 'struct',
          'name': 'Foo',
          'mro': ['Foo', 'RDFProtoStruct'],
        },
        'PrimitiveType': {
          'default': {
            'type': 'PrimitiveType',
            'value': '',
          },
          'doc': 'Test primitive type description.',
          'kind': 'primitive',
          'name': 'PrimitiveType',
          'mro': ['PrimitiveType'],
        },
      };

      const reflectionDeferred = $q.defer();
      reflectionDeferred.resolve(data);
      spyOn(grrReflectionServiceMock, 'getRDFValueDescriptor')
          .and.callFake((type, opt_withDeps) => {
            const reflectionDeferred = $q.defer();
            reflectionDeferred.resolve(opt_withDeps ? data : data[type]);
            return reflectionDeferred.promise;
          });
    });

    it('renders a form for structure with 3 primitive fields', () => {
      const element = renderTestTemplate(defaultFooStructValue);

      // Check that for every primitive field a grr-form-proto-single-field
      // directive is created.
      expect(element.find('grr-form-proto-single-field').length).toBe(3);
    });

    it('does not overwrite field prefilled with non-default value', () => {
      const fooValue = defaultFooStructValue;
      fooValue.value = {
        field_2: {
          type: 'PrimitiveType',
          value: '42',
        },
      };
      renderTestTemplate(fooValue);

      expect(fooValue.value).toEqual({
        field_2: {
          type: 'PrimitiveType',
          value: '42',
        },
      });
    });

    it('does not erase the field with default value prefilled with default ' +
           'field value not equal to the default type value',
       () => {
         const fooValue = defaultFooStructValue;
         fooValue.value = {
           field_2: {
             type: 'PrimitiveType',
             value: 'a foo bar',
           },
         };
         renderTestTemplate(fooValue);

         expect(fooValue.value).toEqual({
           field_2: {
             type: 'PrimitiveType',
             value: 'a foo bar',
           },
         });
       });

    it('erases the field with default value prefilled with default field ' +
           'value equal to the default type value',
       () => {
         const fooValue = defaultFooStructValue;
         fooValue.value = {
           field_3: {
             type: 'PrimitiveType',
             value: '',
           },
         };
         renderTestTemplate(fooValue);

         expect(fooValue.value).toEqual({});
       });

    it('erases the field without default prefilled with default type ' +
           'value',
       () => {
         const fooValue = defaultFooStructValue;
         fooValue.value = {
           field_1: {
             type: 'PrimitiveType',
             value: '',
           },
         };
         renderTestTemplate(fooValue);

         expect(fooValue.value).toEqual({});
       });

    it('does not erase hidden fields', () => {
      const fooValue = defaultFooStructValue;
      fooValue.value = {
        field_1: {
          type: 'PrimitiveType',
          value: '',
        },
      };
      renderTestTemplate(fooValue, undefined, ['field_1']);

      expect(fooValue.value).toEqual({
        field_1: {
          type: 'PrimitiveType',
          value: '',
        },
      });
    });

    it('does not render fields listed in hidden-fields argument', () => {
      const element =
          renderTestTemplate(defaultFooStructValue, undefined, ['field_1']);

      expect(element.find('grr-form-proto-single-field').length).toBe(2);

      // Check that rendered fields are field_2 and field_3 only.
      let field = element.find('grr-form-proto-single-field:nth(0)');
      expect(field.scope().$eval(field.attr('value'))).toEqual({
        type: 'PrimitiveType',
        value: 'a foo bar',
      });

      field = element.find('grr-form-proto-single-field:nth(1)');
      expect(field.scope().$eval(field.attr('value'))).toEqual({
        type: 'PrimitiveType',
        value: '',
      });
    });

    it('does not prefill the model with defaults', () => {
      const fooValue = defaultFooStructValue;
      renderTestTemplate(fooValue);

      expect(fooValue.value).toEqual({});
    });

    it('prefills nested form elements with defaults', () => {
      const fooValue = defaultFooStructValue;
      const element = renderTestTemplate(fooValue);

      let field = element.find('grr-form-proto-single-field:nth(0)');
      expect(field.scope().$eval(field.attr('value'))).toEqual({
        type: 'PrimitiveType',
        value: '',
      });

      field = element.find('grr-form-proto-single-field:nth(1)');
      expect(field.scope().$eval(field.attr('value'))).toEqual({
        type: 'PrimitiveType',
        value: 'a foo bar',
      });

      field = element.find('grr-form-proto-single-field:nth(2)');
      expect(field.scope().$eval(field.attr('value'))).toEqual({
        type: 'PrimitiveType',
        value: '',
      });
    });

    it('updates model when a field is changed', () => {
      const fooValue = defaultFooStructValue;
      const element = renderTestTemplate(fooValue);

      const field = element.find('grr-form-proto-single-field:nth(0)');
      const fieldValue = field.scope().$eval(field.attr('value'));
      fieldValue.value = '42';

      $rootScope.$apply();

      expect(fooValue.value).toEqual({
        field_1: {
          type: 'PrimitiveType',
          value: '42',
        },
      });
    });

    it('updates fields when value.field_1 is changed externally', () => {
      const fooValue = defaultFooStructValue;
      const element = renderTestTemplate(fooValue);

      let field = element.find('grr-form-proto-single-field:nth(0)');
      let fieldValue = field.scope().$eval(field.attr('value'));
      expect(fieldValue['value']).toBe('');

      fooValue['value']['field_1'] = {
        type: 'PrimitiveType',
        value: 'foo',
      };

      $rootScope.$apply();

      field = element.find('grr-form-proto-single-field:nth(0)');
      fieldValue = field.scope().$eval(field.attr('value'));
      expect(fieldValue).toEqual({
        type: 'PrimitiveType',
        value: 'foo',
      });
    });

    it('with set fields: updates only changed on external change', () => {
      const fooValue = defaultFooStructValue;
      fooValue['value']['field_1'] = {
        type: 'PrimitiveType',
        value: 'foo',
      };
      fooValue['value']['field_2'] = {
        type: 'PrimitiveType',
        value: 'bar',
      };
      const element = renderTestTemplate(fooValue);

      const field1 = element.find('grr-form-proto-single-field:nth(0)');
      const field2 = element.find('grr-form-proto-single-field:nth(1)');
      const field1Value = field1.scope().$eval(field1.attr('value'));
      const field2Value = field2.scope().$eval(field2.attr('value'));

      fooValue['value']['field_1'] = {
        type: 'PrimitiveType',
        value: 'fooNew',
      };

      $rootScope.$apply();

      const field1New = element.find('grr-form-proto-single-field:nth(0)');
      const field2New = element.find('grr-form-proto-single-field:nth(1)');
      const field1ValueNew = field1New.scope().$eval(field1New.attr('value'));
      const field2ValueNew = field2New.scope().$eval(field2New.attr('value'));

      // Only field1 value has to actually change since the field got
      // updated. field2 should simply stay the same.
      expect(field1ValueNew).not.toBe(field1Value);
      expect(field2ValueNew).toBe(field2Value);
    });

    it('with unset fields: updates only changed on external change', () => {
      const fooValue = defaultFooStructValue;
      const element = renderTestTemplate(fooValue);

      const field1 = element.find('grr-form-proto-single-field:nth(0)');
      const field2 = element.find('grr-form-proto-single-field:nth(1)');
      const field1Value = field1.scope().$eval(field1.attr('value'));
      const field2Value = field2.scope().$eval(field2.attr('value'));

      fooValue['value']['field_1'] = {
        type: 'PrimitiveType',
        value: 'foo',
      };

      $rootScope.$apply();

      const field1New = element.find('grr-form-proto-single-field:nth(0)');
      const field2New = element.find('grr-form-proto-single-field:nth(1)');
      const field1ValueNew = field1New.scope().$eval(field1New.attr('value'));
      const field2ValueNew = field2New.scope().$eval(field2New.attr('value'));

      // Only field1 value has to actually change since the field got
      // updated. field2 should simply stay the same.
      expect(field1ValueNew).not.toBe(field1Value);
      expect(field2ValueNew).toBe(field2Value);
    });

    it('updates fields when value.field_1 is cleared externally', () => {
      const fooValue = defaultFooStructValue;
      fooValue['value']['field_1'] = {
        type: 'PrimitiveType',
        value: 'foo',
      };
      const element = renderTestTemplate(fooValue);

      let field = element.find('grr-form-proto-single-field:nth(0)');
      let fieldValue = field.scope().$eval(field.attr('value'));
      expect(fieldValue['value']).toBe('foo');

      fooValue['value']['field_1'] = undefined;

      $rootScope.$apply();

      field = element.find('grr-form-proto-single-field:nth(0)');
      fieldValue = field.scope().$eval(field.attr('value'));
      expect(fieldValue).toEqual({
        type: 'PrimitiveType',
        value: '',
      });
    });

    const icon =
        ((element, iconName) => element.find(`i.glyphicon-${iconName}`));

    const expectIcon = ((element, iconName) => {
      expect(icon(element, iconName).length).toBe(1);
    });

    const expectNoIcon = ((element, iconName) => {
      expect(icon(element, iconName).length).toBe(0);
    });

    it('does not render collapse/expand icon if depth is not set', () => {
      const element =
          renderTestTemplate(defaultFooStructValue, {depth: undefined});

      expectNoIcon(element, 'plus');
    });

    it('does not render collapse/expand icon if depth is 0 or 1', () => {
      let element = renderTestTemplate(defaultFooStructValue, {depth: 0});
      expectNoIcon(element, 'plus');

      element = renderTestTemplate(defaultFooStructValue, {depth: 1});
      expectNoIcon(element, 'plus');
    });

    it('renders as collapsed if metadata.depth is 2', () => {
      const element = renderTestTemplate(defaultFooStructValue, {depth: 2});
      expectIcon(element, 'plus');
    });

    it('expands if collapsed plus icons is clicked', () => {
      const element = renderTestTemplate(defaultFooStructValue, {depth: 2});
      // Nothing is shown by default, field is collapsed.
      expect(element.find('grr-form-proto-single-field').length).toBe(0);

      // Click on the '+' icon to expand it.
      browserTriggerEvent(icon(element, 'plus'), 'click');
      // Check that fields got displayed.
      expect(element.find('grr-form-proto-single-field').length).toBe(3);
      // Check that '+' icon became '-' icon.
      expectNoIcon(element, 'plus');
      expectIcon(element, 'minus');
    });

    it('collapses if expanded and minus icon is clicked', () => {
      const element = renderTestTemplate(defaultFooStructValue, {depth: 2});

      // Click on the '+' icon to expand element.
      browserTriggerEvent(icon(element, 'plus'), 'click');

      // Click on the '-' icon to collapse it.
      browserTriggerEvent(icon(element, 'minus'), 'click');
      // Check that fields disappeared.
      expect(element.find('grr-form-proto-single-field').length).toBe(0);

      // Check that '-' icon became '+' icon.
      expectNoIcon(element, 'minus');
      expectIcon(element, 'plus');
    });
  });

  describe('form for structure with 1 dynamic field', () => {
    let defaultFooStructValue;

    beforeEach(() => {
      defaultFooStructValue = {
        type: 'Foo',
        mro: ['Foo', 'RDFProtoStruct'],
        value: {},
      };
    });

    beforeEach(() => {
      // Reflection service is a mock. Stub out the getRDFValueDescriptor method
      // and return a promise with the reflection data.
      const data = {
        'Foo': {
          'default': {
            'type': 'Foo',
            'value': {},
          },
          'doc': 'This is a structure Foo.',
          'fields': [
            {
              'doc': 'Field 1 description.',
              'dynamic': true,
              'friendly_name': 'Field 1',
              'index': 1,
              'name': 'field_1',
              'repeated': false,
            },
          ],
          'kind': 'struct',
          'name': 'Foo',
          'mro': ['Foo', 'RDFProtoStruct'],
        },
        'PrimitiveType': {
          'default': {
            'type': 'PrimitiveType',
            'value': '',
          },
          'doc': 'Test primitive type description.',
          'kind': 'primitive',
          'name': 'PrimitiveType',
          'mro': ['PrimitiveType'],
        },
      };

      const reflectionDeferred = $q.defer();
      reflectionDeferred.resolve(data);
      spyOn(grrReflectionServiceMock, 'getRDFValueDescriptor')
          .and.callFake((type, opt_withDeps) => {
            const reflectionDeferred = $q.defer();
            reflectionDeferred.resolve(opt_withDeps ? data : data[type]);
            return reflectionDeferred.promise;
          });
    });

    it('does not prefill non-prefilled dynamic field', () => {
      const fooValue = defaultFooStructValue;
      const element = renderTestTemplate(fooValue);

      const field = element.find('grr-form-proto-repeated-field:nth(0)');
      const fieldValue = field.scope().$eval(field.attr('value'));
      expect(fieldValue).toBeUndefined();
    });

    it('uses existing dynamic field value when it\'s prefilled', () => {
      const fooValue = defaultFooStructValue;
      fooValue.value = {
        field_1: {
          type: 'PrimitiveType',
          value: '42',
        },
      };
      const element = renderTestTemplate(angular.copy(fooValue));

      const field = element.find('grr-form-proto-repeated-field:nth(0)');
      const fieldValue = field.scope().$eval(field.attr('value'));
      expect(fieldValue).toEqual(fooValue.value.field_1);
    });
  });

  describe('form for structure with 1 repeated field', () => {
    beforeEach(() => {
      // Reflection service is a mock. Stub out the getRDFValueDescriptor method
      // and return a promise with the reflection data.
      const data = {
        'Foo': {
          'default': {
            'type': 'Foo',
            'value': {},
          },
          'doc': 'This is a structure Foo.',
          'fields': [
            {
              'default': {
                'mro': ['PrimitiveType'],
                'type': 'PrimitiveType',
                'value': '',
              },
              'doc': 'Field 1 description.',
              'dynamic': false,
              'friendly_name': 'Field 1',
              'index': 1,
              'name': 'field_1',
              'repeated': true,
              'type': 'PrimitiveType',
            },
          ],
          'kind': 'struct',
          'mro': ['Foo', 'RDFProtoStruct'],
        },
        'PrimitiveType': {
          'default': {
            'type': 'PrimitiveType',
            'value': '',
          },
          'doc': 'Test primitive type description.',
          'kind': 'primitive',
          'mro': ['PrimitiveType'],
          'name': 'PrimitiveType',
        },
      };

      spyOn(grrReflectionServiceMock, 'getRDFValueDescriptor')
          .and.callFake((type, opt_withDeps) => {
            const reflectionDeferred = $q.defer();
            reflectionDeferred.resolve(opt_withDeps ? data : data[type]);
            return reflectionDeferred.promise;
          });
    });

    it('does not prefill the model', () => {
      const fooValue = {
        type: 'Foo',
        value: {},
      };
      renderTestTemplate(fooValue);

      expect(fooValue.value).toEqual({});
    });

    it('does not overwrite prefilled data', () => {
      const fooValue = {
        type: 'Foo',
        value: {
          field_1: [
            {
              type: 'PrimitiveType',
              value: '42',
            },
          ],
        },
      };
      renderTestTemplate(fooValue);
      expect(fooValue.value.field_1.length).toBe(1);
      expect(fooValue.value.field_1[0]).toEqual({
        type: 'PrimitiveType',
        value: '42',
      });
    });

    it('updates the model when repeated field is changed', () => {
      const fooValue = {
        type: 'Foo',
        value: {},
      };
      const element = renderTestTemplate(fooValue);

      const field = element.find('grr-form-proto-repeated-field:nth(0)');
      const fieldValue = field.scope().$eval(field.attr('value'));
      fieldValue.push({'type': 'PrimitiveType', value: '42'});
      $rootScope.$apply();

      expect(fooValue.value).toEqual({
        field_1: [
          {
            type: 'PrimitiveType',
            value: '42',
          },
        ],
      });
    });

    it('updates fields when repeated field is changed externally', () => {
      const fooValue = {
        type: 'Foo',
        value: {},
      };
      const element = renderTestTemplate(fooValue);

      let field = element.find('grr-form-proto-repeated-field:nth(0)');
      let fieldValue = field.scope().$eval(field.attr('value'));
      expect(fieldValue).toEqual([]);

      fooValue['value']['field_1'] = [{'type': 'PrimitiveType', value: '42'}];
      $rootScope.$apply();

      field = element.find('grr-form-proto-repeated-field:nth(0)');
      fieldValue = field.scope().$eval(field.attr('value'));
      expect(fieldValue).toEqual([{'type': 'PrimitiveType', value: '42'}]);
    });

    it('renders the repeated field with corresponding directive', () => {
      const fooValue = {
        type: 'Foo',
        value: {},
      };
      const element = renderTestTemplate(fooValue);

      // Check that grr-form-proto-repeated-field directive is used to trender
      // the repeated field.
      expect(element.find('grr-form-proto-repeated-field').length).toBe(1);
    });

    it('does not render repeated field from the hidden-fields', () => {
      const fooValue = {
        type: 'Foo',
        value: {},
      };
      const element = renderTestTemplate(fooValue, undefined, ['field_1']);

      expect(element.find('grr-form-proto-repeated-field').length).toBe(0);
    });
  });

  describe('form for union-type structure', () => {
    beforeEach(() => {
      // Reflection service is a mock. Stub out the getRDFValueDescriptor method
      // and return a promise with the reflection data.
      const data = {
        'Foo': {
          'default': {
            'type': 'Foo',
            'value': {},
          },
          'doc': 'This is a structure Foo.',
          // Non-empty union_field attribute forces GRR to treat this structure
          // as a union-type structure.
          'union_field': 'type',
          'fields': [
            {
              'default': {
                'mro': ['PrimitiveType'],
                'type': 'PrimitiveType',
                'value': '',
              },
              'doc': 'Field 1 description.',
              'dynamic': false,
              'friendly_name': 'Union Type',
              'index': 1,
              'name': 'type',
              'repeated': true,
              'type': 'PrimitiveType',
            },
          ],
          'kind': 'struct',
          'mro': ['Foo', 'RDFProtoStruct'],
        },
        'PrimitiveType': {
          'default': {
            'type': 'PrimitiveType',
            'value': '',
          },
          'doc': 'Test primitive type description.',
          'kind': 'primitive',
          'mro': ['PrimitiveType'],
          'name': 'PrimitiveType',
        },
      };

      const reflectionDeferred = $q.defer();
      reflectionDeferred.resolve(data);
      spyOn(grrReflectionServiceMock, 'getRDFValueDescriptor')
          .and.callFake((type, opt_withDeps) => {
            const reflectionDeferred = $q.defer();
            reflectionDeferred.resolve(opt_withDeps ? data : data[type]);
            return reflectionDeferred.promise;
          });
    });

    it('delegates union-type structure rendering', () => {
      const fooValue = {
        type: 'Foo',
        value: {},
      };
      const element = renderTestTemplate(fooValue);

      // Check that rendering is delegated to grr-form-proto-union.
      expect(element.find('grr-form-proto-union').length).toBe(1);
    });
  });
});


exports = {};

;return exports;});

//angular-components/forms/semantic-proto-repeated-field-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.semanticProtoRepeatedFieldFormDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, stubDirective, testsModule} = goog.require('grrUi.tests');
const {formsModule} = goog.require('grrUi.forms.forms');


describe('semantic proto repeated field form directive', () => {
  let $compile;
  let $q;
  let $rootScope;

  beforeEach(module('/static/angular-components/forms/semantic-proto-repeated-field-form.html'));
  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  // Stub out grrFormValue directive, as all rendering is going to be
  // delegated to it.
  stubDirective('grrFormValue');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
  }));

  const renderTestTemplate = (value, descriptor, field, noCustomTemplate) => {
    $rootScope.value = value;
    $rootScope.descriptor = descriptor;
    $rootScope.field = field;
    $rootScope.noCustomTemplate = noCustomTemplate;

    const template = '<grr-form-proto-repeated-field value="value" ' +
        'descriptor="descriptor" field="field" ' +
        'no-custom-template="noCustomTemplate" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  const typedPrimitiveValue = {
    type: 'PrimitiveType',
    value: 42,
  };

  const primitiveValueDescriptor = {
    type: 'PrimitiveType',
    mro: ['PrimitiveType'],
    kind: 'primitive',
    default: angular.copy(typedPrimitiveValue),
  };

  describe('without custom directives', () => {
    beforeEach(inject(($injector) => {
      // Always return false here - i.e. no custom directives are registered for
      // repeated fields.
      const grrSemanticRepeatedFormDirectivesRegistryService =
          $injector.get('grrSemanticRepeatedFormDirectivesRegistryService');

      spyOn(
          grrSemanticRepeatedFormDirectivesRegistryService,
          'findDirectiveForType')
          .and.callFake((type) => {
            const q = $q.defer();
            q.reject();

            return q.promise;
          });
    }));

    it('renders doc and friendly name', () => {
      const element = renderTestTemplate([], primitiveValueDescriptor, {
        doc: 'Field documentation',
        friendly_name: 'Field friendly name',
      });

      expect(element.find('label[title="Field documentation"]').length)
          .not.toBe(0);
      expect(element.text()).toContain('Field friendly name');
    });

    it('delegates items rendering to grr-form-value', () => {
      const element = renderTestTemplate(
          [
            {type: 'PrimitiveType', value: 42},
            {type: 'PrimitiveType', value: 43}
          ],
          primitiveValueDescriptor, {});

      expect(element.find('grr-form-value').length).toBe(2);
    });

    it('adds new item when "Add" is clicked', () => {
      const value = [];

      const element = renderTestTemplate(value, primitiveValueDescriptor, {});
      expect(element.find('grr-form-value').length).toBe(0);

      browserTriggerEvent($('button[name=Add]', element), 'click');
      expect(element.find('grr-form-value').length).toBe(1);
      // Please see http://stackoverflow.com/a/26370331 on why we're using here
      // angular.equals() and not Jasmine's toEqual here.
      expect(angular.equals(value, [typedPrimitiveValue])).toBe(true);

      browserTriggerEvent($('button[name=Add]', element), 'click');
      expect(element.find('grr-form-value').length).toBe(2);
      expect(angular.equals(value, [typedPrimitiveValue,
                                    typedPrimitiveValue])).toBe(true);
    });

    it('removes an item when "Remove" is clicked', () => {
      const value = [
        angular.copy(typedPrimitiveValue), angular.copy(typedPrimitiveValue)
      ];

      const element = renderTestTemplate(value, primitiveValueDescriptor, {});
      expect(element.find('grr-form-value').length).toBe(2);

      browserTriggerEvent($('button[name=Remove]:nth(0)', element), 'click');
      expect(element.find('grr-form-value').length).toBe(1);
      expect(angular.equals(value, [typedPrimitiveValue])).toBe(true);

      browserTriggerEvent($('button[name=Remove]:nth(0)', element), 'click');
      expect(element.find('grr-form-value').length).toBe(0);
      expect(value).toEqual([]);
    });
  });

  describe('with custom directive', () => {
    beforeEach(inject(($injector) => {
      const grrSemanticRepeatedFormDirectivesRegistryService =
          $injector.get('grrSemanticRepeatedFormDirectivesRegistryService');

      spyOn(
          grrSemanticRepeatedFormDirectivesRegistryService,
          'findDirectiveForType')
          .and.callFake((type) => {
            const q = $q.defer();
            q.resolve({
              directive_name: 'fooBar',
            });

            return q.promise;
          });
    }));

    it('renders doc and friendly name', () => {
      const element = renderTestTemplate([], primitiveValueDescriptor, {
        doc: 'Field documentation',
        friendly_name: 'Field friendly name',
      });

      expect(element.find('label[title="Field documentation"]').length)
          .not.toBe(0);
      expect(element.text()).toContain('Field friendly name');
    });

    it('delegates items rendering to grr-form-value', () => {
      const element = renderTestTemplate([], primitiveValueDescriptor, {});

      expect(element.find('foo-bar').length).toBe(1);
    });

    it('ignores custom directive if no-custom-template binding is true', () => {
      const element = renderTestTemplate(
          [
            {type: 'PrimitiveType', value: 42},
            {type: 'PrimitiveType', value: 43}
          ],
          primitiveValueDescriptor, {}, true);

      // No custom directive should be present.
      expect(element.find('foo-bar').length).toBe(0);

      // Default rendering should be used instead.
      expect(element.find('grr-form-value').length).toBe(2);
    });
  });

  describe('with custom directive with "hideCustomTemplateLabel" set', () => {
    beforeEach(inject(($injector) => {
      const grrSemanticRepeatedFormDirectivesRegistryService =
          $injector.get('grrSemanticRepeatedFormDirectivesRegistryService');

      spyOn(
          grrSemanticRepeatedFormDirectivesRegistryService,
          'findDirectiveForType')
          .and.callFake((type) => {
            const q = $q.defer();
            q.resolve({
              directive_name: 'fooBar',
              hideCustomTemplateLabel: true,
            });

            return q.promise;
          });
    }));

    it('does not render custom field\'s label', () => {
      const element = renderTestTemplate([], primitiveValueDescriptor, {});

      expect(element.find('label[title="Field documentation"]').length)
          .toBe(0);
      expect(element.text()).not.toContain('Field friendly name');
      expect(element.find('foo-bar').length).toBe(1);
    });
  });
});


exports = {};

;return exports;});

//angular-components/forms/semantic-proto-single-field-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.semanticProtoSingleFieldFormDirectiveTest');
goog.setTestOnly();

const {formsModule} = goog.require('grrUi.forms.forms');
const {testsModule} = goog.require('grrUi.tests');


describe('semantic proto single field form directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module('/static/angular-components/forms/semantic-proto-form.html'));
  beforeEach(module('/static/angular-components/forms/semantic-proto-union-form.html'));
  beforeEach(module('/static/angular-components/forms/semantic-proto-single-field-form.html'));
  beforeEach(module('/static/angular-components/forms/semantic-proto-repeated-field-form.html'));
  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value, field) => {
    $rootScope.value = value;
    $rootScope.field = field;

    const template = '<grr-form-proto-single-field value="value" ' +
        'field="field" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('renders doc and friendly name', () => {
    const element = renderTestTemplate({}, {
      doc: 'Field documentation',
      friendly_name: 'Field friendly name',
    });

    expect(element.find('label[title="Field documentation"]').length).toBe(1);
    expect(element.text()).toContain('Field friendly name');
  });

  it('delegates rendering to grr-form-value', () => {
    const element = renderTestTemplate({}, {});

    expect(element.find('grr-form-value').length).toBe(1);
  });
});


exports = {};

;return exports;});

//angular-components/forms/semantic-proto-union-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.semanticProtoUnionFormDirectiveTest');
goog.setTestOnly();

const {formsModule} = goog.require('grrUi.forms.forms');
const {testsModule} = goog.require('grrUi.tests');


describe('semantic proto union form directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let descriptor;
  let grrReflectionService;


  beforeEach(module('/static/angular-components/forms/semantic-proto-form.html'));
  beforeEach(module('/static/angular-components/forms/semantic-proto-union-form.html'));
  beforeEach(module('/static/angular-components/forms/semantic-proto-single-field-form.html'));
  beforeEach(module('/static/angular-components/forms/semantic-proto-repeated-field-form.html'));
  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrReflectionService = $injector.get('grrReflectionService');

    grrReflectionService.getRDFValueDescriptor = ((valueType) => {
      const deferred = $q.defer();
      deferred.resolve({
        name: valueType,
        mro: [valueType],
      });
      return deferred.promise;
    });

    descriptor = {
      'default': {
        'mro': ['Foo', 'RDFProtoStruct'],
        'type': 'Foo',
        'value': {},
      },
      'doc': 'This is a structure Foo.',
      'union_field': 'type',
      'fields': [
        {
          'default': {
            'mro': ['PrimitiveType'],
            'type': 'PrimitiveType',
            'value': '',
          },
          'index': 1,
          'name': 'type',
          'repeated': false,
          'type': 'PrimitiveType',
        },
        {
          'default': {
            'mro': ['PrimitiveTypeFoo'],
            'type': 'PrimitiveTypeFoo',
            'value': 'foo',
          },
          'index': 2,
          'name': 'foo',
          'repeated': false,
          'type': 'PrimitiveTypeFoo',
        },
        {
          'default': {
            'mro': ['PrimitiveTypeBar'],
            'type': 'PrimitiveTypeBar',
            'value': 'bar',
          },
          'index': 3,
          'name': 'bar',
          'repeated': false,
          'type': 'PrimitiveTypeBar',
        },
      ],
    };
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;
    $rootScope.descriptor = descriptor;

    const template = '<grr-form-proto-union value="value" ' +
        'descriptor="descriptor" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('displays field based on union field value', () => {
    const value = {
      type: 'Foo',
      mro: ['Foo'],
      value: {
        type: {
          type: 'PrimitiveType',
          mro: ['PrimitiveType'],
          value: 'foo',
        },
        foo: {
          type: 'PrimitiveTypeFoo',
          mro: ['PrimitiveTypeFoo'],
          value: 42,
        },
        bar: {
          type: 'PrimitiveTypeBar',
          mro: ['PrimitiveTypeBar'],
          value: 43,
        },
      },
    };
    const element = renderTestTemplate(value);

    // Check that only the field 'foo' is displayed. As we expect no directives
    // to be registered for PrimitiveTypeFoo and PrimitiveTypeBar, we check
    // for the stub message produced by grr-form-value.
    expect(element.text()).toContain('No directive for type: PrimitiveTypeFoo');
    expect(element.text()).not.toContain(
        'No directive for type: PrimitiveTypeBar');

    value.value.type.value = 'bar';
    $rootScope.$apply();

    // Check that form got updated and now field 'bar' is displayed.
    expect(element.text()).toContain('No directive for type: PrimitiveTypeBar');
    expect(element.text()).not.toContain(
        'No directive for type: PrimitiveTypeFoo');
  });

  it('resets entered data when union type is changed', () => {
    const value = {
      type: 'Foo',
      mro: ['Foo'],
      value: {
        type: {
          type: 'PrimitiveType',
          mro: ['PrimitiveType'],
          value: 'foo',
        },
        foo: {
          type: 'PrimitiveTypeFoo',
          mro: ['PrimitiveTypeFoo'],
        },
        bar: {
          type: 'PrimitiveTypeBar',
          mro: ['PrimitiveTypeBar'],
        },
      },
    };
    renderTestTemplate(value);
    $rootScope.$apply();

    value.value.foo.value = 42;
    $rootScope.$apply();
    expect(value.value.foo.value).toBe(42);

    value.value.type.value = 'bar';
    $rootScope.$apply();
    expect(value.value.foo.value).toEqual({});
  });
});


exports = {};

;return exports;});

//angular-components/forms/semantic-value-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.semanticValueFormDirectiveTest');
goog.setTestOnly();

const {clearCaches} = goog.require('grrUi.forms.semanticValueFormDirective');
const {formsModule} = goog.require('grrUi.forms.forms');
const {testsModule} = goog.require('grrUi.tests');


describe('semantic value form directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrSemanticFormDirectivesRegistryService;

  let grrReflectionService;

  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    clearCaches();

    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrSemanticFormDirectivesRegistryService = $injector.get(
        'grrSemanticFormDirectivesRegistryService');
    grrReflectionService = $injector.get('grrReflectionService');

    grrReflectionService.getRDFValueDescriptor = ((valueType) => {
      const deferred = $q.defer();
      deferred.resolve({
        name: valueType,
        mro: [valueType],
      });
      return deferred.promise;
    });
  }));

  const renderTestTemplate = (value, metadata) => {
    $rootScope.value = value;
    $rootScope.metadata = metadata;

    const template = '<grr-form-value value="value" metadata="metadata" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows error message if no corresponding directive found', () => {
    const fooValue = {
      type: 'Foo',
      mro: ['Foo'],
      value: 'foo',
    };

    const element = renderTestTemplate(fooValue);
    expect(element.text()).toContain('No directive for type: Foo');
  });

  it('renders registered type with a corresponding directive', () => {
    // This directive does not exist and Angular won't process it,
    // but it still will be inserted into DOM and we can check
    // that it's inserted correctly.
    const directiveMock = {
      directive_name: 'theTestDirective',
    };

    grrSemanticFormDirectivesRegistryService.registerDirective(
        'Foo', directiveMock);
    const fooValue = {
      type: 'Foo',
      mro: ['Foo'],
      value: 'foo',
    };

    const element = renderTestTemplate(fooValue);
    expect($('the-test-directive', element).length).toBe(1);
    // Check that registered directive has "value" attribute specified.
    expect($('the-test-directive[value]', element).length).toBe(1);
    // Check that metadata are passed to the nested directive.
    expect($('the-test-directive[metadata]', element).length).toBe(1);
  });

  it('destroys nested directive\'s scope if value type is changed', () => {
    const directiveFooMock = {
      directive_name: 'theFooTestDirective',
    };
    const directiveBarMock = {
      directive_name: 'theBarTestDirective',
    };

    grrSemanticFormDirectivesRegistryService.registerDirective(
        'Foo', directiveFooMock);
    grrSemanticFormDirectivesRegistryService.registerDirective(
        'Bar', directiveBarMock);

    const fooValue = {
      type: 'Foo',
      mro: ['Foo'],
      value: 'foo',
    };
    const barValue = {
      type: 'Bar',
      mro: ['Bar'],
      value: 'bar',
    };

    const element = renderTestTemplate(fooValue);
    expect($('the-foo-test-directive', element).length).toBe(1);

    const fooScope = $('the-foo-test-directive', element).scope();
    fooScope.foo = 42;

    let firesCount = 0;
    fooScope.$watch('foo', () => {
      firesCount += 1;
    });

    // Watcher should be called once when it's installed.
    $rootScope.$apply();
    expect(firesCount).toBe(1);

    // Then it should be called when the value changes.
    fooScope.foo = 43;
    $rootScope.$apply();
    expect(firesCount).toBe(2);

    // Change the type of the value handled by directive. This should trigger
    // it to replace the nested directive.
    angular.extend(fooValue, barValue);
    $rootScope.$apply();
    expect($('the-bar-test-directive', element).length).toBe(1);

    // Watchers installed on the directive's scope that corresponds to the
    // previous value type (the-foo-test-directive), shouldn't be fired,
    // as this scope is supposed to be destroyed.
    fooScope.foo = 44;
    $rootScope.$apply();
    expect(firesCount).toBe(2);
  });
});


exports = {};

;return exports;});

//angular-components/forms/timerange-form-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.timerangeFormDirectiveTest');
goog.setTestOnly();

const {formsModule} = goog.require('grrUi.forms.forms');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('timerange form directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrReflectionService;


  beforeEach(module('/static/angular-components/forms/timerange-form.html'));
  beforeEach(module(formsModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrFormValue');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrReflectionService = $injector.get('grrReflectionService');

    spyOn(grrReflectionService, 'getRDFValueDescriptor')
        .and.callFake((valueType) => {
          const deferred = $q.defer();

          if (valueType === 'RDFDatetime') {
            deferred.resolve({
              default: {
                type: 'RDFDatetime',
                value: 0,
              },
            });
          } else if (valueType === 'DurationSeconds') {
            deferred.resolve({
              default: {
                type: 'DurationSeconds',
                value: 0,
              },
            });
          }

          return deferred.promise;
        });
  }));

  const renderTestTemplate =
      ((startTimeSecs, durationSecs, startTimeLabel, durationLabel) => {
        $rootScope.startTimeSecs = startTimeSecs;
        $rootScope.durationSecs = durationSecs;

        if (angular.isDefined(startTimeLabel)) {
          $rootScope.startTimeLabel = startTimeLabel;
        }

        if (angular.isDefined(durationLabel)) {
          $rootScope.durationLabel = durationLabel;
        }

        const template = '<grr-form-timerange ' +
            'start-time-secs="startTimeSecs" ' +
            'duration-secs="durationSecs" ' +

            (angular.isDefined(startTimeLabel) ?
                 'start-time-label="startTimeLabel" ' :
                 '') +

            (angular.isDefined(durationLabel) ?
                 'duration-label="durationLabel" ' :
                 '') +
            '></grr-form-timerange>';
        const element = $compile(template)($rootScope);
        $rootScope.$apply();

        return element;
      });

  it('shows the given scope params initially', () => {
    const element = renderTestTemplate(123, 456);

    let directive = element.find('grr-form-value:nth(0)');
    expect(directive.scope().$eval(directive.attr('value'))).toEqual(
        {
          type: 'RDFDatetime',
          value: 123000000  // This should be converted to μs.
        });

    directive = element.find('grr-form-value:nth(1)');
    expect(directive.scope().$eval(directive.attr('value'))).toEqual({
      type: 'DurationSeconds',
      value: 456,
    });
  });

  it('shows default labels by default', () => {
    const element = renderTestTemplate(123, 456);

    expect(element.find('label:nth(0)').text()).toBe('Time range start time');
    expect(element.find('label:nth(1)').text()).toBe('Time range duration');
  });

  it('shows custom labels if given', () => {
    const element = renderTestTemplate(
        123, 456, 'Custom start time label', 'Custom duration label');

    expect(element.find('label:nth(0)').text()).toBe('Custom start time label');
    expect(element.find('label:nth(1)').text()).toBe('Custom duration label');
  });

  it('forwards changed values to parent scope', () => {
    const element = renderTestTemplate(123, 456);

    let directive = element.find('grr-form-value:nth(0)');
    directive.scope().$eval(directive.attr('value') + '.value = 321000000');

    directive = element.find('grr-form-value:nth(1)');
    directive.scope().$eval(directive.attr('value') + '.value = 654');

    $rootScope.$apply();

    expect(element.scope().$eval(element.attr('start-time-secs'))).toEqual(321);
    expect(element.scope().$eval(element.attr('duration-secs'))).toEqual(654);
  });
});


exports = {};

;return exports;});

//angular-components/forms/utils_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.forms.utilsTest');
goog.setTestOnly();

const {valueHasErrors} = goog.require('grrUi.forms.utils');

describe('forms utils', () => {
  describe('valueHasErrors', () => {

    it('returns false for a primitive value without errors', () => {
      expect(valueHasErrors({
        type: 'RDFString',
        value: 'blah',
      })).toBe(false);
    });

    it('returns true for a primitive value with an error', () => {
      expect(valueHasErrors({
        type: 'RDFString',
        value: 'blah',
        validationError: 'Oh no!',
      })).toBe(true);
    });

    it('returns false for a value with a struct field without errors', () => {
      expect(valueHasErrors({
        type: 'RDFString',
        value: {
          foo: {
            type: 'RDFInteger',
            value: 42,
          },
        },
      })).toBe(false);
    });

    it('returns true for a value with a struct field with an error', () => {
      expect(valueHasErrors({
        type: 'RDFString',
        value: {
          foo: {
            type: 'RDFInteger',
            value: 42,
            validationError: 'Oh no!',
          },
        },
      })).toBe(true);
    });

    it('returns false for a value with an array field without errors', () => {
      expect(valueHasErrors({
        type: 'RDFString',
        value: {
          foo: [
            {
              type: 'RDFInteger',
              value: 42,
            },
          ],
        },
      })).toBe(false);
    });

    it('returns true for a value with an array field with an error', () => {
      expect(valueHasErrors({
        type: 'RDFString',
        value: {
          foo: [
            {
              type: 'RDFInteger',
              value: 42,
              validationError: 'Oh no!',
            },
          ],
        },
      })).toBe(true);
    });
  });
});


exports = {};

;return exports;});

//angular-components/hunt/hunt-stats-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.hunt.huntStatsDirectiveTest');
goog.setTestOnly();

const {huntModule} = goog.require('grrUi.hunt.hunt');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('hunt stats directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;


  beforeEach(module('/static/angular-components/hunt/hunt-stats.html'));
  beforeEach(module(huntModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrComparisonChart');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrApiService = $injector.get('grrApiService');
  }));

  const render = (huntId) => {
    $rootScope.huntId = huntId;

    const template = '<grr-hunt-stats hunt-id="huntId" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows user cpu usage histogram', () => {
    const statsResponse = {
      stats: {
        type: 'ClientResourcesStats',
        value: {
          user_cpu_stats: {
            type: 'RunningStats',
            value: {
              histogram: {
                type: 'StatsHistogram',
                value: {
                  bins: [
                    {
                      type: 'StatsHistogramBin',
                      value: {
                        num: {
                          type: 'long',
                          value: 10.55,
                        },
                        range_max_value: {
                          type: 'float',
                          value: 16.55,
                        },
                      },
                    },
                    {
                      type: 'StatsHistogramBin',
                      value: {
                        num: {
                          type: 'long',
                          value: 5.55,
                        },
                        range_max_value: {
                          type: 'float',
                          value: 32768.55,
                        },
                      },
                    },
                    {
                      type: 'StatsHistogramBin',
                      value: {
                        num: {
                          type: 'long',
                          value: 9.55,
                        },
                        range_max_value: {
                          type: 'float',
                          value: 62768.55,
                        },
                      },
                    }
                  ],
                },
              },
            },
          },
        },
      },
    };

    const deferred = $q.defer();
    deferred.resolve({ data: statsResponse });
    spyOn(grrApiService, 'get').and.returnValue(deferred.promise);

    const element = render('H:12345678');
    const directive = element.find('grr-comparison-chart:nth(0)');
    const directiveTypedData =
          directive.scope().$eval(directive.attr('typed-data'));
    expect(directiveTypedData['value']).toEqual({
      data: [
        {value: {label: {value: '< 16.6s'}, x: {value: 10.55}}},
        {value: {label: {value: '< 32768.6s'}, x: {value: 5.55}}},
        // Max range value of the last bucket should be ignored and
        // the one of the one-before-the-last bucket should be used.
        {value: {label: {value: '> 32768.6s'}, x: {value: 9.55}}},
      ]
    });
  });

  it('shows system cpu usage histogram', () => {
    const statsResponse = {
      stats: {
        type: 'ClientResourcesStats',
        value: {
          system_cpu_stats: {
            type: 'RunningStats',
            value: {
              histogram: {
                type: 'StatsHistogram',
                value: {
                  bins: [
                    {
                      type: 'StatsHistogramBin',
                      value: {
                        num: {
                          type: 'long',
                          value: 10.55,
                        },
                        range_max_value: {
                          type: 'float',
                          value: 16.55,
                        },
                      },
                    },
                    {
                      type: 'StatsHistogramBin',
                      value: {
                        num: {
                          type: 'long',
                          value: 5.55,
                        },
                        range_max_value: {
                          type: 'float',
                          value: 32768.55,
                        },
                      },
                    },
                    {
                      type: 'StatsHistogramBin',
                      value: {
                        num: {
                          type: 'long',
                          value: 9.55,
                        },
                        range_max_value: {
                          type: 'float',
                          value: 62768.55,
                        },
                      },
                    }
                  ],
                },
              },
            },
          },
        },
      },
    };

    const deferred = $q.defer();
    deferred.resolve({ data: statsResponse });
    spyOn(grrApiService, 'get').and.returnValue(deferred.promise);

    const element = render('H:12345678');
    const directive = element.find('grr-comparison-chart:nth(1)');
    const directiveTypedData =
        directive.scope().$eval(directive.attr('typed-data'));
    expect(directiveTypedData['value']).toEqual({
      data: [
        {value: {label: {value: '< 16.6s'}, x: {value: 10.55}}},
        {value: {label: {value: '< 32768.6s'}, x: {value: 5.55}}},
        // Max range value of the last bucket should be ignored and
        // the one of the one-before-the-last bucket should be used.
        {value: {label: {value: '> 32768.6s'}, x: {value: 9.55}}},
      ]
    });
  });

  it('shows network bytes histogram with correct values and xaxis labels',
     () => {
       const statsResponse = {
         stats: {
           type: 'ClientResourcesStats',
           value: {
             network_bytes_sent_stats: {
               type: 'RunningStats',
               value: {
                 histogram: {
                   type: 'StatsHistogram',
                   value: {
                     bins: [
                       {
                         type: 'StatsHistogramBin',
                         value: {
                           num: {
                             type: 'long',
                             value: 10,
                           },
                           range_max_value: {
                             type: 'float',
                             value: 16.0,
                           },
                         },
                       },
                       {
                         type: 'StatsHistogramBin',
                         value: {
                           num: {
                             type: 'long',
                             value: 5,
                           },
                           range_max_value: {
                             type: 'float',
                             value: 32768.0,
                           },
                         },
                       },
                       {
                         type: 'StatsHistogramBin',
                         value: {
                           num: {
                             type: 'long',
                             value: 9,
                           },
                           range_max_value: {
                             type: 'float',
                             value: 62768.0,
                           },
                         },
                       }
                     ],
                   },
                 },
               },
             },
           },
         },
       };

       const deferred = $q.defer();
       deferred.resolve({data: statsResponse});
       spyOn(grrApiService, 'get').and.returnValue(deferred.promise);

       const element = render('H:12345678');
       const directive = element.find('grr-comparison-chart:nth(2)');
       const directiveTypedData =
           directive.scope().$eval(directive.attr('typed-data'));
       expect(directiveTypedData['value']).toEqual({
         data: [
           {value: {label: {value: '< 16 B'}, x: {value: 10}}},
           {value: {label: {value: '< 32 KiB'}, x: {value: 5}}},
           // Max range value of the last bucket should be ignored and
           // the one of the one-before-the-last bucket should be used.
           {value: {label: {value: '> 32 KiB'}, x: {value: 9}}},
         ]
       });
     });
});


exports = {};

;return exports;});

//angular-components/hunt/rapid-hunt-status-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.hunt.rapidHuntStatusDirectiveTest');
goog.setTestOnly();

const {huntModule} = goog.require('grrUi.hunt.hunt');
const {isEligible} = goog.require('grrUi.hunt.rapidHuntStatusDirective');
const {testsModule} = goog.require('grrUi.tests');


describe('rapidHuntStatusDirective.isEligible', () => {

  it('considers FileFinder flow with default args eligible', () => {
    expect(
        isEligible(
            'FileFinder',
            {type: 'FileFinderArgs', value: {}}
        )
    ).toBe(true);
  });

  it('considers ClientFileFinder flow with default args eligible', () => {
    expect(
        isEligible(
            'ClientFileFinder',
            {type: 'FileFinderArgs', value: {}}
        )
    ).toBe(true);
  });

  it('considers ListProcesses flow not eligible', () => {
    expect(
        isEligible(
            'ListProcesses',
            {type: 'ListProcessesArgs', value: {}}
        )
    ).toBe(false);
  });

  it('considers FileFinder with a recursive glob non-eligible', () => {
    expect(
        isEligible(
            'ClientFileFinder',
            {
              type: 'FileFinderArgs',
              value: {
                paths: [
                  {
                    type: 'GlobExpression',
                    value: '/foo/**'
                  }
                ]
              }
            }
        )
    ).toBe(false);
  });

  it('considers FileFinder with a single star in a glob eligible', () => {
    expect(
        isEligible(
            'ClientFileFinder',
            {
              type: 'FileFinderArgs',
              value: {
                paths: [
                  {
                    type: 'GlobExpression',
                    value: '/foo/*'
                  }
                ]
              }
            }
        )
    ).toBe(true);
  });

  it('considers FileFinder with two stars in a glob eligible', () => {
    expect(
        isEligible(
            'ClientFileFinder',
            {
              type: 'FileFinderArgs',
              value: {
                paths: [
                  {
                    type: 'GlobExpression',
                    value: '/foo/*/bar/*'
                  }
                ]
              }
            }
        )
    ).toBe(false);
  });

  it('considers FileFinder with DOWNLOAD action non eligible', () => {
    expect(
        isEligible(
            'ClientFileFinder',
            {
              type: 'FileFinderArgs',
              value: {
                action: {
                  type: 'FileFinderAction',
                  value: {
                    action_type: {
                      type: 'EnumNamedValue',
                      value: 'DOWNLOAD'
                    }
                  }
                }
              }
            }
        )
    ).toBe(false);
  });
});

describe('grr-rapid-hunt-status directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;


  beforeEach(module('/static/angular-components/hunt/rapid-hunt-status.html'));
  beforeEach(module(huntModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrApiService = $injector.get('grrApiService');
  }));

  const setRapidHuntsEnabled = (flag) => {
    const deferred = $q.defer();
    deferred.resolve({
      data: {
        value: {
          value: flag
        }
      }
    });
    spyOn(grrApiService, 'getCached').and.returnValue(deferred.promise);
  };

  const render = (flowName, flowArgs, clientRate) => {
    $rootScope.flowName = flowName;
    $rootScope.flowArgs = flowArgs;
    $rootScope.clientRate = clientRate;

    const template = '<grr-rapid-hunt-status flow-name="flowName" ' +
          'flow-args="flowArgs" client-rate="clientRate" ' +
          'is-eligible="isEligible" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('does nothing if rapid hunts are turned off', () => {
    setRapidHuntsEnabled(false);

    const element = render('FileFinder',
                           {type: 'FileFinderArgs', value:{}},
                           0);
    expect(element.text()).not.toContain('eligible');
  });

  it('correctly renders "eligible" note if rapid hunts are on', () => {
    setRapidHuntsEnabled(true);

    const element = render('FileFinder',
                           {type: 'FileFinderArgs', value:{}},
                           0);
    expect(element.text()).toContain('is eligible');
  });

  it('renders "Client rate set to 0" if rapid hunts are on and client rate is 0',
     () => {
       setRapidHuntsEnabled(true);

       const element = render('FileFinder',
                              {type: 'FileFinderArgs', value:{}},
                              0);
       expect(element.text()).toContain('Client rate set to 0');
     });

  it('omits "Client rate set to 0" if rapid hunts are on but client rate not 0',
     () => {
       setRapidHuntsEnabled(true);

       const element = render('FileFinder',
                              {type: 'FileFinderArgs', value:{}},
                              42);
       expect(element.text()).not.toContain('Client rate set to 0');
     });

  it('correctly renders "non eligible" note if rapid hunts are off', () => {
    setRapidHuntsEnabled(true);

    const element = render('ListProcesses',
                           {type: 'ListProcessesArgs', value:{}},
                           0);
    expect(element.text()).toContain('is not eligible');
  });

});

;return exports;});

//angular-components/hunt/utils_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.hunt.utilsTest');
goog.setTestOnly();

const {huntExpirationTime} = goog.require('grrUi.hunt.utils');


describe('hunt utils', () => {
  describe('huntExpirationTime', () => {
    const getDuration = (millis) => ({
      type: 'DurationSeconds',
      value: millis / 1000000,
    });

    const getTimestamp = (epoch_millis) => ({
      type: 'RDFDatetime',
      value: epoch_millis,
    });

    const getHunt = (value) => ({
      type: 'ApiHunt',
      value: value,
    });

    it('returns `undefined` if initial start time is not defined', () => {
      const hunt = getHunt({
        duration: getDuration(42),
      });

      expect(huntExpirationTime(hunt)).toBeUndefined();
    });

    it('returns `undefined` if duration is not defined', () => {
      const hunt = getHunt({
        init_start_time: getTimestamp(1337),
      });

      expect(huntExpirationTime(hunt)).toBeUndefined();
    });

    it('returns a value if both start time and duration are defined', () => {
      const hunt = getHunt({
        init_start_time: getTimestamp(10800),
        duration: getDuration(2000),
      });

      expect(huntExpirationTime(hunt)).toEqual(getTimestamp(12800));
    });
  });
});

;return exports;});

//angular-components/output-plugins/output-plugin-logs-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.outputPlugins.outputPluginLogsDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, stubDirective, testsModule} = goog.require('grrUi.tests');
const {outputPluginsModule} = goog.require('grrUi.outputPlugins.outputPlugins');


describe('output plugin logs directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let $timeout;
  let grrApiService;


  beforeEach(module('/static/angular-components/output-plugins/output-plugin-logs.html'));
  beforeEach(module('/static/angular-components/output-plugins/output-plugin-logs-modal.html'));
  beforeEach(module(outputPluginsModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrPagedFilteredTable');

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $timeout = $injector.get('$timeout');
    $rootScope = $injector.get('$rootScope');
    grrApiService = $injector.get('grrApiService');
  }));

  const renderTestTemplate = () => {
    $rootScope.url = 'foo/bar';

    const template = '<grr-output-plugin-logs url="url" label="a foo"' +
        'css-class="label-danger" icon="foo-icon" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('fetches and show items count', () => {
    const deferred = $q.defer();
    deferred.resolve({
      data: {
        total_count: 42,
      },
    });
    spyOn(grrApiService, 'get').and.returnValue(deferred.promise);

    const element = renderTestTemplate();

    expect(element.text()).toContain('42');
  });

  it('shows nothing if total_count is 0', () => {
    const deferred = $q.defer();
    deferred.resolve({
      data: {
        total_count: 0,
      },
    });
    spyOn(grrApiService, 'get').and.returnValue(deferred.promise);

    const element = renderTestTemplate();

    expect(element.text().trim()).toBe('');
  });


  describe('inspect dialog', () => {
    beforeEach(() => {
      const deferred = $q.defer();
      deferred.resolve({
        data: {
          total_count: 42,
        },
      });
      spyOn(grrApiService, 'get').and.returnValue(deferred.promise);
    });

    afterEach(() => {
      // We have to clean document's body to remove modal windows that were not
      // closed.
      $(document.body).html('');
    });

    it('is shown when label is clicked', () => {
      const element = renderTestTemplate();
      browserTriggerEvent(element.find('.label'), 'click');

      expect($(document.body).text()).toContain(
          'Inspect a foo');
    });

    it('closes when close button is clicked', () => {
      const element = renderTestTemplate();
      browserTriggerEvent(element.find('.label'), 'click');

      browserTriggerEvent($('button.close'), 'click');
      $timeout.flush();
      expect($(document.body).text()).not.toContain(
          'Inspect a foo');
    });
  });
});


exports = {};

;return exports;});

//angular-components/output-plugins/output-plugin-note-body-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.outputPlugins.outputPluginNoteBodyDirectiveTest');
goog.setTestOnly();

const {outputPluginsModule} = goog.require('grrUi.outputPlugins.outputPlugins');
const {testsModule} = goog.require('grrUi.tests');


describe('output plugin note directive', () => {
  let $compile;
  let $rootScope;

  let grrOutputPluginsDirectivesRegistryService;

  beforeEach(module(outputPluginsModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');

    grrOutputPluginsDirectivesRegistryService = $injector.get(
        'grrOutputPluginsDirectivesRegistryService');
  }));

  const defaultOutputPlugin = {
    value: {
      plugin_descriptor: {
        value: {
          plugin_name: {
            value: 'Foo',
          },
        },
      },
    },
  };

  const renderTestTemplate = (outputPlugin) => {
    $rootScope.outputPlugin = outputPlugin || angular.copy(defaultOutputPlugin);

    const template = '<grr-output-plugin-note-body ' +
        'output-plugin="outputPlugin" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows nothing if no corresponding directive found', () => {
    const element = renderTestTemplate();
    expect(element.text().trim()).toBe('');
  });

  it('renders registered type with a corresponding directive', () => {
    // This directive does not exist and Angular won't process it,
    // but it still will be inserted into DOM and we can check
    // that it's inserted correctly.
    const directiveMock = {
      directive_name: 'theTestDirective',
    };

    grrOutputPluginsDirectivesRegistryService.registerDirective(
        'Foo', directiveMock);

    const element = renderTestTemplate();
    expect($('the-test-directive', element).length).toBe(1);
  });

  it('passes outputPlugin to the corresponding directive', () => {
    const directiveMock = {
      directive_name: 'theTestDirective',
    };

    grrOutputPluginsDirectivesRegistryService.registerDirective(
        'Foo', directiveMock);

    const element = renderTestTemplate();
    const directive = element.find('the-test-directive');
    expect(directive.scope().$eval(directive.attr('output-plugin'))).toEqual(
        defaultOutputPlugin);
  });
});


exports = {};

;return exports;});

//angular-components/output-plugins/output-plugin-note-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.outputPlugins.outputPluginNoteDirectiveTest');
goog.setTestOnly();

const {outputPluginsModule} = goog.require('grrUi.outputPlugins.outputPlugins');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('output plugin note directive', () => {
  let $compile;
  let $rootScope;

  let grrOutputPluginsDirectivesRegistryService;

  beforeEach(module('/static/angular-components/output-plugins/output-plugin-note.html'));
  beforeEach(module(outputPluginsModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrOutputPluginNoteBody');
  stubDirective('grrOutputPluginLogs');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');

    grrOutputPluginsDirectivesRegistryService = $injector.get(
        'grrOutputPluginsDirectivesRegistryService');
  }));

  const defaultOutputPlugin = {
    value: {
      plugin_descriptor: {
        value: {
          plugin_name: {
            value: 'Foo',
          },
        },
      },
      id: {
        value: '42',
      },
    },
  };

  const renderTestTemplate = (outputPlugin) => {
    $rootScope.outputPlugin = outputPlugin || angular.copy(defaultOutputPlugin);
    $rootScope.outputPluginsUrl = '/foo/bar';

    const template = '<grr-output-plugin-note output-plugin="outputPlugin" ' +
        'output-plugins-url="outputPluginsUrl" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows registered plugin title if registered', () => {
    let element = renderTestTemplate();
    expect(element.text()).toContain('');

    const directiveMock = {
      directive_name: 'theTestDirective',
      output_plugin_title: 'a bar plugin',
    };
    grrOutputPluginsDirectivesRegistryService.registerDirective(
        'Foo', directiveMock);

    element = renderTestTemplate();
    expect(element.text()).toContain('a bar plugin');
  });

  it('shows plugin descriptor name if not registered', () => {
    const element = renderTestTemplate();
    expect(element.text()).toContain('Foo');
  });

  it('delegates rendering to grr-output-plugin-note-body', () => {
    const element = renderTestTemplate();

    const body = element.find('grr-output-plugin-note-body');
    expect(body.scope().$eval(body.attr('output-plugin'))).toEqual(
        defaultOutputPlugin);
  });

  it('delegates logs info rendering to grr-output-plugin-logs', () => {
    const element = renderTestTemplate();

    const logs = element.find('grr-output-plugin-logs:nth(0)');
    expect(logs.scope().$eval(logs.attr('url'))).toEqual(
        '/foo/bar/42/logs');
  });

  it('delegates errors info rendering to grr-output-plugin-logs', () => {
    const element = renderTestTemplate();

    const errors = element.find('grr-output-plugin-logs:nth(1)');
    expect(errors.scope().$eval(errors.attr('url'))).toEqual(
        '/foo/bar/42/errors');
  });
});


exports = {};

;return exports;});

//angular-components/output-plugins/output-plugins-notes-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.outputPlugins.outputPluginsNotesDirectiveTest');
goog.setTestOnly();

const {outputPluginsModule} = goog.require('grrUi.outputPlugins.outputPlugins');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('output plugins notes list directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;


  beforeEach(module('/static/angular-components/output-plugins/' +
        'output-plugins-notes.html'));
  beforeEach(module(outputPluginsModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrOutputPluginNote');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrApiService = $injector.get('grrApiService');
  }));

  const renderTestTemplate = () => {
    $rootScope.outputPluginsUrl = '/foo/bar/plugins';

    const template = '<grr-output-plugins-notes ' +
        'output-plugins-url="outputPluginsUrl" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('requests output plugins metadata via API service', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'get').and.returnValue(deferred.promise);

    renderTestTemplate();

    expect(grrApiService.get).toHaveBeenCalledWith('/foo/bar/plugins');
  });

  it('shows an error when API request fails', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'get').and.returnValue(deferred.promise);
    deferred.reject({data: {message: 'FAIL'}});

    const element = renderTestTemplate();
    expect(element.text()).toContain('Can\'t fetch output plugins list: ' +
        'FAIL');
  });

  it('delegates every plugin display to grr-output-plugin-note', () => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'get').and.returnValue(deferred.promise);

    const plugin1 = {
      value: 'foo',
    };
    const plugin2 = {
      value: 'bar',
    };
    deferred.resolve({
      data: {
        items: [plugin1, plugin2],
      },
    });

    const element = renderTestTemplate();
    expect(element.find('grr-output-plugin-note').length).toBe(2);

    let directive = element.find('grr-output-plugin-note:nth(0)');
    expect(directive.scope().$eval(directive.attr('output-plugin'))).toEqual(
        plugin1);

    directive = element.find('grr-output-plugin-note:nth(1)');
    expect(directive.scope().$eval(directive.attr('output-plugin'))).toEqual(
        plugin2);
  });
});


exports = {};

;return exports;});

//angular-components/routing/aff4-urn-to-url_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.routing.aff4UrnToUrlTest');
goog.setTestOnly();

const {aff4UrnToUrl} = goog.require('grrUi.routing.aff4UrnToUrl');


describe('aff4UrnToUrl()', () => {
  it('doesn\'t convert random string', () => {
    expect(aff4UrnToUrl('aff4:/foo/bar')).toBe(null);
  });

  it('converts non-flow and non-vfs URN in client scope to the client link',
     () => {
       expect(aff4UrnToUrl('aff4:/C.0001000200030004/foo/bar')).toEqual({
         state: 'client',
         params: {clientId: 'C.0001000200030004'},
       });
     });

  it('converts client-scoped fs/os-prefixed URN to VFS link', () => {
    expect(aff4UrnToUrl('aff4:/C.0001000200030004/fs/os/foo/bar')).toEqual({
      state: 'client.vfs',
      params: {clientId: 'C.0001000200030004', path: 'fs/os/foo/bar'},
    });
  });

  it('converts client-scoped flow URN to a flow link', () => {
    expect(aff4UrnToUrl('aff4:/C.0001000200030004/flows/F:123456')).toEqual({
      state: 'client.flows',
      params: {clientId: 'C.0001000200030004', flowId: 'F:123456'},
    });
  });

  it('converts hunt URN to a hunt link', () => {
    expect(aff4UrnToUrl('aff4:/hunts/H:123456')).toEqual({
      state: 'hunts',
      params: {huntId: 'H:123456'},
    });
  });

  it('converts cron job URN to a cron job link', () => {
    expect(aff4UrnToUrl('aff4:/cron/SomeCronJob')).toEqual({
      state: 'crons',
      params: {cronJobId: 'SomeCronJob'},
    });
  });

  it('converts client approval URN to a client approval link', () => {
    expect(aff4UrnToUrl('aff4:/ACL/C.0001000200030004/test/approval_id'))
        .toEqual({
          state: 'clientApproval',
          params: {
            clientId: 'C.0001000200030004',
            username: 'test',
            approvalId: 'approval_id',
          },
        });
  });

  it('converts hunt approval URN to a hunt approval link', () => {
    expect(aff4UrnToUrl('aff4:/ACL/hunts/H:123456/test/approval_id')).toEqual({
      state: 'huntApproval',
      params: {
        huntId: 'H:123456',
        username: 'test',
        approvalId: 'approval_id',
      },
    });
  });

  it('converts cron job approval URN to a cron job approval link', () => {
    expect(aff4UrnToUrl('aff4:/ACL/cron/SomeCronJob/test/approval_id'))
        .toEqual({
          state: 'cronJobApproval',
          params: {
            cronJobId: 'SomeCronJob',
            username: 'test',
            approvalId: 'approval_id',
          },
        });
  });

  it('handles non-URL-friendly characters correctly', () => {
    expect(aff4UrnToUrl('aff4:/C.0001000200030004/fs/os/_f$o/bA%')).toEqual({
      state: 'client.vfs',
      params: {clientId: 'C.0001000200030004', path: 'fs/os/_f$o/bA%'},
    });
  });
});


exports = {};

;return exports;});

//angular-components/routing/rewrite-url_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.routing.rewriteUrlTest');
goog.setTestOnly();

const {rewriteUrl} = goog.require('grrUi.routing.rewriteUrl');


describe('rewriteUrl()', () => {
  const mapping = {};

  // Crons.
  mapping['main=ManageCron'] = '/crons/';
  mapping['main=ManageCron&cron_job_urn=aff4:/cron/CleanTemp'] = '/crons/CleanTemp';

  // Hunts.
  mapping['main=ManageHunts'] = '/hunts/';
  mapping['main=ManageHunts&hunt_id=aff4:/hunts/H:123456'] = '/hunts/H:123456';

  // Virtual File System.
  mapping['main=VirtualFileSystemView&c=C.dc1a70ddaaba407a&tag=AFF4Stats' +
      '&t=_fs-tsk-_5C_5C_3F_5CVolume_7B649ac6fa_2D9ab4_' +
      '2D11e5_2Db332_2D806e6f6e6963_7D'] = '/clients/C.dc1a70ddaaba407a/vfs/fs/tsk/' +
          '%5C%5C%3F%5CVolume%7B649ac6fa-9ab4-11e5-b332-806e6f6e6963%7D/';

  // Misc.
  mapping['main=GlobalLaunchFlows'] = '/global-flows';
  mapping['main=ServerLoadView'] = '/server-load';
  mapping['main=BinaryConfigurationView'] = '/manage-binaries';
  mapping['main=ConfigManager'] = '/config';
  mapping['main=ArtifactManagerView'] = '/artifacts';
  mapping['main=ApiDocumentation'] = '/api-docs';

  // ACL checks.
  mapping['main=GrantAccess&acl=' +
      'aff4%3A%2FACL%2Fhunts%2FH%3A55AAAA70%2Ftest%2Fapproval%3A6AFF3CC9'] =
      '/users/test/approvals/hunt/H:55AAAA70/approval:6AFF3CC9';
  mapping['main=GrantAccess&acl=' +
      'aff4%3A%2FACL%2FC.833c593a0fe6aca0%2Ftest%2Fapproval%3A8935BE23'] =
      '/users/test/approvals/client/C.833c593a0fe6aca0/approval:8935BE23';

  // Canary test.
  mapping['main=CanaryTestRenderer'] = '/canary-test';

  // HostTable.
  mapping['main=HostTable'] = '/search?q=';
  mapping['main=HostTable&q=test'] = '/search?q=test';

  it('should map legacy URLs to correct sane URLs', () => {
    angular.forEach(mapping, (targetUrl, legacyUrl) => {
      expect(rewriteUrl(legacyUrl)).toEqual(targetUrl);
    });
  });
});


exports = {};

;return exports;});

//angular-components/semantic/byte-size-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.byteSizeDirectiveTest');
goog.setTestOnly();

const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {testsModule} = goog.require('grrUi.tests');


describe('grrByteSize directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module('/static/angular-components/semantic/byte-size.html'));
  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-byte-size value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows "-" when value is empty', () => {
    const value = {
      type: 'ByteSize',
      value: null,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('-');
  });

  it('shows 0 if value is 0', () => {
    const value = {
      type: 'ByteSize',
      value: 0,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('0');
  });

  it('shows value in bytes if it is less than 1024', () => {
    const value = {
      type: 'ByteSize',
      value: 42,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('42B');
  });

  it('shows value in kibibytes if it is less than 1024**2', () => {
    const value = {
      type: 'ByteSize',
      value: 1124,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('1.1KiB');
  });

  it('shows value in mebibytes if it is less than 1024**3', () => {
    const value = {
      type: 'ByteSize',
      value: 44040192,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('42MiB');
  });

  it('shows value in gibibytes if it is more than 1024**3', () => {
    const value = {
      type: 'ByteSize',
      value: 1610612736,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('1.5GiB');
  });

  it('shows value in bytes in the tooltip', () => {
    const value = {
      type: 'ByteSize',
      value: 1610612736,
    };
    const element = renderTestTemplate(value);
    expect(element.find('span').attr('title')).toBe('1610612736 bytes');
  });
});


exports = {};

;return exports;});

//angular-components/semantic/bytes-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.bytesDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {semanticModule} = goog.require('grrUi.semantic.semantic');


describe('bytes directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module('/static/angular-components/semantic/bytes.html'));
  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-bytes value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows nothing when value is empty', () => {
    const value = {
      type: 'RDFBytes',
      value: null,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('');
  });

  it('shows error message if value is incorrectly base64-encoded', () => {
    const value = {
      type: 'RDFBytes',
      value: '--',
    };

    const element = renderTestTemplate(value);
    expect(element.text().trim()).toMatch(/base64decodeerror.*:--/);
  });

  it('converts base64-encoded value into a hex-encoded string', () => {
    const macAddress = {
      type: 'MacAddress',
      value: 'Zm9vDcg=',
    };
    const element = renderTestTemplate(macAddress);
    expect(element.text()).toContain('foo\\x0d\\xc8');
  });

  it('hides content behind a link if its longer than 1024 bytes', () => {
    const value = {
      type: 'RDFBytes',
      value: Array(1025).join('-'),
    };

    const element = renderTestTemplate(value);
    expect(element.text()).not.toMatch(/base64decodeerror.*:--/);
    expect(element.text()).toContain('Show bytes...');

    browserTriggerEvent($('a', element), 'click');
    expect(element.text()).toMatch(/base64decodeerror.*:--/);
    expect(element.text()).not.toContain('Show bytes...');
  });
});


exports = {};

;return exports;});

//angular-components/semantic/client-urn-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.clientUrnDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {semanticModule} = goog.require('grrUi.semantic.semantic');


describe('client urn directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let $timeout;
  let grrApiService;


  beforeEach(module('/static/angular-components/semantic/client-urn.html'));
  beforeEach(module('/static/angular-components/semantic/client-urn-modal.html'));
  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $timeout = $injector.get('$timeout');
    grrApiService = $injector.get('grrApiService');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-client-urn value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('does not show anything when value is empty', () => {
    const element = renderTestTemplate(null);
    expect(element.text().trim()).toBe('');
  });

  it('shows string value', () => {
    const element = renderTestTemplate('aff4:/C.0000000000000001');
    expect(element.text()).toContain('C.0000000000000001');
  });

  it('shows value with type information', () => {
    const clientUrn = {
      age: 0,
      type: 'ClientURN',
      value: 'aff4:/C.0000000000000001',
    };
    const element = renderTestTemplate(clientUrn);
    expect(element.text()).toContain('C.0000000000000001');
  });

  it('has a proper href', () => {
    const clientUrn = {
      age: 0,
      type: 'ClientURN',
      value: 'aff4:/C.0000000000000001',
    };

    const element = renderTestTemplate(clientUrn);
    expect(element.find('a').attr('href')).toBe(
        '#!/clients/C.0000000000000001/host-info');
  });

  describe('client urn summary modal dialog', () => {
    beforeEach(() => {
      grrApiService.get = ((urn, params) => {
        expect(urn).toBe('clients/C.0000000000000001');

        return $q((resolve, reject) => {
          resolve({
            data: 'This is a summary',
          });
        });
      });
    });

    afterEach(() => {
      // We have to clean document's body to remove modal windows that were not
      // closed.
      $(document.body).html('');
    });

    it('is shown when info button is clicked', () => {
      const element = renderTestTemplate('aff4:/C.0000000000000001');
      browserTriggerEvent($('button', element), 'click');
      expect($(document.body).text()).toContain(
          'Client C.0000000000000001');
    });

    it('is shown when info button is clicked and value has no "aff4" prefix',
       () => {
         const element = renderTestTemplate('C.0000000000000001');
         browserTriggerEvent($('button', element), 'click');
         expect($(document.body).text()).toContain('Client C.0000000000000001');
       });

    it('closed when close button is clicked', () => {
      const element = renderTestTemplate('aff4:/C.0000000000000001');
      browserTriggerEvent($('button', element), 'click');
      expect($(document.body).text()).toContain(
          'Client C.0000000000000001');

      browserTriggerEvent($('button.close'), 'click');
      $timeout.flush();

      expect($(document.body).text()).not.toContain(
          'Client C.0000000000000001');
    });
  });
});


exports = {};

;return exports;});

//angular-components/semantic/data-object-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.dataObjectDirectiveTest');
goog.setTestOnly();

const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('data object semantic directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module('/static/angular-components/semantic/data-object.html'));
  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrSemanticValue');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-data-object value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows empty table when value is empty', () => {
    const element = renderTestTemplate({
      type: 'ApiDataObject',
      value: {},
    });
    expect(element.find('table').length).toBe(1);
    expect(element.find('tr').length).toBe(0);
  });

  it('shows 2 rows for a data object with two key-value pairs', () => {
    const element = renderTestTemplate({
      type: 'ApiDataObject',
      value: {
        items: [
          {
            type: 'ApiDataObjectKeyValuePair',
            value: {
              key: {
                type: 'unicode',
                value: 'Test Integer Value',
              },
              value: {
                type: 'RDFInteger',
                value: 1000,
              },
            },
          },
          {
            type: 'ApiDataObjectKeyValuePair',
            value: {
              key: {
                type: 'unicode',
                value: 'Test String Value',
              },
              value: {
                type: 'RDFString',
                value: '<some value>',
              },
            },
          }
        ],
      },
    });
    expect(element.find('table').length).toBe(1);
    expect(element.find('tr').length).toBe(2);

    expect(element.text()).toContain('Test Integer Value');
    expect(element.text()).toContain('Test String Value');

    expect(element.find('grr-semantic-value').length).toBe(2);
  });
});


exports = {};

;return exports;});

//angular-components/semantic/dict-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.dictDirectiveTest');
goog.setTestOnly();

const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('dict semantic directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module('/static/angular-components/semantic/dict.html'));
  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrSemanticValue');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-dict value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows empty table when value is empty', () => {
    const element = renderTestTemplate({
      type: 'dict',
      value: {},
    });
    expect(element.find('table').length).toBe(1);
    expect(element.find('tr').length).toBe(0);
  });

  it('shows 2 keys and corresponding values for value with 2 keys', () => {
    const element = renderTestTemplate({
      type: 'dict',
      value: {
        fooKey: {type: 'Foo'},
        barKey: {type: 'Bar'},
      },
    });
    expect(element.find('table').length).toBe(1);
    expect(element.find('tr').length).toBe(2);

    expect(element.text()).toContain('fooKey');
    expect(element.text()).toContain('barKey');

    expect(element.find('grr-semantic-value').length).toBe(2);
  });
});


exports = {};

;return exports;});

//angular-components/semantic/duration-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.durationDirectiveTest');
goog.setTestOnly();

const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {testsModule} = goog.require('grrUi.tests');


describe('duration directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-duration value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('gracefully handles cases where passed wrapped object is not defined', () => {
    const element = renderTestTemplate(undefined);
    expect(element.text().trim()).toBe('-');
  });

  it('shows "-" when value is empty', () => {
    const value = {
      type: 'DurationSeconds',
      value: null,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('-');
  });

  it('shows 0 if duration value is 0', () => {
    const value = {
      type: 'DurationSeconds',
      value: 0,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('0');
  });

  it('shows duration in seconds if it\'s not divisible by 60', () => {
    const value = {
      type: 'DurationSeconds',
      value: 122,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('122s');
  });

  it('shows duration in minutes if possible', () => {
    const value = {
      type: 'DurationSeconds',
      value: 120,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('2m');
  });

  it('shows duration in hours if possible', () => {
    const value = {
      type: 'DurationSeconds',
      value: 7200,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('2h');
  });

  it('shows duration in days if possible', () => {
    const value = {
      type: 'DurationSeconds',
      value: 172800,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('2d');
  });

  it('shows duration in weeks if possible', () => {
    const value = {
      type: 'DurationSeconds',
      value: 1209600,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('2w');
  });

  it('shows duration in days if not divisible by 7', () => {
    const value = {
      type: 'DurationSeconds',
      value: 1036800,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('12d');
  });
});


exports = {};

;return exports;});

//angular-components/semantic/encryption-key-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.encryptionKeyDirectiveTest');
goog.setTestOnly();

const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {stringifyEncryptionKey} = goog.require('grrUi.semantic.encryptionKeyDirective');
const {testsModule} = goog.require('grrUi.tests');


describe('encryption key directive', () => {
  describe('stringifyEncryptionKey()', () => {

    it('converts base64 encoded string of zeroes to a hex-string', () => {
      expect(stringifyEncryptionKey('AAAAAA==')).toBe('00000000');
    });

    it('converts sample base64 encoded string to a hex-string', () => {
      expect(stringifyEncryptionKey('AAABAgMEBQYHCAkKCwwNDg8Q')).toBe(
          '00000102030405060708090a0b0c0d0e0f10');
    });
  });

  let $compile;
  let $rootScope;


  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-encryption-key value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows nothing when value is empty', () => {
    const element = renderTestTemplate(undefined);
    expect(element.text().trim()).toBe('');
  });

  it('shows hex-stringified bytes when value is not empty', () => {
    const element = renderTestTemplate({
      type: 'EncryptionKey',
      value: 'AAABAgMEBQYHCAkKCwwNDg8Q',
    });
    expect(element.text().trim()).toBe('00000102030405060708090a0b0c0d0e0f10');
  });
});


exports = {};

;return exports;});

//angular-components/semantic/exact-duration-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.exactDurationDirectiveTest');
goog.setTestOnly();


const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {testsModule} = goog.require('grrUi.tests');


describe('exact duration directive', () => {
  /*
   * TODO(hanuszczak): Local variables should not contain special characters if
   * not required by the framework. Consider these declarations across all
   * test files.
   */
  let $compile;
  let $rootScope;

  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderExactDuration = (value) => {
    $rootScope.value = value;

    const template = '<grr-exact-duration value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows "-" when value is empty', () => {
    const value = {
      type: 'DurationSeconds',
      value: null,
    };

    const element = renderExactDuration(value);
    expect(element.text().trim()).toBe('-');
  });

  it('handles zero-values correctly', () => {
    const value = {
      type: 'DurationSeconds',
      value: 0,
    };

    const element = renderExactDuration(value);
    expect(element.text().trim()).toBe('0s');
  });

  it('correctly renders whole seconds', () => {
    const value = {
      type: 'DurationSeconds',
      value: 42,
    };

    const element = renderExactDuration(value);
    expect(element.text().trim()).toBe('42s');
  });

  it('correctly renders whole minutes', () => {
    const value = {
      type: 'DurationSeconds',
      value: 120,
    };

    const element = renderExactDuration(value);
    expect(element.text().trim()).toBe('2m');
  });

  it('correctly renders whole hours', () => {
    const value = {
      type: 'DurationSeconds',
      value: 3600,
    };

    const element = renderExactDuration(value);
    expect(element.text().trim()).toBe('1h');
  });

  it('correctly renders compound values', () => {
    const value = {
      type: 'DurationSeconds',
      value: 131 + (new Date('2000-01-20') - new Date('2000-01-01')) / 1000,
    };

    const element = renderExactDuration(value);
    expect(element.text().trim()).toBe('2w 5d 2m 11s');
  });

  it('rounds to nearest seconds', () => {
    const value = {
      type: 'DurationSeconds',
      value: 62.5,
    };

    const element = renderExactDuration(value);
    expect(element.text().trim()).toBe('1m 3s');
  });

  it('raises on negative duration values', () => {
    const value = {
      type: 'DurationSeconds',
      value: -11,
    };

    expect(() => renderExactDuration(value)).toThrow();
  });
});

;return exports;});

//angular-components/semantic/flow-id-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.flowIdDirectiveTest');
goog.setTestOnly();

const {clientModule} = goog.require('grrUi.client.client');
const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {testsModule} = goog.require('grrUi.tests');


describe('grr-flow-id directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module('/static/angular-components/semantic/flow-id.html'));
  beforeEach(module(clientModule.name));
  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value, clientId) => {
    $rootScope.value = value;

    let template = '<grr-flow-id value="value"></grr-flow-id>';
    if (clientId) {
      $rootScope.clientId = clientId;
      template = '<grr-client-context client-id="clientId">' +
          template + '</grr-client-context>';
    }
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  const sampleValue = {
    value: 'F:112233',
    type: 'ApiFlowId',
  };

  describe('without client context', () => {
    it('renders flow id as a string', () => {
      const element = renderTestTemplate(sampleValue);
      expect(element.find('span:contains("F:112233")').length).toBe(1);
      expect(element.find('a:contains("F:112233")').length).toBe(0);
    });
  });

  describe('with client context', () => {
    it('renders flow id as a link', () => {
      const element = renderTestTemplate(sampleValue, 'C.1111222233334444');
      expect(element.find('span:contains("F:112233")').length).toBe(0);

      const aRef = element.find('a:contains("F:112233")');
      expect(aRef.length).toBe(1);
      expect(aRef.attr('href')).toBe(
          '#!/clients/C.1111222233334444/flows/F%3A112233');
    });

    it('renders a tooltip with a client id', () => {
      const element = renderTestTemplate(sampleValue, 'C.1111222233334444');
      const aref = element.find('a:contains("F:112233")');
      expect(aref.attr('title')).toBe('Flow F:112233 ran on client C.1111222233334444');
    });
  });
});


exports = {};

;return exports;});

//angular-components/semantic/hash-digest-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.hashDigestDirectiveTest');
goog.setTestOnly();

const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {testsModule} = goog.require('grrUi.tests');


describe('hash digest directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-hash-digest value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows nothing when value is empty', () => {
    const value = {
      type: 'HashDigest',
      value: null,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('');
  });

  it('shows error message if value is incorrectly base64-encoded', () => {
    const value = {
      type: 'HashDigest',
      value: '--',
    };

    const element = renderTestTemplate(value);
    expect(element.text().trim()).toMatch(/base64decodeerror.*:--/);
  });

  it('converts base64-encoded value into a hex-encoded string', () => {
    const base64EncodedHash = {
      type: 'HashDigest',
      value: 'dGVzdA==',
    };
    const element = renderTestTemplate(base64EncodedHash);
    expect(element.text()).toContain('74657374');
  });
});


exports = {};

;return exports;});

//angular-components/semantic/hash-list-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.hashListDirectiveTest');
goog.setTestOnly();

const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('hash list directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrSemanticValue');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-hash-list value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows nothing when value is empty', () => {
    const value = {
      type: 'HashList',
      value: null,
    };
    const element = renderTestTemplate(value);
    expect(element.find('grr-hash-digest').length).toBe(0);
  });

  it('delegates single item to grr-semantic-value', () => {
    const base64EncodedHashList = {
      type: 'HashList',
      value: 'MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTE=',
    };
    const element = renderTestTemplate(base64EncodedHashList);
    const directive = element.find('grr-semantic-value');
    expect(angular.equals(directive.scope().$eval(directive.attr('value'))), [{
             type: 'HashDigest',
             value: 'MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTE=',
           }]);
  });

  it('delegates two items to grr-hash-digest', () => {
    const base64EncodedHashList = {
      type: 'HashList',
      value: 'MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTE' +
          'yMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMg==',
    };
    const element = renderTestTemplate(base64EncodedHashList);
    const directive = element.find('grr-semantic-value');
    expect(angular.equals(directive.scope().$eval(directive.attr('value'))), [
      {
        type: 'HashDigest',
        value: 'MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTE=',
      },
      {
        type: 'HashDigest',
        value: 'MjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjI=',
      },
    ]);
  });
});


exports = {};

;return exports;});

//angular-components/semantic/json-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.jsonDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {semanticModule} = goog.require('grrUi.semantic.semantic');


describe('json directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module('/static/angular-components/semantic/json.html'));
  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-json value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows nothing when value is empty', () => {
    const value = {
      type: 'ZippedJSONBytes',
      value: null,
    };
    const element = renderTestTemplate(value);
    expect(element.text().trim()).toBe('');
  });

  it('shows error message if value is not correct json', () => {
    const value = {
      type: 'ZippedJSONBytes',
      value: '--',
    };

    const element = renderTestTemplate(value);
    expect(element.text().trim()).toMatch(/jsonerror.*:--/);
  });

  it('shows json string when it\'s a correct json string', () => {
    const value = {
      type: 'ZippedJSONBytes',
      value: '[{"foo": 42}]',
    };

    const element = renderTestTemplate(value);
    expect(element.text()).toContain('"foo": 42');
  });

  it('hides content behind a link if its longer than 1024 bytes', () => {
    const value = {
      type: 'ZippedJSONBytes',
      value: Array(1025).join('-'),
    };

    const element = renderTestTemplate(value);
    expect(element.text()).not.toMatch(/base64decodeerror.*:--/);
    expect(element.text()).toContain('Show JSON...');

    browserTriggerEvent($('a', element), 'click');
    expect(element.text()).toMatch(/jsonerror.*:--/);
    expect(element.text()).not.toContain('Show JSON...');
  });
});


exports = {};

;return exports;});

//angular-components/semantic/mac-address-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.macAddressDirectiveTest');
goog.setTestOnly();

const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {testsModule} = goog.require('grrUi.tests');


describe('mac address directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-mac-address value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows "-" when value is empty', () => {
    const macAddress = {
      type: 'MacAddress',
      value: null,
    };
    const element = renderTestTemplate(macAddress);
    expect(element.text().trim()).toBe('-');
  });

  it('expands base64-encoded value into a human-readable string', () => {
    const macAddress = {
      type: 'MacAddress',
      value: '+BZUBnli',
    };
    const element = renderTestTemplate(macAddress);
    expect(element.text()).toContain('f8:16:54:06:79:62');
  });
});


exports = {};

;return exports;});

//angular-components/semantic/network-address-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.networkAddressDirectiveTest');
goog.setTestOnly();

const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {testsModule} = goog.require('grrUi.tests');


describe('mac address directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-network-address value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows "-" when value is null', () => {
    const element = renderTestTemplate(null);
    expect(element.text().trim()).toBe('-');
  });

  it('shows IPv4 value for IPv4 address without metadata', () => {
    const element = renderTestTemplate({
      packed_bytes: '+BZUBn',
      address_type: 'INET',
    });
    expect(element.text()).toContain('248.22.84.06');
  });

  it('shows IPv4 value for IPv4 address with metadata', () => {
    const element = renderTestTemplate({
      value: {
        packed_bytes: {value: '+BZUBn'},
        address_type: {value: 'INET'},
      },
    });
    expect(element.text()).toContain('248.22.84.06');
  });

  it('shows IPv6 value for IPv6 address without metadata', () => {
    const element = renderTestTemplate({
      packed_bytes: '+BZUBnliBnl',
      address_type: 'INET6',
    });
    expect(element.text()).toContain('f816:5406:7962:0679');
  });

  it('shows IPv6 value for IPv6 address with metadata', () => {
    const element = renderTestTemplate({
      value: {
        packed_bytes: {
          value: '+BZUBnliBnl',
        },
        address_type: {
          value: 'INET6',
        },
      },
    });
    expect(element.text()).toContain('f816:5406:7962:0679');
  });
});


exports = {};

;return exports;});

//angular-components/semantic/semantic-diff-annotated-proto-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.semanticDiffAnnotatedProtoDirectiveTest');
goog.setTestOnly();

const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {testsModule} = goog.require('grrUi.tests');


describe('grr-semantic-diff-annotated-proto directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrReflectionService;


  beforeEach(module('/static/angular-components/semantic/semantic-diff-annotated-proto.html'));
  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');

    grrReflectionService = $injector.get('grrReflectionService');
    spyOn(grrReflectionService, 'getRDFValueDescriptor');

    const descriptors = {
      'Bar': {},
      'Foo': {
        fields: [
          {name: 'a'},
          {name: 'foo'},
          {name: 'bar'},
        ],
      },
    };
    grrReflectionService.getRDFValueDescriptor.and.callFake((typeName) => {
      const deferred = $q.defer();
      deferred.resolve(descriptors[typeName]);
      return deferred.promise;
    });
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-semantic-diff-annotated-proto value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('renders only after the "value" binding is set', () => {
    const element = renderTestTemplate(undefined);
    expect(element.find('td:contains("foo")').length).toBe(0);

    $rootScope.value = {
      type: 'Foo',
      value: {
        foo: {
          type: 'Bar',
          value: 42,
        },
      },
    };
    $rootScope.$apply();

    expect(element.find('td:contains("foo")').length).toBe(1);
  });

  it('"value" binding is effectively a one-time binding', () => {
    const value = {
      type: 'Foo',
      value: {
        foo: {
          type: 'Bar',
          value: 42,
        },
      },
    };
    const element = renderTestTemplate(value);
    expect(element.find('td:contains("bar")').length).toBe(0);

    const newValue = angular.copy(value);
    newValue['value']['bar'] = {
      type: 'Bar',
      value: 43,
    };
    $rootScope.value = newValue;
    $rootScope.$apply();

    expect(element.find('td:contains("bar")').length).toBe(0);
  });

  angular.forEach(['added', 'changed', 'removed'], (annotation) => {
    it(`renders "${annotation}" annotation on the value itself correctly`,
       () => {
         const value = {
           type: 'Foo',
           value: 42,
           _diff: annotation,
         };

         const element = renderTestTemplate(value);
         expect(element.find(`table.diff-${annotation}`).length).toBe(1);
       });

    it(`renders "${annotation}"-annotated non-repeated field correctly`, () => {
      const value = {
        type: 'Foo',
        value: {
          a: {
            type: 'Bar',
            value: 42,
            _diff: annotation,
          },
        },
      };

      const element = renderTestTemplate(value);
      expect(element.find(`tr.diff-${annotation}`).length).toBe(1);
    });

    it(`renders "${annotation}"-annotated repeated field correctly`, () => {
      const value = {
        type: 'Foo',
        value: {
          a: [
            {
              type: 'Bar',
              value: 42,
              _diff: annotation,
            },
          ],
        },
      };

      const element = renderTestTemplate(value);
      expect(element.find(`div.repeated.diff-${annotation}`).length).toBe(1);
    });
  });
});


exports = {};

;return exports;});

//angular-components/semantic/semantic-proto-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.semanticProtoDirectiveTest');
goog.setTestOnly();

const {buildItems, buildNonUnionItems, buildUnionItems, getUnionFieldValue} = goog.require('grrUi.semantic.semanticProtoDirective');
const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('semantic proto directive', () => {
  describe('getUnionFieldValue()', () => {

    it('throws if descriptor doesn\'t have union_field', () => {
      expect(() => {
        getUnionFieldValue({}, {});
      }).toThrow();
    });

    it('returns union field value when it\'s set', () => {
      const value = {
        type: 'FooType',
        value: {
          action_type: {
            type: 'unicode',
            value: 'DOWNLOAD',
          },
        },
      };
      const descriptor = {
        union_field: 'action_type',
      };
      expect(getUnionFieldValue(value, descriptor)).toBe('download');
    });

    it('returns union field value default if it\'s not set', () => {
      const value = {
        type: 'FooType',
        value: {},
      };
      const descriptor = {
        union_field: 'action_type',
        fields: [
          {
            name: 'action_type',
            default: {
              type: 'unicode',
              value: 'DOWNLOAD',
            },
          },
        ],
      };
      expect(getUnionFieldValue(value, descriptor)).toBe('download');
    });
  });

  describe('buildUnionItems()', () => {

    const descriptor = {
      union_field: 'action_type',
      fields: [
        {
          name: 'action_type',
          default: {
            type: 'unicode',
            value: 'SEND_TO_SOCKET',
          },
        },
        {
          name: 'download',
          default: {
            type: 'unicode',
            value: 'defaultFoo',
          },
        },
        {
          name: 'send_to_socket',
          default: {
            type: 'unicode',
            value: 'defaultBar',
          },
        },
      ],
    };

    const valueWithSetFields = {
      type: 'FooType',
      value: {
        action_type: {
          type: 'unicode',
          value: 'DOWNLOAD',
        },
        download: {
          type: 'unicode',
          value: 'foo',
        },
        send_to_socket: {
          type: 'unicode',
          value: 'bar',
        },
      },
    };

    const valueWithUnsetFields = {
      type: 'FooType',
      value: {},
    };

    it('excludes fields not pointed by set union field value', () => {
      const items = buildUnionItems(valueWithSetFields, descriptor);
      for (let i = 0; i < items.length; ++i) {
        expect(items[i].key).not.toBe('send_to_socket');
      }
    });

    it('excludes fields not pointed by default union field value', () => {
      const items = buildUnionItems(valueWithUnsetFields, descriptor);
      for (let i = 0; i < items.length; ++i) {
        expect(items[i].key).not.toBe('download');
      }
    });

    it('includes union field when it\'s set', () => {
      const items = buildUnionItems(valueWithSetFields, descriptor);
      expect(items[0]['key']).toBe('action_type');
      expect(items[0]['value']['value']).toBe('DOWNLOAD');
    });

    it('includes union field value when it\'s set', () => {
      const items = buildUnionItems(valueWithSetFields, descriptor);
      expect(items[1]['key']).toBe('download');
      expect(items[1]['value']['value']).toBe('foo');
    });

    it('includes union field default when it\'s not set', () => {
      const items = buildUnionItems(valueWithUnsetFields, descriptor);
      expect(items[0]['key']).toBe('action_type');
      expect(items[0]['value']['value']).toBe('SEND_TO_SOCKET');
    });

    it('includes union field value default when it\'s not set', () => {
      const items = buildUnionItems(valueWithUnsetFields, descriptor);
      expect(items[1]['key']).toBe('send_to_socket');
      expect(items[1]['value']['value']).toBe('defaultBar');
    });
  });

  describe('buildNonUnionItems()', () => {

    const descriptor = {
      fields: [
        {
          name: 'foo',
          default: {
            type: 'unicode',
            value: 'defaultFoo',
          },
        },
        {
          name: 'bar',
          default: {
            type: 'unicode',
            value: 'defaultBar',
          },
        },
      ],
    };

    const value = {
      type: 'Struct',
      value: {
        foo: {
          type: 'unicode',
          value: 'theFoo',
        },
      },
    };

    it('includes set fields only', () => {
      const items = buildNonUnionItems(value, descriptor);
      expect(items.length).toBe(1);
      expect(items[0]['key']).toBe('foo');
      expect(items[0]['value']['value']).toBe('theFoo');
    });
  });

  describe('buildItems()', () => {

    it('builds items for a non-union-type value', () => {
      const descriptor = {
        fields: [
          {
            name: 'foo',
          },
        ],
      };

      const value = {
        type: 'Struct',
        value: {
          foo: {
            type: 'unicode',
            value: 'theFoo',
          },
        },
      };

      const items = buildItems(value, descriptor);
      expect(items.length).toBe(1);
      expect(items[0]['key']).toBe('foo');
      expect(items[0]['value']['value']).toBe('theFoo');
    });

    it('builds items for a union-type value', () => {
      const descriptor = {
        union_field: 'action_type',
        fields: [
          {
            name: 'action_type',
          },
          {
            name: 'send_to_socket',
          },
        ],
      };


      const valueWithSetFields = {
        type: 'FooType',
        value: {
          action_type: {
            type: 'unicode',
            value: 'SEND_TO_SOCKET',
          },
          send_to_socket: {
            type: 'unicode',
            value: 'bar',
          },
        },
      };

      const items = buildItems(valueWithSetFields, descriptor);
      expect(items[0]['key']).toBe('action_type');
      expect(items[0]['value']['value']).toBe('SEND_TO_SOCKET');
      expect(items[1]['key']).toBe('send_to_socket');
      expect(items[1]['value']['value']).toBe('bar');
    });
  });


  let $compile;
  let $q;
  let $rootScope;
  let grrReflectionService;


  beforeEach(module('/static/angular-components/semantic/semantic-proto.html'));
  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrSemanticValue');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrReflectionService = $injector.get('grrReflectionService');

    spyOn(grrReflectionService, 'getRDFValueDescriptor');
  }));

  const renderTestTemplate = (value, descriptors) => {
    $rootScope.value = value;

    if (descriptors) {
      grrReflectionService.getRDFValueDescriptor.and.callFake((typeName) => {
        const deferred = $q.defer();
        deferred.resolve(descriptors[typeName]);
        return deferred.promise;
      });
    }

    const template = '<grr-semantic-proto value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('renders only after the "value" binding is set', () => {
    const element = renderTestTemplate(undefined, {
      'Struct': {
        fields: [
          {
            name: 'foo',
          },
        ],
      },
    });

    expect(element.find('td:contains("foo")').length).toBe(0);

    $rootScope.value = {
      type: 'Struct',
      value: {
        foo: {
          type: 'unicode',
          value: 'theFoo',
        },
      },
    };
    $rootScope.$apply();
    expect(element.find('td:contains("foo")').length).toBe(1);
  });

  it('"value" binding is effectively a one-time binding', () => {
    const value = {
      type: 'Struct',
      value: {
        foo: {
          type: 'unicode',
          value: 'theFoo',
        },
      },
    };
    const element = renderTestTemplate(value, {
      'Struct': {
        fields: [
          {
            name: 'foo',
            name: 'bar',
          },
        ],
      },
    });
    expect(element.find('td:contains("bar")').length).toBe(0);
    const newValue = angular.copy(value);
    newValue['value']['bar'] = {
      type: 'unicode',
      value: 'theBar',
    };
    $rootScope.value = newValue;
    $rootScope.$apply();
    expect(element.find('td:contains("bar")').length).toBe(0);
  });

  describe('with non-union-type value', () => {
    it('does not show anything when value is empty', () => {
      const element = renderTestTemplate(null);
      expect(element.text().trim()).toBe('');
    });

    it('respects fields order', () => {
      let element = renderTestTemplate(
          {
            type: 'RDFProtoStruct',
            value: {
              client_id: {
                type: 'unicode',
                value: 'client_id',
              },
              system_info: {
                type: 'unicode',
                value: 'system_info',
              },
              client_info: {
                type: 'unicode',
                value: 'client_info',
              },
            },
          },
          {
            'unicode': {},
            'RDFProtoStruct': {
              fields: [
                {
                  name: 'client_id',
                },
                {
                  name: 'system_info',
                },
                {
                  name: 'client_info',
                },
              ],
            },
          });
      expect($('tr:nth(0)', element).text()).toContain('client_id');
      expect($('tr:nth(1)', element).text()).toContain('system_info');
      expect($('tr:nth(2)', element).text()).toContain('client_info');

      element = renderTestTemplate(
          {
            type: 'RDFProtoStruct',
            value: {
              client_id: {
                type: 'unicode',
                value: 'client_id',
              },
              system_info: {
                type: 'unicode',
                value: 'system_info',
              },
              client_info: {
                type: 'unicode',
                value: 'client_info',
              },
            },
          },
          {
            'RDFProtoStruct': {
              fields: [
                {
                  name: 'client_info',
                },
                {
                  name: 'system_info',
                },
                {
                  name: 'client_id',
                },
              ],
            },
          });
      expect($('tr:nth(0)', element).text()).toContain('client_info');
      expect($('tr:nth(1)', element).text()).toContain('system_info');
      expect($('tr:nth(2)', element).text()).toContain('client_id');
    });
  });

  describe('with union-type values', () => {
    const valueWithSetValues = {
      type: 'RDFProtoStruct',
      value: {
        action_type: {
          type: 'unicode',
          value: 'SEND_TO_SOCKET',
        },
        download: {
          type: 'unicode',
          value: 'foo',
        },
        send_to_socket: {
          type: 'unicode',
          value: 'bar',
        },
      },
    };

    const valueWithUnsetValues = {
      type: 'RDFProtoStruct',
      value: {},
    };

    const descriptors = {
      'unicode': {},
      'RDFProtoStruct': {
        union_field: 'action_type',
        fields: [
          {
            name: 'action_type',
            default: {
              type: 'unicode',
              value: 'DOWNLOAD',
            },
          },
          {
            name: 'download',
            default: {
              type: 'unicode',
              value: 'foo',
            },
          },
          {
            name: 'send_to_socket',
            default: {
              type: 'unicode',
              value: 'bar',
            },
          },
        ],
      },
    };

    it('doesn\'t show inactive union fields', () => {
      const element = renderTestTemplate(valueWithSetValues, descriptors);
      expect($('tr', element).length).toBe(2);
      expect($('tr:nth(0)', element).text()).not.toContain('download');
      expect($('tr:nth(1)', element).text()).not.toContain('download');
    });

    it('shows action type when explicitly set', () => {
      const element = renderTestTemplate(valueWithSetValues, descriptors);
      expect($('tr:nth(0)', element).text()).toContain('action_type');
    });

    it('shows active union field when explicitly set', () => {
      const element = renderTestTemplate(valueWithSetValues, descriptors);
      expect($('tr:nth(1)', element).text()).toContain('send_to_socket');
    });

    it('shows action type when not explicitly set', () => {
      const element = renderTestTemplate(valueWithUnsetValues, descriptors);
      expect($('tr:nth(0)', element).text()).toContain('action_type');
    });

    it('shows active union field when not explicitly set', () => {
      const element = renderTestTemplate(valueWithUnsetValues, descriptors);
      expect($('tr:nth(1)', element).text()).toContain('download');
    });
  });
});


exports = {};

;return exports;});

//angular-components/semantic/semantic-protos-diff-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.semanticProtosDiffDirectiveTest');
goog.setTestOnly();

const {diffAnnotate} = goog.require('grrUi.semantic.semanticProtosDiffDirective');

describe('grrSemanticProtosDiff directive', () => {
  describe('diffAnnotate()', () => {

    it('does nothing for 2 plain equal data structures', () => {
      const value = {
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
          },
          b: {
            type: 'RDFInteger',
            value: 42,
          },
        },
      };

      const originalValue = angular.copy(value);
      const newValue = angular.copy(value);
      diffAnnotate(originalValue, newValue);

      // Check that the values haven't changed.
      expect(originalValue).toEqual(value);
      expect(newValue).toEqual(value);
    });

    it('marks changed primitive attribute as changed', () => {
      const originalValue = {
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
          },
        },
      };
      const newValue = {
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'bar',
          },
        },
      };

      diffAnnotate(originalValue, newValue);

      expect(originalValue).toEqual({
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
            _diff: 'changed',
          },
        },
      });
      expect(newValue).toEqual({
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'bar',
            _diff: 'changed',
          },
        },
      });
    });

    it('marks primitive attribute as changed on type change', () => {
      const originalValue = {
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
          },
        },
      };
      const newValue = {
        type: 'Foo',
        value: {
          a: {
            type: 'ClientURN',
            value: 'foo',
          },
        },
      };

      diffAnnotate(originalValue, newValue);

      expect(originalValue).toEqual({
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
            _diff: 'changed',
          },
        },
      });
      expect(newValue).toEqual({
        type: 'Foo',
        value: {
          a: {
            type: 'ClientURN',
            value: 'foo',
            _diff: 'changed',
          },
        },
      });
    });

    it('marks added primitive attribute as added', () => {
      const originalValue = {
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
          },
        },
      };
      const newValue = {
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
          },
          b: {
            type: 'RDFString',
            value: 'bar',
          },
        },
      };

      diffAnnotate(originalValue, newValue);

      expect(originalValue).toEqual({
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
          },
        },
      });
      expect(newValue).toEqual({
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
          },
          b: {
            type: 'RDFString',
            value: 'bar',
            _diff: 'added',
          },
        },
      });
    });

    it('marks removed primitive attribute as removed', () => {
      const originalValue = {
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
          },
          b: {
            type: 'RDFString',
            value: 'bar',
          },
        },
      };
      const newValue = {
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
          },
        },
      };

      diffAnnotate(originalValue, newValue);

      expect(originalValue).toEqual({
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
          },
          b: {
            type: 'RDFString',
            value: 'bar',
            _diff: 'removed',
          },
        },
      });
      expect(newValue).toEqual({
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
          },
        },
      });
    });

    it('marks added and removed primitive attributes in the same value', () => {
      const originalValue = {
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
          },
          b: {
            type: 'RDFString',
            value: 'bar',
          },
        },
      };
      const newValue = {
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
          },
          c: {
            type: 'RDFString',
            value: 'aha',
          },
        },
      };

      diffAnnotate(originalValue, newValue);

      expect(originalValue).toEqual({
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
          },
          b: {
            type: 'RDFString',
            value: 'bar',
            _diff: 'removed',
          },
        },
      });
      expect(newValue).toEqual({
        type: 'Foo',
        value: {
          a: {
            type: 'RDFString',
            value: 'foo',
          },
          c: {
            type: 'RDFString',
            value: 'aha',
            _diff: 'added',
          },
        },
      });
    });

    it('marks primitive item added to a list as added', () => {
      const originalValue = [
        {
          type: 'RDFString',
          value: 'foo',
        },
      ];
      const newValue = [
        {
          type: 'RDFString',
          value: 'foo',
        },
        {
          type: 'RDFString',
          value: 'bar',
        },
      ];

      diffAnnotate(originalValue, newValue);

      expect(originalValue).toEqual([
        {
          type: 'RDFString',
          value: 'foo',
        },
      ]);
      expect(newValue).toEqual([
        {
          type: 'RDFString',
          value: 'foo',
        },
        {
          type: 'RDFString',
          value: 'bar',
          _diff: 'added',
        },
      ]);
    });

    it('marks primitive item removed from a list as removed', () => {
      const originalValue = [
        {
          type: 'RDFString',
          value: 'foo',
        },
        {
          type: 'RDFString',
          value: 'bar',
        },
      ];
      const newValue = [
        {
          type: 'RDFString',
          value: 'foo',
        },
      ];

      diffAnnotate(originalValue, newValue);

      expect(originalValue).toEqual([
        {
          type: 'RDFString',
          value: 'foo',
        },
        {
          type: 'RDFString',
          value: 'bar',
          _diff: 'removed',
        },
      ]);
      expect(newValue).toEqual([
        {
          type: 'RDFString',
          value: 'foo',
        },
      ]);
    });

    it('marks changed list item as added and removed', () => {
      const originalValue = [
        {
          type: 'RDFString',
          value: 'foo',
        },
      ];
      const newValue = [
        {
          type: 'RDFString',
          value: 'bar',
        },
      ];

      diffAnnotate(originalValue, newValue);

      expect(originalValue).toEqual([
        {
          type: 'RDFString',
          value: 'foo',
          _diff: 'removed',
        },
      ]);
      expect(newValue).toEqual([
        {
          type: 'RDFString',
          value: 'bar',
          _diff: 'added',
        },
      ]);
    });

    it('treats lists as unchanged if the order of items changed', () => {
      const originalValue = [
        {
          type: 'RDFString',
          value: 'foo',
        },
        {
          type: 'RDFString',
          value: 'bar',
        },

      ];
      const savedOriginalValue = angular.copy(originalValue);

      const newValue = [
        {
          type: 'RDFString',
          value: 'bar',
        },
        {
          type: 'RDFString',
          value: 'foo',
        },
      ];
      const savedNewValue = angular.copy(newValue);

      diffAnnotate(originalValue, newValue);

      // Ensure no '_diff' annotations were added.
      expect(originalValue).toEqual(savedOriginalValue);
      expect(newValue).toEqual(savedNewValue);
    });

    it('treats lists as unchanged if duplicate items were added', () => {
      const originalValue = [
        {
          type: 'RDFString',
          value: 'foo',
        },
      ];
      const savedOriginalValue = angular.copy(originalValue);

      const newValue = [
        {
          type: 'RDFString',
          value: 'foo',
        },
        {
          type: 'RDFString',
          value: 'foo',
        },
      ];
      const savedNewValue = angular.copy(newValue);

      diffAnnotate(originalValue, newValue);

      // Ensure no '_diff' annotations were added.
      expect(originalValue).toEqual(savedOriginalValue);
      expect(newValue).toEqual(savedNewValue);
    });
  });
});


exports = {};

;return exports;});

//angular-components/semantic/semantic-value-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.semanticValueDirectiveTest');
goog.setTestOnly();

const {clearCaches, getCachedSingleValueTemplate} = goog.require('grrUi.semantic.semanticValueDirective');
const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {testsModule} = goog.require('grrUi.tests');


describe('semantic value directive', () => {
  let $compile;
  let $q;
  let $rootScope;

  let grrSemanticValueDirectivesRegistryService;
  let grrReflectionService;

  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    clearCaches();

    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrSemanticValueDirectivesRegistryService = $injector.get(
        'grrSemanticValueDirectivesRegistryService');
    grrReflectionService = $injector.get('grrReflectionService');

    grrReflectionService.getRDFValueDescriptor = ((valueType) => {
      const deferred = $q.defer();
      deferred.resolve({
        name: valueType,
        mro: [valueType],
      });
      return deferred.promise;
    });
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-semantic-value value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('does not show anything when value is null', () => {
    const element = renderTestTemplate(null);
    expect(element.text().trim()).toBe('');
  });

  it('does not show anything when value is undefined', () => {
    const element = renderTestTemplate(undefined);
    expect(element.text().trim()).toBe('');
  });

  it('renders plain string values', () => {
    const element = renderTestTemplate('foobar');
    expect(element.text().trim()).toBe('foobar');
  });

  it('renders plain integer values', () => {
    const element = renderTestTemplate(42);
    expect(element.text().trim()).toBe('42');
  });

  it('renders list of plain string values', () => {
    const element = renderTestTemplate(['elem1', 'elem2', 'elem3']);
    expect(element.text().trim()).toBe('elem1 elem2 elem3');
  });

  it('renders list of plain integer values', () => {
    const element = renderTestTemplate([41, 42, 43]);
    expect(element.text().trim()).toBe('41 42 43');
  });

  it('renders richly typed value with a registered directive', () => {
    // This directive does not exist and Angular won't process it,
    // but it still will be inserted into DOM and we can check
    // that it's inserted correctly.
    const directiveMock = {
      directive_name: 'theTestDirective',
    };
    grrSemanticValueDirectivesRegistryService.registerDirective(
        'NonExistentType', directiveMock);

    const element = renderTestTemplate({
      type: 'NonExistentType',
      value: 42,
    });
    expect($('the-test-directive', element).length).toBe(1);
    expect($('the-test-directive[value="::value"]', element).length).toBe(1);
  });

  it('renders list of typed values with registered directives', () => {
    // These directives do not exist and Angular won't process them,
    // but they still will be inserted into DOM and we can check
    // that they're inserted correctly.
    const directiveMock1 = {
      directive_name: 'theTestDirective1',
    };
    grrSemanticValueDirectivesRegistryService.registerDirective(
        'NonExistentType1', directiveMock1);

    const directiveMock2 = {
      directive_name: 'theTestDirective2',
    };
    grrSemanticValueDirectivesRegistryService.registerDirective(
        'NonExistentType2', directiveMock2);

    const directiveMock3 = {
      directive_name: 'theTestDirective3',
    };
    grrSemanticValueDirectivesRegistryService.registerDirective(
        'NonExistentType3', directiveMock3);

    const element = renderTestTemplate([
      {
        type: 'NonExistentType1',
        value: 41,
      },
      {
        type: 'NonExistentType2',
        value: 42,
      },
      {
        type: 'NonExistentType3',
        value: 43,
      },
    ]);

    expect($('the-test-directive1', element).length).toBe(1);
    expect($('the-test-directive2', element).length).toBe(1);
    expect($('the-test-directive3', element).length).toBe(1);
  });

  it('renders typed values as strings when there\'s no handler', () => {
    const element = renderTestTemplate({
      type: 'NonExistentType',
      value: 42,
    });

    expect(element.text().trim()).toBe('42');
  });

  it('respects type override done with grr-semantic-value-registry-override',
     () => {
       const directiveMock = {
         directive_name: 'theTestDirective',
       };
       grrSemanticValueDirectivesRegistryService.registerDirective(
           'SomeType', directiveMock);

       // This directive does not exist and Angular won't process it,
       // but it still will be inserted into DOM and we can check
       // that it's inserted correctly.
       const overrideDirectiveMock = {
         directive_name: 'theTestDirectiveOverride',
       };
       $rootScope.override = overrideDirectiveMock;

       $rootScope.value = {
         type: 'SomeType',
         value: 42,
       };
       const template = '<grr-semantic-value-registry-override ' +
           'map="{\'SomeType\': override}">' +
           '<grr-semantic-value value="value"></grr-semantic-value>' +
           '</grr-semantic-value-registry-override>';
       const element = $compile(template)($rootScope);
       $rootScope.$apply();

       expect($('the-test-directive-override', element).length).toBe(1);
     });

  it('respects the override even if an RDF type was rendered once before',
     () => {
       const directiveMock = {
         directive_name: 'theTestDirective',
       };
       grrSemanticValueDirectivesRegistryService.registerDirective(
           'SomeType', directiveMock);

       let element = renderTestTemplate({
         type: 'SomeType',
         value: 42,
       });
       expect($('the-test-directive', element).length).toBe(1);

       // This directive does not exist and Angular won't process it,
       // but it still will be inserted into DOM and we can check
       // that it's inserted correctly.
       const overrideDirectiveMock = {
         directive_name: 'theTestDirectiveOverride',
       };
       $rootScope.override = overrideDirectiveMock;

       const template = '<grr-semantic-value-registry-override ' +
           'map="{\'SomeType\': override}">' +
           '<grr-semantic-value value="value"></grr-semantic-value>' +
           '</grr-semantic-value-registry-override>';
       element = $compile(template)($rootScope);
       $rootScope.$apply();

       expect($('the-test-directive-override', element).length).toBe(1);
     });

  it('caches templates for overrides using unique keys', () => {
    const directiveMock = {
      directive_name: 'theTestDirective',
    };
    grrSemanticValueDirectivesRegistryService.registerDirective(
        'SomeType', directiveMock);

    let element = renderTestTemplate({
      type: 'SomeType',
      value: 42,
    });
    expect($('the-test-directive', element).length).toBe(1);

    // This directive does not exist and Angular won't process it,
    // but it still will be inserted into DOM and we can check
    // that it's inserted correctly.
    const overrideDirectiveMock = {
      directive_name: 'theTestDirectiveOverride',
    };
    $rootScope.override = overrideDirectiveMock;

    const template = '<grr-semantic-value-registry-override ' +
        'map="{\'SomeType\': override, \'AnotherType\': override}">' +
        '<grr-semantic-value value="value"></grr-semantic-value>' +
        '</grr-semantic-value-registry-override>';
    element = $compile(template)($rootScope);
    $rootScope.$apply();

    expect(getCachedSingleValueTemplate('SomeType')).toBeDefined();
    expect(getCachedSingleValueTemplate(
               'SomeType:AnotherType_theTestDirectiveOverride:' +
               'SomeType_theTestDirectiveOverride'))
        .toBeDefined();
  });
});


exports = {};

;return exports;});

//angular-components/semantic/semantic-versioned-proto-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.semanticVersionedProtoDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, stubDirective, testsModule} = goog.require('grrUi.tests');
const {semanticModule} = goog.require('grrUi.semantic.semantic');


describe('semantic versioned proto directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrReflectionService;


  beforeEach(module('/static/angular-components/semantic/' +
      'semantic-versioned-proto.html'));
  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrSemanticValue');

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    grrReflectionService = $injector.get('grrReflectionService');

    const deferred = $q.defer();
    deferred.resolve({
      'TheType': {
        kind: 'struct',
        fields: [
          {
            name: 'field',
            type: 'TheType',
          },
          {
            name: 'foo',
            type: 'RDFString',
          },
        ],
        default: {},
      },
      'RDFString': {
        kind: 'primitive',
        default: '',
      },
    });
    grrReflectionService.getRDFValueDescriptor = jasmine.createSpy(
        'getRDFValueDescriptor').and.returnValue(deferred.promise);
  }));

  const renderTestTemplate = (value, callback, depth) => {
    $rootScope.value = value;
    $rootScope.callback = callback;
    $rootScope.depth = depth;

    const template = '<grr-semantic-versioned-proto ' +
        'value="value" history-depth="depth" ' +
        'on-field-click="callback(fieldPath)" />';

    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  const oneLevelValue = {
    type: 'TheType',
    value: {
      foo: {
        type: 'RDFString',
        value: 'blah',
      },
    },
  };

  const twoLevelValue = {
    type: 'TheType',
    value: {
      field: {
        type: 'TheType',
        value: {
          foo: {
            type: 'RDFString',
            value: 'bar',
          },
        },
      },
      foo: {
        type: 'RDFString',
        value: 'blah',
      },
    },
  };

  it('renders only after the "value" binding is set', () => {
    const element = renderTestTemplate(undefined, () => {}, 1);
    expect(element.find('.proto_history button').length).toBe(0);

    $rootScope.value = oneLevelValue;
    $rootScope.$apply();
    expect(element.find('.proto_history button').length).toBe(1);
  });

  it('"value" binding is effectively a one-time binding', () => {
    const element = renderTestTemplate(oneLevelValue, () => {}, 1);
    expect(element.find('.proto_history button').length).toBe(1);

    const newValue = angular.copy(oneLevelValue);
    newValue['value'] = {};
    $rootScope.value = newValue;
    $rootScope.$apply();
    expect(element.find('.proto_history button').length).toBe(1);
  });

  it('adds history button to 1st-level field', () => {
    const element = renderTestTemplate(oneLevelValue, () => {}, 1);
    expect(element.find('.proto_history button').length).toBe(1);
  });

  it('passes a correct field path for a 1st-level field', () => {
    const callback = jasmine.createSpy();
    const element = renderTestTemplate(oneLevelValue, callback, 1);
    browserTriggerEvent(element.find('.proto_history button'), 'click');

    expect(callback.calls.count()).toBe(1);
    expect(callback.calls.first().args).toEqual(['foo']);
  });

  it('adds history button to 2nd-level field', () => {
    const element = renderTestTemplate(twoLevelValue, () => {}, 2);
    expect(element.find('td.proto_value .proto_history button').length).toBe(1);
  });


  it('passes a correct field path for a 2nd-level field', () => {
    const callback = jasmine.createSpy();
    const element = renderTestTemplate(twoLevelValue, callback, 2);
    browserTriggerEvent(element.find('td.proto_value .proto_history button'), 'click');

    expect(callback.calls.count()).toBe(1);
    expect(callback.calls.first().args).toEqual(['field.foo']);
  });

  it('does not add history button outside history-depth', () => {
    const element = renderTestTemplate(twoLevelValue, () => {}, 1);
    expect(element.find('td.proto_value .proto_history button').length).toBe(0);
  });
});


exports = {};

;return exports;});

//angular-components/semantic/stat-ext-flags-linux-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.statExtFlagsLinuxDirectiveTest');
goog.setTestOnly();

const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {testsModule} = goog.require('grrUi.tests');


const HTML_TEMPLATE_PATH =
    '/static/angular-components/semantic/stat-ext-flags-linux.html';

describe('stat ext-flags for Linux directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module(HTML_TEMPLATE_PATH));
  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));
  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const render = (value) => {
    $rootScope.value = value;

    const template = '<grr-stat-ext-flags-linux value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('handles empty values', () => {
    const element = render(undefined);
    expect(element.text().trim()).toBe('none');
  });

  it('handles incorrect input type', () => {
    const element = render({value: 'foo'});
    expect(element.text().trim()).toBe('malformed');
  });

  it('handles negative values', () => {
    const element = render({value: -1});
    expect(element.text().trim()).toBe('malformed');
  });

  it('handles non-integer values', () => {
    const element = render({value: 5.13});
    expect(element.text().trim()).toBe('malformed');
  });

  it('indicates files without special flags', () => {
    const element = render({value: 0});
    expect(element.text().replace(/\s/g, '')).toBe('--------------------');
  });

  it('indicates regular files', () => {
    const element = render({value: 524288});
    expect(element.text().replace(/\s/g, '')).toBe('-----------------e--');
  });

  it('indicates immutable files', () => {
    const element = render({value: 524304});
    expect(element.text().replace(/\s/g, '')).toBe('----i------------e--');
  });

  it('indicates files non-dumpable support', () => {
    const element = render({value: 524352});
    expect(element.text().replace(/\s/g, '')).toBe('------d----------e--');
  });
});


exports = {};

;return exports;});

//angular-components/semantic/stat-ext-flags-osx-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.statExtFlagsOsxDirectiveTest');
goog.setTestOnly();

const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {testsModule} = goog.require('grrUi.tests');


const HTML_TEMPLATE_URL =
    '/static/angular-components/semantic/stat-ext-flags-osx.html';

describe('stat ext-flags for Mac directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module(HTML_TEMPLATE_URL));
  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));
  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const render = (value) => {
    $rootScope.value = value;

    const template = '<grr-stat-ext-flags-osx value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('handles empty values', () => {
    const element = render(undefined);
    expect(element.text().trim()).toBe('none');
  });

  it('handles incorrect input type', () => {
    const element = render({value: 'foo'});
    expect(element.text().trim()).toBe('malformed');
  });

  it('handles negative values', () => {
    const element = render({vaue: -42});
    expect(element.text().trim()).toBe('malformed');
  });

  it('handles non-integer values', () => {
    const element = render({value: 3.14});
    expect(element.text().trim()).toBe('malformed');
  });

  it('indicates regular files', () => {
    const element = render({value: 0});
    expect(element.text().trim()).toBe('');
  });

  it('indicates immutable files', () => {
    const element = render({value: 2});
    expect(element.text().trim()).toBe('uimmutable');
  });

  it('indicates files with nodump flag', () => {
    const element = render({value: 1});
    expect(element.text().trim()).toBe('nodump');
  });

  it('indicates files with multiple flags', () => {
    const element = render({value: 196616});
    const text = element.text().trim();
    expect(text).toContain('opaque');
    expect(text).toContain('archived');
    expect(text).toContain('simmutable');
  });

  it('ignores flags with unknown keywords', () => {
    const element = render({value: 32});
    expect(element.text().trim()).toBe('');
  });
});


exports = {};

;return exports;});

//angular-components/semantic/stat-mode-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.statModeDirectiveTest');
goog.setTestOnly();

const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {testsModule} = goog.require('grrUi.tests');


describe('stat mode directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const render = (value) => {
    $rootScope.value = value;

    const template = '<grr-stat-mode value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('does not show anything when value is null', () => {
    const element = render(null);
    expect(element.text().trim()).toBe('-');
  });

  it('does not show anything when value is empty', () => {
    const element = render({});
    expect(element.text().trim()).toBe('-');
  });

  it('indicates regular files', () => {
    const element = render({value: 33188});
    expect(element.text().trim()).toBe('-rw-r--r--');
  });

  it('indicates directories', () => {
    const element = render({value: 16832});
    expect(element.text().trim()).toBe('drwx------');
  });

  it('indicates character devices', () => {
    const element = render({value: 8592});
    expect(element.text().trim()).toBe('crw--w----');
  });

  it('indicates symbolic links', () => {
    const element = render({value: 41325});
    expect(element.text().trim()).toBe('lr-xr-xr-x');
  });

  it('indicates block devices', () => {
    const element = render({value: 24960});
    expect(element.text().trim()).toBe('brw-------');
  });

  it('indicates FIFO pipes', () => {
    const element = render({value: 4516});
    expect(element.text().trim()).toBe('prw-r--r--');
  });

  it('indicates sockets', () => {
    const element = render({value: 50668});
    expect(element.text().trim()).toBe('srwxr-sr--');
  });

  it('considers the S_ISUID flag', () => {
    let element = render({value: 35300});
    expect(element.text().trim()).toBe('-rwsr--r--');

    element = render({value: 35236});
    expect(element.text().trim()).toBe('-rwSr--r--');
  });

  it('considers the S_ISGID flag', () => {
    let element = render({value: 36332});
    expect(element.text().trim()).toBe('-rwsr-sr--');

    element = render({value: 36324});
    expect(element.text().trim()).toBe('-rwsr-Sr--');
  });

  it('considers the S_ISVTX flag', () => {
    let element = render({value: 35812});
    expect(element.text().trim()).toBe('-rwsr--r-T');

    element = render({value: 35813});
    expect(element.text().trim()).toBe('-rwsr--r-t');
  });
});


exports = {};

;return exports;});

//angular-components/semantic/timestamp-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.timestampDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {semanticModule} = goog.require('grrUi.semantic.semantic');


describe('timestamp directive', () => {
  const MICRO_IN_MILLI = 1000;
  const MILLI_IN_UNIT = 1000;

  const SECONDS = MICRO_IN_MILLI * MILLI_IN_UNIT;
  const MINUTES = 60 * SECONDS;
  let $compile;
  let $rootScope;


  beforeEach(module('/static/angular-components/semantic/timestamp.html'));
  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-timestamp value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('does not show anything when value is undefined', () => {
    const element = renderTestTemplate(undefined);
    expect(element.text().trim()).toBe('');
  });

  it('does not show anything when value is null', () => {
    const element = renderTestTemplate(null);
    expect(element.text().trim()).toBe('-');
  });

  it('shows "-" when value is 0', () => {
    const element = renderTestTemplate(0);
    expect(element.text().trim()).toBe('-');
  });

  it('shows integer value', () => {
    const element = renderTestTemplate(42 * SECONDS);
    expect(element.text()).toContain('1970-01-01 00:00:42');
  });

  it('shows value with type information', () => {
    const timestamp = {
      'mro': [
        'RDFDatetime', 'RDFInteger', 'RDFString', 'RDFBytes', 'RDFValue',
        'object'
      ],
      'value': 42 * SECONDS,
      'age': 0,
      'type': 'RDFDatetime',
    };
    const element = renderTestTemplate(timestamp);
    expect(element.text()).toContain('1970-01-01 00:00:42');
  });

  it('includes a human-readable diff when hovered', () => {
    function assertTimestampRendersDiff(timestamp, diff) {
      const element = renderTestTemplate(timestamp);
      const span = $(element).find('> span');

      // Simulate a mouseenter event on the span.
      // Doing a mouseenter on the parent directive would not work, as the
      // events bubble outwards towards the parent hierarchy, and the span
      // would not see // this event, so the controller wouldn't capture it.
      browserTriggerEvent($(element).find('> span'), 'mouseenter');

      expect(span.attr('title')).toContain(diff);
    }

    const now = (new Date() - 0) * MICRO_IN_MILLI;

    // ignore very small differences from the current time
    assertTimestampRendersDiff(now + 5 * SECONDS,
                               'now');

    assertTimestampRendersDiff(now - 5 * SECONDS,
                               'now');

    // but don't ignore dates in the past
    assertTimestampRendersDiff(now - 2 * MINUTES - 2 * SECONDS,
                               '2 minutes ago');
  });
});


exports = {};

;return exports;});

//angular-components/semantic/timestamp-seconds-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.timestampSecondsDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {semanticModule} = goog.require('grrUi.semantic.semantic');


describe('timestamp seconds directive', () => {
  const MILLI_IN_UNIT = 1000;
  let $compile;
  let $rootScope;


  // grr-timestamp-seconds is a wrapper around grr-timestamp.
  // We declare the dependency on grr-timestamp's template here in
  // order not to stub out the grr-timestamp directive and simply test
  // that the produced markup is correct.
  beforeEach(module('/static/angular-components/semantic/timestamp.html'));
  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-timestamp-seconds value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('does not show anything when value is undefined', () => {
    const element = renderTestTemplate(undefined);
    expect(element.text().trim()).toBe('');
  });

  it('does not show anything when value is null', () => {
    const element = renderTestTemplate(null);
    expect(element.text().trim()).toBe('-');
  });

  it('shows "-" when value is 0', () => {
    const element = renderTestTemplate(0);
    expect(element.text().trim()).toBe('-');
  });

  it('shows integer value', () => {
    const element = renderTestTemplate(42);
    expect(element.text()).toContain('1970-01-01 00:00:42');
  });

  it('shows value with type information', () => {
    const timestamp = {
      'mro': [
        'RDFDatetimeSeconds', 'RDFDatetime', 'RDFInteger', 'RDFString',
        'RDFBytes', 'RDFValue', 'object'
      ],
      'value': 42,
      'age': 0,
      'type': 'RDFDatetimeSeconds',
    };
    const element = renderTestTemplate(timestamp);
    expect(element.text()).toContain('1970-01-01 00:00:42');
  });

  it('includes a human-readable diff when hovered', () => {
    function assertTimestampRendersDiff(timestamp, diff) {
      const element = renderTestTemplate(timestamp);
      const span = $(element).find('span');

      // Simulate a mouseenter event on the span.
      // Doing a mouseenter on the parent directive would not work, as the
      // events bubble outwards towards the parent hierarchy, and the span
      // would not see // this event, so the controller wouldn't capture it.
      browserTriggerEvent($(element).find('span'), 'mouseenter');

      expect(span.attr('title')).toContain(diff);
    }

    const now = (new Date() - 0) / MILLI_IN_UNIT;

    // ignore very small differences from the current time
    assertTimestampRendersDiff(now, 'now');
  });
});


exports = {};

;return exports;});

//angular-components/semantic/urn-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.semantic.urnDirectiveTest');
goog.setTestOnly();

const aff4UrnToUrl = goog.require('grrUi.routing.aff4UrnToUrl');
const {semanticModule} = goog.require('grrUi.semantic.semantic');
const {testsModule} = goog.require('grrUi.tests');


describe('urn directive', () => {
  let $compile;
  let $rootScope;
  let grrRoutingService;


  beforeEach(module('/static/angular-components/semantic/urn.html'));
  beforeEach(module(semanticModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    grrRoutingService = $injector.get('grrRoutingService');
  }));

  const renderTestTemplate = (value) => {
    $rootScope.value = value;

    const template = '<grr-urn value="value" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('does not show anything when value is empty', () => {
    const element = renderTestTemplate(null);
    expect(element.text().trim()).toBe('');
  });

  it('shows plain string if grrRoutingService can\'t convert URN', () => {
    spyOn(aff4UrnToUrl, 'aff4UrnToUrl').and.returnValue(undefined);

    const element = renderTestTemplate('aff4:/foo/bar');
    expect(element.text().trim()).toBe('aff4:/foo/bar');
    expect(element.find('a').length).toBe(0);
  });

  it('shows a link if grrRoutingService can convert URN', () => {
    spyOn(aff4UrnToUrl, 'aff4UrnToUrl').and.returnValue({
      state: 'someState',
      params: {},
    });
    spyOn(grrRoutingService, 'href').and.returnValue('/some/real/link');

    const element =
        renderTestTemplate('aff4:/C.0001000200030004/fs/os/foo/bar');
    expect(element.find('a').text().trim()).toBe(
        'aff4:/C.0001000200030004/fs/os/foo/bar');
    expect(element.find('a').attr('href')).toBe(
        '/some/real/link');
  });
});


exports = {};

;return exports;});

//angular-components/sidebar/client-warnings-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.core.clientWarningsDirectiveTest');
goog.setTestOnly();

const {sidebarModule} = goog.require('grrUi.sidebar.sidebar');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('grr-client-warnings directive', () => {
  let $q;
  let $compile;
  let $rootScope;
  let grrApiService;

  beforeEach(module('/static/angular-components/sidebar/client-warnings.html'));
  beforeEach(module(sidebarModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrMarkdown');

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    grrApiService = $injector.get('grrApiService');
  }));

  const renderTestTemplate = (configOption, client) => {
    $rootScope.client = client || {
      type: 'ApiClient',
      value: {
        labels: [
          {
            type: 'ApiClientLabel',
            value: {
              name: {
                type: 'RDFString',
                value: 'foo'
              }
            }
          },
          {
            type: 'ApiClientLabel',
            value: {
              name: {
                type: 'RDFString',
                value: 'bar'
              }
            }
          }
        ]
      }
    };

    spyOn(grrApiService, 'getV2Cached').and.callFake(() => {
      const deferred = $q.defer();
      const promise = deferred.promise;

      deferred.resolve({
        data: configOption
      });

      return promise;
    });

    const template = '<grr-client-warnings client="client" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('does not render anything on empty config option', () => {
    const element = renderTestTemplate({});
    expect(element.find('grr-markdown').length).toBe(0);
  });

  it('does not render anything on empty rules list', () => {
    const element = renderTestTemplate({
      value: {
        rules: []
      }
    });
    expect(element.find('grr-markdown').length).toBe(0);
  });

  it('shows a single warning when one rule matches a single label', () => {
    const element = renderTestTemplate({
      value: {
        rules: [
          {
            withLabels: ['foo'],
            message: 'blah'
          }
        ]
      }
    });
    expect(element.find('grr-markdown').length).toBe(1);

    const markdownElement = element.find('grr-markdown:nth(0)');
    const source = markdownElement.scope().$eval(markdownElement.attr('source'));
    expect(source).toBe('blah');
  });

  it('matches labels in a case-insensitive way', () => {
    const element = renderTestTemplate({
      value: {
        rules: [
          {
            withLabels: ['FOO'],
            message: 'blah'
          }
        ]
      }
    });
    expect(element.find('grr-markdown').length).toBe(1);

    const markdownElement = element.find('grr-markdown:nth(0)');
    const source = markdownElement.scope().$eval(markdownElement.attr('source'));
    expect(source).toBe('blah');
  });

  it('shows a single warning when one rule matches two labels', () => {
    const element = renderTestTemplate({
      value: {
        rules: [
          {
            withLabels: ['foo', 'bar'],
            message: 'blah'
          }
        ]
      }
    });
    expect(element.find('grr-markdown').length).toBe(1);

    const markdownElement = element.find('grr-markdown:nth(0)');
    const source = markdownElement.scope().$eval(markdownElement.attr('source'));
    expect(source).toBe('blah');
  });

  it('shows two warnings when two rules match', () => {
    const element = renderTestTemplate({
      value: {
        rules: [
          {
            withLabels: ['foo'],
            message: 'blah1'
          },
          {
            withLabels: ['bar'],
            message: 'blah2'
          }
        ]
      }
    });
    expect(element.find('grr-markdown').length).toBe(2);

    let markdownElement = element.find('grr-markdown:nth(0)');
    let source = markdownElement.scope().$eval(markdownElement.attr('source'));
    expect(source).toBe('blah1');

    markdownElement = element.find('grr-markdown:nth(1)');
    source = markdownElement.scope().$eval(markdownElement.attr('source'));
    expect(source).toBe('blah2');
  });
});


exports = {};

;return exports;});

//angular-components/stats/audit-chart-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.stats.auditChartDirectiveTest');
goog.setTestOnly();

const {statsModule} = goog.require('grrUi.stats.stats');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('audit chart directive', () => {
  let $compile;
  let $rootScope;


  beforeEach(module(
      '/static/angular-components/stats/audit-chart.html'));
  beforeEach(module(statsModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrSemanticValue');

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (typedData) => {
    $rootScope.typedData = typedData;

    const template = '<grr-audit-chart typed-data="typedData">' +
        '</grr-audit-chart>';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows nothing if the given data is undefined', () => {
    const element = renderTestTemplate(undefined);

    expect(element.find('th').length).toBe(0);
    expect(element.find('td').length).toBe(0);
  });

  it('shows the given data', () => {
    const element = renderTestTemplate({
      'value': {
        'audit_chart': {
          'value': {
            'rows': [
              {
                'value': {
                  'action': {
                    'value': 'HUNT_CREATED',
                    'type': 'EnumNamedValue',
                  },
                  'user': {
                    'value': 'GRRWorker',
                    'type': 'unicode',
                  },
                  'id': {
                    'value': 123,
                    'type': 'long',
                  },
                  'timestamp': {
                    'value': 1485174411000000,
                    'type': 'RDFDatetime',
                  },
                  'description': {
                    'value': 'Description of the hunt.',
                    'type': 'unicode',
                  },
                  'flow_name': {
                    'value': 'Flow Foo.',
                    'type': 'unicode',
                  },
                  'urn': {
                    'value': 'aff4:/hunts/H:12345678',
                    'type': 'RDFURN',
                  },
                },
                'type': 'AuditEvent',
              },
              {
                'value': {
                  'action': {
                    'value': 'HUNT_STARTED',
                    'type': 'EnumNamedValue',
                  },
                  'user': {
                    'value': 'GRRWorker',
                    'type': 'unicode',
                  },
                  'id': {
                    'value': 456,
                    'type': 'long',
                  },
                  'timestamp': {
                    'value': 1485174502000000,
                    'type': 'RDFDatetime',
                  },
                  'description': {
                    'value': 'Description of another hunt.',
                    'type': 'unicode',
                  },
                  'urn': {
                    'value': 'aff4:/hunts/H:87654321',
                    'type': 'RDFURN',
                  },
                },
                'type': 'AuditEvent',
              },
            ],
            'used_fields': [
              {
                'value': 'action',
                'type': 'unicode',
              },
              {
                'value': 'description',
                'type': 'unicode',
              },
              {
                'value': 'flow_name',
                'type': 'unicode',
              },
              {
                'value': 'timestamp',
                'type': 'unicode',
              },
              {
                'value': 'urn',
                'type': 'unicode',
              },
              {
                'value': 'user',
                'type': 'unicode',
              },
            ],
          },
          'type': 'ApiAuditChartReportData',
        },
        'representation_type': {
          'value': 'AUDIT_CHART',
          'type': 'EnumNamedValue',
        },
      },
      'type': 'ApiReportData',
    });

    const ths = element.find('th');
    expect(ths.length).toBe(6);
    // Labels are sorted alphabetically.
    expect(ths[0].innerText).toContain('Action');
    expect(ths[1].innerText).toContain('Description');
    expect(ths[2].innerText).toContain('Flow name');
    expect(ths[3].innerText).toContain('Timestamp');
    expect(ths[4].innerText).toContain('Urn');
    expect(ths[5].innerText).toContain('User');

    const tbodyTrs = element.find('tbody tr');
    expect(tbodyTrs.length).toBe(2);

    const getCellValue = ((td) => {
      const semVal = $(td).find('grr-semantic-value');
      return semVal.scope().$eval(semVal.attr('value'));
    });

    const row0Tds = $(tbodyTrs[0]).find('td');
    expect(row0Tds.length).toBe(6);
    // Fields are sorted by label.
    expect(getCellValue(row0Tds[0])).toEqual({
      'value': 'HUNT_CREATED',
      'type': 'EnumNamedValue',
    });
    expect(getCellValue(row0Tds[1])).toEqual({
      'value': 'Description of the hunt.',
      'type': 'unicode',
    });
    expect(getCellValue(row0Tds[2])).toEqual({
      'value': 'Flow Foo.',
      'type': 'unicode',
    });
    expect(getCellValue(row0Tds[3])).toEqual({
      'value': 1485174411000000,
      'type': 'RDFDatetime',
    });
    expect(getCellValue(row0Tds[4])).toEqual({
      'value': 'aff4:/hunts/H:12345678',
      'type': 'RDFURN',
    });
    expect(getCellValue(row0Tds[5])).toEqual({
      'value': 'GRRWorker',
      'type': 'unicode',
    });

    const row1Tds = $(tbodyTrs[1]).find('td');
    expect(row1Tds.length).toBe(6);
    // Fields are sorted by label.
    expect(getCellValue(row1Tds[0])).toEqual({
      'value': 'HUNT_STARTED',
      'type': 'EnumNamedValue',
    });
    expect(getCellValue(row1Tds[1])).toEqual({
      'value': 'Description of another hunt.',
      'type': 'unicode',
    });
    expect(getCellValue(row1Tds[2])).toBe(undefined);
    expect(getCellValue(row1Tds[3])).toEqual({
      'value': 1485174502000000,
      'type': 'RDFDatetime',
    });
    expect(getCellValue(row1Tds[4])).toEqual({
      'value': 'aff4:/hunts/H:87654321',
      'type': 'RDFURN',
    });
    expect(getCellValue(row1Tds[5])).toEqual({
      'value': 'GRRWorker',
      'type': 'unicode',
    });
  });
});


exports = {};

;return exports;});

//angular-components/stats/comparison-chart-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.stats.comparisonChartDirectiveTest');
goog.setTestOnly();

const {statsModule} = goog.require('grrUi.stats.stats');
const {testsModule} = goog.require('grrUi.tests');


describe('comparison chart directive', () => {
  let $compile;
  let $rootScope;

  beforeEach(module('/static/angular-components/stats/comparison-chart.html'));
  beforeEach(module(statsModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
  }));

  const renderTestTemplate = (typedData, preserveOrder) => {
    $rootScope.typedData = typedData;
    $rootScope.preserveOrder = preserveOrder;

    const template = '<grr-comparison-chart ' +
        'typed-data="typedData" ' +
        'preserve-order="preserveOrder">' +
        '</grr-comparison-chart>';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows nothing if the given data is undefined', () => {
    const element = renderTestTemplate();
    expect(element.text()).toContain('No data to display.');

    expect(element.find('th').length).toBe(0);
    expect(element.find('td').length).toBe(0);
  });

  const sampleData = {
    value: {
      data: [
        {value: {label: {value: '< 32768.6s'}, x: {value: 5.55}}},
        {value: {label: {value: '< 16.6s'}, x: {value: 10.55}}},
      ]
    }
  };

  it('shows the given data reverse-sorted if preserveOrder is false', () => {
    const element = renderTestTemplate(sampleData, false);
    // First row corresponds to a greater value: 10.55.
    expect(element.find('tr:nth(1) td:nth(0)').text()).toContain('< 16.6s');
    // Second row corresponds to a smaller value: 5.55.
    expect(element.find('tr:nth(2) td:nth(0)').text()).toContain('< 32768.6s');
  });

  it('shows the given data in the original order if preserveOrder is true', () => {
    const element = renderTestTemplate(sampleData, true);
    expect(element.find('tr:nth(1) td:nth(0)').text()).toContain('< 32768.6s');
    expect(element.find('tr:nth(2) td:nth(0)').text()).toContain('< 16.6s');
  });

  it('updates itself on preserve-order binding changes', () => {
    const element = renderTestTemplate(sampleData, false);
    expect(element.find('tr:nth(1) td:nth(0)').text()).toContain('< 16.6s');

    $rootScope.preserveOrder = true;
    $rootScope.$apply();

    expect(element.find('tr:nth(1) td:nth(0)').text()).toContain('< 32768.6s');
  });
});


exports = {};

;return exports;});

//angular-components/stats/report-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.stats.reportDirectiveTest');
goog.setTestOnly();

const {statsModule} = goog.require('grrUi.stats.stats');
const {stripTypeInfo} = goog.require('grrUi.core.apiService');
const {stubDirective, testsModule} = goog.require('grrUi.tests');


describe('report directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;
  let grrTimeService;


  beforeEach(module(
      '/static/angular-components/stats/report.html'));
  beforeEach(module(statsModule.name));
  beforeEach(module(testsModule.name));

  stubDirective('grrFormClientLabel');
  stubDirective('grrFormTimerange');
  stubDirective('grrChart');

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    grrApiService = $injector.get('grrApiService');
    grrTimeService = $injector.get('grrTimeService');

    let clock = grrTimeService.getCurrentTimeMs();
    grrTimeService.getCurrentTimeMs = (() => {
      clock += 42;
      return clock;
    });
  }));

  const renderTestTemplate = (name) => {
    $rootScope.name = name;

    const template = '<grr-report name="name">' +
        '</grr-report>';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  // mockGrrApiService mocks the `stats/reports' and `stats/reports/*' calls.
  //
  // The mocked `stats/reports' handler will return a list of report descriptors
  // used in this test.
  //
  // The mocked `stats/reports/*' call handler will expect deferredWork[path]
  // to be initialized with an angular promise and will pretend to do false
  // work until the corresponding promise is resolved.
  const mockGrrApiService = () => {
    const deferredWork = {};

    spyOn(grrApiService, 'get').and.callFake((path) => {
      const deferred = $q.defer();
      const promise = deferred.promise;

      if (path === 'stats/reports') {
        const response = {
          'reports': [
            {
              'type': 'ApiReport',
              'value': {
                'desc': {
                  'type': 'ApiReportDescriptor',
                  'value': {
                    'name': {
                      'type': 'unicode',
                      'value': 'FooReportPlugin',
                    },
                    'summary': {
                      'type': 'unicode',
                      'value': 'Foo\'s summary.',
                    },
                    'type': {
                      'type': 'EnumNamedValue',
                      'value': 'FOO_TYPE',
                    },
                    'requires_time_range': {
                      'type': 'bool',
                      'value': false,
                    },
                    'title': {'type': 'unicode', 'value': 'Foo Report'},
                  },
                },
              },
            },
            {
              'type': 'ApiReport',
              'value': {
                'desc': {
                  'type': 'ApiReportDescriptor',
                  'value': {
                    'name': {
                      'type': 'unicode',
                      'value': 'BarReportPlugin',
                    },
                    'summary': {
                      'type': 'unicode',
                      'value': 'Bar\'s summary.',
                    },
                    'type': {
                      'type': 'EnumNamedValue',
                      'value': 'BAR_TYPE',
                    },
                    'requires_time_range': {
                      'type': 'bool',
                      'value': false,
                    },
                    'title': {'type': 'unicode', 'value': 'Bar Report'},
                  },
                },
              },
            }
          ],
        };

        deferred.resolve({ data: response });
      }
      else {
        const response = {
          data: {
            data: {
              value: {
                representation_type: {
                  value: 'STACK_CHART',
                },
                stack_chart: {
                  value: {
                    data: [
                      {
                        value: {
                          label: path,
                          points: [],
                        },
                      },
                    ],
                  },
                },
              },
            },
          },
        };

        // Do some fake work and respond.
        deferredWork[path].promise.then(() => {
          deferred.resolve({ data: response });
        });
      }

      return promise;
    });

    return deferredWork;
  };

  it('drops responses to old requests', () => {
    const deferredWork = mockGrrApiService();
    deferredWork['stats/reports/FooReportPlugin'] = $q.defer();
    deferredWork['stats/reports/BarReportPlugin'] = $q.defer();

    const element = renderTestTemplate('FooReportPlugin');

    // Change the report description while the other one's still loading.
    $rootScope.name = 'BarReportPlugin';
    $rootScope.$apply();

    // The chart should still be loading.
    expect(element.find('grr-chart').length).toBe(0);

    // Resolve the old request.
    deferredWork['stats/reports/FooReportPlugin'].resolve();
    $rootScope.$apply();

    // The chart should still be loading.
    expect(element.find('grr-chart').length).toBe(0);

    // Resolve the new request.
    deferredWork['stats/reports/BarReportPlugin'].resolve();
    $rootScope.$apply();

    // The chart should now be loaded.
    const chart = element.find('grr-chart');
    expect(chart.length).toBe(1);

    const attribute = chart.attr('typed-data');
    const typedData = chart.scope().$eval(attribute);
    const data = stripTypeInfo(typedData['data']);

    expect(data['stack_chart']['data'][0]['label']).toBe(
        'stats/reports/BarReportPlugin');

    expect(JSON.stringify(typedData)).not.toContain('FooReportPlugin');
  });
});


exports = {};

;return exports;});

//angular-components/stats/report-listing-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.stats.reportListingDirectiveTest');
goog.setTestOnly();

const {parseStatsReportsApiResponse} = goog.require('grrUi.stats.reportListingDirective');

describe('stats.reportListingDirective.parseStatsReportsApiResponse', () => {
  it('Parses the response into a jsTree-compatible format.', () => {
    const reports = [
      {
        desc: {
          name: 'FooReportPlugin',
          title: 'Foos\' Activity',
          type: 'SERVER',
        },
      },
      {
        desc: {
          name: 'BarReportPlugin',
          title: 'Bars Reported Over Time',
          type: 'SERVER',
        },
      },
      {
        desc: {
          name: 'BazReportPlugin',
          title: 'Baz Statistics',
          type: 'CLIENT',
        },
      }
    ];

    const ret = parseStatsReportsApiResponse(reports);

    expect(ret).toEqual([
      {
        children: [
          {
            desc: {
              name: 'FooReportPlugin',
              title: 'Foos\' Activity',
              type: 'SERVER',
            },
            id: 'FooReportPlugin',
            text: 'Foos\' Activity',
          },
          {
            desc: {
              name: 'BarReportPlugin',
              title: 'Bars Reported Over Time',
              type: 'SERVER',
            },
            id: 'BarReportPlugin',
            text: 'Bars Reported Over Time',
          },
        ],
        state: {
          disabled: true,
          opened: true,
        },
        text: 'Server',
      },
      {
        children: [
          {
            desc: {
              name: 'BazReportPlugin',
              title: 'Baz Statistics',
              type: 'CLIENT',
            },
            id: 'BazReportPlugin',
            text: 'Baz Statistics',
          },
        ],
        state: {
          disabled: true,
          opened: true,
        },
        text: 'Client',
      },
    ]);
  });
});


exports = {};

;return exports;});

//angular-components/user/approver-input-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.user.approverInputDirectiveTest');
goog.setTestOnly();

const {testsModule} = goog.require('grrUi.tests');
const {userModule} = goog.require('grrUi.user.user');


describe('approver input', () => {
  let $compile;
  let $rootScope;
  let grrApiService;

  beforeEach(module('/static/angular-components/user/approver-input.html'));
  beforeEach(module('/static/angular-components/core/typeahead-match.html'));
  beforeEach(module(userModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    const $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    grrApiService = $injector.get('grrApiService');

    const approverSuggestions = [
      {
        value: {username: {value: 'sanchezmorty', type: 'unicode'}},
        type: 'ApproverSuggestion',
      },
      {
        value: {username: {value: 'sanchezrick', type: 'unicode'}},
        type: 'ApproverSuggestion',
      },
      {
        value: {username: {value: 'sanchezsummer', type: 'unicode'}},
        type: 'ApproverSuggestion',
      },
    ];

    spyOn(grrApiService, 'get').and.callFake((url, params) => {
      const deferred = $q.defer();
      const suggestions = approverSuggestions.filter(
          sugg => sugg.value.username.value.startsWith(params.username_query));
      deferred.resolve({data: {suggestions}});
      return deferred.promise;
    });
  }));

  function renderTestTemplate(initialValue = '') {
    const template = '<grr-approver-input ng-model="value"/>';
    $rootScope.value = initialValue;

    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  }

  function type(element, text) {
    $('input', element).val(text).trigger('input').trigger('change');
  }

  it('renders a text input', () => {
    const element = renderTestTemplate();
    expect($('input', element).length).toBe(1);
  });

  it('calls API for username input', () => {
    const element = renderTestTemplate();
    type(element, 'sanchezsu');

    expect(grrApiService.get)
        .toHaveBeenCalledWith('/users/approver-suggestions', {
          username_query: 'sanchezsu',
        });
  });

  it('calls API for rightmost username', () => {
    const element = renderTestTemplate();

    type(element, 'foo,bar');
    expect(grrApiService.get)
        .toHaveBeenCalledWith('/users/approver-suggestions', {
          username_query: 'bar',
        });

    type(element, 'bar,foo');
    expect(grrApiService.get)
        .toHaveBeenCalledWith('/users/approver-suggestions', {
          username_query: 'foo',
        });

    type(element, 'foo , bar , baz');
    expect(grrApiService.get)
        .toHaveBeenCalledWith('/users/approver-suggestions', {
          username_query: 'baz',
        });
  });

  it('shows autocomplete for single suggestion', () => {
    const element = renderTestTemplate();
    type(element, 'sanchezsu');

    expect($('[uib-typeahead-popup] li').length).toBe(1);
    expect($('[uib-typeahead-popup] li:eq(0)').text().trim())
        .toBe('sanchezsummer');
  });

  it('shows autocomplete for multiple suggestions', () => {
    const element = renderTestTemplate();
    type(element, 'san');

    expect($('[uib-typeahead-popup] li').length).toBe(3);
    expect($('[uib-typeahead-popup] li:eq(0)').text().trim())
        .toBe('sanchezmorty');
    expect($('[uib-typeahead-popup] li:eq(1)').text().trim())
        .toBe('sanchezrick');
    expect($('[uib-typeahead-popup] li:eq(2)').text().trim())
        .toBe('sanchezsummer');
  });

  it('does not show previous usernames in autocomplete', () => {
    const element = renderTestTemplate();
    type(element, 'sanchezsummer, san');

    expect($('[uib-typeahead-popup] li').length).toBe(2);
    expect($('[uib-typeahead-popup] li:eq(0)').text().trim())
        .toBe('sanchezmorty');
    expect($('[uib-typeahead-popup] li:eq(1)').text().trim())
        .toBe('sanchezrick');
  });

  it('does not show autocomplete for empty input', () => {
    const element = renderTestTemplate();
    type(element, '');

    expect(grrApiService.get).not.toHaveBeenCalled();
    expect($('[uib-typeahead-popup] li').length).toBe(0);
  });

  it('does not show autocomplete for empty search', () => {
    const element = renderTestTemplate();
    type(element, 'sanchez, ');

    expect(grrApiService.get).not.toHaveBeenCalled();
    expect($('[uib-typeahead-popup] li').length).toBe(0);
  });

  it('uses value from ng-model', () => {
    const element = renderTestTemplate('foo');
    expect($('input', element).val()).toBe('foo');
  });

  it('correctly assigns ng-model', () => {
    const element = renderTestTemplate();
    type(element, 'sanchez, foo, ');
    expect(element.scope().value).toBe('sanchez, foo, ');
  });
});


exports = {};

;return exports;});

//angular-components/user/user-dashboard-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.user.userDashboardDirectiveTest');
goog.setTestOnly();

const {filterOutDuplicateApprovals} = goog.require('grrUi.user.userDashboardDirective');

describe('userDashboardDirective.filterOutDuplicateApprovals', () => {
  let index = 0;
  function approval(clientId, isValid) {
    index += 1;
    return {
      value: {
        is_valid: {
          value: isValid,
        },
        id: {
          value: index,
        },
        subject: {
          value: {
            client_id: {value: clientId,},
          },
        }
      }
    };
  }

  it('does not remove any of 2 invalid approvals for different subjects', () => {
    const approvals = [
      approval('C.0', false),
      approval('C.1', false),
    ];
    const filteredApprovals = filterOutDuplicateApprovals(approvals);

    expect(filteredApprovals).toEqual(approvals);
  });

  it('prefers later invalid approval for the same client', () => {
    // As approvals are expected to be sorted in reversed-timestamp order,
    // "later" means the one that appears earlier in the list.
    const approvals = [
      approval('C.0', false),
      approval('C.0', false),
      approval('C.0', false),
    ];
    const filteredApprovals = filterOutDuplicateApprovals(approvals);

    expect(filteredApprovals).toEqual([approvals[0]]);
  });

  it('prefers later valid approval for the same client', () => {
    // As approvals are expected to be sorted in reversed-timestamp order,
    // "later" means the one that appears earlier in the list.
    const approvals = [
      approval('C.0', true),
      approval('C.0', true),
      approval('C.0', true),
    ];
    const filteredApprovals = filterOutDuplicateApprovals(approvals);

    expect(filteredApprovals).toEqual([approvals[0]]);
  });

  it('removes invalid approval if valid is present', () => {
    const approvals = [
      approval('C.0', true),
      approval('C.0', false),
    ];
    const filteredApprovals = filterOutDuplicateApprovals(approvals);

    expect(filteredApprovals).toEqual([approvals[0]]);
  });

  it('removes multiple invalid approvals if valid is present', () => {
    const approvals = [
      approval('C.0', false),
      approval('C.0', false),
      approval('C.0', true),
      approval('C.0', false),
      approval('C.0', false),
    ];
    const filteredApprovals = filterOutDuplicateApprovals(approvals);

    expect(filteredApprovals).toEqual([approvals[2]]);
  });

  it('removes multiple invalid approvals for multiple clients', () => {
    const approvals = [
      approval('C.0', false),
      approval('C.0', true),
      approval('C.0', false),
      approval('C.1', false),
      approval('C.1', true),
      approval('C.1', false),
    ];
    const filteredApprovals = filterOutDuplicateApprovals(approvals);

    expect(filteredApprovals).toEqual([approvals[1], approvals[4]]);
  });
});

;return exports;});

//angular-components/user/user-desktop-notifications-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.user.userDesktopNotificationsDirectiveTest');
goog.setTestOnly();

const {UserNotificationButtonDirective} = goog.require('grrUi.user.userNotificationButtonDirective');
const {testsModule} = goog.require('grrUi.tests');
const {userModule} = goog.require('grrUi.user.user');


describe('User desktop notifications directive', () => {
  let $compile;
  let $interval;
  let $q;
  let $rootScope;
  let $location;
  let grrApiService;


  const FETCH_INTERVAL = UserNotificationButtonDirective.fetch_interval;

  beforeEach(module(userModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $location = $injector.get('$location');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $interval = $injector.get('$interval');
    grrApiService = $injector.get('grrApiService');

    window.Notification = function() {
      this.close = (() => {});
    };
    spyOn(window, 'Notification').and.callThrough();
  }));

  const render = () => {
    const template = '<grr-user-desktop-notifications />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();
    return element;
  };

  const mockApiServiceResponse = (value) => {
    spyOn(grrApiService, 'get').and.callFake(() => {
      const deferred = $q.defer();
      deferred.resolve({data: {items: value}});
      return deferred.promise;
    });
  };

  it('fetches a single pending notification and displays it as a desktop ' +
         'notification',
     () => {
       mockApiServiceResponse([{
         'type': 'ApiNotification',
         'value': {
           'is_pending': {'type': 'bool', 'value': true},
           'message': {
             'type': 'unicode',
             'value': 'Host-0: <some message>',
           },
           'reference': {
             'type': 'ApiNotificationReference',
             'value': {
               'client': {
                 'type': 'ApiNotificationClientReference',
                 'value': {
                   'client_id': {
                     'type': 'ClientURN',
                     'value': 'aff4:/C.1000000000000000',
                   }
                 }
               },
               'type': {
                 'type': 'EnumNamedValue',
                 'value': 'CLIENT',
               },
             }
           },
           'timestamp': {'type': 'RDFDatetime', 'value': 42000000},
         }
       }]);

       render();
       $interval.flush(FETCH_INTERVAL);

       expect(grrApiService.get).toHaveBeenCalled();

       expect(Notification.calls.count()).toBe(1);

       expect(Notification).toHaveBeenCalledWith('GRR', {
         'body': 'Host-0: <some message>',
         'icon': 'static/images/grr_logo_notification.png',
         'tag': 'GRR42000000'
       });
     });

  it('provides a relevant onclick callback for the desktop notification',
     () => {
       mockApiServiceResponse([{
         'type': 'ApiNotification',
         'value': {
           'is_pending': {'type': 'bool', 'value': true},
           'message': {
             'type': 'unicode',
             'value': 'Host-0: <some message>',
           },
           'reference': {
             'type': 'ApiNotificationReference',
             'value': {
               'client': {
                 'type': 'ApiNotificationClientReference',
                 'value': {
                   'client_id': {
                     'type': 'ClientURN',
                     'value': 'aff4:/C.1000000000000000',
                   }
                 }
               },
               'type': {
                 'type': 'EnumNamedValue',
                 'value': 'CLIENT',
               },
             }
           },
           'timestamp': {'type': 'RDFDatetime', 'value': 42000000},
         }
       }]);

       spyOn(grrApiService, 'delete');
       spyOn($location, 'path');

       render();
       $interval.flush(FETCH_INTERVAL);

       try {
         Notification.calls.mostRecent().returnValue.onclick();
       } catch (e) {
         // TODO: Remove after migration to jasmine 3.6.
         Notification.calls.mostRecent().object.onclick();
       }

       expect(grrApiService.delete)
           .toHaveBeenCalledWith('users/me/notifications/pending/42000000');

       expect($location.path).toHaveBeenCalledWith('clients/C.1000000000000000');
     });

  it('fetches pending notifications and displays the last two of them as ' +
         'desktop notifications',
     () => {
       mockApiServiceResponse([
         {
           'type': 'ApiNotification',
           'value': {
             'is_pending': {'type': 'bool', 'value': true},
             'message':
                 {'type': 'unicode', 'value': 'Host-0: <another message>'},
             'reference': {
               'type': 'ApiNotificationReference',
               'value': {
                 'type': {'type': 'EnumNamedValue', 'value': 'VFS'},
                 'vfs': {
                   'type': 'ApiNotificationVfsReference',
                   'value': {
                     'client_id': {
                       'type': 'ClientURN',
                       'value': 'aff4:/C.1000000000000000',
                     },
                     'vfs_path': {
                       'type': 'RDFURN',
                       'value': 'aff4:/C.1000000000000000',
                     }
                   }
                 }
               }
             },
             'timestamp': {'type': 'RDFDatetime', 'value': 48000000},
           }
         },
         {
           'type': 'ApiNotification',
           'value': {
             'is_pending': {'type': 'bool', 'value': true},
             'message': {
               'type': 'unicode',
               'value': 'Host-0: <some message>',
             },
             'reference': {
               'type': 'ApiNotificationReference',
               'value': {
                 'client': {
                   'type': 'ApiNotificationClientReference',
                   'value': {
                     'client_id': {
                       'type': 'ClientURN',
                       'value': 'aff4:/C.1000000000000000',
                     }
                   }
                 },
                 'type': {
                   'type': 'EnumNamedValue',
                   'value': 'CLIENT',
                 },
               }
             },
             'timestamp': {'type': 'RDFDatetime', 'value': 42000000},
           }
         },
         {
           'type': 'ApiNotification',
           'value': {
             'is_pending': {'type': 'bool', 'value': true},
             'message':
                 {'type': 'unicode', 'value': 'Host-0: <some other message>'},
             'reference': {
               'type': 'ApiNotificationReference',
               'value': {
                 'type': {'type': 'EnumNamedValue', 'value': 'VFS'},
                 'vfs': {
                   'type': 'ApiNotificationVfsReference',
                   'value': {
                     'client_id': {
                       'type': 'ClientURN',
                       'value': 'aff4:/C.1000000000000000',
                     },
                     'vfs_path': {
                       'type': 'RDFURN',
                       'value': 'aff4:/C.1000000000000000',
                     }
                   }
                 }
               }
             },
             'timestamp': {'type': 'RDFDatetime', 'value': 44000000},
           }
         }
       ]);

       render();
       $interval.flush(FETCH_INTERVAL);

       expect(grrApiService.get).toHaveBeenCalled();

       expect(Notification.calls.count()).toBe(2);

       const firstCallArgs = Notification.calls.all()[0]['args'];
       const secondCallArgs = Notification.calls.all()[1]['args'];

       expect(firstCallArgs).toEqual([
         'GRR', {
           'body': 'Host-0: <some other message>',
           'icon': 'static/images/grr_logo_notification.png',
           'tag': 'GRR44000000'
         }
       ]);

       expect(secondCallArgs).toEqual([
         'GRR', {
           'body': 'Host-0: <another message>',
           'icon': 'static/images/grr_logo_notification.png',
           'tag': 'GRR48000000'
         }
       ]);
     });
});


exports = {};

;return exports;});

//angular-components/user/user-label-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.user.userLabelDirectiveTest');
goog.setTestOnly();

const {testsModule} = goog.require('grrUi.tests');
const {userModule} = goog.require('grrUi.user.user');


describe('User label directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;


  beforeEach(module('/static/angular-components/user/user-label.html'));
  beforeEach(module(userModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    grrApiService = $injector.get('grrApiService');
  }));

  const render = () => {
    const template = '<grr-user-label />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();
    return element;
  };


  it('fetches username and shows it', () => {
    const mockUserName = 'Test Username';
    spyOn(grrApiService, 'getCached').and.callFake(() => {
      const deferred = $q.defer();
      deferred.resolve({
        data: {
          value: {
            username: {
              value: mockUserName,
            },
          },
        },
      });
      return deferred.promise;
    });

    const element = render(mockUserName);
    expect(element.text().trim()).toBe(`User: ${mockUserName}`);
  });

  it('shows special message in case of 403 error', () => {
    spyOn(grrApiService, 'getCached').and.callFake(() => {
      const deferred = $q.defer();
      deferred.reject({
        status: 403,
        statusText: 'Unauthorized',
      });
      return deferred.promise;
    });

    const element = render();
    expect(element.text().trim()).toBe('User: Authentication Error');
  });

  it('shows status text in case of a non-403 error', () => {
    spyOn(grrApiService, 'getCached').and.callFake(() => {
      const deferred = $q.defer();
      deferred.reject({
        status: 500,
        statusText: 'Error',
      });
      return deferred.promise;
    });

    const element = render();
    expect(element.text().trim()).toBe('User: Error');
  });
});


exports = {};

;return exports;});

//angular-components/user/user-notification-button-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.user.userNotificationButtonDirectiveTest');
goog.setTestOnly();

const {UserNotificationButtonDirective} = goog.require('grrUi.user.userNotificationButtonDirective');
const {testsModule} = goog.require('grrUi.tests');
const {userModule} = goog.require('grrUi.user.user');


describe('User notification button directive', () => {
  let $compile;
  let $interval;
  let $q;
  let $rootScope;
  let grrApiService;


  const FETCH_INTERVAL = UserNotificationButtonDirective.fetch_interval;

  beforeEach(module('/static/angular-components/user/user-notification-button.html'));
  beforeEach(module(userModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $interval = $injector.get('$interval');
    grrApiService = $injector.get('grrApiService');
  }));

  const render = () => {
    const template = '<grr-user-notification-button />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();
    return element;
  };

  const mockApiServiceResponse = (value) => {
    spyOn(grrApiService, 'get').and.callFake(() => {
      const deferred = $q.defer();
      deferred.resolve({ data: { count: value }});
      return deferred.promise;
    });
  };

  it('fetches pending notifications count and displays an info-styled button on 0',
     () => {
       mockApiServiceResponse(0);

       const element = render();
       $interval.flush(FETCH_INTERVAL);
       expect(grrApiService.get).toHaveBeenCalled();
       expect(element.text().trim()).toBe("0");
       expect(element.find('button').hasClass('btn-info')).toBe(true);
     });

  it('non-zero notifications count is shown as danger-styled button', () => {
    mockApiServiceResponse(5);

    const element = render();
    $interval.flush(FETCH_INTERVAL);
    expect(grrApiService.get).toHaveBeenCalled();
    expect(element.text().trim()).toBe("5");
    expect(element.find('button').hasClass('btn-danger')).toBe(true);
  });
});


exports = {};

;return exports;});

//angular-components/user/user-notification-item-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.user.userNotificationItemDirectiveTest');
goog.setTestOnly();

const {annotateApiNotification} = goog.require('grrUi.user.userNotificationItemDirective');

describe('User notification item directive', () => {
  describe('annotateApiNotification()', () => {

    const buildNotification =
        ((reference) => ({
           value: {
             is_pending: {
               value: true,
             },
             message: {
               value: 'Recursive Directory Listing complete 0 nodes, 0 dirs',
             },
             reference: reference,
             timestamp: {
               value: 1461154705560207,
             },
           },
         }));

    it('annotates CLIENT notification correctly', () => {
      const notification = buildNotification({
        value: {
          type: {
            value: 'CLIENT',
          },
          client: {
            value: {
              client_id: {
                value: 'aff4:/C.0000000000000001',
              },
            },
          },
        },
      });
      annotateApiNotification(notification);

      expect(notification.link).toEqual('clients/C.0000000000000001');
      expect(notification.refType).toEqual('CLIENT');
    });

    it('annotates HUNT notification correctly', () => {
      const notification = buildNotification({
        value: {
          type: {
            value: 'HUNT',
          },
          hunt: {
            value: {
              hunt_id: {
                value: 'H:123456',
              },
            },
          },
        },
      });
      annotateApiNotification(notification);

      expect(notification.link).toEqual('hunts/H:123456');
      expect(notification.refType).toEqual('HUNT');
    });

    it('annotates CRON notification correctly', () => {
      const notification = buildNotification({
        value: {
          type: {
            value: 'CRON',
          },
          cron: {
            value: {
              cron_job_id: {
                value: 'FooBar',
              },
            },
          },
        },
      });
      annotateApiNotification(notification);

      expect(notification.link).toEqual('crons/FooBar');
      expect(notification.refType).toEqual('CRON');
    });

    it('annotates FLOW notification correctly', () => {
      const notification = buildNotification({
        value: {
          type: {
            value: 'FLOW',
          },
          flow: {
            value: {
              client_id: {
                value: 'aff4:/C.0001000200030004',
              },
              flow_id: {
                value: 'F:123456',
              },
            },
          },
        },
      });
      annotateApiNotification(notification);

      expect(notification.link).toEqual(
          'clients/C.0001000200030004/flows/F:123456');
      expect(notification.refType).toEqual('FLOW');
    });

    it('annotates CLIENT_APPROVAL notification correctly', () => {
      const notification = buildNotification({
        value: {
          type: {
            value: 'CLIENT_APPROVAL',
          },
          client_approval: {
            value: {
              client_id: {
                value: 'aff4:/C.0001000200030004',
              },
              approval_id: {
                value: 'foo-bar',
              },
              username: {
                value: 'test',
              },
            },
          },
        },
      });
      annotateApiNotification(notification);

      expect(notification.link).toEqual(
          'users/test/approvals/client/C.0001000200030004/foo-bar');
      expect(notification.refType).toEqual('CLIENT_APPROVAL');
    });

    it('annotates HUNT_APPROVAL notification correctly', () => {
      const notification = buildNotification({
        value: {
          type: {
            value: 'HUNT_APPROVAL',
          },
          hunt_approval: {
            value: {
              hunt_id: {
                value: 'H:123456',
              },
              approval_id: {
                value: 'foo-bar',
              },
              username: {
                value: 'test',
              },
            },
          },
        },
      });
      annotateApiNotification(notification);

      expect(notification.link).toEqual(
          'users/test/approvals/hunt/H:123456/foo-bar');
      expect(notification.refType).toEqual('HUNT_APPROVAL');
    });

    it('annotates CRON_JOB_APPROVAL notification correctly', () => {
      const notification = buildNotification({
        value: {
          type: {
            value: 'CRON_JOB_APPROVAL',
          },
          cron_job_approval: {
            value: {
              cron_job_id: {
                value: 'FooBar',
              },
              approval_id: {
                value: 'foo-bar',
              },
              username: {
                value: 'test',
              },
            },
          },
        },
      });
      annotateApiNotification(notification);

      expect(notification.link).toEqual(
          'users/test/approvals/cron-job/FooBar/foo-bar');
      expect(notification.refType).toEqual('CRON_JOB_APPROVAL');
    });


    it('annotates UNKNOWN notification correctly', () => {
      const notification = buildNotification({
        value: {
          type: {
            value: 'UNKNOWN',
          },
          unknown: {
            value: {
              source_urn: {
                value: 'aff4:/foo/bar',
              },
              subject_urn: {
                value: 'aff4:/blah/blah',
              },
            },
          },
        },
      });
      annotateApiNotification(notification);

      expect(notification.link).toBe(null);
      expect(notification.refType).toEqual('UNKNOWN');
    });

    it('handles missing references correctly', () => {
      const notification = {
        value: {
          is_pending: {
            value: false,
          },
          message: {
            value: 'Recursive Directory Listing complete 0 nodes, 0 dirs',
          },
          timestamp: {
            value: 1461154705560207,
          },
        },
      };
      annotateApiNotification(notification);

      expect(notification.isPending).toBe(false);
      expect(notification.link).toBeUndefined();
      expect(notification.refType).toBeUndefined();
    });
  });
});


exports = {};

;return exports;});

//angular-components/client/virtual-file-system/breadcrumbs-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.client.virtualFileSystem.breadcrumbsDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {clientModule} = goog.require('grrUi.client.client');


describe('breadcrums directive', () => {
  let $compile;
  let $rootScope;
  let $scope;


  beforeEach(module('/static/angular-components/client/virtual-file-system/breadcrumbs.html'));
  beforeEach(module(clientModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $scope = $rootScope.$new();
  }));

  const render = (path, stripEndingSlash) => {
    $scope.obj = {
      // We need to pass an object to see changes.
      path: path,
      stripEndingSlash: stripEndingSlash,
    };

    const template =
        '<grr-breadcrumbs strip-ending-slash="obj.stripEndingSlash" ' +
        'path="obj.path" />';
    const element = $compile(template)($scope);
    $scope.$apply();

    return element;
  };

  it('should show the path components as links', () => {
    const element = render('path/to/some/resource');

    // Last component is dropped as it points to a file. Therefore there
    // should be 2 links: "path" and "to". And "some" should be a
    // non-link element with class .active.
    expect(element.find('a').length).toBe(2);
    expect(element.find('li.active').text().trim()).toBe('some');
  });

  it('strips ending slash if strip-ending-slash is true', () => {
    const element = render('path/to/some/resource/', true);

    // Ending slash is stripped, so behaviour should be similar to the
    // one with render('path/to/some/resource').
    expect(element.find('a').length).toBe(2);
    expect(element.find('li.active').text().trim()).toBe('some');
  });

  it('should change the path when a link is clicked', () => {
    const element = render('path/to/some/resource');
    const links = element.find('a');

    expect(links.length).toBe(2);
    browserTriggerEvent(links[1], 'click');
    $scope.$apply();

    expect(element.find('li.active').text().trim()).toBe('to');
    expect($scope.obj.path).toBe('path/to/');
  });

  it('should change the links when the scope changes', () => {
    const element = render('path/to/some/resource');

    expect(element.find('a').length).toBe(2);
    expect(element.find('li.active').text().trim()).toBe('some');

    $scope.obj.path = "a/path/to/another/file";
    $scope.$apply();
    expect(element.find('a').length).toBe(3);
    expect(element.find('li.active').text().trim()).toBe('another');
  });
});


exports = {};

;return exports;});

//angular-components/client/virtual-file-system/encodings-dropdown-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.client.virtualFileSystem.encodingsDropdownDirectiveTest');
goog.setTestOnly();

const {clientModule} = goog.require('grrUi.client.client');
const {testsModule} = goog.require('grrUi.tests');


describe('encodings dropdown directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let $scope;
  let grrApiService;


  beforeEach(module('/static/angular-components/client/virtual-file-system/encodings-dropdown.html'));
  beforeEach(module(clientModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $q = $injector.get('$q');
    grrApiService = $injector.get('grrApiService');
    $scope = $rootScope.$new();
  }));

  const mockApiService = (responses) => {
    spyOn(grrApiService, 'get').and.callFake((path) => {
      const response = {
        encodings: responses[path]
      };  // Wrap return value in type structure.
      return $q.when({ data: response });
    });
  };

  const render = (encoding) => {
    $scope.obj = {
      // We need to pass an object to see changes.
      encoding: encoding,
    };

    const template = '<grr-encodings-dropdown encoding="obj.encoding" />';
    const element = $compile(template)($scope);
    $scope.$apply();

    return element;
  };

  it('should select the scope value', () => {
    const encodings =
        [{value: 'ENC1'}, {value: 'ENC2'}, {value: 'ENC99'}, {value: 'UTF_8'}];
    mockApiService({
      'reflection/file-encodings': encodings,
    });

    const element = render('ENC1');
    expect(element.find('option').length).toBe(4);
    expect(element.find('option[selected]').text().trim()).toBe('ENC1');
    expect(grrApiService.get).toHaveBeenCalled();
  });

  it('should change the selection when the scope changes', () => {
    const encodings =
        [{value: 'ENC1'}, {value: 'ENC2'}, {value: 'ENC99'}, {value: 'UTF_8'}];
    mockApiService({
      'reflection/file-encodings': encodings,
    });

    const element = render('ENC1');
    expect(element.find('option[selected]').text().trim()).toBe('ENC1');

    $scope.obj.encoding = 'UTF_8';
    $scope.$apply();
    expect(element.find('option[selected]').text().trim()).toBe('UTF_8');
  });

  it('should be disabled when no options are available', () => {
    mockApiService({
      'some/url': [],
    });

    const element = render('UTF_8');
    expect(element.find('select[disabled]').length).toBe(1);
    expect(element.find('option').text().trim()).toBe('No encodings available.');
    expect($scope.obj.encoding).toBe('UTF_8'); // It does not change the model.
  });
});


exports = {};

;return exports;});

//angular-components/client/virtual-file-system/file-context-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.client.virtualFileSystem.fileContextDirectiveTest');
goog.setTestOnly();

const {FileContextController} = goog.require('grrUi.client.virtualFileSystem.fileContextDirective');


describe('file context directive', () => {
  let $rootScope;

  beforeEach(inject(($injector) => {
    $rootScope = $injector.get('$rootScope');
  }));

  const getController = () => {
    let controller;

    inject(($injector) => {
      controller = $injector.instantiate(FileContextController, {
        '$scope': $rootScope,
      });
    });
    // We need to set the controller instance on the scope, so that the watchers apply.
    $rootScope.controller = controller;
    $rootScope.$apply();

    return controller;
  };

  it('changes the controller values when the scope values change', () => {
    const controller = getController();

    expect(controller.clientId).toBeUndefined();
    expect(controller.selectedFilePath).toBeUndefined();
    expect(controller.selectedFileVersion).toBeUndefined();

    $rootScope.clientId = 42;
    $rootScope.selectedFilePath = 'some/path/test.txt';
    $rootScope.selectedFileVersion = 1337;
    $rootScope.$apply();

    expect(controller.clientId).toEqual(42);
    expect(controller.selectedFilePath).toEqual('some/path/test.txt');
    expect(controller.selectedFileVersion).toEqual(1337);
  });

  it('changes the scope whenever the controller values change', () => {
    const controller = getController();

    controller.clientId = 42;
    controller.selectedFilePath = 'some/path/test.txt';
    controller.selectedFileVersion = 1337;
    $rootScope.$apply();

    expect($rootScope.clientId).toEqual(42);
    expect($rootScope.selectedFilePath).toEqual('some/path/test.txt');
    expect($rootScope.selectedFileVersion).toEqual(1337);
  });

  it('allows settings the selected file via selectFile', () => {
    const controller = getController();

    controller.selectFile('some/path/test.txt');
    $rootScope.$apply();

    expect($rootScope.selectedFilePath).toEqual('some/path/test.txt');
    expect($rootScope.selectedFileVersion).toBeUndefined();

    controller.selectFile('some/other/path/test.txt', 1337);
    $rootScope.$apply();

    expect($rootScope.selectedFilePath).toEqual('some/other/path/test.txt');
    expect($rootScope.selectedFileVersion).toEqual(1337);
  });
});


exports = {};

;return exports;});

//angular-components/client/virtual-file-system/file-hex-view-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.client.virtualFileSystem.fileHexViewDirectiveTest');
goog.setTestOnly();

const {testsModule} = goog.require('grrUi.tests');
const {virtualFileSystemModule} = goog.require('grrUi.client.virtualFileSystem.virtualFileSystem');


describe('file hex view directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;


  beforeEach(module('/static/angular-components/client/virtual-file-system/file-hex-view.html'));
  beforeEach(module(virtualFileSystemModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    grrApiService = $injector.get('grrApiService');
  }));

  const render = (clientId, filePath) => {
    $rootScope.clientId = clientId;
    $rootScope.selectedFilePath = filePath;

    const template = '<grr-file-context' +
        '    client-id="clientId"' +
        '    selected-file-path="selectedFilePath">' +
        '  <grr-file-hex-view />' +
        '</grr-file-context>';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('shows proper hex values', () => {
    spyOn(grrApiService, 'head').and.callFake(() => $q.when({
      headers: function() {
        return 100;
      }
    }));
    spyOn(grrApiService, 'get').and.callFake(() => $q.when({
      data: 'some text'
    }));

    const element = render('C.0000111122223333', 'fs/os/c/test.txt');

    expect(grrApiService.get).toHaveBeenCalled();
    expect(element.find('table.offset-area tr:first-child td').text().trim())
        .toEqual('0x00000000');
    expect(element.find('table.offset-area tr:last-child td').text().trim())
        .toEqual('0x00000300');
    expect(element.find('table.hex-area tr:first-child td').text().trim())
        .toEqual('736f6d652074657874');
    expect(element.find('table.content-area tr:first-child td').text().trim())
        .toEqual('some text');
  });

  it('shows a hint when the file is not available', () => {
    spyOn(grrApiService, 'head')
        .and.callFake(() => $q.reject('Some Error Message'));

    const element = render('C.0000111122223333', 'fs/os/c/test.txt');

    expect(grrApiService.head).toHaveBeenCalled();
    expect(element.find('.no-content').length).toEqual(1);
  });
});


exports = {};

;return exports;});

//angular-components/client/virtual-file-system/file-tree-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.client.virtualFileSystem.fileTreeDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {virtualFileSystemModule} = goog.require('grrUi.client.virtualFileSystem.virtualFileSystem');


describe('file tree view directive', () => {
  let $compile;
  let $q;
  let $rootScope;
  let grrApiService;


  beforeEach(module('/static/angular-components/client/virtual-file-system/file-tree.html'));
  beforeEach(module(virtualFileSystemModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    grrApiService = $injector.get('grrApiService');
  }));

  const render = (clientId, filePath) => {
    $rootScope.clientId = clientId;
    $rootScope.selectedFolderPath = filePath;
    $rootScope.selectedFilePath = filePath;

    const template = '<grr-file-context' +
        '    client-id="clientId"' +
        '    selected-folder-path="selectedFolderPath"' +
        '    selected-file-path="selectedFilePath"' +
        '    selected-file-version="selectedFileVersion">' +
        '  <grr-file-tree />' +
        '</grr-file-context>';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  const mockApiService = (responses) => {
    spyOn(grrApiService, 'get').and.callFake((path) => {
      const response = {
        items: responses[path]
      };  // Wrap return value in type structure.
      return $q.when({ data: response });
    });
  };

  const getChildNodeTexts = (jsTree, nodeId) => {
    const treeItems = jsTree.find(nodeId).find('[role=treeitem]');
    const texts = [];
    angular.forEach(treeItems, (item) => {
      texts.push($(item).text());
    });
    return texts;
  };

  it('shows correct nested folder structure', (done) => {
    const responses = {};
    responses['clients/C.0000111122223333/vfs-index/'] = [
      { value: { name: { value: 'fs' }, path: { value: 'fs' } } }];
    responses['clients/C.0000111122223333/vfs-index/fs'] = [
      { value: { name: { value: 'os' }, path: { value: 'fs/os' } } },
      { value: { name: { value: 'tsk' }, path: { value: 'fs/tsk' } } }];
    responses['clients/C.0000111122223333/vfs-index/fs/os'] = [
      { value: { name: { value: 'dir1' }, path: { value: 'fs/os/dir1' } } },
      { value: { name: { value: 'dir2' }, path: { value: 'fs/os/dir2' } } },
      { value: { name: { value: 'dir3' }, path: { value: 'fs/os/dir3' } } }];
    mockApiService(responses);

    const element = render('C.0000111122223333', 'fs');
    const jsTree = element.find('#file-tree');

    jsTree.one('load_node.jstree', () => {
      expect(jsTree.find('[role=treeitem]').length).toBe(1);
      const treeitem_id = jsTree.find('[role=treeitem]').attr('id');
      if (treeitem_id == "_fs_anchor") {
        // jstree 3.3.10
        expect(jsTree.find('[role=treeitem]').attr('id')).toBe('_fs_anchor');

        // Trigger loading of children of fs.
        browserTriggerEvent(jsTree.find('#_fs_anchor'), 'click');
        jsTree.one('open_node.jstree', () => {
          expect(getChildNodeTexts(jsTree, 'li#_fs ul.jstree-children')).toEqual(['os', 'tsk']);

          // Trigger loading of children of fs/os.
          browserTriggerEvent(jsTree.find('#_fs-os_anchor'), 'click');
          jsTree.one('open_node.jstree', () => {
            expect(getChildNodeTexts(jsTree, 'li#_fs-os ul.jstree-children')).toEqual(['dir1', 'dir2', 'dir3']);
            expect(jsTree.find('[role=treeitem]').length).toBe(6); // There should be six tree nodes in total.
            done();
          });
          $rootScope.$apply();
        });
      } else {
        // jstree 3.3.8
        expect(jsTree.find('[role=treeitem]').attr('id')).toBe('_fs');

        // Trigger loading of children of fs.
        browserTriggerEvent(jsTree.find('#_fs a'), 'click');
        jsTree.one('open_node.jstree', () => {
          expect(getChildNodeTexts(jsTree, 'li#_fs')).toEqual(['os', 'tsk']);

          // Trigger loading of children of fs/os.
          browserTriggerEvent(jsTree.find('#_fs-os a'), 'click');
          jsTree.one('open_node.jstree', () => {
            expect(getChildNodeTexts(jsTree, 'li#_fs-os')).toEqual(['dir1', 'dir2', 'dir3']);
            expect(jsTree.find('[role=treeitem]').length).toBe(6); // There should be six tree nodes in total.
            done();
          });
          $rootScope.$apply();
        });
      }
      $rootScope.$apply();
    });
  });
});


exports = {};

;return exports;});

//angular-components/client/virtual-file-system/file-view-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.client.virtualFileSystem.fileViewDirectiveTest');
goog.setTestOnly();

const {getFileId, getFilePathFromId} = goog.require('grrUi.client.virtualFileSystem.fileViewDirective');


describe('file view directive', () => {
  describe('getFileId()', () => {

    it('returns the file id for any given path', () => {
      expect(getFileId('some/regular/path')).toEqual('_some-regular-path');
      expect(getFileId('some/$peci&l/path')).toEqual('_some-_24peci_26l-path');
      expect(getFileId('s0me/numb3r5/p4th')).toEqual('_s0me-numb3r5-p4th');
      expect(getFileId('a slightly/weird_/path')).toEqual('_a_20slightly-weird_5F-path');
      expect(getFileId('')).toEqual('_');
    });

    it('replaces characters with char code > 255 with more than a two-digit hex number',
       () => {
         expect(getFileId('some/sp€cial/path'))
             .toEqual('_some-sp_20ACcial-path');
         expect(getFileId('fs/os/c/中国新闻网新闻中'))
             .toEqual('_fs-os-c-_4E2D_56FD_65B0_95FB_7F51_65B0_95FB_4E2D');
       });
  });

  describe('getFilePathFromId()', () => {

    it('returns the path for any given id', () => {
      expect(getFilePathFromId('_some-regular-path')).toEqual('some/regular/path');
      expect(getFilePathFromId('_some-_24peci_26l-path')).toEqual('some/$peci&l/path');
      expect(getFilePathFromId('_s0me-numb3r5-p4th')).toEqual('s0me/numb3r5/p4th');
      expect(getFilePathFromId('_a_20slightly-weird_5F-path')).toEqual('a slightly/weird_/path');
      expect(getFilePathFromId('_')).toEqual('');
    });

    // The problem with _-based encoding is that it's ambiguous. It can't distinguish
    // a digit following an encoded character from a character encoded with more digits.
    // We limit number of digits we recognize to 2 to minimize potential conflicts.
    it('does not decode chars encoded with more than two hex-digits', () => {
      expect(getFilePathFromId('_some-sp_20ACcial-path')).not.toEqual('some/sp€cial/path');
      expect(getFilePathFromId('_fs-os-c-_4E2D_56FD_65B0_95FB_7F51_65B0_95FB_4E2D'))
          .not.toEqual('fs/os/c/中国新闻网新闻中');
    });
  });
});


exports = {};

;return exports;});

//angular-components/client/virtual-file-system/recursive-list-button-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.client.virtualFileSystem.recursiveListButtonDirectiveTest');
goog.setTestOnly();

const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {virtualFileSystemModule} = goog.require('grrUi.client.virtualFileSystem.virtualFileSystem');


describe('"recursive list directory" button', () => {
  let $compile;
  let $q;
  let $rootScope;
  let $timeout;
  let grrApiService;
  let grrReflectionService;


  beforeEach(module('/static/angular-components/client/virtual-file-system/recursive-list-button.html'));
  beforeEach(module('/static/angular-components/client/virtual-file-system/recursive-list-button-modal.html'));
  beforeEach(module(virtualFileSystemModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $timeout = $injector.get('$timeout');
    grrReflectionService = $injector.get('grrReflectionService');
    grrApiService = $injector.get('grrApiService');
  }));

  const renderTestTemplate = (clientId, filePath) => {
    $rootScope.clientId = clientId || 'C.0000111122223333';
    $rootScope.filePath = filePath || 'fs/os/c/';

    const template = '<grr-recursive-list-button ' +
        'client-id="clientId" file-path="filePath" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('fetches descriptors on click', () => {
    const deferred = $q.defer();
    spyOn(grrReflectionService, 'getRDFValueDescriptor').and.returnValue(
        deferred.promise);

    const element = renderTestTemplate();
    browserTriggerEvent(element.find('button'), 'click');

    expect(grrReflectionService.getRDFValueDescriptor).toHaveBeenCalledWith(
        'ApiCreateVfsRefreshOperationArgs', true);
  });

  describe('modal dialog', () => {
    beforeEach(() => {
      const deferred = $q.defer();
      spyOn(grrReflectionService, 'getRDFValueDescriptor').and.returnValue(
        deferred.promise);

      deferred.resolve({
        'ApiCreateVfsRefreshOperationArgs': {
          default: {
            type: 'ApiCreateVfsRefreshOperationArgs',
            value: {},
          },
        },
        'RDFInteger': {
          default: {
            type: 'RDFInteger',
            value: 0,
          },
        },
        'RDFString': {
          default: {
            type: 'RDFString',
            value: '',
          },
        },
        'ClientURN': {
          default: {
            type: 'ClientURN',
            value: '',
          },
        },
      });
    });

    afterEach(() => {
      // We have to clean document's body to remove modal windows that were not
      // closed.
      $(document.body).html('');
    });

    it('is shown when button clicked and descriptors fetched', () => {
      const element = renderTestTemplate();
      browserTriggerEvent(element.find('button'), 'click');

      expect($(document.body).text()).toContain(
          'Recursive Directory Refresh');
    });

    it('is closed when close button is clicked', () => {
      const element = renderTestTemplate();
      browserTriggerEvent($('button', element), 'click');

      browserTriggerEvent($('button.close'), 'click');
      $timeout.flush();

      expect($(document.body).text()).not.toContain(
          'Recursive Directory Refresh');
    });

    it('is closed when cancel button is clicked', () => {
      const element = renderTestTemplate();
      browserTriggerEvent($('button', element), 'click');

      browserTriggerEvent($('button[name=Cancel]'), 'click');
      $timeout.flush();

      expect($(document.body).text()).not.toContain(
          'Recursive Directory Refresh');
    });

    it('sends an API request when "refresh" is clicked', () => {
      const deferred = $q.defer();
      spyOn(grrApiService, 'post').and.returnValue(deferred.promise);

      const element = renderTestTemplate();
      browserTriggerEvent(element.find('button'), 'click');
      browserTriggerEvent($('button[name=Proceed]'), 'click');

      expect(grrApiService.post)
          .toHaveBeenCalledWith(
              'clients/C.0000111122223333/vfs-refresh-operations', {
                type: 'ApiCreateVfsRefreshOperationArgs',
                value: {
                  file_path: {
                    type: 'RDFString',
                    value: 'fs/os/c',
                  },
                  max_depth: {
                    type: 'RDFInteger',
                    value: 5,
                  },
                  notify_user: true,
                },
              },
              true);
    });

    it('strips "aff4:/" prefix from client id', () => {
      const deferred = $q.defer();
      spyOn(grrApiService, 'post').and.returnValue(deferred.promise);

      const element = renderTestTemplate('aff4:/C.0000111122223333');
      browserTriggerEvent(element.find('button'), 'click');
      browserTriggerEvent($('button[name=Proceed]'), 'click');

      expect(grrApiService.post).toHaveBeenCalled();
      expect(grrApiService.post.calls.mostRecent().args[0]).toBe(
          'clients/C.0000111122223333/vfs-refresh-operations');
    });

    it('disables the button when API request is sent', () => {
      const deferred = $q.defer();
      spyOn(grrApiService, 'post').and.returnValue(deferred.promise);

      const element = renderTestTemplate();
      browserTriggerEvent(element.find('button'), 'click');

      expect(element.find('button[disabled]').length).toBe(0);
      browserTriggerEvent($('button[name=Proceed]'), 'click');
      expect(element.find('button[disabled]').length).toBe(1);
    });

    it('shows success message when API request is successful', () => {
      const deferred = $q.defer();
      spyOn(grrApiService, 'post').and.returnValue(deferred.promise);
      deferred.resolve({
        data: {
          status: 'OK',
        },
      });

      // Polling will start immediately after POST request is successful.
      spyOn(grrApiService, 'get').and.returnValue($q.defer().promise);

      const element = renderTestTemplate();
      browserTriggerEvent(element.find('button'), 'click');
      browserTriggerEvent($('button[name=Proceed]'), 'click');

      expect($(document.body).text()).toContain(
          'Refresh started successfully!');
    });

    it('shows failure message when API request fails', () => {
      const deferred = $q.defer();
      spyOn(grrApiService, 'post').and.returnValue(deferred.promise);
      deferred.reject({
        data: {
          message: 'Oh no!',
        },
      });

      const element = renderTestTemplate();
      browserTriggerEvent(element.find('button'), 'click');
      browserTriggerEvent($('button[name=Proceed]'), 'click');

      expect($(document.body).text()).toContain(
          'Oh no!');
    });
  });
});


exports = {};

;return exports;});

//angular-components/client/virtual-file-system/utils_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.client.virtualFileSystem.utilsTest');
goog.setTestOnly();

const {ensurePathIsFolder, getFolderFromPath} = goog.require('grrUi.client.virtualFileSystem.utils');


describe('client virtual file system utils', () => {
  describe('ensurePathIsFolder()', () => {

    it('does nothing if path ends with "/"', () => {
      expect(ensurePathIsFolder('/')).toBe('/');
      expect(ensurePathIsFolder('a/b/c/')).toBe('a/b/c/');
    });

    it('adds "/" if path does not end with it', () => {
      expect(ensurePathIsFolder('')).toBe('/');
      expect(ensurePathIsFolder('a/b/c')).toBe('a/b/c/');
    });
  });

  describe('getFolderFromPath()', () => {

    it('does nothing for falsey values', () => {
      expect(getFolderFromPath(null)).toBe('');
      expect(getFolderFromPath(undefined)).toBe('');
      expect(getFolderFromPath('')).toBe('');
    });

    it('strips last component from path with no trailing slash', () => {
      expect(getFolderFromPath('a/b/c')).toBe('a/b');
    });

    it('strips trailing slash only, if there is one', () => {
      expect(getFolderFromPath('a/b/c/')).toBe('a/b/c');
    });
  });
});


exports = {};

;return exports;});

//angular-components/client/virtual-file-system/vfs-files-archive-button-directive_test.js
goog.loadModule(function(exports) {'use strict';goog.module('grrUi.client.virtualFileSystem.vfsFilesArchiveButtonDirectiveTest');
goog.setTestOnly();

const {DOWNLOAD_EVERYTHING_REENABLE_DELAY} = goog.require('grrUi.client.virtualFileSystem.vfsFilesArchiveButtonDirective');
const {ServerErrorButtonDirective} = goog.require('grrUi.core.serverErrorButtonDirective');
const {browserTriggerEvent, testsModule} = goog.require('grrUi.tests');
const {virtualFileSystemModule} = goog.require('grrUi.client.virtualFileSystem.virtualFileSystem');


describe('"download vfs archive" button', () => {
  let $compile;
  let $q;
  let $rootScope;
  let $timeout;
  let grrApiService;


  const ERROR_EVENT_NAME = ServerErrorButtonDirective.error_event_name;

  beforeEach(module('/static/angular-components/client/virtual-file-system/vfs-files-archive-button.html'));
  beforeEach(module(virtualFileSystemModule.name));
  beforeEach(module(testsModule.name));

  beforeEach(inject(($injector) => {
    $q = $injector.get('$q');
    $compile = $injector.get('$compile');
    $rootScope = $injector.get('$rootScope');
    $timeout = $injector.get('$timeout');
    grrApiService = $injector.get('grrApiService');
  }));

  const renderTestTemplate = (clientId, filePath) => {
    $rootScope.clientId = clientId || 'C.0000111122223333';
    $rootScope.filePath = filePath || 'fs/os/c/';

    const template = '<grr-vfs-files-archive-button ' +
        'client-id="clientId" file-path="filePath" />';
    const element = $compile(template)($rootScope);
    $rootScope.$apply();

    return element;
  };

  it('sends correct request when "download everything" option is chosen',
     () => {
       const deferred = $q.defer();
       spyOn(grrApiService, 'downloadFile').and.returnValue(deferred.promise);

       const element = renderTestTemplate();
       browserTriggerEvent(element.find('a[name=downloadEverything]'), 'click');

       expect(grrApiService.downloadFile)
           .toHaveBeenCalledWith(
               'clients/C.0000111122223333/vfs-files-archive/');
     });

  it('sends correct request when "download current folder" option is chosen',
     () => {
       const deferred = $q.defer();
       spyOn(grrApiService, 'downloadFile').and.returnValue(deferred.promise);

       const element = renderTestTemplate();
       browserTriggerEvent(element.find('a[name=downloadCurrentFolder]'), 'click');

       expect(grrApiService.downloadFile)
           .toHaveBeenCalledWith(
               'clients/C.0000111122223333/vfs-files-archive/fs/os/c');
     });

  it('disables "download everything" option after the click for 30 seconds',
     () => {
       const deferred = $q.defer();
       spyOn(grrApiService, 'downloadFile').and.returnValue(deferred.promise);

       const element = renderTestTemplate();
       browserTriggerEvent(element.find('a[name=downloadEverything]'), 'click');

       $timeout.flush(DOWNLOAD_EVERYTHING_REENABLE_DELAY - 1000);
       let items = element.find('li:has(a[name=downloadEverything]).disabled');
       expect(items.length).toBe(1);

       $timeout.flush(1001);
       items = element.find(
           'li:has(a[name=downloadEverything]):not(.disabled)');
       expect(items.length).toBe(1);
     });

  it('disables "download current folder" option after the click until folder is changed',
     () => {
       const deferred = $q.defer();
       spyOn(grrApiService, 'downloadFile').and.returnValue(deferred.promise);

       const element = renderTestTemplate();
       browserTriggerEvent(element.find('a[name=downloadCurrentFolder]'), 'click');

       let items = element.find(
           'li:has(a[name=downloadCurrentFolder]).disabled');
       expect(items.length).toBe(1);

       $rootScope.filePath = 'fs/os/c/d';
       $rootScope.$apply();

       items = element.find(
           'li:has(a[name=downloadCurrentFolder]):not(.disabled)');
       expect(items.length).toBe(1);
     });


  it('broadcasts error event on failure', (done) => {
    const deferred = $q.defer();
    spyOn(grrApiService, 'downloadFile').and.returnValue(deferred.promise);

    const element = renderTestTemplate();
    browserTriggerEvent(element.find('a[name=downloadCurrentFolder]'), 'click');

    $rootScope.$on(ERROR_EVENT_NAME, done);

    deferred.reject({data: {message: 'FAIL'}});
    $rootScope.$apply();
  });
});


exports = {};

;return exports;});

//# sourceMappingURL=grr-ui-test.bundle.js.map