<commit_before>'use strict';

define(['angular'], function(angular) {
  return angular.module('portal.messages.controllers', [])

    .controller('MessagesController', ['$q', '$log', '$scope', '$rootScope',
      '$location', '$localStorage', '$sessionStorage', '$filter', '$mdDialog',
      'MESSAGES', 'SERVICE_LOC', 'miscService', 'messagesService',
      function($q, $log, $scope, $rootScope, $location, $localStorage,
               $sessionStorage, $filter, $mdDialog, MESSAGES, SERVICE_LOC,
               miscService, messagesService) {
        // //////////////////
        // Local variables //
        // //////////////////
        var allMessages = [];
        var promiseFilteredMessages = {};

        // ////////////////
        // Local methods //
        // ////////////////
        /**
         * Get all messages, then pass result on for filtering
         */
        var getMessages = function() {
          messagesService.getAllMessages()
            .then(function(result) {
              // Ensure messages exist and check for group filtering
              if (result.messages && result.messages.length > 0) {
                allMessages = result.messages;
              }
              filterMessages();
              return allMessages;
            })
            .catch(function(error) {
              $log.warn('Problem getting all messages for messages controller');
              return error;
            });
        };

        /**
         * Determine whether or not messages need to be filtered
         * by group and data, then execute the relevant promises
         */
        var filterMessages = function() {
          // Check for group filtering on app level and beta settings level
          if (MESSAGES.groupFiltering
            && !$localStorage.disableGroupFilteringForMessages) {
            // Define promises to run if filtering is turned on
            promiseFilteredMessages = {
              filteredByGroup:
                messagesService.getMessagesByGroup(allMessages),
              filteredByData:
                messagesService.getMessagesByData(allMessages),
            };
            // Call filtered notifications promises, then pass on to
            // the completion function
            $q.all(promiseFilteredMessages)
              .then(filterMessagesSuccess)
              .catch(filterMessagesFailure);
          } else {
            // Separate all messages by their types
            $scope.messages = $filter('separateMessageTypes')(allMessages);
            // Change hasMessages so child controllers can pick up on it
            $scope.hasMessages = true;
          }
        };

        /**
         * Separate the message types in scope for child controllers
         * @param result
         */
        var filterMessagesSuccess = function(result) {
          // Check for filtered notifications
          var filteredMessages = [];
          if (result.filteredByGroup && result.filteredByData) {
            // Combine the two filtered arrays into one (no dupes)
            filteredMessages = $filter('filterForCommonElements')(
              result.filteredByGroup,
              result.filteredByData
            );
            $scope.messages = $filter('separateMessageTypes')(filteredMessages);
            $scope.hasMessages = true;
          }
        };

        /**
         * Handle errors that occur while resolving promises to
         * get notifications
         * @param error
         */
        var filterMessagesFailure = function(error) {
          $log.warn('Problem getting messages from messagesService');
        };

        /**
         * Get messages if messaging features are enabled
         */
        var init = function() {
          $scope.hasMessages = false;
          $scope.messages = {};

          if (!$rootScope.GuestMode) {
            getMessages();
          }
        };

        init();
      },
    ])

    .controller('NotificationsController', ['$q', '$log', '$scope',
      '$rootScope', '$location', '$localStorage', '$filter', 'MESSAGES',
      'SERVICE_LOC', 'miscService', 'messagesService',
      function($q, $log, $scope, $rootScope, $location, $localStorage, $filter,
               MESSAGES, SERVICE_LOC, miscService, messagesService) {
        // //////////////////
        // Local variables //
        // //////////////////
        var vm = this;
        var allNotifications = [];
        var separatedNotifications = {};
        var dismissedNotificationIds = [];

        // Promise to get seen message IDs
        var promiseSeenMessageIds = {
          seenMessageIds: messagesService.getSeenMessageIds(),
        };

        // ///////////////////
        // Bindable members //
        // ///////////////////
        vm.notifications = [];
        vm.dismissedNotifications = [];
        vm.priorityNotifications = [];
        vm.notificationsUrl = MESSAGES.notificationsPageURL;
        vm.status = 'View notifications';

        // //////////////////
        // Event listeners //
        // //////////////////
        /**
         * When the parent controller has messages, initialize
         * things dependent on messages
         */
        $scope.$watch('$parent.hasMessages', function(hasMessages) {
          // If the parent scope has messages and notifications are enabled,
          // complete initialization
          if (hasMessages) {
            configureNotificationsScope();
          }
        });

        // ////////////////
        // Local methods //
        // ////////////////
        /**
         * Get notifications from parent scope, then pass them on
         * for filtering by seen/unseen
         */
        var configureNotificationsScope = function() {
          if ($scope.$parent.messages.notifications) {
            allNotifications = $scope.$parent.messages.notifications;
            // Get seen message IDs, then configure scope
            $q.all(promiseSeenMessageIds)
              .then(getSeenMessageIdsSuccess)
              .catch(getSeenMessageIdsFailure);
          }
        };

        /**
         * Separate seen and unseen notifications, then set scope variables
         * @param result
         */
        var getSeenMessageIdsSuccess = function(result) {
          if (result.seenMessageIds && angular.isArray(result.seenMessageIds)
            && result.seenMessageIds.length > 0) {
            // Separate seen and unseen
            separatedNotifications = $filter('filterSeenAndUnseen')(
              allNotifications,
              result.seenMessageIds
            );
            // Set scope notifications and dismissed notifications
            vm.notifications = separatedNotifications.unseen;
            vm.dismissedNotifications = separatedNotifications.seen;

            // Set local dismissedNotificationIds (used for tracking
            // dismissed messages in the K/V store
            angular.forEach(vm.dismissedNotifications, function(value) {
              dismissedNotificationIds.push(value.id);
            });
          } else {
            // If there aren't any seen notification IDs, just set all
            // notifications
            vm.notifications = allNotifications;
          }

          // If user is anywhere other than the notifications page,
          // check for priority notifications and set them in scope
          if ($location.url().indexOf('notifications') === -1) {
            configurePriorityNotificationsScope();
          } else {
            clearPriorityNotificationsFlags();
          }

          // Set aria-label in notifications bell
          vm.status = 'You have '
            + (vm.notifications.length === 0
              ? 'no' : vm.notifications.length)
            + ' notifications';
        };

        /**
         * Handle errors getting seen message IDs
         * @param error
         */
        var getSeenMessageIdsFailure = function(error) {
          $log.warn('Couldn\'t get seen message IDs for notifications ' +
            ' controller.');
        };

        /**
         * Alert the UI to show priority notifications if they exist
         */
        var configurePriorityNotificationsScope = function() {
          // Use angular's built-in filter to grab priority notifications
          vm.priorityNotifications = $filter('filter')(
            vm.notifications,
            {priority: 'high'}
          );
          // If priority notifications exist and the view has a
          // headerController in scope (from directive), set necessary flags
          if ($scope.headerCtrl) {
            $scope.headerCtrl.hasPriorityNotifications
              = vm.priorityNotifications.length > 0;
          }
        };

        /**
         * Alerts the UI that there are no priority notifications to show
         * @param duringWatchedEvent
         */
        var clearPriorityNotificationsFlags = function(duringWatchedEvent) {
          vm.priorityNotifications = [];
          if ($scope.headerCtrl) {
            $scope.headerCtrl.hasPriorityNotifications
              = vm.priorityNotifications.length > 0;
          }
          if (!duringWatchedEvent) {
            $rootScope.$broadcast('portalShutdownPriorityNotifications',
              {disable: true});
          }
        };

        // ////////////////
        // Scope methods //
        // ////////////////
        /**
         * On-click event to mark a notification as "seen"
         * @param notification
         * @param isHighPriority
         */
        vm.dismissNotification = function(notification, isHighPriority) {
          vm.notifications = $filter('filterOutMessageWithId')(
            vm.notifications,
            notification.id
          );

          // Add notification to dismissed array
          vm.dismissedNotifications.push(notification);

          // Add notification's ID to local array of dismissed notification IDs
          dismissedNotificationIds.push(notification.id);

          // Call service to save changes if k/v store enabled
          if (SERVICE_LOC.kvURL) {
            messagesService.seMessagesSeen(dismissedNotificationIds);
          }

          // Clear priority notification flags if it was a priority
          // notification
          if (isHighPriority) {
            clearPriorityNotificationsFlags();
          }
        };

        /**
         * On-click event to mark a notification as "unseen"
         * @param notification
         */
        vm.restoreNotification = function(notification) {
          // Remove notification from dismissed array
          vm.dismissedNotifications = $filter('filterOutMessageWithId')(
            vm.dismissedNotifications,
            notification.id
          );

          // Add notification to non-dismissed array
          vm.notifications.push(notification);

          // Remove the corresponding entry from
          // local array of dismissed notification IDs
          var index = dismissedNotificationIds.indexOf(notification.id);
          if (index !== -1) {
            dismissedNotificationIds.splice(index, 1);
          }
          // Call service to save changes if k/v store enabled
          if (SERVICE_LOC.kvURL) {
            messagesService.setMessagesSeen(dismissedNotificationIds);
          }
        };

        /**
         * Track clicks on "Notifications" links in mobile menu and top bar
         * @param category
         * @param action
         * @param label
         */
        vm.pushGAEvent = function(category, action, label) {
          miscService.pushGAEvent(category, action, label);
        };
    }])

    .controller('AnnouncementsController', ['$q', '$log', '$filter',
      '$sessionStorage', '$scope', '$rootScope', '$document', '$sanitize',
      '$mdDialog', 'miscService', 'messagesService', 'MESSAGES',
      function($q, $log, $filter, $sessionStorage, $scope, $rootScope,
               $document, $sanitize, $mdDialog, miscService, messagesService,
               MESSAGES) {
        // //////////////////
        // Local variables //
        // //////////////////
        var vm = this;
        var allAnnouncements = [];
        var separatedAnnouncements = {};
        var seenAnnouncementIds = [];
        var popups = [];

        // Promise to get seen message IDs
        var promiseSeenMessageIds = {
          seenMessageIds: messagesService.getSeenMessageIds(),
        };

        // ///////////////////
        // Bindable members //
        // ///////////////////
        vm.hover = false;
        vm.active = false;
        vm.mode = $scope.mode;
        vm.announcements = [];

        // //////////////////
        // Event listeners //
        // //////////////////
        /**
         * When the parent controller has messages, initialize
         * things dependent on messages
         */
        $scope.$watch('$parent.hasMessages', function(hasMessages) {
          // If the parent scope has messages and announcements are enabled,
          // complete initialization
          if (hasMessages) {
            configureAnnouncementsScope();
          }
        });

        // ////////////////
        // Local methods //
        // ////////////////
        /**
         * Inherit announcements from parent controller messages
         */
        var configureAnnouncementsScope = function() {
          if ($scope.$parent.messages.announcements) {
            allAnnouncements = $scope.$parent.messages.announcements;
            // Get seen message IDs, then configure scope
            $q.all(promiseSeenMessageIds)
              .then(getSeenMessageIdsSuccess)
              .catch(getSeenMessageIdsFailure);
          }
        };

        /**
         * Separate seen and unseen, then set mascot image or get popups
         * depending on directive mode.
         * @param result Data returned by promises
         */
        var getSeenMessageIdsSuccess = function(result) {
          if (result.seenMessageIds && angular.isArray(result.seenMessageIds)
            && result.seenMessageIds.length > 0) {
            // Separate seen and unseen
            separatedAnnouncements = $filter('filterSeenAndUnseen')(
              allAnnouncements,
              result.seenMessageIds
            );
            // Set local seenAnnouncementsIds (used for tracking seen
            // messages in the K/V store and sessionStorage
            angular.forEach(separatedAnnouncements.seen, function(value) {
              seenAnnouncementIds.push(value.id);
            });
          } else {
            separatedAnnouncements = {
              seen: [],
              unseen: allAnnouncements,
            };
          }

          // If directive mode need mascot, set it, otherwise
          // configure popups
          if (vm.mode === 'mascot' || vm.mode === 'mobile-menu') {
            // Set scope announcements
            vm.announcements = separatedAnnouncements.unseen;
            // Set the mascot image
            setMascot();
          } else {
            // Filter out low priority announcements
            popups = $filter('filter')(
              separatedAnnouncements.unseen,
              {priority: 'high'}
            );
            configurePopups();
          }
        };

        /**
         * Handle errors encountered while resolving promises
         * @param error
         */
        var getSeenMessageIdsFailure = function(error) {
          // HANDLE ERRORS
        };

        /**
         * Get the latest popup announcement and display it
         */
        var configurePopups = function() {
          // If they exist, put them in order by date, then id
          if (popups.length != 0) {
            var orderedPopups = $filter('orderBy')(
              popups,
              ['goLiveDate', 'id']
            );

            // Set the latest announcement as a scope variable
            // so it can be passed to the dialog
            $scope.latestAnnouncement = orderedPopups[0];

            // Display the latest popup announcement
            var displayPopup = function() {
              $mdDialog.show({
                templateUrl:
                  'portal/messages/partials/announcement-popup-template.html',
                parent: angular.element(document).find('div.my-uw')[0],
                clickOutsideToClose: true,
                openFrom: 'left',
                closeTo: 'right',
                preserveScope: true,
                scope: $scope,
                controller: function DialogController($scope, $mdDialog) {
                  $scope.closeDialog = function(action) {
                    $mdDialog.hide(action);
                  };
                },
              })
              .then(function(action) {
                // If dialog is closed by clicking "continue" button
                miscService.pushGAEvent(
                  'popup',
                  action,
                  $scope.latestAnnouncement.id
                );
                seenAnnouncementIds.push($scope.latestAnnouncement.id);
                messagesService.setMessagesSeen(seenAnnouncementIds);
                return action;
              })
              .catch(function() {
                // If popup is closed by clicking outside or pressing escape
                miscService.pushGAEvent(
                  'popup', 'dismissed', $scope.latestAnnouncement.id);
                seenAnnouncementIds.push($scope.latestAnnouncement.id);
                messagesService.setMessagesSeen(seenAnnouncementIds);
              });
            };
            displayPopup();
          }
        };

        /**
         * Set the mascot image and its fallback
         */
        var setMascot = function() {
          if ($rootScope.portal && $rootScope.portal.theme) {
            vm.mascotImage =
              $rootScope.portal.theme.mascotImg || 'img/robot-taco.gif';
          } else {
            vm.mascotImage = 'img/robot-taco.gif';
          }
          // https://github.com/Gillespie59/eslint-plugin-angular/issues/231
          // eslint-disable-next-line angular/on-watch
          $rootScope.$watch('portal.theme', function(newVal, oldVal) {
            if (newVal !== oldVal) {
              vm.mascotImage = newVal.mascotImg || 'img/robot-taco.gif';
            }
          });
        };

        // ////////////////
        // Scope methods //
        // ////////////////
        /**
         * Remove dismissed announcement from scope announcements,
         * then update storage
         * @param id
         */
        vm.markSingleAnnouncementSeen = function(id) {
          // Use $filter to filter out by ID
          vm.announcements = $filter('filterOutMessageWithId')(
            vm.announcements,
            id
          );
          // Add to seenAnnouncementsIds
          seenAnnouncementIds.push(id);
          // Call service to save results
          messagesService.setMessagesSeen(seenAnnouncementIds);
        };

        /**
         * Add all IDs of unseen announcements to the seenAnnouncements
         * array, then call the messagesService to save results
         */
        vm.markAllAnnouncementsSeen = function() {
          angular.forEach(separatedAnnouncements.unseen, function(value) {
            seenAnnouncementIds.push(value.id);
          });
          messagesService.setMessagesSeen(seenAnnouncementIds);
        };

        /**
         * Make mascot bobble when hovered
         */
        vm.toggleHover = function() {
          vm.hover = vm.hover ? false : true;
        };

        /**
         * Make mascot appear when clicked
         */
        vm.toggleActive = function() {
          vm.active = !vm.active;
        };

        /**
         * Reset mascot when menu is closed
         */
        $scope.$on('$mdMenuClose', function() {
          vm.hover = false;
          vm.active = false;
        });
      }])

    .controller('FeaturesPageController', ['$scope', 'MISC_URLS',
      function($scope, MISC_URLS) {
        var vm = this;

        vm.announcements = [];
        vm.MISC_URLS = MISC_URLS;

        // //////////////////
        // Event listeners //
        // //////////////////
        /**
         * When the parent controller has messages, initialize
         * things dependent on messages
         */
        $scope.$watch('$parent.hasMessages', function(hasMessages) {
          // If the parent scope has messages and notifications are enabled,
          // complete initialization
          if (hasMessages) {
            if ($scope.$parent.messages.announcements) {
              vm.announcements = $scope.$parent.messages.announcements;
            }
          }
        });
    }]);
});
<commit_msg>Fix typo in function name when dismissing notifications<commit_after>'use strict';

