<commit_before>'use strict';

angular.module('mwListable', [])

/**
 * @ngdoc directive
 * @name mwListable.directive:mwListable
 * @element table
 * @description
 *
 * Directive for table to add 'Select all' checkbox in header and content related displays like 'None found'
 * or pagination logic. Use this directive when you want to display items in a list without any hassle.
 *
 * @param {string} selectable Instance of selectable for this listable item
 * @param {string} filterable Instance of filterable for this listable item. Needed for pagination
 * @example
 * <doc:example>
 *   <doc:source>
 *

 *

 *

 *
 *

 *

 *

 *

 *
 *          Column content
 *

 *

 *

 *

 *

 *

 *   </doc:source>
 * </doc:example>
 */
    .directive('mwListable', function ($compile, $window, $document) {

      return {
        restrict: 'A',
        scope: {
          selectable: '=',
          filterable: '='
        },
        priority: 1000,
        compile: function (elm, attr) {

          if (attr.filterable) {
            elm.append('

');
          }

          elm.addClass('table table-striped mw-listable');

          return function (scope, elm) {
            var _footer = elm.find('tr#listableFooter');

            var compileFooterNoneFound = function () {
              _footer.prepend(
                  '<td ng-if="filterable.items().length < 1"' +
                      'colspan="{{ columns.length + 2 }}" class="text-center">' +
                      '
{{ \'common.noneFound\' | i18n }}
' +
                      '	');
              $compile(_footer)(scope);
            };

            var compileFooterLoadMore = function () {
              _footer.append(
                  '<td ng-if="filterable.items().length < filterable.total()"' +
                      'colspan="{{ columns.length + 2 }}">' +
                      '<button class="btn btn-default btn-lg col-md-12" ng-click="filterable.loadMore()">' +
                      '{{ \'common.loadMore\' | i18n }}' +
                      '</button>' +
                      '	');
              $compile(_footer)(scope);
            };

            /**
             * Compile fragments of header and footer into template
             * This has to be done this way since td/tr elements cannot be inserted via directive
             *
             * The basic problem behind this approach is documented here:
             * https://github.com/angular/angular.js/issues/1459
             */
            compileFooterNoneFound();
            compileFooterLoadMore();

            /**
             * Infinite scrolling
             */
            var w = angular.element($window);
            var d = angular.element($document);
            var scrollCallback = function () {
              if (w.scrollTop() === d.height() - w.height()) {
                if (scope.filterable) {
                  scope.filterable.loadMore();
                }
              }
            };
            // Register scroll callback
            w.on('scroll', scrollCallback);

            // Deregister scroll callback if scope is destroyed
            scope.$on('$destroy', function () {
              w.off('scroll', scrollCallback);
            });
          };
        },
        controller: function ($scope) {
          var columns = $scope.columns = [];

          this.actionColumns = [];

          this.sort = function (property, order) {
            $scope.filterable.setSortOrder(order + property);
          };

          this.getSort = function () {
            return $scope.filterable.sortOrder();
          };

          this.registerColumn = function (scope) {
            columns.push(scope);
          };

          this.getFilterable = function () {
            return $scope.filterable;
          };

          this.getSelectable = function () {
            return $scope.selectable;
          };

          this.toggleAll = function () {
            if ($scope.selectable.allSelected()) {
              $scope.selectable.unselectAll();
            } else {
              $scope.selectable.selectAll();
            }
          };
        }
      };
    })

/**
 * @ngdoc directive
 * @name mwListable.directive:mwListableHeader
 * @element th
 * @description
 *
 * Directive for table to add 'Select all' checkbox in header and content related displays like 'None found'
 * or pagination logic. Use this directive when you want to display items in a list without any hassle.
 *
 * @param {string} title Title of the table header
 * @param {string} sort Property key of the model to sort by
 */
    .directive('mwListableHeader', function () {
      return {
        restrict: 'A',
        require: '^mwListable',
        scope: {
          title: '@',
          property: '@sort'
        },
        template: '' +
            '' +
            '' +
            '' +
            '' +
            ' {{ title }}' +
            '	',
        link: function (scope, elm, attr, mwListableCtrl) {
          var ascending = '+',
              descending = '-';

          scope.toggleSortOrder = function () {
            if (scope.property) {
              var sortOrder = ascending; //default
              if (mwListableCtrl.getSort() === ascending + scope.property) {
                sortOrder = descending;
              }
              mwListableCtrl.sort(scope.property, sortOrder);
            }
          };

          scope.isSelected = function (prefix) {
            return mwListableCtrl.getSort() === prefix + scope.property;
          };

          mwListableCtrl.registerColumn(scope);
        }
      };
    })

/**
 * @ngdoc directive
 * @name mwListable.directive:mwListableColumnCheckbox
 * @element th
 * @description
 *
 * Directive for table to add 'Select item' checkbox in content.
 */
    .directive('mwListableColumnCheckbox', function () {
      return {
        restrict: 'A',
        require: '^mwListable',
        scope: {
          disabled: '=',
          item: '='
        },
        template: '<input type="checkbox" ' +
            'ng-click="click(item, $event)" ' +
            'ng-disabled="{{ disabled || false }}"' +
            'ng-checked="selectable.isSelected(item)"' +
            'mw-custom-checkbox>',
        link: function (scope, elm, attr, mwListableCtrl) {
          scope.selectable = mwListableCtrl.getSelectable();
          scope.click = function (item, $event) {
            $event.stopPropagation();
            scope.selectable.toggle(item);
          };
        }
      };
    })

/**
 * @ngdoc directive
 * @name mwListable.directive:mwListableHeaderCheckbox
 * @element th
 * @description
 *
 * Directive for table to add 'Select all' checkbox in header.
 */
    .directive('mwListableHeaderCheckbox', function () {
      return {
        restrict: 'A',
        require: '^mwListable',
        scope: true,
        template: '<input type="checkbox" ' +
            'ng-if="!filterable || filterable.items().length > 0"' +
            'ng-click="toggleAll()" ' +
            'ng-checked="selectable.allSelected()"' +
            'mw-custom-checkbox>',
        link: function (scope, elm, attr, mwListableCtrl) {
          scope.filterable = mwListableCtrl.getFilterable();
          scope.selectable = mwListableCtrl.getSelectable();
          scope.toggleAll = mwListableCtrl.toggleAll;
        }
      };
    })

/**
 * @ngdoc directive
 * @name mwListable.directive:mwListableRow
 * @element tr
 * @description
 *
 * Directive for table row. Adds click actions.
 */
    .directive('mwListableBodyRow', function () {
      return {
        restrict: 'A',
        require: '^mwListable',
        priority: 1000,
        compile: function (elm, attr) {
          elm.attr('ng-class', '{ \'selected\': selectable.isSelected(item) }');

          console.log('compiling listable');
          elm.attr('ng-click', 'isDisabled(item) || selectable.toggle(item)');
          elm.addClass('clickable');

          elm.prepend('	');

          var actionColumns = [];

          if (attr.mwListableEditLink) {
            actionColumns.push(null);
            var editLink = '' +
                '' +
                '' +
                '' +
                '	';
            elm.append(editLink);
          }

          if (attr.mwListableShowLink) {
            actionColumns.push(null);
            var showLink = '' +
                '' +
                '' +
                '' +
                '	';
            elm.append(showLink);
          }

          return function (scope, elm, attr, mwListableCtrl) {
            angular.forEach(actionColumns, function () {
              mwListableCtrl.actionColumns.push(null);
            });

            scope.isDisabled = function (item) {
              return scope.$parent.$eval(attr.mwListableDisabled, { item: item });
            };
          };
        }
      };
    })

    .directive('mwListableHeaderRow', function () {
      return {
        restrict: 'A',
        require: '^mwListable',
        priority: 1000,
        compile: function (elm) {
          elm.prepend('	');
          elm.append('	');

          return function (scope, elm, attr, mwListableCtrl) {
            scope.actionColumns = mwListableCtrl.actionColumns;
          };
        }
      };
    })
;<commit_msg>removed log<commit_after>'use strict';

angular.module('mwListable', [])

/**
 * @ngdoc directive
 * @name mwListable.directive:mwListable
 * @element table
 * @description
 *
 * Directive for table to add 'Select all' checkbox in header and content related displays like 'None found'
 * or pagination logic. Use this directive when you want to display items in a list without any hassle.
 *
 * @param {string} selectable Instance of selectable for this listable item
 * @param {string} filterable Instance of filterable for this listable item. Needed for pagination
 * @example
 * <doc:example>
 *   <doc:source>
 *

 *

 *

 *
 *

 *

 *

 *

 *
 *          Column content
 *

 *

 *

 *

 *

 *

 *   </doc:source>
 * </doc:example>
 */
    .directive('mwListable', function ($compile, $window, $document) {

      return {
        restrict: 'A',
        scope: {
          selectable: '=',
          filterable: '='
        },
        priority: 1000,
        compile: function (elm, attr) {

          if (attr.filterable) {
            elm.append('

');
          }

          elm.addClass('table table-striped mw-listable');

          return function (scope, elm) {
            var _footer = elm.find('tr#listableFooter');

            var compileFooterNoneFound = function () {
              _footer.prepend(
                  '<td ng-if="filterable.items().length < 1"' +
                      'colspan="{{ columns.length + 2 }}" class="text-center">' +
                      '
{{ \'common.noneFound\' | i18n }}
' +
                      '	');
              $compile(_footer)(scope);
            };

            var compileFooterLoadMore = function () {
              _footer.append(
                  '<td ng-if="filterable.items().length < filterable.total()"' +
                      'colspan="{{ columns.length + 2 }}">' +
                      '<button class="btn btn-default btn-lg col-md-12" ng-click="filterable.loadMore()">' +
                      '{{ \'common.loadMore\' | i18n }}' +
                      '</button>' +
                      '	');
              $compile(_footer)(scope);
            };

            /**
             * Compile fragments of header and footer into template
             * This has to be done this way since td/tr elements cannot be inserted via directive
             *
             * The basic problem behind this approach is documented here:
             * https://github.com/angular/angular.js/issues/1459
             */
            compileFooterNoneFound();
            compileFooterLoadMore();

            /**
             * Infinite scrolling
             */
            var w = angular.element($window);
            var d = angular.element($document);
            var scrollCallback = function () {
              if (w.scrollTop() === d.height() - w.height()) {
                if (scope.filterable) {
                  scope.filterable.loadMore();
                }
              }
            };
            // Register scroll callback
            w.on('scroll', scrollCallback);

            // Deregister scroll callback if scope is destroyed
            scope.$on('$destroy', function () {
              w.off('scroll', scrollCallback);
            });
          };
        },
        controller: function ($scope) {
          var columns = $scope.columns = [];

          this.actionColumns = [];

          this.sort = function (property, order) {
            $scope.filterable.setSortOrder(order + property);
          };

          this.getSort = function () {
            return $scope.filterable.sortOrder();
          };

          this.registerColumn = function (scope) {
            columns.push(scope);
          };

          this.getFilterable = function () {
            return $scope.filterable;
          };

          this.getSelectable = function () {
            return $scope.selectable;
          };

          this.toggleAll = function () {
            if ($scope.selectable.allSelected()) {
              $scope.selectable.unselectAll();
            } else {
              $scope.selectable.selectAll();
            }
          };
        }
      };
    })

/**
 * @ngdoc directive
 * @name mwListable.directive:mwListableHeader
 * @element th
 * @description
 *
 * Directive for table to add 'Select all' checkbox in header and content related displays like 'None found'
 * or pagination logic. Use this directive when you want to display items in a list without any hassle.
 *
 * @param {string} title Title of the table header
 * @param {string} sort Property key of the model to sort by
 */
    .directive('mwListableHeader', function () {
      return {
        restrict: 'A',
        require: '^mwListable',
        scope: {
          title: '@',
          property: '@sort'
        },
        template: '' +
            '' +
            '' +
            '' +
            '' +
            ' {{ title }}' +
            '	',
        link: function (scope, elm, attr, mwListableCtrl) {
          var ascending = '+',
              descending = '-';

          scope.toggleSortOrder = function () {
            if (scope.property) {
              var sortOrder = ascending; //default
              if (mwListableCtrl.getSort() === ascending + scope.property) {
                sortOrder = descending;
              }
              mwListableCtrl.sort(scope.property, sortOrder);
            }
          };

          scope.isSelected = function (prefix) {
            return mwListableCtrl.getSort() === prefix + scope.property;
          };

          mwListableCtrl.registerColumn(scope);
        }
      };
    })

/**
 * @ngdoc directive
 * @name mwListable.directive:mwListableColumnCheckbox
 * @element th
 * @description
 *
 * Directive for table to add 'Select item' checkbox in content.
 */
    .directive('mwListableColumnCheckbox', function () {
      return {
        restrict: 'A',
        require: '^mwListable',
        scope: {
          disabled: '=',
          item: '='
        },
        template: '<input type="checkbox" ' +
            'ng-click="click(item, $event)" ' +
            'ng-disabled="{{ disabled || false }}"' +
            'ng-checked="selectable.isSelected(item)"' +
            'mw-custom-checkbox>',
        link: function (scope, elm, attr, mwListableCtrl) {
          scope.selectable = mwListableCtrl.getSelectable();
          scope.click = function (item, $event) {
            $event.stopPropagation();
            scope.selectable.toggle(item);
          };
        }
      };
    })

/**
 * @ngdoc directive
 * @name mwListable.directive:mwListableHeaderCheckbox
 * @element th
 * @description
 *
 * Directive for table to add 'Select all' checkbox in header.
 */
    .directive('mwListableHeaderCheckbox', function () {
      return {
        restrict: 'A',
        require: '^mwListable',
        scope: true,
        template: '<input type="checkbox" ' +
            'ng-if="!filterable || filterable.items().length > 0"' +
            'ng-click="toggleAll()" ' +
            'ng-checked="selectable.allSelected()"' +
            'mw-custom-checkbox>',
        link: function (scope, elm, attr, mwListableCtrl) {
          scope.filterable = mwListableCtrl.getFilterable();
          scope.selectable = mwListableCtrl.getSelectable();
          scope.toggleAll = mwListableCtrl.toggleAll;
        }
      };
    })

/**
 * @ngdoc directive
 * @name mwListable.directive:mwListableRow
 * @element tr
 * @description
 *
 * Directive for table row. Adds click actions.
 */
    .directive('mwListableBodyRow', function () {
      return {
        restrict: 'A',
        require: '^mwListable',
        priority: 1000,
        compile: function (elm, attr) {
          elm.attr('ng-class', '{ \'selected\': selectable.isSelected(item) }');

          elm.attr('ng-click', 'isDisabled(item) || selectable.toggle(item)');
          elm.addClass('clickable');

          elm.prepend('	');

          var actionColumns = [];

          if (attr.mwListableEditLink) {
            actionColumns.push(null);
            var editLink = '' +
                '' +
                '' +
                '' +
                '	';
            elm.append(editLink);
          }

          if (attr.mwListableShowLink) {
            actionColumns.push(null);
            var showLink = '' +
                '' +
                '' +
                '' +
                '	';
            elm.append(showLink);
          }

          return function (scope, elm, attr, mwListableCtrl) {
            angular.forEach(actionColumns, function () {
              mwListableCtrl.actionColumns.push(null);
            });

            scope.isDisabled = function (item) {
              return scope.$parent.$eval(attr.mwListableDisabled, { item: item });
            };
          };
        }
      };
    })

    .directive('mwListableHeaderRow', function () {
      return {
        restrict: 'A',
        require: '^mwListable',
        priority: 1000,
        compile: function (elm) {
          elm.prepend('	');
          elm.append('	');

          return function (scope, elm, attr, mwListableCtrl) {
            scope.actionColumns = mwListableCtrl.actionColumns;
          };
        }
      };
    })
;<|endoftext|>


<commit_before>'use strict';

const fs = require('fs');

const env = require('../../modules/environment');

describe('browser/settings.html', function desc() {
  this.timeout(10000);

  const config = {
    version: 1,
    teams: [{
      name: 'example_1',
      url: env.mattermostURL
    }, {
      name: 'example_2',
      url: env.mattermostURL
    }]
  };

  beforeEach(() => {
    fs.writeFileSync(env.configFilePath, JSON.stringify(config));
    this.app = env.getSpectronApp();
    return this.app.start();
  });

  afterEach(() => {
    if (this.app && this.app.isRunning()) {
      return this.app.stop();
    }
    return true;
  });

  it('should show index.html when Close button is clicked', () => {
    env.addClientCommands(this.app.client);
    return this.app.client.
      loadSettingsPage().
      click('#btnClose').
      pause(1000).
      getUrl().should.eventually.match(/\/index.html(\?.+)?$/);
  });

  it('should show NewServerModal after all servers are removed', () => {
    const modalTitleSelector = '.modal-title=Remove Server';
    env.addClientCommands(this.app.client);
    return this.app.client.
      loadSettingsPage().
      click('=Remove').
      waitForVisible(modalTitleSelector).
      element('.modal-dialog').click('.btn=Remove').
      pause(500).
      click('=Remove').
      waitForVisible(modalTitleSelector).
      element('.modal-dialog').click('.btn=Remove').
      pause(500).
      isExisting('#newServerModal').should.eventually.equal(true);
  });

  describe('Server list', () => {
    it('should open the corresponding tab when a server list item is clicked', () => {
      env.addClientCommands(this.app.client);
      return this.app.client.
      loadSettingsPage().
      click('h4=example_1').
      pause(100).
      waitUntilWindowLoaded().
      getUrl().should.eventually.match(/\/index.html(\?.+)?$/).
      isVisible('#mattermostView0').should.eventually.be.true.
      isVisible('#mattermostView1').should.eventually.be.false.

      loadSettingsPage().
      click('h4=example_2').
      pause(100).
      waitUntilWindowLoaded().
      getUrl().should.eventually.match(/\/index.html(\?.+)?$/).
      isVisible('#mattermostView0').should.eventually.be.false.
      isVisible('#mattermostView1').should.eventually.be.true;
    });
  });

  describe('Options', () => {
    describe.skip('Hide Menu Bar', () => {
      it('should appear on win32 or linux', () => {
        const expected = (process.platform === 'win32' || process.platform === 'linux');
        env.addClientCommands(this.app.client);
        return this.app.client.
        loadSettingsPage().
        isExisting('#inputHideMenuBar').should.eventually.equal(expected);
      });

      [true, false].forEach((v) => {
        env.shouldTest(it, env.isOneOf(['win32', 'linux']))(`should be saved and loaded: ${v}`, () => {
          env.addClientCommands(this.app.client);
          return this.app.client.
            loadSettingsPage().
            scroll('#inputHideMenuBar').
            isSelected('#inputHideMenuBar').then((isSelected) => {
              if (isSelected !== v) {
                return this.app.client.click('#inputHideMenuBar');
              }
              return true;
            }).
            pause(600).
            click('#btnClose').
            pause(1000).then(() => {
              const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
              savedConfig.hideMenuBar.should.equal(v);
            }).
            browserWindow.isMenuBarAutoHide().should.eventually.equal(v).then(() => { // confirm actual behavior
              return this.app.restart();
            }).then(() => {
              env.addClientCommands(this.app.client);
              return this.app.client. // confirm actual behavior
                browserWindow.isMenuBarAutoHide().should.eventually.equal(v).
                loadSettingsPage().
                isSelected('#inputHideMenuBar').should.eventually.equal(v);
            });
        });
      });
    });

    describe('Display secure content only', () => {
      [true, false].forEach((v) => {
        it(`should be saved and loaded: ${v}`, () => {
          const webPreferences = v ? '' : 'allowDisplayingInsecureContent';
          env.addClientCommands(this.app.client);

          return this.app.client.
            loadSettingsPage().
            scroll('#inputDisableWebSecurity').
            isSelected('#inputDisableWebSecurity').then((isSelected) => {
              if (isSelected !== v) {
                return this.app.client.click('#inputDisableWebSecurity');
              }
              return true;
            }).
            pause(600).
            click('#btnClose').
            pause(1000).then(() => {
              const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
              savedConfig.disablewebsecurity.should.equal(!v);
            }).
            getAttribute('.mattermostView', 'webpreferences').then((disablewebsecurity) => { // confirm actual behavior
              // disablewebsecurity is an array of String
              disablewebsecurity.forEach((d) => {
                d.should.equal(webPreferences);
              });
            }).then(() => {
              return this.app.restart();
            }).then(() => {
              env.addClientCommands(this.app.client);
              return this.app.client. // confirm actual behavior
                getAttribute('.mattermostView', 'webpreferences').then((disablewebsecurity) => { // disablewebsecurity is an array of String
                  disablewebsecurity.forEach((d) => {
                    d.should.equal(webPreferences);
                  });
                }).
                loadSettingsPage().
                isSelected('#inputDisableWebSecurity').should.eventually.equal(v);
            });
        });
      });
    });

    describe('Start app on login', () => {
      it('should appear on win32 or linux', () => {
        const expected = (process.platform === 'win32' || process.platform === 'linux');
        env.addClientCommands(this.app.client);
        return this.app.client.
          loadSettingsPage().
          isExisting('#inputAutoStart').should.eventually.equal(expected);
      });
    });

    describe('Show icon in menu bar / notification area', () => {
      it('should appear on darwin or linux', () => {
        const expected = (process.platform === 'darwin' || process.platform === 'linux');
        env.addClientCommands(this.app.client);
        return this.app.client.
          loadSettingsPage().
          isExisting('#inputShowTrayIcon').should.eventually.equal(expected);
      });
    });

    describe('Leave app running in notification area when application window is closed', () => {
      it('should appear on linux', () => {
        const expected = (process.platform === 'linux');
        env.addClientCommands(this.app.client);
        return this.app.client.
          loadSettingsPage().
          isExisting('#inputMinimizeToTray').should.eventually.equal(expected);
      });
    });

    describe.skip('Toggle window visibility when clicking on the tray icon', () => {
      it('should appear on win32', () => {
        const expected = (process.platform === 'win32');
        env.addClientCommands(this.app.client);
        return this.app.client.
          loadSettingsPage().
          isExisting('#inputToggleWindowOnTrayIconClick').should.eventually.equal(expected);
      });
    });

    describe('Flash taskbar icon when a new message is received', () => {
      it('should appear on win32 and linux', () => {
        const expected = (process.platform === 'win32' || process.platform === 'linux');
        env.addClientCommands(this.app.client);
        return this.app.client.
          loadSettingsPage().
          isExisting('#inputflashWindow').should.eventually.equal(expected);
      });
    });

    describe('Show red badge on taskbar icon to indicate unread messages', () => {
      it('should appear on darwin or win32', () => {
        const expected = (process.platform === 'darwin' || process.platform === 'win32');
        env.addClientCommands(this.app.client);
        return this.app.client.
          loadSettingsPage().
          isExisting('#inputShowUnreadBadge').should.eventually.equal(expected);
      });
    });
  });

  describe('RemoveServerModal', () => {
    const modalTitleSelector = '.modal-title=Remove Server';

    beforeEach(() => {
      env.addClientCommands(this.app.client);
      return this.app.client.
        loadSettingsPage().
        isExisting(modalTitleSelector).should.eventually.false.
        isVisible(modalTitleSelector).should.eventually.false.
        click('=Remove').
        waitForVisible(modalTitleSelector);
    });

    it('should remove existing team on click Remove', (done) => {
      this.app.client.
        element('.modal-dialog').click('.btn=Remove').
        pause(500).
        isExisting(modalTitleSelector).should.eventually.false.
        click('#btnClose').
        pause(500).then(() => {
          const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
          savedConfig.teams.should.deep.equal(config.teams.slice(1));
          done();
        });
    });

    it('should NOT remove existing team on click Cancel', (done) => {
      this.app.client.
        element('.modal-dialog').click('.btn=Cancel').
        pause(500).
        isExisting(modalTitleSelector).should.eventually.false.
        click('#btnClose').
        pause(500).then(() => {
          const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
          savedConfig.teams.should.deep.equal(config.teams);
          done();
        });
    });

    it('should disappear on click Close', () => {
      return this.app.client.
        click('.modal-dialog button.close').
        pause(500).
        isExisting(modalTitleSelector).should.eventually.false;
    });

    it('should disappear on click background', () => {
      return this.app.client.
        click('body').
        pause(500).
        isExisting(modalTitleSelector).should.eventually.false;
    });
  });

  describe('NewTeamModal', () => {
    beforeEach(() => {
      env.addClientCommands(this.app.client);
      return this.app.client.
        loadSettingsPage().
        click('#addNewServer');
    });

    it('should open the new server modal', () => {
      return this.app.client.isExisting('#newServerModal').should.eventually.equal(true);
    });

    it('should close the window after clicking cancel', () => {
      return this.app.client.
        click('#cancelNewServerModal').
        pause(1000). // Animation
        isExisting('#newServerModal').should.eventually.equal(false);
    });

    it('should not be valid if no team name has been set', () => {
      return this.app.client.
        click('#saveNewServerModal').
        isExisting('.has-error #teamNameInput').should.eventually.equal(true);
    });

    it('should not be valid if no server address has been set', () => {
      return this.app.client.
        click('#saveNewServerModal').
        isExisting('.has-error #teamUrlInput').should.eventually.equal(true);
    });

    describe('Valid server name', () => {
      beforeEach(() => {
        return this.app.client.
            setValue('#teamNameInput', 'TestTeam').
            click('#saveNewServerModal');
      });

      it('should not be marked invalid', () => {
        return this.app.client.
            isExisting('.has-error #teamNameInput').should.eventually.equal(false);
      });

      it('should not be possible to click save', () => {
        return this.app.client.
          getAttribute('#saveNewServerModal', 'disabled').should.eventually.equal('true');
      });
    });

    describe('Valid server url', () => {
      beforeEach(() => {
        return this.app.client.
            setValue('#teamUrlInput', 'http://example.org').
            click('#saveNewServerModal');
      });

      it('should be valid', () => {
        return this.app.client.
          isExisting('.has-error #teamUrlInput').should.eventually.equal(false);
      });

      it('should not be possible to click save', () => {
        return this.app.client.
          getAttribute('#saveNewServerModal', 'disabled').should.eventually.equal('true');
      });
    });

    it('should not be valid if an invalid server address has been set', () => {
      return this.app.client.
        setValue('#teamUrlInput', 'superInvalid url').
        click('#saveNewServerModal').
        isExisting('.has-error #teamUrlInput').should.eventually.equal(true);
    });

    describe('Valid Team Settings', () => {
      beforeEach(() => {
        return this.app.client.
            setValue('#teamUrlInput', 'http://example.org').
            setValue('#teamNameInput', 'TestTeam');
      });

      it('should be possible to click add', () => {
        return this.app.client.
          getAttribute('#saveNewServerModal', 'disabled').should.eventually.equal(null);
      });

      it('should add the team to the config file', (done) => {
        this.app.client.
          click('#saveNewServerModal').
          pause(1000). // Animation
          click('#btnClose').
          pause(1000).then(() => {
            const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
            savedConfig.teams.should.contain({
              name: 'TestTeam',
              url: 'http://example.org'
            });
            return done();
          });
      });
    });
  });
});
<commit_msg>Update settings_test.js<commit_after>'use strict';

const fs = require('fs');

const env = require('../../modules/environment');

describe('browser/settings.html', function desc() {
  this.timeout(10000);

  const config = {
    version: 1,
    teams: [{
      name: 'example_1',
      url: env.mattermostURL
    }, {
      name: 'example_2',
      url: env.mattermostURL
    }]
  };

  beforeEach(() => {
    fs.writeFileSync(env.configFilePath, JSON.stringify(config));
    this.app = env.getSpectronApp();
    return this.app.start();
  });

  afterEach(() => {
    if (this.app && this.app.isRunning()) {
      return this.app.stop();
    }
    return true;
  });

  it('should show index.html when Close button is clicked', () => {
    env.addClientCommands(this.app.client);
    return this.app.client.
      loadSettingsPage().
      click('#btnClose').
      pause(1000).
      getUrl().should.eventually.match(/\/index.html(\?.+)?$/);
  });

  it('should show NewServerModal after all servers are removed', () => {
    const modalTitleSelector = '.modal-title=Remove Server';
    env.addClientCommands(this.app.client);
    return this.app.client.
      loadSettingsPage().
      click('=Remove').
      waitForVisible(modalTitleSelector).
      element('.modal-dialog').click('.btn=Remove').
      pause(500).
      click('=Remove').
      waitForVisible(modalTitleSelector).
      element('.modal-dialog').click('.btn=Remove').
      pause(500).
      isExisting('#newServerModal').should.eventually.equal(true);
  });

  describe('Server list', () => {
    it('should open the corresponding tab when a server list item is clicked', () => {
      env.addClientCommands(this.app.client);
      return this.app.client.
      loadSettingsPage().
      click('h4=example_1').
      pause(100).
      waitUntilWindowLoaded().
      getUrl().should.eventually.match(/\/index.html(\?.+)?$/).
      isVisible('#mattermostView0').should.eventually.be.true.
      isVisible('#mattermostView1').should.eventually.be.false.

      loadSettingsPage().
      click('h4=example_2').
      pause(100).
      waitUntilWindowLoaded().
      getUrl().should.eventually.match(/\/index.html(\?.+)?$/).
      isVisible('#mattermostView0').should.eventually.be.false.
      isVisible('#mattermostView1').should.eventually.be.true;
    });
  });

  describe('Options', () => {
    describe.skip('Hide Menu Bar', () => {
      it('should appear on win32 or linux', () => {
        const expected = (process.platform === 'win32' || process.platform === 'linux');
        env.addClientCommands(this.app.client);
        return this.app.client.
        loadSettingsPage().
        isExisting('#inputHideMenuBar').should.eventually.equal(expected);
      });

      [true, false].forEach((v) => {
        env.shouldTest(it, env.isOneOf(['win32', 'linux']))(`should be saved and loaded: ${v}`, () => {
          env.addClientCommands(this.app.client);
          return this.app.client.
            loadSettingsPage().
            scroll('#inputHideMenuBar').
            isSelected('#inputHideMenuBar').then((isSelected) => {
              if (isSelected !== v) {
                return this.app.client.click('#inputHideMenuBar');
              }
              return true;
            }).
            pause(600).
            click('#btnClose').
            pause(1000).then(() => {
              const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
              savedConfig.hideMenuBar.should.equal(v);
            }).
            browserWindow.isMenuBarAutoHide().should.eventually.equal(v).then(() => { // confirm actual behavior
              return this.app.restart();
            }).then(() => {
              env.addClientCommands(this.app.client);
              return this.app.client. // confirm actual behavior
                browserWindow.isMenuBarAutoHide().should.eventually.equal(v).
                loadSettingsPage().
                isSelected('#inputHideMenuBar').should.eventually.equal(v);
            });
        });
      });
    });

    describe('Display secure content only', () => {
      [true, false].forEach((v) => {
        it(`should be saved and loaded: ${v}`, () => {
          const webPreferences = v ? '' : 'allowDisplayingInsecureContent';
          env.addClientCommands(this.app.client);

          return this.app.client.
            loadSettingsPage().
            scroll('#inputDisableWebSecurity').
            isSelected('#inputDisableWebSecurity').then((isSelected) => {
              if (isSelected !== v) {
                return this.app.client.click('#inputDisableWebSecurity');
              }
              return true;
            }).
            pause(600).
            click('#btnClose').
            pause(1000).then(() => {
              const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
              savedConfig.disablewebsecurity.should.equal(!v);
            }).
            getAttribute('.mattermostView', 'webpreferences').then((disablewebsecurity) => { // confirm actual behavior
              // disablewebsecurity is an array of String
              disablewebsecurity.forEach((d) => {
                d.should.equal(webPreferences);
              });
            }).then(() => {
              return this.app.restart();
            }).then(() => {
              env.addClientCommands(this.app.client);
              return this.app.client. // confirm actual behavior
                getAttribute('.mattermostView', 'webpreferences').then((disablewebsecurity) => { // disablewebsecurity is an array of String
                  disablewebsecurity.forEach((d) => {
                    d.should.equal(webPreferences);
                  });
                }).
                loadSettingsPage().
                isSelected('#inputDisableWebSecurity').should.eventually.equal(v);
            });
        });
      });
    });

    describe('Start app on login', () => {
      it('should appear on win32 or linux', () => {
        const expected = (process.platform === 'win32' || process.platform === 'linux');
        env.addClientCommands(this.app.client);
        return this.app.client.
          loadSettingsPage().
          isExisting('#inputAutoStart').should.eventually.equal(expected);
      });
    });

    describe('Show icon in menu bar / notification area', () => {
      it('should appear on darwin or linux', () => {
        const expected = (process.platform === 'darwin' || process.platform === 'linux');
        env.addClientCommands(this.app.client);
        return this.app.client.
          loadSettingsPage().
          isExisting('#inputShowTrayIcon').should.eventually.equal(expected);
      });
    });

    describe('Leave app running in notification area when application window is closed', () => {
      it('should appear on linux', () => {
        const expected = (process.platform === 'linux');
        env.addClientCommands(this.app.client);
        return this.app.client.
          loadSettingsPage().
          isExisting('#inputMinimizeToTray').should.eventually.equal(expected);
      });
    });

    describe.skip('Toggle window visibility when clicking on the tray icon', () => {
      it('should appear on win32', () => {
        const expected = (process.platform === 'win32');
        env.addClientCommands(this.app.client);
        return this.app.client.
          loadSettingsPage().
          isExisting('#inputToggleWindowOnTrayIconClick').should.eventually.equal(expected);
      });
    });

    describe('Flash app window and taskbar icon when a new message is received', () => {
      it('should appear on win32 and linux', () => {
        const expected = (process.platform === 'win32' || process.platform === 'linux');
        env.addClientCommands(this.app.client);
        return this.app.client.
          loadSettingsPage().
          isExisting('#inputflashWindow').should.eventually.equal(expected);
      });
    });

    describe('Show red badge on taskbar icon to indicate unread messages', () => {
      it('should appear on darwin or win32', () => {
        const expected = (process.platform === 'darwin' || process.platform === 'win32');
        env.addClientCommands(this.app.client);
        return this.app.client.
          loadSettingsPage().
          isExisting('#inputShowUnreadBadge').should.eventually.equal(expected);
      });
    });
  });

  describe('RemoveServerModal', () => {
    const modalTitleSelector = '.modal-title=Remove Server';

    beforeEach(() => {
      env.addClientCommands(this.app.client);
      return this.app.client.
        loadSettingsPage().
        isExisting(modalTitleSelector).should.eventually.false.
        isVisible(modalTitleSelector).should.eventually.false.
        click('=Remove').
        waitForVisible(modalTitleSelector);
    });

    it('should remove existing team on click Remove', (done) => {
      this.app.client.
        element('.modal-dialog').click('.btn=Remove').
        pause(500).
        isExisting(modalTitleSelector).should.eventually.false.
        click('#btnClose').
        pause(500).then(() => {
          const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
          savedConfig.teams.should.deep.equal(config.teams.slice(1));
          done();
        });
    });

    it('should NOT remove existing team on click Cancel', (done) => {
      this.app.client.
        element('.modal-dialog').click('.btn=Cancel').
        pause(500).
        isExisting(modalTitleSelector).should.eventually.false.
        click('#btnClose').
        pause(500).then(() => {
          const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
          savedConfig.teams.should.deep.equal(config.teams);
          done();
        });
    });

    it('should disappear on click Close', () => {
      return this.app.client.
        click('.modal-dialog button.close').
        pause(500).
        isExisting(modalTitleSelector).should.eventually.false;
    });

    it('should disappear on click background', () => {
      return this.app.client.
        click('body').
        pause(500).
        isExisting(modalTitleSelector).should.eventually.false;
    });
  });

  describe('NewTeamModal', () => {
    beforeEach(() => {
      env.addClientCommands(this.app.client);
      return this.app.client.
        loadSettingsPage().
        click('#addNewServer');
    });

    it('should open the new server modal', () => {
      return this.app.client.isExisting('#newServerModal').should.eventually.equal(true);
    });

    it('should close the window after clicking cancel', () => {
      return this.app.client.
        click('#cancelNewServerModal').
        pause(1000). // Animation
        isExisting('#newServerModal').should.eventually.equal(false);
    });

    it('should not be valid if no team name has been set', () => {
      return this.app.client.
        click('#saveNewServerModal').
        isExisting('.has-error #teamNameInput').should.eventually.equal(true);
    });

    it('should not be valid if no server address has been set', () => {
      return this.app.client.
        click('#saveNewServerModal').
        isExisting('.has-error #teamUrlInput').should.eventually.equal(true);
    });

    describe('Valid server name', () => {
      beforeEach(() => {
        return this.app.client.
            setValue('#teamNameInput', 'TestTeam').
            click('#saveNewServerModal');
      });

      it('should not be marked invalid', () => {
        return this.app.client.
            isExisting('.has-error #teamNameInput').should.eventually.equal(false);
      });

      it('should not be possible to click save', () => {
        return this.app.client.
          getAttribute('#saveNewServerModal', 'disabled').should.eventually.equal('true');
      });
    });

    describe('Valid server url', () => {
      beforeEach(() => {
        return this.app.client.
            setValue('#teamUrlInput', 'http://example.org').
            click('#saveNewServerModal');
      });

      it('should be valid', () => {
        return this.app.client.
          isExisting('.has-error #teamUrlInput').should.eventually.equal(false);
      });

      it('should not be possible to click save', () => {
        return this.app.client.
          getAttribute('#saveNewServerModal', 'disabled').should.eventually.equal('true');
      });
    });

    it('should not be valid if an invalid server address has been set', () => {
      return this.app.client.
        setValue('#teamUrlInput', 'superInvalid url').
        click('#saveNewServerModal').
        isExisting('.has-error #teamUrlInput').should.eventually.equal(true);
    });

    describe('Valid Team Settings', () => {
      beforeEach(() => {
        return this.app.client.
            setValue('#teamUrlInput', 'http://example.org').
            setValue('#teamNameInput', 'TestTeam');
      });

      it('should be possible to click add', () => {
        return this.app.client.
          getAttribute('#saveNewServerModal', 'disabled').should.eventually.equal(null);
      });

      it('should add the team to the config file', (done) => {
        this.app.client.
          click('#saveNewServerModal').
          pause(1000). // Animation
          click('#btnClose').
          pause(1000).then(() => {
            const savedConfig = JSON.parse(fs.readFileSync(env.configFilePath, 'utf8'));
            savedConfig.teams.should.contain({
              name: 'TestTeam',
              url: 'http://example.org'
            });
            return done();
          });
      });
    });
  });
});
<|endoftext|>


<commit_before>import Promise from 'bluebird';
import mongoose from 'mongoose';
import httpStatus from 'http-status';
import APIError from '../helpers/APIError';

/**
 * User Schema
 */
const UserSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
  },
  mobileNumber: {
    type: String,
    required: true,
    match: [/^[1-9][0-9]{9}$/, 'The value of path {PATH} ({VALUE}) is not a valid mobile number.'],
  },
  createdAt: {
    type: Date,
    default: Date.now,
  },
});

/**
 * Add your
 * - pre-save hooks
 * - validations
 * - virtuals
 */

/**
 * Methods
 */
UserSchema.method({
});

/**
 * Statics
 */
UserSchema.statics = {
  /**
   * Get user
   * @param {ObjectId} id - The objectId of user.
   * @returns {Promise<User, APIError>}
   */
  get(id) {
    return this.findById(id)
      .exec()
      .then((user) => {
        if (user) {
          return user;
        }
        const err = new APIError('No such user exists!', httpStatus.NOT_FOUND);
        return Promise.reject(err);
      });
  },

  /**
   * List users in descending order of 'createdAt' timestamp.
   * @param {number} skip - Number of users to be skipped.
   * @param {number} limit - Limit number of users to be returned.
   * @returns {Promise<User[]>}
   */
  list({ skip = 0, limit = 50 } = {}) {
    return this.find()
      .sort({ createdAt: -1 })
      .skip(+skip)
      .limit(+limit)
      .exec();
  },
};

/**
 * @typedef User
 */
export default mongoose.model('User', UserSchema);
<commit_msg>updated user model<commit_after>import Promise from 'bluebird';
import mongoose from 'mongoose';
import httpStatus from 'http-status';
import APIError from '../helpers/APIError';

/**
 * User Schema
 */
const UserSchema = new mongoose.Schema({
  username: {
    type: String,
    required: true,
  },
  mobileNumber: {
    type: String,
    required: true,
    match: [/^[1-9][0-9]{9}$/, 'The value of path {PATH} ({VALUE}) is not a valid mobile number.'],
  },
  createdAt: {
    type: Date,
    default: Date.now,
  },
});

/**
 * Add your
 * - pre-save hooks
 * - validations
 * - virtuals
 */

/**
 * Methods
 */
UserSchema.method({
});

/**
 * Statics
 */
UserSchema.statics = {
  /**
   * Get user
   * @param {ObjectId} id - The objectId of user.
   * @returns {Promise<User, APIError>}
   */
  get(id) {
    return this.findById(id)
      .exec()
      .then((user) => {
        if (user) {
          return user;
        }
        const err = new APIError('No such user exists!', httpStatus.NOT_FOUND);
        return Promise.reject(err);
      });
  },

  /**
   * List users in descending order of 'createdAt' timestamp.
   * @param {number} skip - Number of users to be skipped.
   * @param {number} limit - Limit number of users to be returned.
   * @returns {Promise<User[]>}
   */
  list({ skip = 0, limit = 50 } = {}) {
    return this.find()
      .sort({ createdAt: -1 })
      .skip(+skip)
      .limit(+limit)
      .exec();
  },
};

/**
 * @typedof User
 */
export default mongoose.model('User', UserSchema);
<|endoftext|>


<commit_before>
describe("oboe integration (real http)", function() {

   var callbackSpy;
   var fullResponse;
   function whenDoneFn(obj) {
      fullResponse = obj;
   }

   beforeEach(function () {
      fullResponse = null;
      callbackSpy = jasmine.createSpy('callbackSpy');
   });

   it('gets all expected callbacks by time request finishes', function () {

      oboe('/testServer/tenSlowNumbers')
          .node('![*]', callbackSpy)
          .done(whenDoneFn);

      waitsFor(function () {
         return !!fullResponse
      }, 'the request to complete', ASYNC_TEST_TIMEOUT);

      runs(function () {

         expect(callbackSpy).toHaveBeenCalledWith(0, [0], [fullResponse, 0]);
         expect(callbackSpy).toHaveBeenCalledWith(1, [1], [fullResponse, 1]);
         expect(callbackSpy).toHaveBeenCalledWith(2, [2], [fullResponse, 2]);
         expect(callbackSpy).toHaveBeenCalledWith(3, [3], [fullResponse, 3]);
         expect(callbackSpy).toHaveBeenCalledWith(4, [4], [fullResponse, 4]);
         expect(callbackSpy).toHaveBeenCalledWith(5, [5], [fullResponse, 5]);
         expect(callbackSpy).toHaveBeenCalledWith(6, [6], [fullResponse, 6]);
         expect(callbackSpy).toHaveBeenCalledWith(7, [7], [fullResponse, 7]);
         expect(callbackSpy).toHaveBeenCalledWith(8, [8], [fullResponse, 8]);
         expect(callbackSpy).toHaveBeenCalledWith(9, [9], [fullResponse, 9]);

      });
   })

   it('can abort once some data has been found in streamed response', function () {

      var aborted = false;

      var asserter = givenAnOboeInstance('/testServer/tenSlowNumbers')
          .andWeAreListeningForNodes('![5]', function () {
             asserter.andWeAbortTheRequest();
             aborted = true;
          });

      waitsFor(function () {
         return aborted
      }, 'the request to be aborted', ASYNC_TEST_TIMEOUT);

      // in case we didn't abort, wait a little longer. If we didn't really abort we'd get the
      // rest of the data now and the test would fail:
      waitsFor(someSecondsToPass(3), ASYNC_TEST_TIMEOUT);

      runs(function () {
         asserter.thenTheInstance(
             // because the request was aborted on index array 5, we got 6 numbers (inc zero)
             // not the whole ten.
             hasRootJson([0, 1, 2, 3, 4, 5])
         );
      });
   })

   it('can abort once some data has been found in not very streamed response', function () {

      // like above but we're getting a static file not the streamed numbers. This means
      // we'll almost certainly read in the whole response as one onprogress it is on localhost
      // and the json is very small

      var aborted = false;

      var asserter = givenAnOboeInstance('/testServer/static/json/firstTenNaturalNumbers.json')
          .andWeAreListeningForNodes('![5]', function () {
             asserter.andWeAbortTheRequest();
             aborted = true;
          });

      waitsFor(function () {
         return aborted
      }, 'the request to be aborted', ASYNC_TEST_TIMEOUT);

      // in case we didn't abort, wait a little longer. If we didn't really abort we'd get the
      // rest of the data now and the test would fail:
      waitsFor(someSecondsToPass(1), ASYNC_TEST_TIMEOUT);

      runs(function () {
         asserter.thenTheInstance(
             // because the request was aborted on index array 5, we got 6 numbers (inc zero)
             // not the whole ten.
             hasRootJson([0, 1, 2, 3, 4, 5])
         );
      });
   })

   it('gives full json to callback when request finishes', function () {

      oboe.doGet('/testServer/static/json/firstTenNaturalNumbers.json')
          .done(whenDoneFn);

      waitsFor(function () {
         return !!fullResponse
      }, 'the request to complete', ASYNC_TEST_TIMEOUT)

      runs(function () {
         expect(fullResponse).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
      });
   })

   it('gives header to server side', function () {

      var countGotBack = 0;

      oboe(
          {  url:'/testServer/echoBackHeaders',
             headers:{'x-snarfu':'SNARF', 'x-foo':'BAR'}
          }

      ).node('x-snarfu',
          function (headerValue) {

             expect(headerValue).toBe('SNARF')
             countGotBack++;

          }).node('x-foo', function (headerValue) {

             expect(headerValue).toBe('BAR')
             countGotBack++;
          })

      waitsFor(function () {
         return countGotBack == 2
      }, 'the request to complete', ASYNC_TEST_TIMEOUT)
   })

   it('can listen for nodes via nodejs-style syntax', function () {

      var countGotBack = 0;

      oboe(
          '/testServer/static/json/firstTenNaturalNumbers.json'
      ).on('node', '!.*', function (number) {
             countGotBack++;
          });

      waitsFor(function () {
         return countGotBack == 10
      }, 'ten callbacks', ASYNC_TEST_TIMEOUT)
   })

   it('can listen for paths via nodejs-style syntax', function () {

      var countGotBack = 0;

      oboe(
          '/testServer/static/json/firstTenNaturalNumbers.json'
      ).on('path', '!.*', function (number) {
             countGotBack++;
          });

      waitsFor(function () {
         return countGotBack == 10
      }, 'ten callbacks', ASYNC_TEST_TIMEOUT)
   })

   it('fires error on 404', function () {

      var gotError = false

      oboe('/testServer/doesNotExist'
      ).fail(function () {

             gotError = true
          });

      waitsFor(function () {
         return gotError
      }, 'the request to fail', ASYNC_TEST_TIMEOUT)
   })

   function someSecondsToPass(waitSecs) {

      function now() {
         // IE8 doesn't have Date.now()
         return new Date().getTime();
      }

      var waitStart = now(),
          waitMs = waitSecs * 1000;

      return function () {
         return now() > (waitStart + waitMs);
      }
   }

});

<commit_msg>de-asserter a test<commit_after>
describe("oboe integration (real http)", function() {

   it('gets all expected callbacks by time request finishes', function () {

      oboe('/testServer/tenSlowNumbers')
          .node('![*]', callbackSpy)
          .done(whenDoneFn);

      waitsFor(function () {
         return !!fullResponse
      }, 'the request to complete', ASYNC_TEST_TIMEOUT);

      runs(function () {

         expect(callbackSpy).toHaveBeenCalledWith(0, [0], [fullResponse, 0]);
         expect(callbackSpy).toHaveBeenCalledWith(1, [1], [fullResponse, 1]);
         expect(callbackSpy).toHaveBeenCalledWith(2, [2], [fullResponse, 2]);
         expect(callbackSpy).toHaveBeenCalledWith(3, [3], [fullResponse, 3]);
         expect(callbackSpy).toHaveBeenCalledWith(4, [4], [fullResponse, 4]);
         expect(callbackSpy).toHaveBeenCalledWith(5, [5], [fullResponse, 5]);
         expect(callbackSpy).toHaveBeenCalledWith(6, [6], [fullResponse, 6]);
         expect(callbackSpy).toHaveBeenCalledWith(7, [7], [fullResponse, 7]);
         expect(callbackSpy).toHaveBeenCalledWith(8, [8], [fullResponse, 8]);
         expect(callbackSpy).toHaveBeenCalledWith(9, [9], [fullResponse, 9]);

      });
   })

   it('can abort once some data has been found in streamed response', function () {

      var req = oboe('/testServer/tenSlowNumbers')
                  .node('![5]', abortCallback);

      waitsFor(theRequestToBeAborted, 'the request to be aborted', ASYNC_TEST_TIMEOUT);

      // in case we didn't abort, wait a little longer. If we didn't really abort we'd get the
      // rest of the data now and the test would fail:
      waitsFor(someSecondsToPass(2), ASYNC_TEST_TIMEOUT);

      runs(function () {
         // because the request was aborted on index array 5, we got 6 numbers (inc zero)
         // not the whole ten.

         expect(req.root()).toEqual([0, 1, 2, 3, 4, 5]);

      });
   })

   it('can abort once some data has been found in not very streamed response', function () {

      // like above but we're getting a static file not the streamed numbers. This means
      // we'll almost certainly read in the whole response as one onprogress it is on localhost
      // and the json is very small

      var req = oboe('/testServer/static/json/firstTenNaturalNumbers.json')
                  .node('![5]', abortCallback);

      waitsFor(theRequestToBeAborted, 'the request to be aborted', ASYNC_TEST_TIMEOUT);

      // in case we didn't abort, wait a little longer. If we didn't really abort we'd get the
      // rest of the data now and the test would fail:
      waitsFor(someSecondsToPass(2), ASYNC_TEST_TIMEOUT);

      runs(function () {
         // because the request was aborted on index array 5, we got 6 numbers (inc zero)
         // not the whole ten.

         expect(req.root()).toEqual([0, 1, 2, 3, 4, 5]);
      });
   })

   it('gives full json to callback when request finishes', function () {

      oboe.doGet('/testServer/static/json/firstTenNaturalNumbers.json')
          .done(whenDoneFn);

      waitsFor(function () {
         return !!fullResponse
      }, 'the request to complete', ASYNC_TEST_TIMEOUT)

      runs(function () {
         expect(fullResponse).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
      });
   })

   it('gives header to server side', function () {

      var countGotBack = 0;

      oboe(
          {  url:'/testServer/echoBackHeaders',
             headers:{'x-snarfu':'SNARF', 'x-foo':'BAR'}
          }

      ).node('x-snarfu',
          function (headerValue) {

             expect(headerValue).toBe('SNARF')
             countGotBack++;

          }).node('x-foo', function (headerValue) {

             expect(headerValue).toBe('BAR')
             countGotBack++;
          })

      waitsFor(function () {
         return countGotBack == 2
      }, 'the request to complete', ASYNC_TEST_TIMEOUT)
   })

   it('can listen for nodes via nodejs-style syntax', function () {

      var countGotBack = 0;

      oboe(
          '/testServer/static/json/firstTenNaturalNumbers.json'
      ).on('node', '!.*', function (number) {
             countGotBack++;
          });

      waitsFor(function () {
         return countGotBack == 10
      }, 'ten callbacks', ASYNC_TEST_TIMEOUT)
   })

   it('can listen for paths via nodejs-style syntax', function () {

      var countGotBack = 0;

      oboe(
          '/testServer/static/json/firstTenNaturalNumbers.json'
      ).on('path', '!.*', function (number) {
             countGotBack++;
          });

      waitsFor(function () {
         return countGotBack == 10
      }, 'ten callbacks', ASYNC_TEST_TIMEOUT)
   })

   it('fires error on 404', function () {

      var gotError = false

      oboe('/testServer/doesNotExist'
      ).fail(function () {

             gotError = true
          });

      waitsFor(function () {
         return gotError
      }, 'the request to fail', ASYNC_TEST_TIMEOUT)
   })

   function someSecondsToPass(waitSecs) {

      function now() {
         // IE8 doesn't have Date.now()
         return new Date().getTime();
      }

      var waitStart = now(),
          waitMs = waitSecs * 1000;

      return function () {
         return now() > (waitStart + waitMs);
      }
   }

   var callbackSpy,
       fullResponse,
       aborted;

   function abortCallback() {
      this.abort();
      aborted = true;
   }

   function theRequestToBeAborted() {
      return aborted
   }

   function whenDoneFn(obj) {
      fullResponse = obj;
   }

   beforeEach(function () {
      aborted = false;
      fullResponse = null;
      callbackSpy = jasmine.createSpy('callbackSpy');
   });

});

<|endoftext|>


<commit_before>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

describe('Geolocation (navigator.geolocation)', function () {
    it("geolocation.spec.1 should exist", function() {
        expect(navigator.geolocation).toBeDefined();
    });

    it("geolocation.spec.2 should contain a getCurrentPosition function", function() {
        expect(typeof navigator.geolocation.getCurrentPosition).toBeDefined();
        expect(typeof navigator.geolocation.getCurrentPosition == 'function').toBe(true);
    });

    it("geolocation.spec.3 should contain a watchPosition function", function() {
        expect(typeof navigator.geolocation.watchPosition).toBeDefined();
        expect(typeof navigator.geolocation.watchPosition == 'function').toBe(true);
    });

    it("geolocation.spec.4 should contain a clearWatch function", function() {
        expect(typeof navigator.geolocation.clearWatch).toBeDefined();
        expect(typeof navigator.geolocation.clearWatch == 'function').toBe(true);
    });

    describe('getCurrentPosition method', function() {
        describe('error callback', function() {
            it("geolocation.spec.5 should be called if we set timeout to 0 and maximumAge to a very small number", function() {
                console.log("Here I am");
                var win = jasmine.createSpy(),
                    fail = jasmine.createSpy();

                runs(function () {
                    navigator.geolocation.getCurrentPosition(win, fail, {
                        maximumAge: 0,
                        timeout: 0
                    });
                });

                waitsFor(function () { return fail.wasCalled; }, "fail never called", 250); //small timeout as this should fire very fast

                runs(function () {
                    expect(win).not.toHaveBeenCalled();
                });
            });
        });

        describe('success callback', function() {
            it("geolocation.spec.6 should be called with a Position object", function() {
                var providerAvailable=true;
                var win = jasmine.createSpy().andCallFake(function(p) {
                          expect(p.coords).toBeDefined();
                          expect(p.timestamp).toBeDefined();
                      }),
                      fail = jasmine.createSpy().andCallFake(function(e) {
                          if(e.code == 2) {
                              providerAvailable=false;
                          }
                      });

                runs(function () {
                    navigator.geolocation.getCurrentPosition(win, fail, {
                        maximumAge:300000 // 5 minutes maximum age of cached position
                    });
                });

                waitsFor(function () { return (win.wasCalled || fail.wasCalled); }, "win/fail never called", 20000);

                runs(function () {
                    if(providerAvailable) {
                        expect(fail).not.toHaveBeenCalled();
                    }
                });
            });
        });
    });

    describe('watchPosition method', function() {
        describe('error callback', function() {
            var errorWatch = null;

            afterEach(function() {
                navigator.geolocation.clearWatch(errorWatch);
            });
            it("geolocation.spec.7 should be called if we set timeout to 0 and maximumAge to a very small number", function() {
                var win = jasmine.createSpy(),
                    fail = jasmine.createSpy();

                runs(function () {
                    errorWatch = navigator.geolocation.watchPosition(win, fail, {
                        maximumAge: 0,
                        timeout: 0
                    });
                });

                waitsFor(function () { return fail.wasCalled; }, "fail never called", 250); // small timeout as this should fire very quickly

                runs(function () {
                    expect(win).not.toHaveBeenCalled();
                });
            });
        });

        describe('success callback', function() {
            var successWatch = null;

            afterEach(function() {
                navigator.geolocation.clearWatch(successWatch);
            });
            it("geolocation.spec.8 should be called with a Position object", function() {
                var providerAvailable=true;
                var win = jasmine.createSpy().andCallFake(function(p) {
                          expect(p.coords).toBeDefined();
                          expect(p.timestamp).toBeDefined();
                      }),
                      fail = jasmine.createSpy().andCallFake(function(e) {
                          if(e.code ==2) {
                              providerAvailable=false;
                          }
                      });

                runs(function () {
                    successWatch = navigator.geolocation.watchPosition(win, fail, {
                        maximumAge:(5 * 60 * 1000) // 5 minutes maximum age of cached position
                    });
                });

                waitsFor(function () { return (win.wasCalled || fail.wasCalled); }, "win/fail never called", 20000);

                runs(function () {
                    if(providerAvailable) {
                        expect(fail).not.toHaveBeenCalled();
                    }
                });
            });
        });
    });
});
<commit_msg>CB-5358 Make mobile spec tests on Windows8 to run w/o user interaction<commit_after>/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
*/

describe('Geolocation (navigator.geolocation)', function () {
    it("geolocation.spec.1 should exist", function() {
        expect(navigator.geolocation).toBeDefined();
    });

    it("geolocation.spec.2 should contain a getCurrentPosition function", function() {
        expect(typeof navigator.geolocation.getCurrentPosition).toBeDefined();
        expect(typeof navigator.geolocation.getCurrentPosition == 'function').toBe(true);
    });

    it("geolocation.spec.3 should contain a watchPosition function", function() {
        expect(typeof navigator.geolocation.watchPosition).toBeDefined();
        expect(typeof navigator.geolocation.watchPosition == 'function').toBe(true);
    });

    it("geolocation.spec.4 should contain a clearWatch function", function() {
        expect(typeof navigator.geolocation.clearWatch).toBeDefined();
        expect(typeof navigator.geolocation.clearWatch == 'function').toBe(true);
    });

    describe('getCurrentPosition method', function() {
        // this api requires manual user confirmation on windows8 so skip it
        if (cordova.platformId == 'windows8') return;

        describe('error callback', function() {
            it("geolocation.spec.5 should be called if we set timeout to 0 and maximumAge to a very small number", function() {
                console.log("Here I am");
                var win = jasmine.createSpy(),
                    fail = jasmine.createSpy();

                runs(function () {
                    navigator.geolocation.getCurrentPosition(win, fail, {
                        maximumAge: 0,
                        timeout: 0
                    });
                });

                waitsFor(function () { return fail.wasCalled; }, "fail never called", 250); //small timeout as this should fire very fast

                runs(function () {
                    expect(win).not.toHaveBeenCalled();
                });
            });
        });

        describe('success callback', function() {
            it("geolocation.spec.6 should be called with a Position object", function() {
                var providerAvailable=true;
                var win = jasmine.createSpy().andCallFake(function(p) {
                          expect(p.coords).toBeDefined();
                          expect(p.timestamp).toBeDefined();
                      }),
                      fail = jasmine.createSpy().andCallFake(function(e) {
                          if(e.code == 2) {
                              providerAvailable=false;
                          }
                      });

                runs(function () {
                    navigator.geolocation.getCurrentPosition(win, fail, {
                        maximumAge:300000 // 5 minutes maximum age of cached position
                    });
                });

                waitsFor(function () { return (win.wasCalled || fail.wasCalled); }, "win/fail never called", 20000);

                runs(function () {
                    if(providerAvailable) {
                        expect(fail).not.toHaveBeenCalled();
                    }
                });
            });
        });
    });

    describe('watchPosition method', function() {
        // this api requires manual user confirmation on windows8 so skip it
        if (cordova.platformId == 'windows8') return;

        describe('error callback', function() {
            var errorWatch = null;

            afterEach(function() {
                navigator.geolocation.clearWatch(errorWatch);
            });
            it("geolocation.spec.7 should be called if we set timeout to 0 and maximumAge to a very small number", function() {
                var win = jasmine.createSpy(),
                    fail = jasmine.createSpy();

                runs(function () {
                    errorWatch = navigator.geolocation.watchPosition(win, fail, {
                        maximumAge: 0,
                        timeout: 0
                    });
                });

                waitsFor(function () { return fail.wasCalled; }, "fail never called", 250); // small timeout as this should fire very quickly

                runs(function () {
                    expect(win).not.toHaveBeenCalled();
                });
            });
        });

        describe('success callback', function() {
            var successWatch = null;

            afterEach(function() {
                navigator.geolocation.clearWatch(successWatch);
            });
            it("geolocation.spec.8 should be called with a Position object", function() {
                var providerAvailable=true;
                var win = jasmine.createSpy().andCallFake(function(p) {
                          expect(p.coords).toBeDefined();
                          expect(p.timestamp).toBeDefined();
                      }),
                      fail = jasmine.createSpy().andCallFake(function(e) {
                          if(e.code ==2) {
                              providerAvailable=false;
                          }
                      });

                runs(function () {
                    successWatch = navigator.geolocation.watchPosition(win, fail, {
                        maximumAge:(5 * 60 * 1000) // 5 minutes maximum age of cached position
                    });
                });

                waitsFor(function () { return (win.wasCalled || fail.wasCalled); }, "win/fail never called", 20000);

                runs(function () {
                    if(providerAvailable) {
                        expect(fail).not.toHaveBeenCalled();
                    }
                });
            });
        });
    });
});
<|endoftext|>


<commit_before>/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2005-2017 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function () {

"use strict";

var ui = angular.module("axelor.ui");

EditorCtrl.$inject = ['$scope', '$element', 'DataSource', 'ViewService', '$q'];

function EditorCtrl($scope, $element, DataSource, ViewService, $q) {

	var parent = $scope.$parent;

	$scope._viewParams = parent._viewParams;
	$scope.editorCanSave = parent.editorCanSave;
	$scope.editorCanReload = parent.editorCanReload;

	ui.ViewCtrl.call(this, $scope, DataSource, ViewService);
	ui.FormViewCtrl.call(this, $scope, $element);

	var closeCallback = null;
	var originalEdit = $scope.edit;
	var originalShow = $scope.show;

	var recordVersion = -1;
	var canClose = false;
	var isClosed = true;

	$scope.show = function(record, callback) {
		originalShow();
		if (_.isFunction(record)) {
			callback = record;
			record = null;
		}
		closeCallback = callback;
		isClosed = false;
		recordVersion = record ? record.version : -1;
		if (recordVersion === undefined && record) {
			recordVersion = record.$version;
		}
		this.edit(record);
	};

	function doEdit(record, fireOnLoad) {
		if (record && record.id > 0 && (!_.isNumber(record.version) || !record.$fetched)) {
			$scope.doRead(record.id).success(function(rec) {
				if (record.$dirty) {
					rec = _.extend({}, rec, record);
				}
				originalEdit(rec, fireOnLoad);
			});
		} else {
			originalEdit(record, fireOnLoad);
		}
		canClose = false;
	}

	var parentCanEditTarget = null;

	$scope.canEditTarget =  function () {
		if (parentCanEditTarget === null) {
			var parent = $scope.$parent;
			var func = parent.canEditTarget;
			while (parent && func === $scope.canEditTarget) {
				parent = parent.$parent;
				func = parent.canEditTarget;
			}
			parentCanEditTarget = func || angular.noop;
		}
		return parentCanEditTarget() !== false;
	};

	var isEditable = $scope.isEditable;
	$scope.isEditable = function () {
		var id = ($scope.record || {}).id;
		var perm = id > 0 ? 'write' : 'create';
		return !parent.isReadonly() && $scope.hasPermission(perm)
			&& (id > 0 ? $scope.canEditTarget() : true)
			&& isEditable.call($scope);
	};

	var canEdit = $scope.canEdit;
	$scope.canEdit = function() {
		return $scope.canEditTarget() && canEdit.call($scope);
	};

	$scope.edit = function(record, fireOnLoad) {
		if (isClosed) return;
		$scope._viewPromise.then(function(){
			doEdit(record, fireOnLoad);
			$scope.setEditable(!$scope.$parent.$$readonly);
		});
	};

	function isChanged() {
		if ($scope.isDirty()) return true;
		var record = $scope.record || {};
		var version = record.version;
		return recordVersion !== version || record.$forceDirty;
	}

	function canOK() {
		if (isClosed) return false;
		return isChanged();
	}

	function onOK() {

		var record = $scope.record;

		function close(value, forceSelect) {
			if (value && (forceSelect || canOK())) {
				value.$fetched = true;
				value.selected = true;
				$scope.$parent.select(value);
			}
			canClose = true;
			$element.dialog('close');
			if ($scope.editorCanReload) {
				$scope.$parent.parentReload();
			}
			if (closeCallback && value) {
				closeCallback(value);
			}
			closeCallback = null;
			isClosed = true;
		}

		var event = $scope.$broadcast('on:before-save', record);
		if (event.defaultPrevented) {
			if (event.error) {
				axelor.dialogs.error(event.error);
			}
			return;
 		}

		// wait for onChange actions
		$scope.waitForActions(function() {
			if ($scope.editorCanSave && isChanged()) {
				if (record.id < 0)
					record.id = null;
				return $scope.onSave({force: true}).then(function(record, page) {
					// wait for onSave actions
					$scope.waitForActions(function(){
						close(record, true);
					});
				});
			}
			if ($scope.isValid()) {
				close(record);
			}
		}, 100);
	}

	$scope.onOK = function() {
		$scope.$timeout(onOK, 10);
	};

	$scope.onBeforeClose = function(event, ui) {

		if (canClose || !$scope.isDirty()) {
			$scope.$evalAsync(function () {
				$scope.edit(null, false);
			});
			return true;
		}

		event.preventDefault();

		$scope.confirmDirty(function(){
			canClose = true;
			$element.dialog('close');
		});
	};

	$scope.onHotKey = function (e, action) {

		if (action === "save") {
			$(e.target).blur().focus();
			$scope.onOK();
		}

		$scope.$applyAsync();

		return false;
	};
}

SelectorCtrl.$inject = ['$scope', '$element', 'DataSource', 'ViewService'];
function SelectorCtrl($scope, $element, DataSource, ViewService) {

	var parent = $scope.$parent;

	$scope._viewParams = parent._viewParams;
	$scope.getDomain = parent.getDomain;

	ui.ViewCtrl.call(this, $scope, DataSource, ViewService);
	ui.GridViewCtrl.call(this, $scope, $element);

	var searchLimit = (parent.field||{}).searchLimit || 0;
	if (searchLimit > 0) {
		$scope._dataSource._page.limit = searchLimit;
	}

	function doFilter() {
		$scope.filter($scope.getDomain());
	}

	var initialized = false;
	var origShow = $scope.show;
	$scope.show = function() {
		origShow();
		if (initialized) {
			doFilter();
		}
	};

	var _getContext = $scope.getContext;
	$scope.getContext = function() {
		// selector popup should return parent's context
		if ($scope.$parent && $scope.$parent.getContext) {
			return $scope.$parent.getContext();
		}
		return _getContext();
	};

	$scope.onItemClick = function(e, args) {
		$scope.$applyAsync($scope.onOK.bind($scope));
	};

	var origOnShow = $scope.onShow;
	$scope.onShow = function(viewPromise) {

		viewPromise.then(function(){
			$element.dialog('open');
			initialized = true;
			origOnShow(viewPromise);
		});
	};

	$scope.onOK = function() {

		var selection = _.map($scope.selection, function(index){
			return $scope.dataView.getItem(index);
		});

		if (!_.isEmpty(selection)) {
			$scope.$applyAsync(function () {
				$scope.$parent.select(selection);
				$scope.selection = [];
			});
		}

		$element.dialog('close');
	};

	$scope.onCreate = function () {
		$element.dialog('close');
		$scope.$parent.onNew();
	};

	$scope.canNew = function () {
		return $scope.hasPermission('create') && $scope.$parent.canNew();
	};
}

ui.directive('uiDialogSize', function() {

	return function (scope, element, attrs) {

		// use only with dialogs
		if (attrs.uiDialog === undefined && !element.hasClass('ui-dialog-content')) {
			return;
		}

		var loaded = false;
		var addMaximizeButton = _.once(function () {
			var elemDialog = element.parent();
			var elemTitle = elemDialog.find('.ui-dialog-title');
			$('').click(function (e) {
				$(this).children('i').toggleClass('fa-expand fa-compress');
				elemDialog.toggleClass('maximized');
				axelor.$adjustSize();
				return false;
			}).insertAfter(elemTitle);

			// remove maximized state on close
			element.on('dialogclose', function(e, ui) {
				elemTitle.parent().find('i.fa-compress').toggleClass('fa-expand fa-compress');
				elemDialog.removeClass('maximized');
			});
		});
		var addCollapseButton = _.once(function () {
			var elemDialog = element.parent();
			var elemTitle = elemDialog.find('.ui-dialog-title');
			$('')
			.click(function (e) {
				$(this).children('i').toggleClass('fa-chevron-up fa-chevron-down');
				elemDialog.toggleClass('collapsed');
				axelor.$adjustSize();
				return false;
			}).insertAfter(elemTitle);

			// remove maximized and collapsed states on close
			element.on('dialogclose', function(e, ui) {
				elemTitle.parent().find('i.fa-compress').toggleClass('fa-expand fa-compress');
				elemTitle.parent().find('i.fa-chevron-down').toggleClass('fa-chevron-down fa-chevron-up');
				elemDialog.removeClass('maximized collapsed');
			});
		});

		function doAdjust() {
			element.dialog('open');
			element.scrollTop(0);
			setTimeout(doFocus);
			if (scope._afterPopupShow) {
				scope._afterPopupShow();
			}
		}

		function doShow() {
			addMaximizeButton();
			addCollapseButton();
			if (loaded) {
				return setTimeout(doAdjust);
			}
			loaded = true;
			scope.waitForActions(doAdjust);
		}

		function doFocus() {
			var focusElem = element.find('input:tabbable');
			if (focusElem.length == 0) {
				focusElem = element.parent().find('.ui-dialog-buttonset').find(':tabbable');
			}
			if (focusElem[0]) {
				focusElem[0].focus();
			}

			//XXX: ui-dialog issue
			element.find('.slick-headerrow-column,.slickgrid,[ui-embedded-editor]').zIndex(element.zIndex());
			element.find('.record-toolbar .btn').zIndex(element.zIndex()+1);
		}

		// a flag used by evalScope to detect popup (see form.base.js)
		scope._isPopup = true;
		scope._doShow = function(viewPromise) {
			if (viewPromise && viewPromise.then) {
				viewPromise.then(doShow);
			} else {
				doShow();
			}
		};

		scope._setTitle = function (title) {
			if (title) {
				element.closest('.ui-dialog').find('.ui-dialog-title').text(title);
			}
		};

		scope.adjustSize = function() {
		};
	};
});

ui.directive('uiEditorPopup', function() {

	return {
		restrict: 'EA',
		controller: EditorCtrl,
		scope: {},
		link: function(scope, element, attrs) {

			scope.onShow = function(viewPromise) {
				scope._doShow(viewPromise);
			};

			scope.$watch('schema.title', function popupTitleWatch(title) {
				scope._setTitle(title);
			});

			element.scroll(function (e) {
				$(document).trigger('adjust:scroll', element);
			});

			var onNewHandler = scope.onNewHandler;
			scope.onNewHandler = function (event) {
				if (element.dialog('isOpen')) {
					return onNewHandler.apply(scope, arguments);
				}
			};

			scope.isPopupOpen = true;
			setTimeout(function () {
				var isOpen = false;
				element.on('dialogclose', function (e) {
					isOpen = false;
					scope.waitForActions(function () {
						scope.isPopupOpen = isOpen;
					}, 2000); // delay couple of seconds to that popup can cleanup
				});
				element.on('dialogopen', function (e) {
					scope.isPopupOpen = isOpen = true;
					scope.$applyAsync();
				});
			});
		},
		replace: true,
		template:
		'
'+
		    '

'+
		'
'
	};
});

ui.directive('uiSelectorPopup', function(){

	return {
		restrict: 'EA',
		controller: SelectorCtrl,
		scope: {
			selectMode: "@"
		},
		link: function(scope, element, attrs) {

			var onShow = scope.onShow;
			scope.onShow = function (viewPromise) {
				if (scope.clearFilters) {
					scope.clearFilters();
					scope.selection = [];
				}
				onShow(viewPromise);
				scope._doShow(viewPromise);
			};

			scope.$watch('schema.title', function popupTitleWatch(title){
				scope._setTitle(title);
			});

			var btnOK = null;

			function buttonState(count) {
				if (btnOK === null) {
					btnOK = element.siblings('.ui-dialog-buttonpane').find('.btn:last');
				}
				return btnOK.attr('disabled', !count || count <= 0);
			}

			scope.$watch('selection.length', buttonState);

			setTimeout(function(){
				var footer = element.closest('.ui-dialog').find('.ui-dialog-buttonpane'),
					header = element.closest('.ui-dialog').find('.ui-dialog-titlebar'),
					pager = element.find('.record-pager'),
					buttons = element.find('.ui-dialog-buttonset-left');
				header.find('.ui-dialog-title').after(pager);
				footer.prepend(buttons);
				footer.find('.button-ok').html(_t("Select"));
			});
		},
		replace: true,
		template:
		'
'+
		    '

'+
		    '

'+
		    '
'+
		    	'<button class="btn" ng-click="onCreate()" x-translate>Create</button>'+
		    '
'+
		'
'
	};
});

})();
<commit_msg>Fix popup editor regression<commit_after>/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2005-2017 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function () {

"use strict";

var ui = angular.module("axelor.ui");

EditorCtrl.$inject = ['$scope', '$element', 'DataSource', 'ViewService', '$q'];

function EditorCtrl($scope, $element, DataSource, ViewService, $q) {

	var parent = $scope.$parent;

	$scope._viewParams = parent._viewParams;
	$scope.editorCanSave = parent.editorCanSave;
	$scope.editorCanReload = parent.editorCanReload;

	ui.ViewCtrl.call(this, $scope, DataSource, ViewService);
	ui.FormViewCtrl.call(this, $scope, $element);

	var closeCallback = null;
	var originalEdit = $scope.edit;
	var originalShow = $scope.show;

	var recordVersion = -1;
	var canClose = false;
	var isClosed = true;

	$scope.show = function(record, callback) {
		originalShow();
		if (_.isFunction(record)) {
			callback = record;
			record = null;
		}
		closeCallback = callback;
		isClosed = false;
		recordVersion = record ? record.version : -1;
		if (recordVersion === undefined && record) {
			recordVersion = record.$version;
		}
		this.edit(record);
	};

	function doEdit(record, fireOnLoad) {
		if (record && record.id > 0 && (!_.isNumber(record.version) || !record.$fetched)) {
			$scope.doRead(record.id).success(function(rec) {
				if (record.$dirty) {
					rec = _.extend({}, rec, record);
				}
				originalEdit(rec, fireOnLoad);
			});
		} else {
			originalEdit(record, fireOnLoad);
		}
		canClose = false;
	}

	var parentCanEditTarget = null;

	$scope.canEditTarget =  function () {
		if (parentCanEditTarget === null) {
			var parent = $scope.$parent;
			var func = parent.canEditTarget;
			while (parent && func === $scope.canEditTarget) {
				parent = parent.$parent;
				func = parent.canEditTarget;
			}
			parentCanEditTarget = func || angular.noop;
		}
		return parentCanEditTarget() !== false;
	};

	var isEditable = $scope.isEditable;
	$scope.isEditable = function () {
		var id = ($scope.record || {}).id;
		var perm = id > 0 ? 'write' : 'create';
		if (parent.isReadonly && parent.isReadonly()) return true;
		return $scope.hasPermission(perm)
			&& (id > 0 ? $scope.canEditTarget() : true)
			&& isEditable.call($scope);
	};

	var canEdit = $scope.canEdit;
	$scope.canEdit = function() {
		return $scope.canEditTarget() && canEdit.call($scope);
	};

	$scope.edit = function(record, fireOnLoad) {
		if (isClosed) return;
		$scope._viewPromise.then(function(){
			doEdit(record, fireOnLoad);
			$scope.setEditable(!$scope.$parent.$$readonly);
		});
	};

	function isChanged() {
		if ($scope.isDirty()) return true;
		var record = $scope.record || {};
		var version = record.version;
		return recordVersion !== version || record.$forceDirty;
	}

	function canOK() {
		if (isClosed) return false;
		return isChanged();
	}

	function onOK() {

		var record = $scope.record;

		function close(value, forceSelect) {
			if (value && (forceSelect || canOK())) {
				value.$fetched = true;
				value.selected = true;
				$scope.$parent.select(value);
			}
			canClose = true;
			$element.dialog('close');
			if ($scope.editorCanReload) {
				$scope.$parent.parentReload();
			}
			if (closeCallback && value) {
				closeCallback(value);
			}
			closeCallback = null;
			isClosed = true;
		}

		var event = $scope.$broadcast('on:before-save', record);
		if (event.defaultPrevented) {
			if (event.error) {
				axelor.dialogs.error(event.error);
			}
			return;
 		}

		// wait for onChange actions
		$scope.waitForActions(function() {
			if ($scope.editorCanSave && isChanged()) {
				if (record.id < 0)
					record.id = null;
				return $scope.onSave({force: true}).then(function(record, page) {
					// wait for onSave actions
					$scope.waitForActions(function(){
						close(record, true);
					});
				});
			}
			if ($scope.isValid()) {
				close(record);
			}
		}, 100);
	}

	$scope.onOK = function() {
		$scope.$timeout(onOK, 10);
	};

	$scope.onBeforeClose = function(event, ui) {

		if (canClose || !$scope.isDirty()) {
			$scope.$evalAsync(function () {
				$scope.edit(null, false);
			});
			return true;
		}

		event.preventDefault();

		$scope.confirmDirty(function(){
			canClose = true;
			$element.dialog('close');
		});
	};

	$scope.onHotKey = function (e, action) {

		if (action === "save") {
			$(e.target).blur().focus();
			$scope.onOK();
		}

		$scope.$applyAsync();

		return false;
	};
}

SelectorCtrl.$inject = ['$scope', '$element', 'DataSource', 'ViewService'];
function SelectorCtrl($scope, $element, DataSource, ViewService) {

	var parent = $scope.$parent;

	$scope._viewParams = parent._viewParams;
	$scope.getDomain = parent.getDomain;

	ui.ViewCtrl.call(this, $scope, DataSource, ViewService);
	ui.GridViewCtrl.call(this, $scope, $element);

	var searchLimit = (parent.field||{}).searchLimit || 0;
	if (searchLimit > 0) {
		$scope._dataSource._page.limit = searchLimit;
	}

	function doFilter() {
		$scope.filter($scope.getDomain());
	}

	var initialized = false;
	var origShow = $scope.show;
	$scope.show = function() {
		origShow();
		if (initialized) {
			doFilter();
		}
	};

	var _getContext = $scope.getContext;
	$scope.getContext = function() {
		// selector popup should return parent's context
		if ($scope.$parent && $scope.$parent.getContext) {
			return $scope.$parent.getContext();
		}
		return _getContext();
	};

	$scope.onItemClick = function(e, args) {
		$scope.$applyAsync($scope.onOK.bind($scope));
	};

	var origOnShow = $scope.onShow;
	$scope.onShow = function(viewPromise) {

		viewPromise.then(function(){
			$element.dialog('open');
			initialized = true;
			origOnShow(viewPromise);
		});
	};

	$scope.onOK = function() {

		var selection = _.map($scope.selection, function(index){
			return $scope.dataView.getItem(index);
		});

		if (!_.isEmpty(selection)) {
			$scope.$applyAsync(function () {
				$scope.$parent.select(selection);
				$scope.selection = [];
			});
		}

		$element.dialog('close');
	};

	$scope.onCreate = function () {
		$element.dialog('close');
		$scope.$parent.onNew();
	};

	$scope.canNew = function () {
		return $scope.hasPermission('create') && $scope.$parent.canNew();
	};
}

ui.directive('uiDialogSize', function() {

	return function (scope, element, attrs) {

		// use only with dialogs
		if (attrs.uiDialog === undefined && !element.hasClass('ui-dialog-content')) {
			return;
		}

		var loaded = false;
		var addMaximizeButton = _.once(function () {
			var elemDialog = element.parent();
			var elemTitle = elemDialog.find('.ui-dialog-title');
			$('').click(function (e) {
				$(this).children('i').toggleClass('fa-expand fa-compress');
				elemDialog.toggleClass('maximized');
				axelor.$adjustSize();
				return false;
			}).insertAfter(elemTitle);

			// remove maximized state on close
			element.on('dialogclose', function(e, ui) {
				elemTitle.parent().find('i.fa-compress').toggleClass('fa-expand fa-compress');
				elemDialog.removeClass('maximized');
			});
		});
		var addCollapseButton = _.once(function () {
			var elemDialog = element.parent();
			var elemTitle = elemDialog.find('.ui-dialog-title');
			$('')
			.click(function (e) {
				$(this).children('i').toggleClass('fa-chevron-up fa-chevron-down');
				elemDialog.toggleClass('collapsed');
				axelor.$adjustSize();
				return false;
			}).insertAfter(elemTitle);

			// remove maximized and collapsed states on close
			element.on('dialogclose', function(e, ui) {
				elemTitle.parent().find('i.fa-compress').toggleClass('fa-expand fa-compress');
				elemTitle.parent().find('i.fa-chevron-down').toggleClass('fa-chevron-down fa-chevron-up');
				elemDialog.removeClass('maximized collapsed');
			});
		});

		function doAdjust() {
			element.dialog('open');
			element.scrollTop(0);
			setTimeout(doFocus);
			if (scope._afterPopupShow) {
				scope._afterPopupShow();
			}
		}

		function doShow() {
			addMaximizeButton();
			addCollapseButton();
			if (loaded) {
				return setTimeout(doAdjust);
			}
			loaded = true;
			scope.waitForActions(doAdjust);
		}

		function doFocus() {
			var focusElem = element.find('input:tabbable');
			if (focusElem.length == 0) {
				focusElem = element.parent().find('.ui-dialog-buttonset').find(':tabbable');
			}
			if (focusElem[0]) {
				focusElem[0].focus();
			}

			//XXX: ui-dialog issue
			element.find('.slick-headerrow-column,.slickgrid,[ui-embedded-editor]').zIndex(element.zIndex());
			element.find('.record-toolbar .btn').zIndex(element.zIndex()+1);
		}

		// a flag used by evalScope to detect popup (see form.base.js)
		scope._isPopup = true;
		scope._doShow = function(viewPromise) {
			if (viewPromise && viewPromise.then) {
				viewPromise.then(doShow);
			} else {
				doShow();
			}
		};

		scope._setTitle = function (title) {
			if (title) {
				element.closest('.ui-dialog').find('.ui-dialog-title').text(title);
			}
		};

		scope.adjustSize = function() {
		};
	};
});

ui.directive('uiEditorPopup', function() {

	return {
		restrict: 'EA',
		controller: EditorCtrl,
		scope: {},
		link: function(scope, element, attrs) {

			scope.onShow = function(viewPromise) {
				scope._doShow(viewPromise);
			};

			scope.$watch('schema.title', function popupTitleWatch(title) {
				scope._setTitle(title);
			});

			element.scroll(function (e) {
				$(document).trigger('adjust:scroll', element);
			});

			var onNewHandler = scope.onNewHandler;
			scope.onNewHandler = function (event) {
				if (element.dialog('isOpen')) {
					return onNewHandler.apply(scope, arguments);
				}
			};

			scope.isPopupOpen = true;
			setTimeout(function () {
				var isOpen = false;
				element.on('dialogclose', function (e) {
					isOpen = false;
					scope.waitForActions(function () {
						scope.isPopupOpen = isOpen;
					}, 2000); // delay couple of seconds to that popup can cleanup
				});
				element.on('dialogopen', function (e) {
					scope.isPopupOpen = isOpen = true;
					scope.$applyAsync();
				});
			});
		},
		replace: true,
		template:
		'
'+
		    '

'+
		'
'
	};
});

ui.directive('uiSelectorPopup', function(){

	return {
		restrict: 'EA',
		controller: SelectorCtrl,
		scope: {
			selectMode: "@"
		},
		link: function(scope, element, attrs) {

			var onShow = scope.onShow;
			scope.onShow = function (viewPromise) {
				if (scope.clearFilters) {
					scope.clearFilters();
					scope.selection = [];
				}
				onShow(viewPromise);
				scope._doShow(viewPromise);
			};

			scope.$watch('schema.title', function popupTitleWatch(title){
				scope._setTitle(title);
			});

			var btnOK = null;

			function buttonState(count) {
				if (btnOK === null) {
					btnOK = element.siblings('.ui-dialog-buttonpane').find('.btn:last');
				}
				return btnOK.attr('disabled', !count || count <= 0);
			}

			scope.$watch('selection.length', buttonState);

			setTimeout(function(){
				var footer = element.closest('.ui-dialog').find('.ui-dialog-buttonpane'),
					header = element.closest('.ui-dialog').find('.ui-dialog-titlebar'),
					pager = element.find('.record-pager'),
					buttons = element.find('.ui-dialog-buttonset-left');
				header.find('.ui-dialog-title').after(pager);
				footer.prepend(buttons);
				footer.find('.button-ok').html(_t("Select"));
			});
		},
		replace: true,
		template:
		'
'+
		    '

'+
		    '

'+
		    '
'+
		    	'<button class="btn" ng-click="onCreate()" x-translate>Create</button>'+
		    '
'+
		'
'
	};
});

})();
<|endoftext|>