define(['angular'], function(angular) {
  return angular.module('portal.messages.controllers', [])

    .controller('MessagesController', ['$q', '$log', '$scope', '$rootScope',
      '$location', '$localStorage', '$sessionStorage', '$filter', '$mdDialog',
      'MESSAGES', 'SERVICE_LOC', 'miscService', 'messagesService',
      function($q, $log, $scope, $rootScope, $location, $localStorage,
               $sessionStorage, $filter, $mdDialog, MESSAGES, SERVICE_LOC,
               miscService, messagesService) {
        // //////////////////
        // Local variables //
        // //////////////////
        var allMessages = [];
        var promiseFilteredMessages = {};

        // ////////////////
        // Local methods //
        // ////////////////
        /**
         * Get all messages, then pass result on for filtering
         */
        var getMessages = function() {
          messagesService.getAllMessages()
            .then(function(result) {
              // Ensure messages exist and check for group filtering
              if (result.messages && result.messages.length > 0) {
                allMessages = result.messages;
              }
              filterMessages();
              return allMessages;
            })
            .catch(function(error) {
              $log.warn('Problem getting all messages for messages controller');
              return error;
            });
        };

        /**
         * Determine whether or not messages need to be filtered
         * by group and data, then execute the relevant promises
         */
        var filterMessages = function() {
          // Check for group filtering on app level and beta settings level
          if (MESSAGES.groupFiltering
            && !$localStorage.disableGroupFilteringForMessages) {
            // Define promises to run if filtering is turned on
            promiseFilteredMessages = {
              filteredByGroup:
                messagesService.getMessagesByGroup(allMessages),
              filteredByData:
                messagesService.getMessagesByData(allMessages),
            };
            // Call filtered notifications promises, then pass on to
            // the completion function
            $q.all(promiseFilteredMessages)
              .then(filterMessagesSuccess)
              .catch(filterMessagesFailure);
          } else {
            // Separate all messages by their types
            $scope.messages = $filter('separateMessageTypes')(allMessages);
            // Change hasMessages so child controllers can pick up on it
            $scope.hasMessages = true;
          }
        };

        /**
         * Separate the message types in scope for child controllers
         * @param result
         */
        var filterMessagesSuccess = function(result) {
          // Check for filtered notifications
          var filteredMessages = [];
          if (result.filteredByGroup && result.filteredByData) {
            // Combine the two filtered arrays into one (no dupes)
            filteredMessages = $filter('filterForCommonElements')(
              result.filteredByGroup,
              result.filteredByData
            );
            $scope.messages = $filter('separateMessageTypes')(filteredMessages);
            $scope.hasMessages = true;
          }
        };

        /**
         * Handle errors that occur while resolving promises to
         * get notifications
         * @param error
         */
        var filterMessagesFailure = function(error) {
          $log.warn('Problem getting messages from messagesService');
        };

        /**
         * Get messages if messaging features are enabled
         */
        var init = function() {
          $scope.hasMessages = false;
          $scope.messages = {};

          if (!$rootScope.GuestMode) {
            getMessages();
          }
        };

        init();
      },
    ])

    .controller('NotificationsController', ['$q', '$log', '$scope',
      '$rootScope', '$location', '$localStorage', '$filter', 'MESSAGES',
      'SERVICE_LOC', 'miscService', 'messagesService',
      function($q, $log, $scope, $rootScope, $location, $localStorage, $filter,
               MESSAGES, SERVICE_LOC, miscService, messagesService) {
        // //////////////////
        // Local variables //
        // //////////////////
        var vm = this;
        var allNotifications = [];
        var separatedNotifications = {};
        var dismissedNotificationIds = [];

        // Promise to get seen message IDs
        var promiseSeenMessageIds = {
          seenMessageIds: messagesService.getSeenMessageIds(),
        };

        // ///////////////////
        // Bindable members //
        // ///////////////////
        vm.notifications = [];
        vm.dismissedNotifications = [];
        vm.priorityNotifications = [];
        vm.notificationsUrl = MESSAGES.notificationsPageURL;
        vm.status = 'View notifications';

        // //////////////////
        // Event listeners //
        // //////////////////
        /**
         * When the parent controller has messages, initialize
         * things dependent on messages
         */
        $scope.$watch('$parent.hasMessages', function(hasMessages) {
          // If the parent scope has messages and notifications are enabled,
          // complete initialization
          if (hasMessages) {
            configureNotificationsScope();
          }
        });

        // ////////////////
        // Local methods //
        // ////////////////
        /**
         * Get notifications from parent scope, then pass them on
         * for filtering by seen/unseen
         */
        var configureNotificationsScope = function() {
          if ($scope.$parent.messages.notifications) {
            allNotifications = $scope.$parent.messages.notifications;
            // Get seen message IDs, then configure scope
            $q.all(promiseSeenMessageIds)
              .then(getSeenMessageIdsSuccess)
              .catch(getSeenMessageIdsFailure);
          }
        };

        /**
         * Separate seen and unseen notifications, then set scope variables
         * @param result
         */
        var getSeenMessageIdsSuccess = function(result) {
          if (result.seenMessageIds && angular.isArray(result.seenMessageIds)
            && result.seenMessageIds.length > 0) {
            // Separate seen and unseen
            separatedNotifications = $filter('filterSeenAndUnseen')(
              allNotifications,
              result.seenMessageIds
            );
            // Set scope notifications and dismissed notifications
            vm.notifications = separatedNotifications.unseen;
            vm.dismissedNotifications = separatedNotifications.seen;

            // Set local dismissedNotificationIds (used for tracking
            // dismissed messages in the K/V store
            angular.forEach(vm.dismissedNotifications, function(value) {
              dismissedNotificationIds.push(value.id);
            });
          } else {
            // If there aren't any seen notification IDs, just set all
            // notifications
            vm.notifications = allNotifications;
          }

          // If user is anywhere other than the notifications page,
          // check for priority notifications and set them in scope
          if ($location.url().indexOf('notifications') === -1) {
            configurePriorityNotificationsScope();
          } else {
            clearPriorityNotificationsFlags();
          }

          // Set aria-label in notifications bell
          vm.status = 'You have '
            + (vm.notifications.length === 0
              ? 'no' : vm.notifications.length)
            + ' notifications';
        };

        /**
         * Handle errors getting seen message IDs
         * @param error
         */
        var getSeenMessageIdsFailure = function(error) {
          $log.warn('Couldn\'t get seen message IDs for notifications ' +
            ' controller.');
        };

        /**
         * Alert the UI to show priority notifications if they exist
         */
        var configurePriorityNotificationsScope = function() {
          // Use angular's built-in filter to grab priority notifications
          vm.priorityNotifications = $filter('filter')(
            vm.notifications,
            {priority: 'high'}
          );
          // If priority notifications exist and the view has a
          // headerController in scope (from directive), set necessary flags
          if ($scope.headerCtrl) {
            $scope.headerCtrl.hasPriorityNotifications
              = vm.priorityNotifications.length > 0;
          }
        };

        /**
         * Alerts the UI that there are no priority notifications to show
         * @param duringWatchedEvent
         */
        var clearPriorityNotificationsFlags = function(duringWatchedEvent) {
          vm.priorityNotifications = [];
          if ($scope.headerCtrl) {
            $scope.headerCtrl.hasPriorityNotifications
              = vm.priorityNotifications.length > 0;
          }
          if (!duringWatchedEvent) {
            $rootScope.$broadcast('portalShutdownPriorityNotifications',
              {disable: true});
          }
        };

        // ////////////////
        // Scope methods //
        // ////////////////
        /**
         * On-click event to mark a notification as "seen"
         * @param notification
         * @param isHighPriority
         */
        vm.dismissNotification = function(notification, isHighPriority) {
          vm.notifications = $filter('filterOutMessageWithId')(
            vm.notifications,
            notification.id
          );

          // Add notification to dismissed array
          vm.dismissedNotifications.push(notification);

          // Add notification's ID to local array of dismissed notification IDs
          dismissedNotificationIds.push(notification.id);

          // Call service to save changes if k/v store enabled
          if (SERVICE_LOC.kvURL) {
            messagesService.setMessagesSeen(dismissedNotificationIds);
          }

          // Clear priority notification flags if it was a priority
          // notification
          if (isHighPriority) {
            clearPriorityNotificationsFlags();
          }
        };

        /**
         * On-click event to mark a notification as "unseen"
         * @param notification
         */
        vm.restoreNotification = function(notification) {
          // Remove notification from dismissed array
          vm.dismissedNotifications = $filter('filterOutMessageWithId')(
            vm.dismissedNotifications,
            notification.id
          );

          // Add notification to non-dismissed array
          vm.notifications.push(notification);

          // Remove the corresponding entry from
          // local array of dismissed notification IDs
          var index = dismissedNotificationIds.indexOf(notification.id);
          if (index !== -1) {
            dismissedNotificationIds.splice(index, 1);
          }
          // Call service to save changes if k/v store enabled
          if (SERVICE_LOC.kvURL) {
            messagesService.setMessagesSeen(dismissedNotificationIds);
          }
        };

        /**
         * Track clicks on "Notifications" links in mobile menu and top bar
         * @param category
         * @param action
         * @param label
         */
        vm.pushGAEvent = function(category, action, label) {
          miscService.pushGAEvent(category, action, label);
        };
    }])

    .controller('AnnouncementsController', ['$q', '$log', '$filter',
      '$sessionStorage', '$scope', '$rootScope', '$document', '$sanitize',
      '$mdDialog', 'miscService', 'messagesService', 'MESSAGES',
      function($q, $log, $filter, $sessionStorage, $scope, $rootScope,
               $document, $sanitize, $mdDialog, miscService, messagesService,
               MESSAGES) {
        // //////////////////
        // Local variables //
        // //////////////////
        var vm = this;
        var allAnnouncements = [];
        var separatedAnnouncements = {};
        var seenAnnouncementIds = [];
        var popups = [];

        // Promise to get seen message IDs
        var promiseSeenMessageIds = {
          seenMessageIds: messagesService.getSeenMessageIds(),
        };

        // ///////////////////
        // Bindable members //
        // ///////////////////
        vm.hover = false;
        vm.active = false;
        vm.mode = $scope.mode;
        vm.announcements = [];

        // //////////////////
        // Event listeners //
        // //////////////////
        /**
         * When the parent controller has messages, initialize
         * things dependent on messages
         */
        $scope.$watch('$parent.hasMessages', function(hasMessages) {
          // If the parent scope has messages and announcements are enabled,
          // complete initialization
          if (hasMessages) {
            configureAnnouncementsScope();
          }
        });

        // ////////////////
        // Local methods //
        // ////////////////
        /**
         * Inherit announcements from parent controller messages
         */
        var configureAnnouncementsScope = function() {
          if ($scope.$parent.messages.announcements) {
            allAnnouncements = $scope.$parent.messages.announcements;
            // Get seen message IDs, then configure scope
            $q.all(promiseSeenMessageIds)
              .then(getSeenMessageIdsSuccess)
              .catch(getSeenMessageIdsFailure);
          }
        };

        /**
         * Separate seen and unseen, then set mascot image or get popups
         * depending on directive mode.
         * @param result Data returned by promises
         */
        var getSeenMessageIdsSuccess = function(result) {
          if (result.seenMessageIds && angular.isArray(result.seenMessageIds)
            && result.seenMessageIds.length > 0) {
            // Separate seen and unseen
            separatedAnnouncements = $filter('filterSeenAndUnseen')(
              allAnnouncements,
              result.seenMessageIds
            );
            // Set local seenAnnouncementsIds (used for tracking seen
            // messages in the K/V store and sessionStorage
            angular.forEach(separatedAnnouncements.seen, function(value) {
              seenAnnouncementIds.push(value.id);
            });
          } else {
            separatedAnnouncements = {
              seen: [],
              unseen: allAnnouncements,
            };
          }

          // If directive mode need mascot, set it, otherwise
          // configure popups
          if (vm.mode === 'mascot' || vm.mode === 'mobile-menu') {
            // Set scope announcements
            vm.announcements = separatedAnnouncements.unseen;
            // Set the mascot image
            setMascot();
          } else {
            // Filter out low priority announcements
            popups = $filter('filter')(
              separatedAnnouncements.unseen,
              {priority: 'high'}
            );
            configurePopups();
          }
        };

        /**
         * Handle errors encountered while resolving promises
         * @param error
         */
        var getSeenMessageIdsFailure = function(error) {
          // HANDLE ERRORS
        };

        /**
         * Get the latest popup announcement and display it
         */
        var configurePopups = function() {
          // If they exist, put them in order by date, then id
          if (popups.length != 0) {
            var orderedPopups = $filter('orderBy')(
              popups,
              ['goLiveDate', 'id']
            );

            // Set the latest announcement as a scope variable
            // so it can be passed to the dialog
            $scope.latestAnnouncement = orderedPopups[0];

            // Display the latest popup announcement
            var displayPopup = function() {
              $mdDialog.show({
                templateUrl:
                  'portal/messages/partials/announcement-popup-template.html',
                parent: angular.element(document).find('div.my-uw')[0],
                clickOutsideToClose: true,
                openFrom: 'left',
                closeTo: 'right',
                preserveScope: true,
                scope: $scope,
                controller: function DialogController($scope, $mdDialog) {
                  $scope.closeDialog = function(action) {
                    $mdDialog.hide(action);
                  };
                },
              })
              .then(function(action) {
                // If dialog is closed by clicking "continue" button
                miscService.pushGAEvent(
                  'popup',
                  action,
                  $scope.latestAnnouncement.id
                );
                seenAnnouncementIds.push($scope.latestAnnouncement.id);
                messagesService.setMessagesSeen(seenAnnouncementIds);
                return action;
              })
              .catch(function() {
                // If popup is closed by clicking outside or pressing escape
                miscService.pushGAEvent(
                  'popup', 'dismissed', $scope.latestAnnouncement.id);
                seenAnnouncementIds.push($scope.latestAnnouncement.id);
                messagesService.setMessagesSeen(seenAnnouncementIds);
              });
            };
            displayPopup();
          }
        };

        /**
         * Set the mascot image and its fallback
         */
        var setMascot = function() {
          if ($rootScope.portal && $rootScope.portal.theme) {
            vm.mascotImage =
              $rootScope.portal.theme.mascotImg || 'img/robot-taco.gif';
          } else {
            vm.mascotImage = 'img/robot-taco.gif';
          }
          // https://github.com/Gillespie59/eslint-plugin-angular/issues/231
          // eslint-disable-next-line angular/on-watch
          $rootScope.$watch('portal.theme', function(newVal, oldVal) {
            if (newVal !== oldVal) {
              vm.mascotImage = newVal.mascotImg || 'img/robot-taco.gif';
            }
          });
        };

        // ////////////////
        // Scope methods //
        // ////////////////
        /**
         * Remove dismissed announcement from scope announcements,
         * then update storage
         * @param id
         */
        vm.markSingleAnnouncementSeen = function(id) {
          // Use $filter to filter out by ID
          vm.announcements = $filter('filterOutMessageWithId')(
            vm.announcements,
            id
          );
          // Add to seenAnnouncementsIds
          seenAnnouncementIds.push(id);
          // Call service to save results
          messagesService.setMessagesSeen(seenAnnouncementIds);
        };

        /**
         * Add all IDs of unseen announcements to the seenAnnouncements
         * array, then call the messagesService to save results
         */
        vm.markAllAnnouncementsSeen = function() {
          angular.forEach(separatedAnnouncements.unseen, function(value) {
            seenAnnouncementIds.push(value.id);
          });
          messagesService.setMessagesSeen(seenAnnouncementIds);
        };

        /**
         * Make mascot bobble when hovered
         */
        vm.toggleHover = function() {
          vm.hover = vm.hover ? false : true;
        };

        /**
         * Make mascot appear when clicked
         */
        vm.toggleActive = function() {
          vm.active = !vm.active;
        };

        /**
         * Reset mascot when menu is closed
         */
        $scope.$on('$mdMenuClose', function() {
          vm.hover = false;
          vm.active = false;
        });
      }])

    .controller('FeaturesPageController', ['$scope', 'MISC_URLS',
      function($scope, MISC_URLS) {
        var vm = this;

        vm.announcements = [];
        vm.MISC_URLS = MISC_URLS;

        // //////////////////
        // Event listeners //
        // //////////////////
        /**
         * When the parent controller has messages, initialize
         * things dependent on messages
         */
        $scope.$watch('$parent.hasMessages', function(hasMessages) {
          // If the parent scope has messages and notifications are enabled,
          // complete initialization
          if (hasMessages) {
            if ($scope.$parent.messages.announcements) {
              vm.announcements = $scope.$parent.messages.announcements;
            }
          }
        });
    }]);
});
<|endoftext|>


<commit_before>
/*
 * grunt-qunit-amd
 * https://github.com/cedmax/grunt-qunit-amd
 *
 * Copyright (c) 2013 cedmax
 * Licensed under the MIT license.
 */

'use strict';

var exec = require('child_process').exec;
var phantombin = require('phantomjs');
var phantom = require('node-phantom');

module.exports = function(grunt) {
	var callback = require('../libs/runner.js'), util = grunt.util;

	grunt.registerMultiTask('qunit_amd', 'Your task description goes here.', function() {
		var done = this.async();

		if (util._.isFunction(this.data)) {
			this.data = this.data.apply(grunt, [].slice.call(arguments, 0));
		}

		phantom.create(callback(grunt, this, done) , {
			phantomPath:require('phantomjs').path,
			parameters: {
				'local-storage-path': '/dev/null'
			}
		});

	});
};
<commit_msg>set phantomjs web-security to false<commit_after>
/*
 * grunt-qunit-amd
 * https://github.com/cedmax/grunt-qunit-amd
 *
 * Copyright (c) 2013 cedmax
 * Licensed under the MIT license.
 */

'use strict';

var exec = require('child_process').exec;
var phantombin = require('phantomjs');
var phantom = require('node-phantom');

module.exports = function(grunt) {
	var callback = require('../libs/runner.js'), util = grunt.util;

	grunt.registerMultiTask('qunit_amd', 'Your task description goes here.', function() {
		var done = this.async();

		if (util._.isFunction(this.data)) {
			this.data = this.data.apply(grunt, [].slice.call(arguments, 0));
		}

		phantom.create(callback(grunt, this, done) , {
			phantomPath:require('phantomjs').path,
			parameters: {
				'local-storage-path': '/dev/null',
				'web-security': false
			}
		});

	});
};
<|endoftext|>


<commit_before>angular.module('app').controller('CommentController', ['$scope', '$http', function ($scope, $http) {
    $scope.comments = [
        {
            username: 'Ash',
            date: Date.now(),
            comment: 'Cool beans',
            link: '/google.com'
                    },
        {
            username: 'Ash',
            date: Date.now(),
            comment: 'Cool beans',
            link: 'html'
                    },
        {
            username: 'Ash',
            date: Date.now(),
            comment: 'Cool beans',
            link: 'html'
                    }
        ];
}]);<commit_msg>removes dummy comments<commit_after>angular.module('app').controller('CommentController', ['$scope', '$http', function ($scope, $http) {
}]);<|endoftext|>


<commit_before>/*
Copyright 2012 Google Inc.

Licensed 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.

Author: Renato Mangini (mangini@chromium.org)
*/

const DEFAULT_MAX_CONNECTIONS=5;

(function(exports) {

  // Define some local variables here.
  var socket = chrome.sockets.tcpServer;

  /**
   * Creates an instance of the client
   *
   * @param {String} host The remote host to connect to
   * @param {Number} port The port to connect to at the remote host
   */
  function TcpServer(addr, port, options) {
    this.addr = addr;
    this.port = port;
    this.maxConnections = typeof(options) != 'undefined'
        && options.maxConnections || DEFAULT_MAX_CONNECTIONS;

    this._onAccept = this._onAccept.bind(this);
    this._onAcceptError = this._onAcceptError.bind(this);

    // Callback functions.
    this.callbacks = {
      listen: null,    // Called when socket is connected.
      connect: null,    // Called when socket is connected.
      disconnect: null, // Called when socket is disconnected.
      recv: null,       // Called when client receives data from server.
      sent: null        // Called when client sends data to server.
    };

    // Sockets open
    this.openSockets=[];

    // server socket (one server connection, accepts and opens one socket per client)
    this.serverSocketId = null;

    log('initialized tcp server, not listening yet');
  }

  /**
   * Static method to return available network interfaces.
   *
   * @see https://developer.chrome.com/apps/system_network#method-getNetworkInterfaces
   *
   * @param {Function} callback The function to call with the available network
   * interfaces. The callback parameter is an array of
   * {name(string), address(string)} objects. Use the address property of the
   * preferred network as the addr parameter on TcpServer contructor.
   */
  TcpServer.getNetworkAddresses=function(callback) {
    chrome.system.network.getNetworkInterfaces(callback);
  }

  TcpServer.prototype.isConnected=function() {
    return this.serverSocketId > 0;
  }

  /**
   * Connects to the TCP socket, and creates an open socket.
   *
   * @see https://developer.chrome.com/apps/sockets_tcpServer#method-create
   * @param {Function} callback The function to call on connection
   */
  TcpServer.prototype.listen = function(callback) {
    // Register connect callback.
    this.callbacks.connect = callback;
    socket.create({}, this._onCreate.bind(this));
  };

  /**
   * Disconnects from the remote side
   *
   * @see https://developer.chrome.com/apps/sockets_tcpServer#method-disconnect
   */
  TcpServer.prototype.disconnect = function() {
    if (this.serverSocketId) {
      socket.onAccept.removeListener(this._onAccept);
      socket.onAcceptError.removeListener(this._onAcceptError);
      socket.close(this.serverSocketId);
    }
    for (var i=0; i<this.openSockets.length; i++) {
      try {
        this.openSockets[i].close();
      } catch (ex) {
        console.log(ex);
      }
    }
    this.openSockets=[];
    this.serverSocketId=0;
  };

  /**
   * The callback function used for when we attempt to have Chrome
   * create a socket. If the socket is successfully created
   * we go ahead and start listening for incoming connections.
   *
   * @private
   * @see https://developer.chrome.com/apps/sockets_tcpServer#method-listen
   * @param {Object} createInfo The socket details
   */
  TcpServer.prototype._onCreate = function(createInfo) {
    this.serverSocketId = createInfo.socketId;
    if (this.serverSocketId > 0) {
      socket.onAccept.addListener(this._onAccept);
      socket.onAcceptError.addListener(this._onAcceptError);
      socket.listen(this.serverSocketId, this.addr, this.port, 50,
        this._onListenComplete.bind(this));
      this.isListening = true;
    } else {
      error('Unable to create socket');
    }
  };

  /**
   * The callback function used for when we attempt to have Chrome
   * connect to the remote side. If a successful connection is
   * made then we accept it by opening it in a new socket (accept method)
   *
   * @private
   */
  TcpServer.prototype._onListenComplete = function(resultCode) {
    if (resultCode !==0) {
      error('Unable to listen to socket. Resultcode='+resultCode);
    }
  }

  TcpServer.prototype._onAccept = function (info) {
    if (info.socketId != this.serverSocketId)
      return;

    if (this.openSockets.length >= this.maxConnections) {
      this._onNoMoreConnectionsAvailable(info.clientSocketId);
      return;
    }

    var tcpConnection = new TcpConnection(info.clientSocketId);
    this.openSockets.push(tcpConnection);

    tcpConnection.requestSocketInfo(this._onSocketInfo.bind(this));
    log('Incoming connection handled.');
  }

  TcpServer.prototype._onAcceptError = function(info) {
    if (info.socketId != this.serverSocketId)
      return;

    error('Unable to accept incoming connection. Error code=' + info.resultCode);
  }

  TcpServer.prototype._onNoMoreConnectionsAvailable = function(socketId) {
    var msg="No more connections available. Try again later\n";
    _stringToArrayBuffer(msg, function(arrayBuffer) {
      chrome.sockets.tcp.send(socketId, arrayBuffer,
        function() {
          chrome.sockets.tcp.close(socketId);
        });
    });
  }

  TcpServer.prototype._onSocketInfo = function(tcpConnection, socketInfo) {
    if (this.callbacks.connect) {
      this.callbacks.connect(tcpConnection, socketInfo);
    }
  }

  /**
   * Holds a connection to a client
   *
   * @param {number} socketId The ID of the server<->client socket
   */
  function TcpConnection(socketId) {
    this.socketId = socketId;
    this.socketInfo = null;

    // Callback functions.
    this.callbacks = {
      disconnect: null, // Called when socket is disconnected.
      recv: null,       // Called when client receives data from server.
      sent: null        // Called when client sends data to server.
    };

    log('Established client connection. Listening...');

    // Start polling for reads.
    this._onReceive = this._onReceive.bind(this);
    this._onReceiveError = this._onReceiveError.bind(this);
    chrome.sockets.tcp.onReceive.addListener(this._onReceive);
    chrome.sockets.tcp.onReceiveError.addListener(this._onReceiveError);
    chrome.sockets.tcp.setPaused(this.socketId, false);
  };

  TcpConnection.prototype.setSocketInfo = function(socketInfo) {
    this.socketInfo = socketInfo;
  };

  TcpConnection.prototype.requestSocketInfo = function(callback) {
    chrome.sockets.tcp.getInfo(this.socketId,
      this._onSocketInfo.bind(this, callback));
  };

  /**
   * Sets the callback for when a message is received
   *
   * @param {Function} callback The function to call when a message has arrived
   */
  TcpConnection.prototype.addDataReceivedListener = function(callback) {
    // Register received callback.
    this.callbacks.recv = callback;
  };

  /**
   * Sends a message down the wire to the remote side
   *
   * @see https://developer.chrome.com/apps/sockets_tcp#method-send
   * @param {String} msg The message to send
   * @param {Function} callback The function to call when the message has sent
   */
  TcpConnection.prototype.sendMessage = function(msg, callback) {
    _stringToArrayBuffer(msg + '\n', function(arrayBuffer) {
      chrome.sockets.tcp.send(this.socketId, arrayBuffer, this._onWriteComplete.bind(this));
    }.bind(this));

    // Register sent callback.
    this.callbacks.sent = callback;
  };

  /**
   * Disconnects from the remote side
   *
   * @see https://developer.chrome.com/apps/sockets_tcp#method-close
   */
  TcpConnection.prototype.close = function() {
    if (this.socketId) {
      chrome.sockets.tcp.onReceive.removeListener(this._onReceive);
      chrome.sockets.tcp.onReceiveError.removeListener(this._onReceiveError);
      chrome.sockets.tcp.close(this.socketId);
    }
  };

  /**
   * Callback function for when socket details (socketInfo) is received.
   * Stores the socketInfo for future reference and pass it to the
   * callback sent in its parameter.
   *
   * @private
   */
  TcpConnection.prototype._onSocketInfo = function(callback, socketInfo) {
    if (callback && typeof(callback)!='function') {
      throw "Illegal value for callback: "+callback;
    }
    this.socketInfo = socketInfo;
    callback(this, socketInfo);
  }

  /**
   * Callback function for when data has been read from the socket.
   * Converts the array buffer that is read in to a string
   * and sends it on for further processing by passing it to
   * the previously assigned callback function.
   *
   * @private
   * @see TcpConnection.prototype.addDataReceivedListener
   * @param {Object} readInfo The incoming message
   */
  TcpConnection.prototype._onReceive = function(info) {
    if (this.socketId != info.socketId)
      return;

    // Call received callback if there's data in the response.
    if (this.callbacks.recv) {
      log('onDataRead');
      // Convert ArrayBuffer to string.
      _arrayBufferToString(info.data, this.callbacks.recv.bind(this));
    }
  };

  TcpConnection.prototype._onReceiveError = function (info) {
    if (this.socketId != info.socketId)
      return;
    this.close();
  };

  /**
   * Callback for when data has been successfully
   * written to the socket.
   *
   * @private
   * @param {Object} writeInfo The outgoing message
   */
  TcpConnection.prototype._onWriteComplete = function(writeInfo) {
    log('onWriteComplete');
    // Call sent callback.
    if (this.callbacks.sent) {
      this.callbacks.sent(writeInfo);
    }
  };

  /**
   * Converts an array buffer to a string
   *
   * @private
   * @param {ArrayBuffer} buf The buffer to convert
   * @param {Function} callback The function to call when conversion is complete
   */
  function _arrayBufferToString(buf, callback) {
    var bb = new Blob([new Uint8Array(buf)]);
    var f = new FileReader();
    f.onload = function(e) {
      callback(e.target.result);
    };
    f.readAsText(bb);
  }

  /**
   * Converts a string to an array buffer
   *
   * @private
   * @param {String} str The string to convert
   * @param {Function} callback The function to call when conversion is complete
   */
  function _stringToArrayBuffer(str, callback) {
    var bb = new Blob([str]);
    var f = new FileReader();
    f.onload = function(e) {
        callback(e.target.result);
    };
    f.readAsArrayBuffer(bb);
  }

  /**
   * Wrapper function for logging
   */
  function log(msg) {
    console.log(msg);
  }

  /**
   * Wrapper function for error logging
   */
  function error(msg) {
    console.error(msg);
  }

  exports.TcpServer = TcpServer;
  exports.TcpConnection = TcpConnection;

})(window);
<commit_msg>[tcpServer] Fix issue when data is sent early<commit_after>/*
Copyright 2012 Google Inc.

Licensed 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.

Author: Renato Mangini (mangini@chromium.org)
*/

const DEFAULT_MAX_CONNECTIONS=5;

(function(exports) {

  // Define some local variables here.
  var socket = chrome.sockets.tcpServer;

  /**
   * Creates an instance of the client
   *
   * @param {String} host The remote host to connect to
   * @param {Number} port The port to connect to at the remote host
   */
  function TcpServer(addr, port, options) {
    this.addr = addr;
    this.port = port;
    this.maxConnections = typeof(options) != 'undefined'
        && options.maxConnections || DEFAULT_MAX_CONNECTIONS;

    this._onAccept = this._onAccept.bind(this);
    this._onAcceptError = this._onAcceptError.bind(this);

    // Callback functions.
    this.callbacks = {
      listen: null,    // Called when socket is connected.
      connect: null,    // Called when socket is connected.
      disconnect: null, // Called when socket is disconnected.
      recv: null,       // Called when client receives data from server.
      sent: null        // Called when client sends data to server.
    };

    // Sockets open
    this.openSockets=[];

    // server socket (one server connection, accepts and opens one socket per client)
    this.serverSocketId = null;

    log('initialized tcp server, not listening yet');
  }

  /**
   * Static method to return available network interfaces.
   *
   * @see https://developer.chrome.com/apps/system_network#method-getNetworkInterfaces
   *
   * @param {Function} callback The function to call with the available network
   * interfaces. The callback parameter is an array of
   * {name(string), address(string)} objects. Use the address property of the
   * preferred network as the addr parameter on TcpServer contructor.
   */
  TcpServer.getNetworkAddresses=function(callback) {
    chrome.system.network.getNetworkInterfaces(callback);
  }

  TcpServer.prototype.isConnected=function() {
    return this.serverSocketId > 0;
  }

  /**
   * Connects to the TCP socket, and creates an open socket.
   *
   * @see https://developer.chrome.com/apps/sockets_tcpServer#method-create
   * @param {Function} callback The function to call on connection
   */
  TcpServer.prototype.listen = function(callback) {
    // Register connect callback.
    this.callbacks.connect = callback;
    socket.create({}, this._onCreate.bind(this));
  };

  /**
   * Disconnects from the remote side
   *
   * @see https://developer.chrome.com/apps/sockets_tcpServer#method-disconnect
   */
  TcpServer.prototype.disconnect = function() {
    if (this.serverSocketId) {
      socket.onAccept.removeListener(this._onAccept);
      socket.onAcceptError.removeListener(this._onAcceptError);
      socket.close(this.serverSocketId);
    }
    for (var i=0; i<this.openSockets.length; i++) {
      try {
        this.openSockets[i].close();
      } catch (ex) {
        console.log(ex);
      }
    }
    this.openSockets=[];
    this.serverSocketId=0;
  };

  /**
   * The callback function used for when we attempt to have Chrome
   * create a socket. If the socket is successfully created
   * we go ahead and start listening for incoming connections.
   *
   * @private
   * @see https://developer.chrome.com/apps/sockets_tcpServer#method-listen
   * @param {Object} createInfo The socket details
   */
  TcpServer.prototype._onCreate = function(createInfo) {
    this.serverSocketId = createInfo.socketId;
    if (this.serverSocketId > 0) {
      socket.onAccept.addListener(this._onAccept);
      socket.onAcceptError.addListener(this._onAcceptError);
      socket.listen(this.serverSocketId, this.addr, this.port, 50,
        this._onListenComplete.bind(this));
      this.isListening = true;
    } else {
      error('Unable to create socket');
    }
  };

  /**
   * The callback function used for when we attempt to have Chrome
   * connect to the remote side. If a successful connection is
   * made then we accept it by opening it in a new socket (accept method)
   *
   * @private
   */
  TcpServer.prototype._onListenComplete = function(resultCode) {
    if (resultCode !==0) {
      error('Unable to listen to socket. Resultcode='+resultCode);
    }
  }

  TcpServer.prototype._onAccept = function (info) {
    if (info.socketId != this.serverSocketId)
      return;

    if (this.openSockets.length >= this.maxConnections) {
      this._onNoMoreConnectionsAvailable(info.clientSocketId);
      return;
    }

    var tcpConnection = new TcpConnection(info.clientSocketId);
    this.openSockets.push(tcpConnection);

    tcpConnection.requestSocketInfo(this._onSocketInfo.bind(this));
    log('Incoming connection handled.');
  }

  TcpServer.prototype._onAcceptError = function(info) {
    if (info.socketId != this.serverSocketId)
      return;

    error('Unable to accept incoming connection. Error code=' + info.resultCode);
  }

  TcpServer.prototype._onNoMoreConnectionsAvailable = function(socketId) {
    var msg="No more connections available. Try again later\n";
    _stringToArrayBuffer(msg, function(arrayBuffer) {
      chrome.sockets.tcp.send(socketId, arrayBuffer,
        function() {
          chrome.sockets.tcp.close(socketId);
        });
    });
  }

  TcpServer.prototype._onSocketInfo = function(tcpConnection, socketInfo) {
    if (this.callbacks.connect) {
      this.callbacks.connect(tcpConnection, socketInfo);
    }
  }

  /**
   * Holds a connection to a client
   *
   * @param {number} socketId The ID of the server<->client socket
   */
  function TcpConnection(socketId) {
    this.socketId = socketId;
    this.socketInfo = null;

    // Callback functions.
    this.callbacks = {
      disconnect: null, // Called when socket is disconnected.
      recv: null,       // Called when client receives data from server.
      sent: null        // Called when client sends data to server.
    };

    log('Established client connection. Listening...');

  };

  TcpConnection.prototype.setSocketInfo = function(socketInfo) {
    this.socketInfo = socketInfo;
  };

  TcpConnection.prototype.requestSocketInfo = function(callback) {
    chrome.sockets.tcp.getInfo(this.socketId,
      this._onSocketInfo.bind(this, callback));
  };

  /**
   * Add receive listeners for when a message is received
   *
   * @param {Function} callback The function to call when a message has arrived
   */
  TcpConnection.prototype.startListening = function(callback) {
    this.callbacks.recv = callback;

    // Add receive listeners.
    this._onReceive = this._onReceive.bind(this);
    this._onReceiveError = this._onReceiveError.bind(this);
    chrome.sockets.tcp.onReceive.addListener(this._onReceive);
    chrome.sockets.tcp.onReceiveError.addListener(this._onReceiveError);

    chrome.sockets.tcp.setPaused(this.socketId, false);
  };

  /**
   * Sets the callback for when a message is received
   *
   * @param {Function} callback The function to call when a message has arrived
   */
  TcpConnection.prototype.addDataReceivedListener = function(callback) {
    // If this is the first time a callback is set, start listening for incoming data.
    if (!this.callbacks.recv) {
      this._startListening(callback);
    } else {
      this.callbacks.recv = callback;
    }
  };

  /**
   * Sends a message down the wire to the remote side
   *
   * @see https://developer.chrome.com/apps/sockets_tcp#method-send
   * @param {String} msg The message to send
   * @param {Function} callback The function to call when the message has sent
   */
  TcpConnection.prototype.sendMessage = function(msg, callback) {
    _stringToArrayBuffer(msg + '\n', function(arrayBuffer) {
      chrome.sockets.tcp.send(this.socketId, arrayBuffer, this._onWriteComplete.bind(this));
    }.bind(this));

    // Register sent callback.
    this.callbacks.sent = callback;
  };

  /**
   * Disconnects from the remote side
   *
   * @see https://developer.chrome.com/apps/sockets_tcp#method-close
   */
  TcpConnection.prototype.close = function() {
    if (this.socketId) {
      chrome.sockets.tcp.onReceive.removeListener(this._onReceive);
      chrome.sockets.tcp.onReceiveError.removeListener(this._onReceiveError);
      chrome.sockets.tcp.close(this.socketId);
    }
  };

  /**
   * Callback function for when socket details (socketInfo) is received.
   * Stores the socketInfo for future reference and pass it to the
   * callback sent in its parameter.
   *
   * @private
   */
  TcpConnection.prototype._onSocketInfo = function(callback, socketInfo) {
    if (callback && typeof(callback)!='function') {
      throw "Illegal value for callback: "+callback;
    }
    this.socketInfo = socketInfo;
    callback(this, socketInfo);
  }

  /**
   * Callback function for when data has been read from the socket.
   * Converts the array buffer that is read in to a string
   * and sends it on for further processing by passing it to
   * the previously assigned callback function.
   *
   * @private
   * @see TcpConnection.prototype.addDataReceivedListener
   * @param {Object} readInfo The incoming message
   */
  TcpConnection.prototype._onReceive = function(info) {
    if (this.socketId != info.socketId)
      return;

    // Call received callback if there's data in the response.
    if (this.callbacks.recv) {
      log('onDataRead');
      // Convert ArrayBuffer to string.
      _arrayBufferToString(info.data, this.callbacks.recv.bind(this));
    }
  };

  TcpConnection.prototype._onReceiveError = function (info) {
    if (this.socketId != info.socketId)
      return;
    this.close();
  };

  /**
   * Callback for when data has been successfully
   * written to the socket.
   *
   * @private
   * @param {Object} writeInfo The outgoing message
   */
  TcpConnection.prototype._onWriteComplete = function(writeInfo) {
    log('onWriteComplete');
    // Call sent callback.
    if (this.callbacks.sent) {
      this.callbacks.sent(writeInfo);
    }
  };

  /**
   * Converts an array buffer to a string
   *
   * @private
   * @param {ArrayBuffer} buf The buffer to convert
   * @param {Function} callback The function to call when conversion is complete
   */
  function _arrayBufferToString(buf, callback) {
    var bb = new Blob([new Uint8Array(buf)]);
    var f = new FileReader();
    f.onload = function(e) {
      callback(e.target.result);
    };
    f.readAsText(bb);
  }

  /**
   * Converts a string to an array buffer
   *
   * @private
   * @param {String} str The string to convert
   * @param {Function} callback The function to call when conversion is complete
   */
  function _stringToArrayBuffer(str, callback) {
    var bb = new Blob([str]);
    var f = new FileReader();
    f.onload = function(e) {
        callback(e.target.result);
    };
    f.readAsArrayBuffer(bb);
  }

  /**
   * Wrapper function for logging
   */
  function log(msg) {
    console.log(msg);
  }

  /**
   * Wrapper function for error logging
   */
  function error(msg) {
    console.error(msg);
  }

  exports.TcpServer = TcpServer;
  exports.TcpConnection = TcpConnection;

})(window);
<|endoftext|>


<commit_before>var cells = []

function setup() {
  createCanvas(windowWidth,windowHeight)
  background(51)

  cells.push(new Cell())
  cells[0].render()
}
<commit_msg>Added mousePressed function in which i check which cell was clicked, and rerender function which rerenders all the cells<commit_after>var cells = []

function setup() {
  createCanvas(windowWidth,windowHeight)

  cells.push(new Cell())
  rerender()
}

function mousePressed() {
  mousePosition = createVector(mouseX,mouseY)

  for (var i = 0; i < cells.length; i++) {
    if (p5.Vector.dist(cells[i].location, mousePosition) <= cells[i].diameter / 2) {
      console.log('pressed')
      //rerender()
    }
  }
}

function rerender() {
  background(51)

  for (var i = 0; i < cells.length; i++) {
    cells[i].render()
  }
}
<|endoftext|>


<commit_before>'use strict';
const yeoman = require('yeoman-generator');
const slug = require('slug');
const normalizeUrl = require('normalize-url');
const humanizeUrl = require('humanize-url');

let _props = {};

module.exports = yeoman.Base.extend({
  prompting() {
    const prompts = [{
      name: 'pluginName',
      message: 'What do you want to name your plugin?'
    }, {
      name: 'pluginDescription',
      message: 'What does your plugin do?'
    }, {
      name: 'pluginWebsite',
      message: 'What is the URL of the plugin\'s website?',
      validate: x => x.length > 0 ? true : 'You have to provide a website URL',
      filter: x => normalizeUrl(x)
    }, {
      name: 'authorName',
      message: 'What is your name?',
      store: true,
      validate: x => x.length > 0 ? true : 'You have to provide a name'
    }, {
      name: 'authorEmail',
      message: 'What is your email?',
      store: true,
      validate: x => x.length > 0 ? true : 'You have to provide an email'
    }, {
      name: 'authorWebsite',
      message: 'What is the URL of your website?',
      store: true,
      validate: x => x.length > 0 ? true : 'You have to provide a website URL',
      filter: x => normalizeUrl(x)
    }, {
      name: 'hasAdmin',
      message: 'Do you need admin-specific functionality?',
      type: 'confirm',
      default: false
    }];

    return this.prompt(prompts)
      .then(props => {
        _props.plugin = {
          name: {
            human: props.pluginName,
            fileName: slug(props.pluginName, {
              lower: true
            }),
            className: slug(props.pluginName, {
              lower: false,
              replacement: '_'
            }),
            instanceName: slug(props.pluginName, {
              lower: true,
              replacement: '_'
            })
          },
          website: humanizeUrl(props.pluginWebsite),
          hasAdmin: props.hasAdmin
        };

        _props.author = {
          name: props.authorName,
          email: props.authorEmail,
          website: humanizeUrl(props.authorWebsite)
        };
      });
  },

  writing() {
    this.fs.copyTpl(this.templatePath(), this.destinationPath(), _props);
  }
});
<commit_msg>add description prop to _props object<commit_after>'use strict';
const yeoman = require('yeoman-generator');
const slug = require('slug');
const normalizeUrl = require('normalize-url');
const humanizeUrl = require('humanize-url');

let _props = {};

module.exports = yeoman.Base.extend({
  prompting() {
    const prompts = [{
      name: 'pluginName',
      message: 'What do you want to name your plugin?'
    }, {
      name: 'pluginDescription',
      message: 'What does your plugin do?'
    }, {
      name: 'pluginWebsite',
      message: 'What is the URL of the plugin\'s website?',
      validate: x => x.length > 0 ? true : 'You have to provide a website URL',
      filter: x => normalizeUrl(x)
    }, {
      name: 'authorName',
      message: 'What is your name?',
      store: true,
      validate: x => x.length > 0 ? true : 'You have to provide a name'
    }, {
      name: 'authorEmail',
      message: 'What is your email?',
      store: true,
      validate: x => x.length > 0 ? true : 'You have to provide an email'
    }, {
      name: 'authorWebsite',
      message: 'What is the URL of your website?',
      store: true,
      validate: x => x.length > 0 ? true : 'You have to provide a website URL',
      filter: x => normalizeUrl(x)
    }, {
      name: 'hasAdmin',
      message: 'Do you need admin-specific functionality?',
      type: 'confirm',
      default: false
    }];

    return this.prompt(prompts)
      .then(props => {
        _props.plugin = {
          name: {
            human: props.pluginName,
            fileName: slug(props.pluginName, {
              lower: true
            }),
            className: slug(props.pluginName, {
              lower: false,
              replacement: '_'
            }),
            instanceName: slug(props.pluginName, {
              lower: true,
              replacement: '_'
            })
          },
          description: props.pluginDescription,
          website: humanizeUrl(props.pluginWebsite),
          hasAdmin: props.hasAdmin
        };

        _props.author = {
          name: props.authorName,
          email: props.authorEmail,
          website: humanizeUrl(props.authorWebsite)
        };
      });
  },

  writing() {
    this.fs.copyTpl(this.templatePath(), this.destinationPath(), _props);
  }
});
<|endoftext|>


<commit_before>define(['common/runtime', 'widgets/appWidgets2/input/selectInput'], (Runtime, SelectInput) => {
    'use strict';
    let bus, testConfig, runtime, container;
    const required = false,
        defaultValue = 'apple';

    function buildTestConfig(_required, _defaultValue, _bus) {
        return {
            bus: _bus,
            parameterSpec: {
                data: {
                    defaultValue: _defaultValue,
                    nullValue: '',
                    constraints: {
                        required: _required,
                        defaultValue: _defaultValue,
                        options: [
                            {
                                value: 'apple',
                                display: 'Apple',
                            },
                            {
                                value: 'banana',
                                display: 'Banana',
                            },
                            {
                                value: 'carrot',
                                display: 'Carrot',
                            },
                        ],
                    },
                },
            },
            channelName: _bus.channelName,
            initialValue: 'apple',
        };
    }

    describe('Select Input tests', () => {
        beforeEach(() => {
            runtime = Runtime.make();
            container = document.createElement('div');
            bus = runtime.bus().makeChannelBus({
                description: 'select input testing',
            });
            testConfig = buildTestConfig(required, defaultValue, bus);
        });

        afterEach(() => {
            bus.stop();
            window.kbaseRuntime = null;
            container.remove();
        });

        it('should be defined', () => {
            expect(SelectInput).not.toBeNull();
        });

        it('should be instantiable', () => {
            const widget = SelectInput.make(testConfig);
            expect(widget).toEqual(jasmine.any(Object));
            ['start', 'stop'].forEach((fn) => {
                expect(widget[fn]).toEqual(jasmine.any(Function));
            });
        });

        it('Should start and stop a widget', () => {
            const widget = SelectInput.make(testConfig);

            return widget
                .start({ node: container })
                .then(() => {
                    // verify it's there.
                    const inputElem = container.querySelector('select[data-element="input"]');
                    expect(inputElem).toBeDefined();
                    return widget.stop();
                })
                .then(() => {
                    // verify it's gone.
                    expect(container.childElementCount).toBe(0);
                });
        });

        it('Should update value via bus', () => {
            //and reset model properly via bus', (done) => {
            // start with one value, change it, then reset.
            // check along the way.
            const widget = SelectInput.make(testConfig);
            return widget.start({ node: container }).then(() => {
                return new Promise((resolve) => {
                    bus.on('validation', (message) => {
                        expect(message.isValid).toBeTruthy();
                        resolve();
                    });
                    bus.emit('update', { value: 'banana' });
                });
            });
        });

        it('Should reset to default via bus', () => {
            const widget = SelectInput.make(testConfig);
            return widget.start({ node: container }).then(() => {
                return new Promise((resolve) => {
                    bus.on('validation', (message) => {
                        expect(message.isValid).toBeTruthy();
                        resolve();
                    });
                    bus.emit('reset-to-defaults');
                });
            });
        });

        it('Should respond to input change events with "changed"', () => {
            const widget = SelectInput.make(testConfig);
            return widget.start({ node: container }).then(() => {
                return new Promise((resolve) => {
                    bus.on('changed', (message) => {
                        expect(message.newValue).toEqual('banana');
                        resolve();
                    });
                    const inputElem = container.querySelector('select[data-element="input"]');
                    inputElem.selectedIndex = 1;
                    inputElem.dispatchEvent(new Event('change'));
                });
            });
        });

        it('Should respond to input change events with "validation"', () => {
            const widget = SelectInput.make(testConfig);
            return widget.start({ node: container }).then(() => {
                return new Promise((resolve) => {
                    bus.on('validation', (message) => {
                        expect(message.isValid).toBeTruthy();
                        expect(message.errorMessage).toBeUndefined();
                        resolve();
                    });
                    const inputElem = container.querySelector('select[data-element="input"]');
                    inputElem.selectedIndex = 1;
                    inputElem.dispatchEvent(new Event('change'));
                });
            });
        });

        it('Should show message when configured', () => {
            testConfig.showOwnMessages = true;
            const widget = SelectInput.make(testConfig);
            return widget.start({ node: container }).then(() => {
                return new Promise((resolve) => {
                    bus.on('validation', (message) => {
                        expect(message.isValid).toBeTruthy();
                        // ...detect something?
                        resolve();
                    });
                    const inputElem = container.querySelector('select[data-element="input"]');
                    inputElem.selectedIndex = 1;
                    inputElem.dispatchEvent(new Event('change'));
                });
            });
        });

        it('Should return a diagnosis of required-missing if so', () => {
            bus = runtime.bus().makeChannelBus();
            testConfig = buildTestConfig(true, '', bus);
            const widget = SelectInput.make(testConfig);
            return widget.start({ node: container }).then(() => {
                return new Promise((resolve, reject) => {
                    let msgCount = 0,
                        okCount = 0;
                    bus.on('validation', (message) => {
                        msgCount++;
                        if (message.isValid) {
                            okCount++;
                        }
                        if (msgCount === 2) {
                            if (okCount > 1) {
                                reject('too many ok messages');
                            }
                        }
                        if (!message.isValid) {
                            expect(message.diagnosis).toBe('required-missing');
                            resolve();
                        }
                    });
                    const inputElem = container.querySelector('select[data-element="input"]');
                    inputElem.selectedIndex = -1;
                    inputElem.dispatchEvent(new Event('change'));
                });
            });
        });

        it('Should take a list of disabledValues on startup', () => {
            const config = Object.assign({}, testConfig, { disabledValues: ['carrot'] });
            const widget = SelectInput.make(config);

            return widget.start({ node: container }).then(() => {
                // verify it's there.
                const inputElem = container.querySelector('select[data-element="input"]');
                const carrotItem = inputElem.querySelector('option[value="carrot"]');
                expect(carrotItem.hasAttribute('disabled')).toBeTrue();
                const bananaItem = inputElem.querySelector('option[value="banana"]');
                expect(bananaItem.hasAttribute('disabled')).toBeFalse();
            });
        });

        xit('Should obey a message to disable selection options', () => {});

        xit('Should obey a message to enable selection options', () => {});

        it('Should take a set of options that override the options from the parameter spec', () => {
            const values = [
                {
                    display: 'Dirigible',
                    value: 'dirigible',
                },
                {
                    display: 'Elephant',
                    value: 'elephant',
                },
                {
                    display: 'Frittata',
                    value: 'frittata',
                },
            ];
            const config = Object.assign({}, testConfig, {
                availableValues: values,
                initialValue: 'elephant',
            });
            const widget = SelectInput.make(config);
            return widget.start({ node: container }).then(() => {
                const inputElem = container.querySelector('select[data-element="input"]');
                expect(inputElem.value).toEqual('elephant');
                expect(inputElem.childElementCount).toBe(3);
                for (const child of inputElem.children) {
                    expect(['dirigible', 'elephant', 'frittata'].includes(child.value)).toBeTrue();
                }
            });
        });
    });
});
<commit_msg>add more tests<commit_after>define(['common/runtime', 'widgets/appWidgets2/input/selectInput', 'testUtil'], (
    Runtime,
    SelectInput,
    TestUtil
) => {
    'use strict';
    let bus, testConfig, runtime, container;
    const required = false,
        defaultValue = 'apple';

    function buildTestConfig(_required, _defaultValue, _bus) {
        return {
            bus: _bus,
            parameterSpec: {
                data: {
                    defaultValue: _defaultValue,
                    nullValue: '',
                    constraints: {
                        required: _required,
                        defaultValue: _defaultValue,
                        options: [
                            {
                                value: 'apple',
                                display: 'Apple',
                            },
                            {
                                value: 'banana',
                                display: 'Banana',
                            },
                            {
                                value: 'carrot',
                                display: 'Carrot',
                            },
                        ],
                    },
                },
            },
            channelName: _bus.channelName,
            initialValue: 'apple',
        };
    }

    describe('Select Input tests', () => {
        beforeEach(() => {
            runtime = Runtime.make();
            container = document.createElement('div');
            bus = runtime.bus().makeChannelBus({
                description: 'select input testing',
            });
            testConfig = buildTestConfig(required, defaultValue, bus);
        });

        afterEach(() => {
            bus.stop();
            window.kbaseRuntime = null;
            container.remove();
        });

        it('should be defined', () => {
            expect(SelectInput).not.toBeNull();
        });

        it('should be instantiable', () => {
            const widget = SelectInput.make(testConfig);
            expect(widget).toEqual(jasmine.any(Object));
            ['start', 'stop'].forEach((fn) => {
                expect(widget[fn]).toEqual(jasmine.any(Function));
            });
        });

        it('Should start and stop a widget', () => {
            const widget = SelectInput.make(testConfig);

            return widget
                .start({ node: container })
                .then(() => {
                    // verify it's there.
                    const inputElem = container.querySelector('select[data-element="input"]');
                    expect(inputElem).toBeDefined();
                    return widget.stop();
                })
                .then(() => {
                    // verify it's gone.
                    expect(container.childElementCount).toBe(0);
                });
        });

        it('Should update value via bus', () => {
            //and reset model properly via bus', (done) => {
            // start with one value, change it, then reset.
            // check along the way.
            const widget = SelectInput.make(testConfig);
            return widget.start({ node: container }).then(() => {
                return new Promise((resolve) => {
                    bus.on('validation', (message) => {
                        expect(message.isValid).toBeTruthy();
                        resolve();
                    });
                    bus.emit('update', { value: 'banana' });
                });
            });
        });

        it('Should reset to default via bus', () => {
            const widget = SelectInput.make(testConfig);
            return widget.start({ node: container }).then(() => {
                return new Promise((resolve) => {
                    bus.on('validation', (message) => {
                        expect(message.isValid).toBeTruthy();
                        resolve();
                    });
                    bus.emit('reset-to-defaults');
                });
            });
        });

        it('Should respond to input change events with "changed"', () => {
            const widget = SelectInput.make(testConfig);
            return widget.start({ node: container }).then(() => {
                return new Promise((resolve) => {
                    bus.on('changed', (message) => {
                        expect(message.newValue).toEqual('banana');
                        resolve();
                    });
                    const inputElem = container.querySelector('select[data-element="input"]');
                    inputElem.selectedIndex = 1;
                    inputElem.dispatchEvent(new Event('change'));
                });
            });
        });

        it('Should respond to input change events with "validation"', () => {
            const widget = SelectInput.make(testConfig);
            return widget.start({ node: container }).then(() => {
                return new Promise((resolve) => {
                    bus.on('validation', (message) => {
                        expect(message.isValid).toBeTruthy();
                        expect(message.errorMessage).toBeUndefined();
                        resolve();
                    });
                    const inputElem = container.querySelector('select[data-element="input"]');
                    inputElem.selectedIndex = 1;
                    inputElem.dispatchEvent(new Event('change'));
                });
            });
        });

        it('Should show message when configured', () => {
            testConfig.showOwnMessages = true;
            const widget = SelectInput.make(testConfig);
            return widget.start({ node: container }).then(() => {
                return new Promise((resolve) => {
                    bus.on('validation', (message) => {
                        expect(message.isValid).toBeTruthy();
                        // ...detect something?
                        resolve();
                    });
                    const inputElem = container.querySelector('select[data-element="input"]');
                    inputElem.selectedIndex = 1;
                    inputElem.dispatchEvent(new Event('change'));
                });
            });
        });

        it('Should return a diagnosis of required-missing if so', () => {
            bus = runtime.bus().makeChannelBus();
            testConfig = buildTestConfig(true, '', bus);
            const widget = SelectInput.make(testConfig);
            return widget.start({ node: container }).then(() => {
                return new Promise((resolve, reject) => {
                    let msgCount = 0,
                        okCount = 0;
                    bus.on('validation', (message) => {
                        msgCount++;
                        if (message.isValid) {
                            okCount++;
                        }
                        if (msgCount === 2) {
                            if (okCount > 1) {
                                reject('too many ok messages');
                            }
                        }
                        if (!message.isValid) {
                            expect(message.diagnosis).toBe('required-missing');
                            resolve();
                        }
                    });
                    const inputElem = container.querySelector('select[data-element="input"]');
                    inputElem.selectedIndex = -1;
                    inputElem.dispatchEvent(new Event('change'));
                });
            });
        });

        it('Should take a list of disabledValues on startup', () => {
            const config = Object.assign({}, testConfig, { disabledValues: ['carrot'] });
            const widget = SelectInput.make(config);

            return widget.start({ node: container }).then(() => {
                // verify it's there.
                const inputElem = container.querySelector('select[data-element="input"]');
                const carrotItem = inputElem.querySelector('option[value="carrot"]');
                expect(carrotItem.hasAttribute('disabled')).toBeTrue();
                const bananaItem = inputElem.querySelector('option[value="banana"]');
                expect(bananaItem.hasAttribute('disabled')).toBeFalse();
            });
        });

        function checkItems(expectedStates, inputElem) {
            Object.keys(expectedStates).forEach((item) => {
                const elem = inputElem.querySelector(`option[value="${item}"]`);
                expect(elem.hasAttribute('disabled')).toBe(expectedStates[item]);
            });
        }

        it('Should obey a message to disable selection options', async () => {
            const widget = SelectInput.make(testConfig);

            await widget.start({ node: container });
            // verify it's there and the initial item states are all enabled (i.e. not disabled)
            const itemsDisabled = {
                apple: false,
                banana: false,
                carrot: false,
            };
            const inputElem = container.querySelector('select[data-element="input"]');
            checkItems(itemsDisabled, inputElem);

            const carrotItem = inputElem.querySelector('option[value="carrot"]');
            await TestUtil.waitForElementChange(carrotItem, () => {
                bus.emit('disable-values', {
                    values: ['carrot'],
                });
            });
            itemsDisabled.carrot = true;
            checkItems(itemsDisabled, inputElem);
        });

        it('Should obey a message to enable selection options', async () => {
            const config = Object.assign({}, testConfig, { disabledValues: ['carrot'] });
            const widget = SelectInput.make(config);

            await widget.start({ node: container });

            const itemsDisabled = {
                apple: false,
                banana: false,
                carrot: true,
            };

            const inputElem = container.querySelector('select[data-element="input"]');
            checkItems(itemsDisabled, inputElem);

            const carrotItem = inputElem.querySelector('option[value="carrot"]');
            await TestUtil.waitForElementChange(carrotItem, () => {
                bus.emit('enable-values', {
                    values: ['carrot'],
                });
            });
            itemsDisabled.carrot = false;
            checkItems(itemsDisabled, inputElem);
        });

        it('Should take a set of options that override the options from the parameter spec', () => {
            const values = [
                {
                    display: 'Dirigible',
                    value: 'dirigible',
                },
                {
                    display: 'Elephant',
                    value: 'elephant',
                },
                {
                    display: 'Frittata',
                    value: 'frittata',
                },
            ];
            const config = Object.assign({}, testConfig, {
                availableValues: values,
                initialValue: 'elephant',
            });
            const widget = SelectInput.make(config);
            return widget.start({ node: container }).then(() => {
                const inputElem = container.querySelector('select[data-element="input"]');
                expect(inputElem.value).toEqual('elephant');
                expect(inputElem.childElementCount).toBe(3);
                for (const child of inputElem.children) {
                    expect(['dirigible', 'elephant', 'frittata'].includes(child.value)).toBeTrue();
                }
            });
        });
    });
});
<|endoftext|>