<commit_before>(function (w, d) {
  'use strict';

  var config, logger;

  config = w.goodies = w.goodies || { };

  config.debugMode = !!localStorage.goodiesDebugMode;

  logger = {};
  ['debug', 'log', 'info', 'error', 'warn'].forEach(function (funcName) {
    logger[funcName] = function () {
      if (config.debugMode) {
        console[funcName].apply(console, arguments);
      }
    };
  });

  function addPlayerPuzzlesPageFunctionality(scope, titleElement) {
    var chk, ui = d.createElement('div');

    scope.allPuzzles = scope.puzzles;

    ui.innerHTML = '<input type="checkbox" id="chkTodo" />  ' +
                   'Only show unsolved unambiguous puzzles';

    titleElement.parentElement.insertBefore(ui, titleElement.nextElementSibling);
    chk = d.getElementById('chkTodo');
    chk.addEventListener('click', function () {
      if (this.checked) {
        scope.puzzles = scope.allPuzzles.filter(function (p) {
          return !(p.solved || p.ambiguous);
        });
      } else {
        scope.puzzles = scope.allPuzzles;
      }

      scope.$apply();
    });
  }

  function addPuzzleFunctionality(scope, topElement) {
    var puzzleAdapter, persistenceUI, persistence,
      puzzle = scope.puzzle,

      Cell = function (inputElement, angularModel) {
        Cell.emptyCellPlaceholder = '\xB7';

        var self = this;

        this.ui = inputElement;
        this.regexes = [];

        inputElement.addEventListener('input', function () {
          self.regexes.forEach(function (re) {
            re.validate();
          });
        });

        this.registerRegexValidator = function (regexValidator) {
          this.regexes.push(regexValidator);
        };

        this.getValue = function () {
          if (inputElement.classList.contains('space')) {
            return ' ';
          }

          if (inputElement.value) {
            return inputElement.value.toUpperCase();
          }

          return null;
        };

        this.setValue = function (newValue) {
          if (newValue === ' ') {
            inputElement.classList.add('space');
          } else {
            inputElement.classList.remove('space');
          }

          if (newValue === Cell.emptyCellPlaceholder) {
            newValue = null;
          }
          angularModel.value = newValue;
        };
      },

      RegexValidator = function (cells, expression, uiClueElement) {
        var self = this;

        cells.forEach(function (c) {
          c.registerRegexValidator(self);
        });

        this.regex = new RegExp('^(?:' + expression + ')$');

        this.validate = function () {
          logger.debug('Validating: ' + this.regex);

          var answer = '', i, chr;

          for (i = 0; i < cells.length; i++) {
            chr = cells[i].getValue();

            if (chr) {
              answer += chr;
            } else {
              answer = null;
              break;
            }
          }

          uiClueElement.classList.remove('clue-error');
          uiClueElement.classList.remove('clue-ok');

          if (answer) {
            if (answer.match(this.regex)) {
              uiClueElement.classList.add('clue-ok');
            } else {
              uiClueElement.classList.add('clue-error');
            }
          }
        };
      },

      BasePuzzleAdapter = function () {
        this.regexValidators = [];

        this.getAnswer = function () {
          var answer = '';

          this.cells.forEach(function (c) {
            answer += c.getValue() || Cell.emptyCellPlaceholder;
          });

          return answer;
        };

        this.setAnswer = function (answer) {
          var i;

          for (i = 0; i < this.cells.length; i++) {
            if (i >= answer.length) {
              break;
            }

            this.cells[i].setValue(answer[i]);

            scope.$apply();
          }

          this.regexValidators.forEach(function (re) {
            re.validate();
          });
        };
      },

      RectangularPuzzleAdapter = function () {
        BasePuzzleAdapter.call(this);

        var rowCount, colCount, self = this;

        function getCells(textBoxes) {
          var cells = [], i, j;

          for (i = 0; i < rowCount; i++) {
            for (j = 0; j < colCount; j++) {
              cells.push(new Cell(
                textBoxes[i * colCount + j],
                scope.puzzle.answer.rows[i][j]
              ));
            }
          }

          return cells;
        }

        function addHorizontalRegexValidators(cells) {
          var usedCells = [], patterns, i,
            clueUIElements = topElement.querySelectorAll('th.clue');

          for (i = 0; i < rowCount; i++) {
            usedCells = cells.slice(i * colCount, (i + 1) * colCount);

            patterns = scope.puzzle.patternsY.patterns[i];

            // left clue
            if (patterns.a.value) {
              self.regexValidators.push(
                new RegexValidator(usedCells, patterns.a.value, clueUIElements[i * 2])
              );
            }

            // right clue
            if (patterns.b.value) {
              self.regexValidators.push(
                new RegexValidator(usedCells, patterns.b.value, clueUIElements[i * 2 + 1])
              );
            }
          }
        }

        function addVerticalRegexValidators(cells) {
          var usedCells, patterns, i, j,
            clueUIElements = topElement.querySelectorAll('div.clue');

          for (j = 0; j < colCount; j++) {
            usedCells = [];

            for (i = 0; i < rowCount; i++) {
              usedCells.push(cells[i * colCount + j]);
            }

            patterns = scope.puzzle.patternsX.patterns[j];

            // top clue
            if (patterns.a.value) {
              self.regexValidators.push(
                new RegexValidator(usedCells, patterns.a.value, clueUIElements[j])
              );
            }

            // bottom clue
            if (patterns.b.value) {
              self.regexValidators.push(
                new RegexValidator(usedCells, patterns.b.value, clueUIElements[j + colCount])
              );
            }
          }
        }

        this.init = function () {
          var textBoxes;

          rowCount = scope.puzzle.answer.rows.length;
          colCount = scope.puzzle.answer.rows[0].length;

          logger.debug('Rectangular puzzle ' + colCount + ' by ' + rowCount);
          textBoxes = topElement.querySelectorAll('input.char');

          if (textBoxes.length < rowCount * colCount) {
            logger.debug('Waiting for textboxes to render...');
            w.setTimeout(function () {
              self.init();
            }, 10);

            return;
          }

          this.cells = getCells(textBoxes);

          addHorizontalRegexValidators(this.cells);
          addVerticalRegexValidators(this.cells);
        };
      },

      HexagonalPuzzleAdapter = function () {
        var Hexagon = function (rowCount, middleRowSize, elements) {
          // tests for this class available at http://jsbin.com/biwoya/13/edit
          var i, j, diagonal, rowDelta, idx,
            currentOffset = 0,
            middleRowIndex = (rowCount - 1) / 2,
            currentRowSize = middleRowSize - middleRowIndex;

          this.rows = [];
          for (i = 0; i < rowCount; i++) {
            this.rows.push(elements.slice(currentOffset, currentOffset + currentRowSize));
            currentOffset = currentOffset + currentRowSize;
            currentRowSize += i < rowCount / 2 ? 1 : -1;
          }

          this.diagonalsNW_SE = [];
          for (j = 0; j < middleRowSize; j++) {
            diagonal = [];

            for (i = 0; i < rowCount; i++) {
              rowDelta = i < middleRowIndex ? (i - middleRowIndex) : 0;
              idx = j + rowDelta;

              if (idx >= 0 && idx < this.rows[i].length) {
                diagonal.push(this.rows[i][idx]);
              }
            }

            this.diagonalsNW_SE.push(diagonal.reverse());
          }

          this.diagonalsNE_SW = [];
          for (j = 0; j < middleRowSize; j++) {
            diagonal = [];

            for (i = 0; i < rowCount; i++) {
              rowDelta = i > middleRowIndex ? (middleRowIndex - i) : 0;
              idx = j + rowDelta;

              if (idx >= 0 && idx < this.rows[i].length) {
                diagonal.push(this.rows[i][idx]);
              }
            }

            this.diagonalsNE_SW.push(diagonal);
          }
        };

        BasePuzzleAdapter.call(this);

        function getLeftClueElements() {
          return topElement.querySelectorAll('div.clue-left');
        }

        function getTopClueElements() {
          return topElement.querySelectorAll('div.clue-top');
        }

        function getBottomClueElements() {
          var elements = [], i, j, clues,
            rows = topElement.querySelectorAll('div.row');

          for (i = rows.length - 1; i >= 0; i--) {
            clues = rows[i].querySelectorAll('div.clue-bottom');
            for (j = 0; j < clues.length; j++) {
              elements.push(clues[j]);
            }
          }

          return elements;
        }

        this.init = function () {
          var hexPuzzle = scope.puzzle, cellHexagon, i, uiClues, textBoxes, cellModels;

          this.rowCount = hexPuzzle.patternsY.length;
          this.middleRowLength = hexPuzzle.patternsX.length;

          textBoxes = topElement.querySelectorAll('input.char');
          cellModels = [];
          hexPuzzle.answer.rows.forEach(function (row) {
            row.forEach(function (cell) {
              if (cell) {
                cellModels.push(cell);
              }
            });
          });
          this.cells = [];
          for (i = 0; i < textBoxes.length; i++) {
            this.cells.push(new Cell(textBoxes[i], cellModels[i]));
          }

          this.regexValidators = [];

          cellHexagon = new Hexagon(this.rowCount, this.middleRowLength, this.cells);

          // hexagon rows
          uiClues = getLeftClueElements();
          for (i = 0; i < hexPuzzle.patternsY.length; i++) {
            this.regexValidators.push(new RegexValidator(
              cellHexagon.rows[i],
              hexPuzzle.patternsY.patterns[i].a.value,
              uiClues[i]
            ));
          }

          // hexagon NW-SE diagonals
          uiClues = getBottomClueElements();
          for (i = 0; i < hexPuzzle.patternsZ.length; i++) {
            this.regexValidators.push(new RegexValidator(
              cellHexagon.diagonalsNW_SE[i],
              hexPuzzle.patternsZ.patterns[i].a.value,
              uiClues[i]
            ));
          }

          // hexagon NE-SW diagonals
          uiClues = getTopClueElements();
          for (i = 0; i < hexPuzzle.patternsX.length; i++) {
            this.regexValidators.push(new RegexValidator(
              cellHexagon.diagonalsNE_SW[i],
              hexPuzzle.patternsX.patterns[i].a.value,
              uiClues[i]
            ));
          }
        };
      },

      PersistenceUI = function () {
        var addButton = function (name) {
          var newBtn, controlContainer;

          newBtn = d.createElement('button');
          newBtn.type = 'button';
          newBtn.classList.add('btn');
          newBtn.classList.add('btn-default');
          newBtn.textContent = name;

          controlContainer = topElement.querySelector('h1');

          controlContainer.appendChild(document.createTextNode(' '));
          controlContainer.appendChild(newBtn);

          return newBtn;
        };

        this.init = function () {
          var saveBtn, loadBtn;

          saveBtn = addButton('Save');
          saveBtn.addEventListener('click', this.onSaveClick);

          loadBtn = addButton('Load');
          loadBtn.addEventListener('click', this.onLoadClick);
        };

        this.getTextBoxes = function () {
          return topElement.querySelectorAll('input.char');
        };
      };

    if (!puzzle) {
      // puzzle not yet loaded...
      return;
    }

    persistence = {
      load: function () {
        return w.prompt('Enter previous solution:');
      },
      save: function (currentSolution) {
        w.prompt('Put this in a safe place:', currentSolution);
      }
    };

    persistenceUI = new PersistenceUI();

    puzzleAdapter = puzzle.hexagonal
      ? new HexagonalPuzzleAdapter()
      : new RectangularPuzzleAdapter();

    persistenceUI.onLoadClick = function () {
      logger.info('loading.');
      puzzleAdapter.setAnswer(persistence.load());
    };
    persistenceUI.onSaveClick = function () {
      logger.info('saving.');
      persistence.save(puzzleAdapter.getAnswer());
    };

    persistenceUI.init();
    puzzleAdapter.init();

    puzzleAdapter.onAnswerChange = function () {
      puzzleAdapter.validate();
    };
  }

  function onViewChanged() {
    logger.log('View changed!');

    var scope, puzzleListTitleElement,
      puzzleElement = d.querySelector('div.challenge-puzzle') || d.querySelector('div.playerpuzzle');

    if (puzzleElement) {
      scope = angular.element(puzzleElement).scope();
      addPuzzleFunctionality(scope, puzzleElement);
      return;
    }

    puzzleListTitleElement = d.querySelector('div.container h2');

    if (puzzleListTitleElement && puzzleListTitleElement.textContent === 'Puzzles') {
      scope = angular.element(puzzleListTitleElement).scope();
      addPlayerPuzzlesPageFunctionality(scope, puzzleListTitleElement);
    }
  }

  function appendStyles() {
    var styleTag = d.createElement('style');
    styleTag.type = 'text/css';
    styleTag.textContent = '.clue-error{color:red;}.clue-ok{color:#2A2;}';
    d.head.appendChild(styleTag);
  }

  function start() {
    config.rootElement = d.querySelector('div[ng-view]');

    if (!config.rootElement) {
      logger.warn('root element not yet found.');
      w.setTimeout(start, 100);
      return;
    }

    appendStyles();

    logger.info('root element found!');
    config.rootScope = angular.element(config.rootElement).scope().$root;

    config.rootScope.$on('$viewContentLoaded', onViewChanged);
    onViewChanged();
  }

  start();

}(window, document));

<commit_msg>fixed psolved puzzle filter<commit_after>(function (w, d) {
  'use strict';

  var config, logger;

  config = w.goodies = w.goodies || { };

  config.debugMode = !!localStorage.goodiesDebugMode;

  logger = {};
  ['debug', 'log', 'info', 'error', 'warn'].forEach(function (funcName) {
    logger[funcName] = function () {
      if (config.debugMode) {
        console[funcName].apply(console, arguments);
      }
    };
  });

  function addPlayerPuzzlesPageFunctionality(scope, titleElement) {
    var chk, ui = d.createElement('div');

    scope.allPuzzles = scope.puzzles;

    ui.innerHTML = '<input type="checkbox" id="chkTodo" />  ' +
                   'Only show unsolved unambiguous puzzles';

    titleElement.parentElement.insertBefore(ui, titleElement.nextElementSibling);
    chk = d.getElementById('chkTodo');
    chk.addEventListener('click', function () {
      if (this.checked) {
        scope.puzzles = scope.allPuzzles.filter(function (p) {
          return !(scope.isSolved(p.id) || p.ambiguous);
        });
      } else {
        scope.puzzles = scope.allPuzzles;
      }

      scope.$apply();
    });
  }

  function addPuzzleFunctionality(scope, topElement) {
    var puzzleAdapter, persistenceUI, persistence,
      puzzle = scope.puzzle,

      Cell = function (inputElement, angularModel) {
        Cell.emptyCellPlaceholder = '\xB7';

        var self = this;

        this.ui = inputElement;
        this.regexes = [];

        inputElement.addEventListener('input', function () {
          self.regexes.forEach(function (re) {
            re.validate();
          });
        });

        this.registerRegexValidator = function (regexValidator) {
          this.regexes.push(regexValidator);
        };

        this.getValue = function () {
          if (inputElement.classList.contains('space')) {
            return ' ';
          }

          if (inputElement.value) {
            return inputElement.value.toUpperCase();
          }

          return null;
        };

        this.setValue = function (newValue) {
          if (newValue === ' ') {
            inputElement.classList.add('space');
          } else {
            inputElement.classList.remove('space');
          }

          if (newValue === Cell.emptyCellPlaceholder) {
            newValue = null;
          }
          angularModel.value = newValue;
        };
      },

      RegexValidator = function (cells, expression, uiClueElement) {
        var self = this;

        cells.forEach(function (c) {
          c.registerRegexValidator(self);
        });

        this.regex = new RegExp('^(?:' + expression + ')$');

        this.validate = function () {
          logger.debug('Validating: ' + this.regex);

          var answer = '', i, chr;

          for (i = 0; i < cells.length; i++) {
            chr = cells[i].getValue();

            if (chr) {
              answer += chr;
            } else {
              answer = null;
              break;
            }
          }

          uiClueElement.classList.remove('clue-error');
          uiClueElement.classList.remove('clue-ok');

          if (answer) {
            if (answer.match(this.regex)) {
              uiClueElement.classList.add('clue-ok');
            } else {
              uiClueElement.classList.add('clue-error');
            }
          }
        };
      },

      BasePuzzleAdapter = function () {
        this.regexValidators = [];

        this.getAnswer = function () {
          var answer = '';

          this.cells.forEach(function (c) {
            answer += c.getValue() || Cell.emptyCellPlaceholder;
          });

          return answer;
        };

        this.setAnswer = function (answer) {
          var i;

          for (i = 0; i < this.cells.length; i++) {
            if (i >= answer.length) {
              break;
            }

            this.cells[i].setValue(answer[i]);

            scope.$apply();
          }

          this.regexValidators.forEach(function (re) {
            re.validate();
          });
        };
      },

      RectangularPuzzleAdapter = function () {
        BasePuzzleAdapter.call(this);

        var rowCount, colCount, self = this;

        function getCells(textBoxes) {
          var cells = [], i, j;

          for (i = 0; i < rowCount; i++) {
            for (j = 0; j < colCount; j++) {
              cells.push(new Cell(
                textBoxes[i * colCount + j],
                scope.puzzle.answer.rows[i][j]
              ));
            }
          }

          return cells;
        }

        function addHorizontalRegexValidators(cells) {
          var usedCells = [], patterns, i,
            clueUIElements = topElement.querySelectorAll('th.clue');

          for (i = 0; i < rowCount; i++) {
            usedCells = cells.slice(i * colCount, (i + 1) * colCount);

            patterns = scope.puzzle.patternsY.patterns[i];

            // left clue
            if (patterns.a.value) {
              self.regexValidators.push(
                new RegexValidator(usedCells, patterns.a.value, clueUIElements[i * 2])
              );
            }

            // right clue
            if (patterns.b.value) {
              self.regexValidators.push(
                new RegexValidator(usedCells, patterns.b.value, clueUIElements[i * 2 + 1])
              );
            }
          }
        }

        function addVerticalRegexValidators(cells) {
          var usedCells, patterns, i, j,
            clueUIElements = topElement.querySelectorAll('div.clue');

          for (j = 0; j < colCount; j++) {
            usedCells = [];

            for (i = 0; i < rowCount; i++) {
              usedCells.push(cells[i * colCount + j]);
            }

            patterns = scope.puzzle.patternsX.patterns[j];

            // top clue
            if (patterns.a.value) {
              self.regexValidators.push(
                new RegexValidator(usedCells, patterns.a.value, clueUIElements[j])
              );
            }

            // bottom clue
            if (patterns.b.value) {
              self.regexValidators.push(
                new RegexValidator(usedCells, patterns.b.value, clueUIElements[j + colCount])
              );
            }
          }
        }

        this.init = function () {
          var textBoxes;

          rowCount = scope.puzzle.answer.rows.length;
          colCount = scope.puzzle.answer.rows[0].length;

          logger.debug('Rectangular puzzle ' + colCount + ' by ' + rowCount);
          textBoxes = topElement.querySelectorAll('input.char');

          if (textBoxes.length < rowCount * colCount) {
            logger.debug('Waiting for textboxes to render...');
            w.setTimeout(function () {
              self.init();
            }, 10);

            return;
          }

          this.cells = getCells(textBoxes);

          addHorizontalRegexValidators(this.cells);
          addVerticalRegexValidators(this.cells);
        };
      },

      HexagonalPuzzleAdapter = function () {
        var Hexagon = function (rowCount, middleRowSize, elements) {
          // tests for this class available at http://jsbin.com/biwoya/13/edit
          var i, j, diagonal, rowDelta, idx,
            currentOffset = 0,
            middleRowIndex = (rowCount - 1) / 2,
            currentRowSize = middleRowSize - middleRowIndex;

          this.rows = [];
          for (i = 0; i < rowCount; i++) {
            this.rows.push(elements.slice(currentOffset, currentOffset + currentRowSize));
            currentOffset = currentOffset + currentRowSize;
            currentRowSize += i < rowCount / 2 ? 1 : -1;
          }

          this.diagonalsNW_SE = [];
          for (j = 0; j < middleRowSize; j++) {
            diagonal = [];

            for (i = 0; i < rowCount; i++) {
              rowDelta = i < middleRowIndex ? (i - middleRowIndex) : 0;
              idx = j + rowDelta;

              if (idx >= 0 && idx < this.rows[i].length) {
                diagonal.push(this.rows[i][idx]);
              }
            }

            this.diagonalsNW_SE.push(diagonal.reverse());
          }

          this.diagonalsNE_SW = [];
          for (j = 0; j < middleRowSize; j++) {
            diagonal = [];

            for (i = 0; i < rowCount; i++) {
              rowDelta = i > middleRowIndex ? (middleRowIndex - i) : 0;
              idx = j + rowDelta;

              if (idx >= 0 && idx < this.rows[i].length) {
                diagonal.push(this.rows[i][idx]);
              }
            }

            this.diagonalsNE_SW.push(diagonal);
          }
        };

        BasePuzzleAdapter.call(this);

        function getLeftClueElements() {
          return topElement.querySelectorAll('div.clue-left');
        }

        function getTopClueElements() {
          return topElement.querySelectorAll('div.clue-top');
        }

        function getBottomClueElements() {
          var elements = [], i, j, clues,
            rows = topElement.querySelectorAll('div.row');

          for (i = rows.length - 1; i >= 0; i--) {
            clues = rows[i].querySelectorAll('div.clue-bottom');
            for (j = 0; j < clues.length; j++) {
              elements.push(clues[j]);
            }
          }

          return elements;
        }

        this.init = function () {
          var hexPuzzle = scope.puzzle, cellHexagon, i, uiClues, textBoxes, cellModels;

          this.rowCount = hexPuzzle.patternsY.length;
          this.middleRowLength = hexPuzzle.patternsX.length;

          textBoxes = topElement.querySelectorAll('input.char');
          cellModels = [];
          hexPuzzle.answer.rows.forEach(function (row) {
            row.forEach(function (cell) {
              if (cell) {
                cellModels.push(cell);
              }
            });
          });
          this.cells = [];
          for (i = 0; i < textBoxes.length; i++) {
            this.cells.push(new Cell(textBoxes[i], cellModels[i]));
          }

          this.regexValidators = [];

          cellHexagon = new Hexagon(this.rowCount, this.middleRowLength, this.cells);

          // hexagon rows
          uiClues = getLeftClueElements();
          for (i = 0; i < hexPuzzle.patternsY.length; i++) {
            this.regexValidators.push(new RegexValidator(
              cellHexagon.rows[i],
              hexPuzzle.patternsY.patterns[i].a.value,
              uiClues[i]
            ));
          }

          // hexagon NW-SE diagonals
          uiClues = getBottomClueElements();
          for (i = 0; i < hexPuzzle.patternsZ.length; i++) {
            this.regexValidators.push(new RegexValidator(
              cellHexagon.diagonalsNW_SE[i],
              hexPuzzle.patternsZ.patterns[i].a.value,
              uiClues[i]
            ));
          }

          // hexagon NE-SW diagonals
          uiClues = getTopClueElements();
          for (i = 0; i < hexPuzzle.patternsX.length; i++) {
            this.regexValidators.push(new RegexValidator(
              cellHexagon.diagonalsNE_SW[i],
              hexPuzzle.patternsX.patterns[i].a.value,
              uiClues[i]
            ));
          }
        };
      },

      PersistenceUI = function () {
        var addButton = function (name) {
          var newBtn, controlContainer;

          newBtn = d.createElement('button');
          newBtn.type = 'button';
          newBtn.classList.add('btn');
          newBtn.classList.add('btn-default');
          newBtn.textContent = name;

          controlContainer = topElement.querySelector('h1');

          controlContainer.appendChild(document.createTextNode(' '));
          controlContainer.appendChild(newBtn);

          return newBtn;
        };

        this.init = function () {
          var saveBtn, loadBtn;

          saveBtn = addButton('Save');
          saveBtn.addEventListener('click', this.onSaveClick);

          loadBtn = addButton('Load');
          loadBtn.addEventListener('click', this.onLoadClick);
        };

        this.getTextBoxes = function () {
          return topElement.querySelectorAll('input.char');
        };
      };

    if (!puzzle) {
      // puzzle not yet loaded...
      return;
    }

    persistence = {
      load: function () {
        return w.prompt('Enter previous solution:');
      },
      save: function (currentSolution) {
        w.prompt('Put this in a safe place:', currentSolution);
      }
    };

    persistenceUI = new PersistenceUI();

    puzzleAdapter = puzzle.hexagonal
      ? new HexagonalPuzzleAdapter()
      : new RectangularPuzzleAdapter();

    persistenceUI.onLoadClick = function () {
      logger.info('loading.');
      puzzleAdapter.setAnswer(persistence.load());
    };
    persistenceUI.onSaveClick = function () {
      logger.info('saving.');
      persistence.save(puzzleAdapter.getAnswer());
    };

    persistenceUI.init();
    puzzleAdapter.init();

    puzzleAdapter.onAnswerChange = function () {
      puzzleAdapter.validate();
    };
  }

  function onViewChanged() {
    logger.log('View changed!');

    var scope, puzzleListTitleElement,
      puzzleElement = d.querySelector('div.challenge-puzzle') || d.querySelector('div.playerpuzzle');

    if (puzzleElement) {
      scope = angular.element(puzzleElement).scope();
      addPuzzleFunctionality(scope, puzzleElement);
      return;
    }

    puzzleListTitleElement = d.querySelector('div.container h2');

    if (puzzleListTitleElement && puzzleListTitleElement.textContent === 'Puzzles') {
      scope = angular.element(puzzleListTitleElement).scope();
      addPlayerPuzzlesPageFunctionality(scope, puzzleListTitleElement);
    }
  }

  function appendStyles() {
    var styleTag = d.createElement('style');
    styleTag.type = 'text/css';
    styleTag.textContent = '.clue-error{color:red;}.clue-ok{color:#2A2;}';
    d.head.appendChild(styleTag);
  }

  function start() {
    config.rootElement = d.querySelector('div[ng-view]');

    if (!config.rootElement) {
      logger.warn('root element not yet found.');
      w.setTimeout(start, 100);
      return;
    }

    appendStyles();

    logger.info('root element found!');
    config.rootScope = angular.element(config.rootElement).scope().$root;

    config.rootScope.$on('$viewContentLoaded', onViewChanged);
    onViewChanged();
  }

  start();

}(window, document));

<|endoftext|>


<commit_before>import { action, computed, observable } from "mobx";
import UiState from "./UiState";

class PlaybackState {
  @observable isPlaying = false;
  @observable currentPlayingIndex = 0;
  @observable currentRunningTest = null;
  @observable currentRunningSuite = null;
  @observable commandsCount = 0;
  @observable commandState = new Map();
  @observable testState = new Map();
  @observable suiteState = new Map();
  @observable runs = 0;
  @observable failures = 0;
  @observable hasFailed = false;

  constructor() {
    this._testsToRun = [];
  }

  @computed get finishedCommandsCount() {
    let counter = 0;

    this.commandState.forEach(({ state }) => {
      if (state !== PlaybackStates.Pending) {
        counter++;
      }
    });

    return counter;
  }

  @computed get hasFinishedSuccessfully() {
    return this.currentRunningTest.commands.filter(({id}) => (
      this.commandState.get(id) ? this.commandState.get(id).state === PlaybackStates.Passed : false
    )).length === this.currentRunningTest.commands.length;
  }

  @action.bound startPlayingSuite() {
    const { suite } = UiState.selectedTest;
    if (this.currentRunningSuite !== (suite ? suite.id : undefined)) {
      this.resetState();
      this.currentRunningSuite = suite.id;
    }
    this.clearCommandStates();
    this.hasFailed = false;
    this.runs++;
    this._testsToRun = [...suite.tests];
    this.commandsCount = this._testsToRun.reduce((counter, test) => (counter + test.commands.length), 0);
    this.playNext();
  }

  @action.bound startPlaying() {
    const { test } = UiState.selectedTest;
    if (this.currentRunningSuite || !this.currentRunningTest || this.currentRunningTest.id !== test.id) {
      this.resetState();
      this.currentRunningSuite = undefined;
      this.currentRunningTest = test;
    }
    this.clearCommandStates();
    this.runs++;
    this.hasFailed = false;
    this.commandsCount = test.commands.length;
    this.isPlaying = true;
  }

  @action.bound playNext() {
    this.currentRunningTest = this._testsToRun.shift();
    UiState.selectTest(this.currentRunningTest, UiState.selectedTest.suite);
    this.isPlaying = true;
  }

  @action.bound stopPlaying() {
    this.isPlaying = false;
  }

  @action.bound abortPlaying() {
    this.isPlaying = false;
    this._testsToRun = [];
  }

  @action.bound finishPlaying() {
    this.isPlaying = false;
    this.testState.set(this.currentRunningTest.id, this.hasFinishedSuccessfully ? PlaybackStates.Passed : PlaybackStates.Failed);
    if (this._testsToRun.length) {
      this.playNext();
    } else {
      this.suiteState.set(this.currentRunningSuite, !this.hasFailed ? PlaybackStates.Passed : PlaybackStates.Failed);
    }
  }

  @action.bound setPlayingIndex(index) {
    this.currentPlayingIndex = index;
  }

  @action.bound setCommandState(commandId, state, message) {
    if (state === PlaybackStates.Failed) {
      this.hasFailed = true;
      this.failures++;
    }
    this.commandState.set(commandId, { state, message });
  }

  @action.bound clearCommandStates() {
    this.commandState.clear();
  }

  @action.bound resetState() {
    this.clearCommandStates();
    this.currentPlayingIndex = 0;
    this.runs = 0;
    this.failures = 0;
    this.hasFailed = false;
  }
}

export const PlaybackStates = {
  Passed: "passed",
  Failed: "failed",
  Pending: "pending"
};

if (!window._playbackState) window._playbackState = new PlaybackState();

export default window._playbackState;
<commit_msg>fail the current running command if the test was aborted<commit_after>import { action, computed, observable } from "mobx";
import UiState from "./UiState";

class PlaybackState {
  @observable isPlaying = false;
  @observable currentPlayingIndex = 0;
  @observable currentRunningTest = null;
  @observable currentRunningSuite = null;
  @observable commandsCount = 0;
  @observable commandState = new Map();
  @observable testState = new Map();
  @observable suiteState = new Map();
  @observable runs = 0;
  @observable failures = 0;
  @observable hasFailed = false;

  constructor() {
    this._testsToRun = [];
  }

  @computed get finishedCommandsCount() {
    let counter = 0;

    this.commandState.forEach(({ state }) => {
      if (state !== PlaybackStates.Pending) {
        counter++;
      }
    });

    return counter;
  }

  @computed get hasFinishedSuccessfully() {
    return this.currentRunningTest.commands.filter(({id}) => (
      this.commandState.get(id) ? this.commandState.get(id).state === PlaybackStates.Passed : false
    )).length === this.currentRunningTest.commands.length;
  }

  @action.bound startPlayingSuite() {
    const { suite } = UiState.selectedTest;
    if (this.currentRunningSuite !== (suite ? suite.id : undefined)) {
      this.resetState();
      this.currentRunningSuite = suite.id;
    }
    this.clearCommandStates();
    this.hasFailed = false;
    this.runs++;
    this._testsToRun = [...suite.tests];
    this.commandsCount = this._testsToRun.reduce((counter, test) => (counter + test.commands.length), 0);
    this.playNext();
  }

  @action.bound startPlaying() {
    const { test } = UiState.selectedTest;
    if (this.currentRunningSuite || !this.currentRunningTest || this.currentRunningTest.id !== test.id) {
      this.resetState();
      this.currentRunningSuite = undefined;
      this.currentRunningTest = test;
    }
    this.clearCommandStates();
    this.runs++;
    this.hasFailed = false;
    this.commandsCount = test.commands.length;
    this.isPlaying = true;
  }

  @action.bound playNext() {
    this.currentRunningTest = this._testsToRun.shift();
    UiState.selectTest(this.currentRunningTest, UiState.selectedTest.suite);
    this.isPlaying = true;
  }

  @action.bound stopPlaying() {
    this.isPlaying = false;
  }

  @action.bound abortPlaying() {
    this._testsToRun = [];
    this.commandState.set(this.currentRunningTest.commands[this.currentPlayingIndex].id, { state: PlaybackStates.Failed, message: "Playback aborted" });
    this.isPlaying = false;
  }

  @action.bound finishPlaying() {
    this.isPlaying = false;
    this.testState.set(this.currentRunningTest.id, this.hasFinishedSuccessfully ? PlaybackStates.Passed : PlaybackStates.Failed);
    if (this._testsToRun.length) {
      this.playNext();
    } else if (this.currentRunningSuite) {
      this.suiteState.set(this.currentRunningSuite, !this.hasFailed ? PlaybackStates.Passed : PlaybackStates.Failed);
    }
  }

  @action.bound setPlayingIndex(index) {
    this.currentPlayingIndex = index;
  }

  @action.bound setCommandState(commandId, state, message) {
    if (state === PlaybackStates.Failed) {
      this.hasFailed = true;
      this.failures++;
    }
    this.commandState.set(commandId, { state, message });
  }

  @action.bound clearCommandStates() {
    this.commandState.clear();
  }

  @action.bound resetState() {
    this.clearCommandStates();
    this.currentPlayingIndex = 0;
    this.runs = 0;
    this.failures = 0;
    this.hasFailed = false;
  }
}

export const PlaybackStates = {
  Passed: "passed",
  Failed: "failed",
  Pending: "pending"
};

if (!window._playbackState) window._playbackState = new PlaybackState();

export default window._playbackState;
<|endoftext|>


<commit_before>/**
 * Created by Jared Rewerts on 3/7/2016.
 */
"use strict";

var Physics = require("physicsjs");

var Paddle = function(config) {
    this.leftBound = config.leftBound;
    this.rightBound = config.rightBound;

    this.body = Physics.body('circle',
        {
            x: config.x,
            y: config.y,
            radius: config.size,
            treatment: static,
            styles: config.style
        }
    );
};

Paddle.prototype.setPosition = function(x) {
    this.body.x = x;
};<commit_msg>Forgot to export Paddle.<commit_after>/**
 * Created by Jared Rewerts on 3/7/2016.
 */
"use strict";

var Physics = require("physicsjs");

var Paddle = function(config) {
    this.leftBound = config.leftBound;
    this.rightBound = config.rightBound;

    this.body = Physics.body('circle',
        {
            x: config.x,
            y: config.y,
            radius: config.size,
            treatment: static,
            styles: config.style
        }
    );
};

Paddle.prototype.setPosition = function(x) {
    this.body.x = x;
};

module.exports = Paddle;<|endoftext|>


<commit_before>/*
	The Cedric's Swiss Knife (CSK) - CSK REST Query

	Copyright (c) 2015 Cédric Ronvel

	The MIT License (MIT)

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in all
	copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	SOFTWARE.
*/

// Load modules
var restQuery = require( './restQuery.js' ) ;

var ErrorStatus = require( 'error-status' ) ;
var async = require( 'async-kit' ) ;

			/* Common Node -- shared between ObjectNode & CollectionNode */

function Node() { throw new Error( '[restQuery] Cannot create a Node object directly' ) ; }
module.exports = Node ;

Node.prototype.constructor = Node ;

Node.create = function createNode( app , node , children )
{
	if ( ! node ) { node = Object.create( Node.prototype ) ; }
	if ( ! children || typeof children !== 'object' ) { children = {} ; }

	Object.defineProperties( node , {
		app: { value: app , enumerable: true } ,
		children: { value: children , enumerable: true }
	} ) ;

	return node ;
} ;

// autoSlugId: the current collection will be assumed if a slugId is given
Node.prototype.contains = function contains( collectionNode , autoSlugId )
{
	if ( ! ( collectionNode instanceof restQuery.CollectionNode ) ) { throw new Error( '[restQuery] .constains() require argument #0 to be an instance of restQuery.CollectionNode' ) ; }

	// First check the child name
	if ( this.children[ collectionNode.name ] ) { throw new Error( '[restQuery] Cannot attach over an existing child: ' + this.children[ collectionNode.name ] ) ; }
	this.children[ collectionNode.name ] = collectionNode ;

	//return odm.collection ;
} ;

Node.parsePathNode = function parsePathNode( str )
{
	if ( typeof str !== 'string' ) { return new TypeError( '[restQuery] parsePathNode() : argument #0 should be a string' ) ; }

	if ( str.length < 1 ) { return new Error( '[restQuery] parsePathNode() : argument #0 length should be > 1' ) ; }
	if ( str.length > 72 ) { return new Error( '[restQuery] parsePathNode() : argument #0 length should be <= 72' ) ; }

	// Firstly, check if it is an object's member (or method): it starts with an uppercase ascii letter
	if ( restQuery.charmap.uppercaseArray.indexOf( str[ 0 ] ) !== -1 )
	{
		if ( ! str.match( restQuery.charmap.memberRegExp ) )
		{
			return new Error( '[restQuery] parsePathNode() : argument #0 start with an uppercase but mismatch an object member type' ) ;
		}

		return {
			type: 'member' ,
			identifier: str[ 0 ].toLowerCase() + str.slice( 1 )
		} ;
	}

	// Secondly, check if it is an ID: it is a 24 characters string containing only hexadecimal
	if ( str.length === 24 && str.match( restQuery.charmap.idRegExp ) )
	{
		return {
			type: 'ID' ,
			identifier: str
		} ;
	}

	// Thirdly, check if it is an offset
	if ( str.match( restQuery.charmap.offsetRegExp ) )
	{
		return {
			type: 'offset' ,
			identifier: parseInt( str )
		} ;
	}

	// Fourthly, check if it is a slugId
	if ( str.match( restQuery.charmap.sidRegExp ) )
	{
		return {
			type: 'slugId' ,
			identifier: str
		} ;
	}

	// Nothing had matched... this is not a valid path node
	return new Error( '[restQuery] parsePathNode() : argument #0 does not validate' ) ;
} ;

Node.prototype.checkAccess = function checkAccess( context , accessNeeded , options , callback )
{
	var objectNode , object , accessCache ;

	if ( ! options ) { options = {} ; }

	// First, check the ancestry chain: the current object should have
	// the previous object in the chain as its parent

	if ( this instanceof restQuery.ObjectNode )
	{
		objectNode = this ;

		if ( ! options.childObject && context.parentNode )
		{
			if (
				! objectNode.object.$.parent ||
				objectNode.object.$.parent.collection !== ( context.parentNode.collectionNode && context.parentNode.collectionNode.collection.name ) ||
				objectNode.object.$.parent.id.toString() !== context.parentNode.id.toString()
			)
			{

				callback( ErrorStatus.notFound( { message: "Ancestry mismatch." } ) ) ;
				return ;
			}
		}
	}
	else
	{
		objectNode = context.parentNode ;
	}

	if ( options.childObject )
	{
		// Used as a faster alternative for GET on collection: no need to instanciate each ObjectNode,
		// just check access on each child object
		object = options.childObject ;
	}
	else if ( objectNode.id === '/' )
	{
		object = objectNode.object ;
	}
	else
	{
		object = objectNode.object.$ ;
	}

	// Get / create access cache for this performer
	accessCache = context.performer.accessCache.get( object ) ;

	if ( ! accessCache )
	{
		accessCache = {
			granted: restQuery.accessLevel.NONE ,
			denied: restQuery.accessLevel.ALL + 1
		} ;

		context.performer.accessCache.set( object , accessCache ) ;
	}

	// Check access cache
	if ( accessNeeded <= accessCache.granted )
	{
		callback() ;
		return ;
	}
	else if ( accessNeeded >= accessCache.denied )
	{
		callback( ErrorStatus.forbidden( { message: "Access forbidden." } ) ) ;
		return ;
	}

	//console.log( "Inherit access: " , object.inheritAccess ) ;

	var itemAccess = function( object , itemAccessCallback )
	{
		if ( object.otherAccess >= accessNeeded )
		{
			// Access granted by 'otherAccess'
			if ( accessNeeded > accessCache.granted ) { accessCache.granted = accessNeeded ; }
			callback() ;
			return ;
		}

		context.performer.getUser( function( error , user ) {

			var userId ;

			if ( error ) { callback( error ) ; return ; }

			if ( ! user )
			{
				// Access denied, performer should be connected
				if ( accessNeeded < accessCache.denied ) { accessCache.denied = accessNeeded ; }
				callback( ErrorStatus.unauthorized( { message: "Public access forbidden." } ) ) ;
				return ;
			}

			userId = user._id.toString() ;

			if ( userId in object.userAccess && object.userAccess[ userId ] >= accessNeeded )
			{
				// Access granted by 'userAccess'
				if ( accessNeeded > accessCache.granted ) { accessCache.granted = accessNeeded ; }
				callback() ;
				return ;
			}

			// Later, we should check group authorization here ------------------------------------

			// Nothing has granted access to this connected performer, so access is denied
			if ( accessNeeded < accessCache.denied ) { accessCache.denied = accessNeeded ; }
			callback( ErrorStatus.forbidden( { message: "Access forbidden." } ) ) ;
		} ) ;
	} ;

	itemAccess( object , callback ) ;
} ;

<commit_msg>inheritAccess: WIP<commit_after>/*
	The Cedric's Swiss Knife (CSK) - CSK REST Query

	Copyright (c) 2015 Cédric Ronvel

	The MIT License (MIT)

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in all
	copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
	SOFTWARE.
*/

// Load modules
var restQuery = require( './restQuery.js' ) ;

var ErrorStatus = require( 'error-status' ) ;
var async = require( 'async-kit' ) ;

			/* Common Node -- shared between ObjectNode & CollectionNode */

function Node() { throw new Error( '[restQuery] Cannot create a Node object directly' ) ; }
module.exports = Node ;

Node.prototype.constructor = Node ;

Node.create = function createNode( app , node , children )
{
	if ( ! node ) { node = Object.create( Node.prototype ) ; }
	if ( ! children || typeof children !== 'object' ) { children = {} ; }

	Object.defineProperties( node , {
		app: { value: app , enumerable: true } ,
		children: { value: children , enumerable: true }
	} ) ;

	return node ;
} ;

// autoSlugId: the current collection will be assumed if a slugId is given
Node.prototype.contains = function contains( collectionNode , autoSlugId )
{
	if ( ! ( collectionNode instanceof restQuery.CollectionNode ) ) { throw new Error( '[restQuery] .constains() require argument #0 to be an instance of restQuery.CollectionNode' ) ; }

	// First check the child name
	if ( this.children[ collectionNode.name ] ) { throw new Error( '[restQuery] Cannot attach over an existing child: ' + this.children[ collectionNode.name ] ) ; }
	this.children[ collectionNode.name ] = collectionNode ;

	//return odm.collection ;
} ;

Node.parsePathNode = function parsePathNode( str )
{
	if ( typeof str !== 'string' ) { return new TypeError( '[restQuery] parsePathNode() : argument #0 should be a string' ) ; }

	if ( str.length < 1 ) { return new Error( '[restQuery] parsePathNode() : argument #0 length should be > 1' ) ; }
	if ( str.length > 72 ) { return new Error( '[restQuery] parsePathNode() : argument #0 length should be <= 72' ) ; }

	// Firstly, check if it is an object's member (or method): it starts with an uppercase ascii letter
	if ( restQuery.charmap.uppercaseArray.indexOf( str[ 0 ] ) !== -1 )
	{
		if ( ! str.match( restQuery.charmap.memberRegExp ) )
		{
			return new Error( '[restQuery] parsePathNode() : argument #0 start with an uppercase but mismatch an object member type' ) ;
		}

		return {
			type: 'member' ,
			identifier: str[ 0 ].toLowerCase() + str.slice( 1 )
		} ;
	}

	// Secondly, check if it is an ID: it is a 24 characters string containing only hexadecimal
	if ( str.length === 24 && str.match( restQuery.charmap.idRegExp ) )
	{
		return {
			type: 'ID' ,
			identifier: str
		} ;
	}

	// Thirdly, check if it is an offset
	if ( str.match( restQuery.charmap.offsetRegExp ) )
	{
		return {
			type: 'offset' ,
			identifier: parseInt( str )
		} ;
	}

	// Fourthly, check if it is a slugId
	if ( str.match( restQuery.charmap.sidRegExp ) )
	{
		return {
			type: 'slugId' ,
			identifier: str
		} ;
	}

	// Nothing had matched... this is not a valid path node
	return new Error( '[restQuery] parsePathNode() : argument #0 does not validate' ) ;
} ;

Node.prototype.checkAccess = function checkAccess( context , accessNeeded , options , callback )
{
	var objectNode , object , accessCache ;

	if ( ! options ) { options = {} ; }

	// First, check the ancestry chain: the current object should have
	// the previous object in the chain as its parent

	if ( this instanceof restQuery.ObjectNode )
	{
		objectNode = this ;

		if ( ! options.childObject && context.parentNode )
		{
			if (
				! objectNode.object.$.parent ||
				objectNode.object.$.parent.collection !== ( context.parentNode.collectionNode && context.parentNode.collectionNode.collection.name ) ||
				objectNode.object.$.parent.id.toString() !== context.parentNode.id.toString()
			)
			{

				callback( ErrorStatus.notFound( { message: "Ancestry mismatch." } ) ) ;
				return ;
			}
		}
	}
	else
	{
		objectNode = context.parentNode ;
	}

	if ( options.childObject )
	{
		// Used as a faster alternative for GET on collection: no need to instanciate each ObjectNode,
		// just check access on each child object
		object = options.childObject ;
	}
	else if ( objectNode.id === '/' )
	{
		object = objectNode.object ;
	}
	else
	{
		object = objectNode.object.$ ;
	}

	// Get / create access cache for this performer
	accessCache = context.performer.accessCache.get( object ) ;

	if ( ! accessCache )
	{
		accessCache = {
			granted: restQuery.accessLevel.NONE ,
			denied: restQuery.accessLevel.ALL + 1
		} ;

		context.performer.accessCache.set( object , accessCache ) ;
	}

	// Check access cache
	if ( accessNeeded <= accessCache.granted )
	{
		callback() ;
		return ;
	}
	else if ( accessNeeded >= accessCache.denied )
	{
		callback( ErrorStatus.forbidden( { message: "Access forbidden." } ) ) ;
		return ;
	}

	//console.log( "Inherit access: " , object.inheritAccess ) ;

	var objectLocalAccess = function( object , objectLocalAccessCallback )
	{
		// Access granted by 'otherAccess'
		if ( object.otherAccess >= accessNeeded ) { callback() ; return ; }

		context.performer.getUser( function( error , user ) {

			var userId ;

			if ( error ) { callback( error ) ; return ; }

			// Access denied, performer should be connected
			if ( ! user ) { callback( ErrorStatus.unauthorized( { message: "Public access forbidden." } ) ) ; return ; }

			userId = user._id.toString() ;

			// Access granted by 'userAccess'
			if ( userId in object.userAccess && object.userAccess[ userId ] >= accessNeeded ) { callback() ; return ; }

			// Later, we should check group authorization here ------------------------------------

			// Nothing has granted access to this connected performer, so access is denied
			callback( ErrorStatus.forbidden( { message: "Access forbidden." } ) ) ;
		} ) ;
	} ;

	objectLocalAccess( object , function( error ) {
		if ( error )
		{
			if ( accessNeeded < accessCache.denied ) { accessCache.denied = accessNeeded ; }
			callback( error ) ;
		}
		else
		{
			if ( accessNeeded > accessCache.granted ) { accessCache.granted = accessNeeded ; }
			callback() ;
		}
	} ) ;
} ;

<|endoftext|>


<commit_before>/**
 * Normalizes focus treatment for custom elements with Shadow DOM.
 *
 * The default behavior for mousedown should set the focus to the closest
 * ancestor of the clicked element that can take the focus. As of Nov 2018,
 * Chrome and Safari don't handle this as expected when the clicked element is
 * reassigned across more than one slot to end up inside a focusable element. In
 * such cases, the focus will end up on the body. Firefox exhibits the behavior
 * we want. See https: *github.com/w3c/webcomponents/issues/773.
 *
 * This mixin normalizes behavior to provide what Firefox does. When the user
 * mouses down inside anywhere inside the component's light DOM or Shadow DOM,
 * we walk up the composed tree to find the first element that can take the
 * focus and put the focus on it.
 *
 * @module ComposedFocusMixin
 */
export default function ComposedFocusMixin(Base) {

  // The class prototype added by the mixin.
  class ComposedFocus extends Base {

    constructor() {
      // @ts-ignore
      super();
      if ('HTMLSlotElement' in window) {
        this.addEventListener('mousedown', event => {
          // Only process events for the main (usually left) button.
          if (event.button !== 0) {
            return;
          }
          const target = findFocusableAncestor(event.target);
          if (target) {
            target.focus();
            event.preventDefault();
          }
        });
      }
    }

  }

  return ComposedFocus;
}

// Return the closest focusable ancestor in the *composed* tree.
function findFocusableAncestor(element) {
  if (element === document.body ||
    (!(element instanceof HTMLSlotElement) && element.tabIndex >= 0)) {
    return element;
  }
  const parent = element.assignedSlot ?
    element.assignedSlot :
    // @ts-ignore
    element.parentNode instanceof ShadowRoot ?
      element.parentNode.host :
      element.parentNode;
  return parent ?
    findFocusableAncestor(parent) :
    null;
}
<commit_msg>Fix formatting in doc comment.<commit_after>/**
 * Normalizes focus treatment for custom elements with Shadow DOM.
 *
 * The default behavior for mousedown should set the focus to the closest
 * ancestor of the clicked element that can take the focus. As of Nov 2018,
 * Chrome and Safari don't handle this as expected when the clicked element is
 * reassigned across more than one slot to end up inside a focusable element. In
 * such cases, the focus will end up on the body. Firefox exhibits the behavior
 * we want. See https://github.com/w3c/webcomponents/issues/773.
 *
 * This mixin normalizes behavior to provide what Firefox does. When the user
 * mouses down inside anywhere inside the component's light DOM or Shadow DOM,
 * we walk up the composed tree to find the first element that can take the
 * focus and put the focus on it.
 *
 * @module ComposedFocusMixin
 */
export default function ComposedFocusMixin(Base) {

  // The class prototype added by the mixin.
  class ComposedFocus extends Base {

    constructor() {
      // @ts-ignore
      super();
      if ('HTMLSlotElement' in window) {
        this.addEventListener('mousedown', event => {
          // Only process events for the main (usually left) button.
          if (event.button !== 0) {
            return;
          }
          const target = findFocusableAncestor(event.target);
          if (target) {
            target.focus();
            event.preventDefault();
          }
        });
      }
    }

  }

  return ComposedFocus;
}

// Return the closest focusable ancestor in the *composed* tree.
function findFocusableAncestor(element) {
  if (element === document.body ||
    (!(element instanceof HTMLSlotElement) && element.tabIndex >= 0)) {
    return element;
  }
  const parent = element.assignedSlot ?
    element.assignedSlot :
    // @ts-ignore
    element.parentNode instanceof ShadowRoot ?
      element.parentNode.host :
      element.parentNode;
  return parent ?
    findFocusableAncestor(parent) :
    null;
}
<|endoftext|>


<commit_before>import nextTick from './nextTick'
import instrumentations from './builtIns'
import {
  storeObservable,
  storeReaction,
  registerReactionForKey,
  iterateReactionsForKey,
  releaseReaction
} from './store'
import { proxyToRaw, rawToProxy } from './internals'

const ENUMERATE = Symbol('enumerate')
const queuedReactions = new Set()
let runningReaction
const handlers = { get, ownKeys, set, deleteProperty }

export function observe (reaction) {
  if (typeof reaction !== 'function') {
    throw new TypeError('Reactions must be functions.')
  }
  // init basic data structures to save and cleanup (observable.prop -> reaction) connections later
  storeReaction(reaction)
  // run the reaction once to discover what observable properties it uses
  runReaction(reaction)
  return reaction
}

export function unobserve (reaction) {
  // do not run this reaction anymore, even if it is already queued
  queuedReactions.delete(reaction)
  // release every (observable.prop -> reaction) connections
  releaseReaction(reaction)
}

export function unqueue (reaction) {
  // do not run this reaction, if it is not queued again by a prop mutation
  queuedReactions.delete(reaction)
}

export function exec (reaction) {
  runReaction(reaction)
}

export function isObservable (obj) {
  if (typeof obj !== 'object') {
    throw new TypeError('First argument must be an object')
  }
  return proxyToRaw.has(obj)
}

export function observable (obj) {
  obj = obj || {}
  if (typeof obj !== 'object') {
    throw new TypeError('First argument must be an object or undefined')
  }
  // if it is already an observable, return it
  if (proxyToRaw.has(obj)) {
    return obj
  }
  return (
    // if it already has a cached observable wrapper, return it
    // if it is a special built-in object, instrument it then wrap it with an observable
    // otherwise simply wrap the object with an observable
    rawToProxy.get(obj) || instrumentObservable(obj) || createObservable(obj)
  )
}

function instrumentObservable (obj) {
  const instrument = instrumentations.get(Object.getPrototypeOf(obj))
  // these objects break, when they are wrapped with proxies
  if (instrument === false) {
    return obj
  }
  // these objects can be wrapped by Proxies, but require special instrumentation beforehand
  if (typeof instrument === 'function') {
    instrument(obj)
  }
}

// wrap the object in a Proxy and save the obj-proxy, proxy-obj pairs
function createObservable (obj) {
  const observable = new Proxy(obj, handlers)
  // init basic data structures to save and cleanup later (observable.prop -> reaction) connections
  storeObservable(obj)
  // save these to switch between the raw object and the wrapped object with ease later
  proxyToRaw.set(observable, obj)
  rawToProxy.set(obj, observable)
  return observable
}

// intercept get operations on observables to know which reaction uses their properties
function get (obj, key, receiver) {
  // make sure to use the raw object here
  const rawObj = proxyToRaw.get(obj) || obj
  // expose the raw object on observable.$raw
  if (key === '$raw') {
    return rawObj
  }
  const result = Reflect.get(obj, key, receiver)
  // do not register (observable.prop -> reaction) pairs for these cases
  if (typeof key === 'symbol' || typeof result === 'function') {
    return result
  }
  // register and save (observable.prop -> runningReaction)
  registerRunningReactionForKey(rawObj, key)
  // if we are inside a reaction and observable.prop is an object wrap it in an observable too
  // this is needed to intercept property access on that object too (dynamic observable tree)
  if (runningReaction && typeof result === 'object' && result !== null) {
    return observable(result)
  }
  // otherwise return the observable wrapper if it is already created and cached or the raw object
  return rawToProxy.get(result) || result
}

function ownKeys (obj) {
  registerRunningReactionForKey(obj, ENUMERATE)
  return Reflect.ownKeys(obj)
}

// register the currently running reaction to be queued again on obj.key mutations
export function registerRunningReactionForKey (obj, key) {
  if (runningReaction) {
    registerReactionForKey(obj, key, runningReaction)
  }
}

// intercept set operations on observables to know when to trigger reactions
function set (obj, key, value, receiver) {
  // make sure to do not pollute the raw object with observables
  if (typeof value === 'object' && value !== null) {
    value = proxyToRaw.get(value) || value
  }
  // do not register reactions if it is a symbol keyed property
  // or if the target of the operation is not the raw object (possible because of prototypal inheritance)
  if (typeof key === 'symbol' || obj !== proxyToRaw.get(receiver)) {
    return Reflect.set(obj, key, value, receiver)
  }
  // only queue reactions if the set operation resulted in a value change
  // array 'length' property is an exception from this, because of it's exotic nature
  if (key === 'length' || value !== obj[key]) {
    queueReactionsForKey(obj, key)
    queueReactionsForKey(obj, ENUMERATE)
  }
  return Reflect.set(obj, key, value, receiver)
}

function deleteProperty (obj, key) {
  // only queue reactions for non symbol keyed property delete which resulted in an actual change
  if (typeof key !== 'symbol' && key in obj) {
    queueReactionsForKey(obj, key)
    queueReactionsForKey(obj, ENUMERATE)
  }
  return Reflect.deleteProperty(obj, key)
}

export function queueReactionsForKey (obj, key) {
  // register a new reaction running task, if there are no reactions queued yet
  if (!queuedReactions.size) {
    nextTick(runQueuedReactions)
  }
  // iterate and queue every reaction, which is triggered by obj.key mutation
  iterateReactionsForKey(obj, key, queueReaction)
}

function queueReaction (reaction) {
  queuedReactions.add(reaction)
}

function runQueuedReactions () {
  queuedReactions.forEach(runReaction)
  queuedReactions.clear()
}

// set the reaction as the currently running one
// this is required so that we can create (observable.prop -> reaction) pairs in the get trap
function runReaction (reaction) {
  try {
    // delete all existing (obj.key -> reaction) connections
    releaseReaction(reaction)
    runningReaction = reaction
    // and reconstruct them in the get trap while the reaction is running
    reaction()
  } finally {
    // always remove the currently running flag from the reaction when it stops execution
    runningReaction = undefined
  }
}
<commit_msg>refactor (reaction execution): remove reactions from the queue one-by-one after running them<commit_after>import nextTick from './nextTick'
import instrumentations from './builtIns'
import {
  storeObservable,
  storeReaction,
  registerReactionForKey,
  iterateReactionsForKey,
  releaseReaction
} from './store'
import { proxyToRaw, rawToProxy } from './internals'

const ENUMERATE = Symbol('enumerate')
const queuedReactions = new Set()
let runningReaction
const handlers = { get, ownKeys, set, deleteProperty }

export function observe (reaction) {
  if (typeof reaction !== 'function') {
    throw new TypeError('Reactions must be functions.')
  }
  // init basic data structures to save and cleanup (observable.prop -> reaction) connections later
  storeReaction(reaction)
  // run the reaction once to discover what observable properties it uses
  runReaction(reaction)
  return reaction
}

export function unobserve (reaction) {
  // do not run this reaction anymore, even if it is already queued
  queuedReactions.delete(reaction)
  // release every (observable.prop -> reaction) connections
  releaseReaction(reaction)
}

export function unqueue (reaction) {
  // do not run this reaction, if it is not queued again by a prop mutation
  queuedReactions.delete(reaction)
}

export function exec (reaction) {
  runReaction(reaction)
}

export function isObservable (obj) {
  if (typeof obj !== 'object') {
    throw new TypeError('First argument must be an object')
  }
  return proxyToRaw.has(obj)
}

export function observable (obj) {
  obj = obj || {}
  if (typeof obj !== 'object') {
    throw new TypeError('First argument must be an object or undefined')
  }
  // if it is already an observable, return it
  if (proxyToRaw.has(obj)) {
    return obj
  }
  return (
    // if it already has a cached observable wrapper, return it
    // if it is a special built-in object, instrument it then wrap it with an observable
    // otherwise simply wrap the object with an observable
    rawToProxy.get(obj) || instrumentObservable(obj) || createObservable(obj)
  )
}

function instrumentObservable (obj) {
  const instrument = instrumentations.get(Object.getPrototypeOf(obj))
  // these objects break, when they are wrapped with proxies
  if (instrument === false) {
    return obj
  }
  // these objects can be wrapped by Proxies, but require special instrumentation beforehand
  if (typeof instrument === 'function') {
    instrument(obj)
  }
}

// wrap the object in a Proxy and save the obj-proxy, proxy-obj pairs
function createObservable (obj) {
  const observable = new Proxy(obj, handlers)
  // init basic data structures to save and cleanup later (observable.prop -> reaction) connections
  storeObservable(obj)
  // save these to switch between the raw object and the wrapped object with ease later
  proxyToRaw.set(observable, obj)
  rawToProxy.set(obj, observable)
  return observable
}

// intercept get operations on observables to know which reaction uses their properties
function get (obj, key, receiver) {
  // make sure to use the raw object here
  const rawObj = proxyToRaw.get(obj) || obj
  // expose the raw object on observable.$raw
  if (key === '$raw') {
    return rawObj
  }
  const result = Reflect.get(obj, key, receiver)
  // do not register (observable.prop -> reaction) pairs for these cases
  if (typeof key === 'symbol' || typeof result === 'function') {
    return result
  }
  // register and save (observable.prop -> runningReaction)
  registerRunningReactionForKey(rawObj, key)
  // if we are inside a reaction and observable.prop is an object wrap it in an observable too
  // this is needed to intercept property access on that object too (dynamic observable tree)
  if (runningReaction && typeof result === 'object' && result !== null) {
    return observable(result)
  }
  // otherwise return the observable wrapper if it is already created and cached or the raw object
  return rawToProxy.get(result) || result
}

function ownKeys (obj) {
  registerRunningReactionForKey(obj, ENUMERATE)
  return Reflect.ownKeys(obj)
}

// register the currently running reaction to be queued again on obj.key mutations
export function registerRunningReactionForKey (obj, key) {
  if (runningReaction) {
    registerReactionForKey(obj, key, runningReaction)
  }
}

// intercept set operations on observables to know when to trigger reactions
function set (obj, key, value, receiver) {
  // make sure to do not pollute the raw object with observables
  if (typeof value === 'object' && value !== null) {
    value = proxyToRaw.get(value) || value
  }
  // do not register reactions if it is a symbol keyed property
  // or if the target of the operation is not the raw object (possible because of prototypal inheritance)
  if (typeof key === 'symbol' || obj !== proxyToRaw.get(receiver)) {
    return Reflect.set(obj, key, value, receiver)
  }
  // only queue reactions if the set operation resulted in a value change
  // array 'length' property is an exception from this, because of it's exotic nature
  if (key === 'length' || value !== obj[key]) {
    queueReactionsForKey(obj, key)
    queueReactionsForKey(obj, ENUMERATE)
  }
  return Reflect.set(obj, key, value, receiver)
}

function deleteProperty (obj, key) {
  // only queue reactions for non symbol keyed property delete which resulted in an actual change
  if (typeof key !== 'symbol' && key in obj) {
    queueReactionsForKey(obj, key)
    queueReactionsForKey(obj, ENUMERATE)
  }
  return Reflect.deleteProperty(obj, key)
}

export function queueReactionsForKey (obj, key) {
  // register a new reaction running task, if there are no reactions queued yet
  if (!queuedReactions.size) {
    nextTick(runQueuedReactions)
  }
  // iterate and queue every reaction, which is triggered by obj.key mutation
  iterateReactionsForKey(obj, key, queueReaction)
}

function queueReaction (reaction) {
  queuedReactions.add(reaction)
}

function runQueuedReactions () {
  queuedReactions.forEach(runReaction)
}

// set the reaction as the currently running one
// this is required so that we can create (observable.prop -> reaction) pairs in the get trap
function runReaction (reaction) {
  try {
    // delete all existing (obj.key -> reaction) connections
    releaseReaction(reaction)
    runningReaction = reaction
    // and reconstruct them in the get trap while the reaction is running
    reaction()
    // remove the reaction from the queue, it can be requeued again by observable mutations
    queuedReactions.delete(reaction)
  } finally {
    // always remove the currently running flag from the reaction when it stops execution
    runningReaction = undefined
  }
}
<|endoftext|>


<commit_before>transporter.module.create('core/testFramework', function(requires) {}, function(thisModule) {

thisModule.addSlots(avocado, function(add) {

  add.creator('testCase', {}, {category: ['test framework']}, {comment: 'Copied a lot of this from the LK test framework. Don\'t want\nto just use theirs because we might not always be using LK.'});

});

thisModule.addSlots(avocado.testCase, function(add) {

  add.data('shouldRun', true);

  add.method('initialize', function (testResult, optTestSelector) {
		this.result = testResult || Object.newChildOf(this.resultProto);
		this.currentSelector = optTestSelector;
		this.statusUpdateFunc = null;
	});

  add.method('log', function (aString) {
    console.log(aString);
	});

  add.method('id', function () { return this.name() + '>>' + this.currentSelector; });

  add.method('setUp', function () {});

  add.method('tearDown', function () {});

  add.method('assert', function (bool, msg) {
        if (bool) return;
        msg = " assert failed " + msg ? '(' + msg + ')' : '';
		this.show(this.id() + msg);
        throw {isAssertion: true, message: msg, toString: function() { return msg }}
    });

  add.method('assertEqual', function (firstValue, secondValue, msg) {
    if (! this.areEqual(firstValue, secondValue)) {
      throw {isAssertion: true, message: (msg ? msg	 : "") + " (" + firstValue + " != " + secondValue + ") "};
    }
  });

  add.method('assertIdentity', function (firstValue, secondValue, msg) {
		if (firstValue === secondValue) { return; }
		this.assert(false, (msg ? msg : '') + ' (' + firstValue +' !== ' + secondValue +')');
	});

  add.method('allTestSelectors', function () {
    var functionNames = [];
    for (var name in this) {
      if ((name.startsWith('test') || name.startsWith('asynchronouslyTest')) && typeof this[name] === 'function') {
        functionNames.push(name);
      }
    }
    return functionNames;
  });

  add.method('toString', function ($super) {
	    return $super() + "(" + this.timeToRun +")";
	});

  add.method('show', function (string) { this.log(string); });

  add.method('running', function () {
		this.show('Running ' + this.id());
		this.statusUpdateFunc && this.statusUpdateFunc(this, 'running');
	});

  add.method('success', function () {
		this.show(this.id()+ ' done', 'color: green;');
		this.statusUpdateFunc && this.statusUpdateFunc(this, 'success');
	});

  add.method('failure', function (error) {
		this._errorOccured = true;
		var message = error.toString();
		var file = error.sourceURL || error.fileName;
		var line = error.line || error.lineNumber;
		message += ' (' + file + ':' + line + ')';
		message += ' in ' + this.id();
		this.show(message , 'color: red;');
		this.statusUpdateFunc && this.statusUpdateFunc(this, 'failure', message);
	});

  add.method('createTests', function () {
    return this.allTestSelectors().collect(function(sel) {
      return this.create(this.result, sel);
    }, this);
  });

  add.method('name', function () {
    return reflect(this).name();
  });

  add.method('create', function (testResult, optTestSelector) {
    return Object.newChildOf(this, testResult, optTestSelector);
  });

  add.method('createAndRun', function () {
    var testCase = this.create();
    testCase.runAll();
    var result = testCase.result;
    result.testCase = testCase;
    return result;
  });

  add.method('runTest', function (callback) {
    if (!this.shouldRun) return;

		this.running();
		try {
			this.setUp();
			this.runTestFunction(function() {
  			this.result.addSuccess(this.name(), this.currentSelector); // fixed by Adam to say this.name() instead of this.constructor.type
  			this.success();
  			callback();
			}.bind(this));
		} catch (e) {
			this.result.addFailure(this.name(), this.currentSelector, e); // fixed by Adam to say this.name() instead of this.constructor.type
			this.failure(e);
		  callback();
		} finally {
		  this.doTearDown();
		}
	});

  add.method('runTestFunction', function (callback) {
		var testFn = this[this.currentSelector];
		if (this.currentSelector.startsWith('asynchronouslyTest')) {
		  testFn.call(this, callback);
	  } else {
	    testFn.call(this);
	    callback();
	  }
  });

  add.method('doTearDown', function () {
		try {
			this.tearDown();
		} catch(e) {
      var errStr = "" + e.constructor.name + ": ";
      for (var i in e) { s += i + ": " + String(e[i]) + ", " }; // get everything out....
			this.log('Couldn\'t run tearDown for ' + this.id() + ' ' + printError(errStr));
		}
  });

  add.method('inspect', function () {
    return this.name();
  });

  add.method('areEqual', function (firstValue, secondValue) {
    if (firstValue === secondValue) { return true; }
    if (firstValue && firstValue.equals && firstValue.equals(secondValue)) { return true; } // changed this to check a general 'equals' method. -- Adam
    if (firstValue == secondValue) { return true; }
    return false;
  });

  add.method('assertNotEqual', function (firstValue, secondValue, msg) {
    if (this.areEqual(firstValue, secondValue)) {
      throw {isAssertion: true, message: (msg ? msg	 : "") + " (" + firstValue + " == " + secondValue + ") "};
    }
  });

  add.method('assertThrowsException', function (func, msg) {
    var thrown = false;
    try {
      func();
    } catch (ex) {
      thrown = true;
    }
    this.assert(thrown, msg); // can't put this inside the try because it works by throwing an exception;
  });

  add.method('runAll', function (statusUpdateFunc) {
		var tests = this.createTests();
		var totalTime = 0;
		tests.forEach(function(test) {
  		var t1 = new Date().getTime();
			test.statusUpdateFunc = statusUpdateFunc;
			test.runTest(function() {
    		var t2 = new Date().getTime();
    		this.result.incrementTimeToRun(this.name(), t2 - t1);
			}.bind(this));
		}.bind(this));
	});

  add.creator('resultProto', {}, {category: ['results']});

  add.method('createAndRunAndShowResult', function () {
    avocado.ui.showNextTo(this, this.createAndRun(), this);
  }, {category: ['user interface', 'commands']});

  add.method('getTestCaseObject', function (evt) {
    avocado.ui.grab(reflect(this), evt);
  }, {category: ['user interface', 'commands']});

  add.method('commands', function () {
    var cmdList = avocado.command.list.create(this);
    cmdList.addItem({label: 'run', pluralLabel: 'run tests', go: this.createAndRunAndShowResult});
    cmdList.addLine();
    cmdList.addItem({label: 'get test case object', go: this.getTestCaseObject});
    return cmdList;
  }, {category: ['user interface', 'commands']});

  add.method('buttonCommands', function () {
    return avocado.command.list.create(this, [
      avocado.command.create('Run', this.createAndRunAndShowResult)
    ]);
  }, {category: ['user interface', 'commands']});

  add.method('addGlobalCommandsTo', function (cmdList) {
    cmdList.addLine();

    cmdList.addItem(["get tests", function(evt) {
      avocado.ui.showObjects(this.allAvocadoTestCasePrototypes(), "test cases for avocado");
    }.bind(this)]);
  }, {category: ['user interface', 'commands']});

  add.method('allAvocadoTestCasePrototypes', function () {
    // aaa - This should be replaced with a more general mechanism for finding tests.
    return [
      avocado.dictionary.tests,
      avocado.set.tests,
      avocado.mirror.tests,
      transporter.tests,
      avocado.objectGraphWalker.tests,
      exitValueOf.tests,
      avocado.enumerator.tests,
      avocado.range.tests,
      avocado.notifier.tests,
      avocado.stringBuffer.tests,
      String.prototype.tests,
      Array.prototype.tests,
      avocado.dependencies.tests,
      avocado.list.tests,
      avocado.prettyPrinter.tests,
      avocado.organization.tests,
      avocado.process.tests,
      avocado.remoteMirror.tests
    ];
  });

});

thisModule.addSlots(avocado.testCase.resultProto, function(add) {

  add.method('initialize', function () {
		this.failed = [];
		this.succeeded = [];
		this.timeToRun = {};
	});

  add.method('incrementTimeToRun', function (testCaseName, time) {
	    return this.timeToRun[testCaseName] = (this.timeToRun[testCaseName] || 0) + time;
	});

  add.method('setTimeToRun', function (testCaseName, time) {
	    return this.timeToRun[testCaseName] = time;
	});

  add.method('getTimeToRun', function (testCaseName) {
	    return this.timeToRun[testCaseName];
	});

  add.method('addSuccess', function (className, selector) {
		this.succeeded.push({
				classname: className,
				selector: selector});
	});

  add.method('addFailure', function (className, selector, error) {
	  // Better error message than the standard LK one.
	  var failure = {
				classname: className,
				selector: selector,
				err: error
		};
		failure.toString = this.failureDescription.bind(this, failure);
		this.failed.push(failure);
  });

  add.method('failureDescription', function (failure) {
    var s = avocado.stringBuffer.create(failure.selector).append(" failed ");
    if (failure.err.sourceURL !== undefined) {
      s.append("(").append(this.getFileNameFromError(failure.err));
      if (failure.err.line !== undefined) {
        s.append(":").append(failure.err.line);
      }
      s.append("): ");
    }
    s.append(typeof(failure.err.message) !== 'undefined' ? failure.err.message : failure.err);
    return s.toString();
  });

  add.method('runs', function () {
		if (!this.failed)
			return 0;
		return this.failed.length + this.succeeded.length;
	});

  add.method('toString', function () {
    return this.inspect();
	});

  add.method('getFileNameFromError', function (err) {
    if (!err.sourceURL) { return ""; }
    var path = err.sourceURL.split("/");
    return path[path.length - 1].split("?")[0];
	});

  add.method('failureList', function () {
		var result = this.failed.collect(function(ea) {
			return ea.classname + '.' + ea.selector + '\n  -->' + ea.err.constructor.name + ":" + ea.err.message  +
	            ' in ' + this.getFileNameFromError(ea.err) +
	            (ea.err.line ? ' ( Line '+ ea.err.line + ')' : "");
		}, this);
		return result;
	});

  add.method('successList', function () {
		return this.succeeded.collect(function(ea) { return ea.classname + '.' + ea.selector });
	});

  add.method('totalTimeToRun', function () {
    var total = 0;
    for (var name in this.timeToRun) {
      if (this.timeToRun.hasOwnProperty(name)) {
        var t = this.timeToRun[name];
        if (typeof(t) === 'number') {
          total += t;
        }
      }
    }
    return total;
  });

  add.method('inspect', function () {
    return this.testCase.name() + "(" + this.totalTimeToRun() + " ms)";
  });

  add.method('allPassed', function () {
    return this.failed.length === 0;
  });

  add.method('anyFailed', function () {
    return ! this.allPassed();
  });

  add.method('getErrorObjects', function (evt) {
    this.failed.each(function(f) {
      avocado.ui.grab(reflect(f.err), evt);
    });
  }, {category: ['user interface', 'commands']});

  add.method('commands', function () {
    var cmdList = avocado.command.list.create();
    cmdList.addItem({label: 'get error objects', go: this.getErrorObjects.bind(this), isApplicable: this.anyFailed.bind(this)});
    return cmdList;
  }, {category: ['user interface', 'commands']});

});

});
<commit_msg>Commented out a couple of annoying log messages.<commit_after>transporter.module.create('core/testFramework', function(requires) {}, function(thisModule) {

thisModule.addSlots(avocado, function(add) {

  add.creator('testCase', {}, {category: ['test framework']}, {comment: 'Copied a lot of this from the LK test framework. Don\'t want\nto just use theirs because we might not always be using LK.'});

});

thisModule.addSlots(avocado.testCase, function(add) {

  add.data('shouldRun', true);

  add.method('initialize', function (testResult, optTestSelector) {
		this.result = testResult || Object.newChildOf(this.resultProto);
		this.currentSelector = optTestSelector;
		this.statusUpdateFunc = null;
	});

  add.method('log', function (aString) {
    console.log(aString);
	});

  add.method('id', function () { return this.name() + '>>' + this.currentSelector; });

  add.method('setUp', function () {});

  add.method('tearDown', function () {});

  add.method('assert', function (bool, msg) {
        if (bool) return;
        msg = " assert failed " + msg ? '(' + msg + ')' : '';
		this.show(this.id() + msg);
        throw {isAssertion: true, message: msg, toString: function() { return msg }}
    });

  add.method('assertEqual', function (firstValue, secondValue, msg) {
    if (! this.areEqual(firstValue, secondValue)) {
      throw {isAssertion: true, message: (msg ? msg	 : "") + " (" + firstValue + " != " + secondValue + ") "};
    }
  });

  add.method('assertIdentity', function (firstValue, secondValue, msg) {
		if (firstValue === secondValue) { return; }
		this.assert(false, (msg ? msg : '') + ' (' + firstValue +' !== ' + secondValue +')');
	});

  add.method('allTestSelectors', function () {
    var functionNames = [];
    for (var name in this) {
      if ((name.startsWith('test') || name.startsWith('asynchronouslyTest')) && typeof this[name] === 'function') {
        functionNames.push(name);
      }
    }
    return functionNames;
  });

  add.method('toString', function ($super) {
	    return $super() + "(" + this.timeToRun +")";
	});

  add.method('show', function (string) { this.log(string); });

  add.method('running', function () {
		// this.show('Running ' + this.id()); // this is annoying -- Adam
		this.statusUpdateFunc && this.statusUpdateFunc(this, 'running');
	});

  add.method('success', function () {
		// this.show(this.id()+ ' done', 'color: green;'); // -- also annoying -- Adam
		this.statusUpdateFunc && this.statusUpdateFunc(this, 'success');
	});

  add.method('failure', function (error) {
		this._errorOccured = true;
		var message = error.toString();
		var file = error.sourceURL || error.fileName;
		var line = error.line || error.lineNumber;
		message += ' (' + file + ':' + line + ')';
		message += ' in ' + this.id();
		this.show(message , 'color: red;');
		this.statusUpdateFunc && this.statusUpdateFunc(this, 'failure', message);
	});

  add.method('createTests', function () {
    return this.allTestSelectors().collect(function(sel) {
      return this.create(this.result, sel);
    }, this);
  });

  add.method('name', function () {
    return reflect(this).name();
  });

  add.method('create', function (testResult, optTestSelector) {
    return Object.newChildOf(this, testResult, optTestSelector);
  });

  add.method('createAndRun', function () {
    var testCase = this.create();
    testCase.runAll();
    var result = testCase.result;
    result.testCase = testCase;
    return result;
  });

  add.method('runTest', function (callback) {
    if (!this.shouldRun) return;

		this.running();
		try {
			this.setUp();
			this.runTestFunction(function() {
  			this.result.addSuccess(this.name(), this.currentSelector); // fixed by Adam to say this.name() instead of this.constructor.type
  			this.success();
  			callback();
			}.bind(this));
		} catch (e) {
			this.result.addFailure(this.name(), this.currentSelector, e); // fixed by Adam to say this.name() instead of this.constructor.type
			this.failure(e);
		  callback();
		} finally {
		  this.doTearDown();
		}
	});

  add.method('runTestFunction', function (callback) {
		var testFn = this[this.currentSelector];
		if (this.currentSelector.startsWith('asynchronouslyTest')) {
		  testFn.call(this, callback);
	  } else {
	    testFn.call(this);
	    callback();
	  }
  });

  add.method('doTearDown', function () {
		try {
			this.tearDown();
		} catch(e) {
      var errStr = "" + e.constructor.name + ": ";
      for (var i in e) { s += i + ": " + String(e[i]) + ", " }; // get everything out....
			this.log('Couldn\'t run tearDown for ' + this.id() + ' ' + printError(errStr));
		}
  });

  add.method('inspect', function () {
    return this.name();
  });

  add.method('areEqual', function (firstValue, secondValue) {
    if (firstValue === secondValue) { return true; }
    if (firstValue && firstValue.equals && firstValue.equals(secondValue)) { return true; } // changed this to check a general 'equals' method. -- Adam
    if (firstValue == secondValue) { return true; }
    return false;
  });

  add.method('assertNotEqual', function (firstValue, secondValue, msg) {
    if (this.areEqual(firstValue, secondValue)) {
      throw {isAssertion: true, message: (msg ? msg	 : "") + " (" + firstValue + " == " + secondValue + ") "};
    }
  });

  add.method('assertThrowsException', function (func, msg) {
    var thrown = false;
    try {
      func();
    } catch (ex) {
      thrown = true;
    }
    this.assert(thrown, msg); // can't put this inside the try because it works by throwing an exception;
  });

  add.method('runAll', function (statusUpdateFunc) {
		var tests = this.createTests();
		var totalTime = 0;
		tests.forEach(function(test) {
  		var t1 = new Date().getTime();
			test.statusUpdateFunc = statusUpdateFunc;
			test.runTest(function() {
    		var t2 = new Date().getTime();
    		this.result.incrementTimeToRun(this.name(), t2 - t1);
			}.bind(this));
		}.bind(this));
	});

  add.creator('resultProto', {}, {category: ['results']});

  add.method('createAndRunAndShowResult', function () {
    avocado.ui.showNextTo(this, this.createAndRun(), this);
  }, {category: ['user interface', 'commands']});

  add.method('getTestCaseObject', function (evt) {
    avocado.ui.grab(reflect(this), evt);
  }, {category: ['user interface', 'commands']});

  add.method('commands', function () {
    var cmdList = avocado.command.list.create(this);
    cmdList.addItem({label: 'run', pluralLabel: 'run tests', go: this.createAndRunAndShowResult});
    cmdList.addLine();
    cmdList.addItem({label: 'get test case object', go: this.getTestCaseObject});
    return cmdList;
  }, {category: ['user interface', 'commands']});

  add.method('buttonCommands', function () {
    return avocado.command.list.create(this, [
      avocado.command.create('Run', this.createAndRunAndShowResult)
    ]);
  }, {category: ['user interface', 'commands']});

  add.method('addGlobalCommandsTo', function (cmdList) {
    cmdList.addLine();

    cmdList.addItem(["get tests", function(evt) {
      avocado.ui.showObjects(this.allAvocadoTestCasePrototypes(), "test cases for avocado");
    }.bind(this)]);
  }, {category: ['user interface', 'commands']});

  add.method('allAvocadoTestCasePrototypes', function () {
    // aaa - This should be replaced with a more general mechanism for finding tests.
    return [
      avocado.dictionary.tests,
      avocado.set.tests,
      avocado.mirror.tests,
      transporter.tests,
      avocado.objectGraphWalker.tests,
      exitValueOf.tests,
      avocado.enumerator.tests,
      avocado.range.tests,
      avocado.notifier.tests,
      avocado.stringBuffer.tests,
      String.prototype.tests,
      Array.prototype.tests,
      avocado.dependencies.tests,
      avocado.list.tests,
      avocado.prettyPrinter.tests,
      avocado.organization.tests,
      avocado.process.tests,
      avocado.remoteMirror.tests
    ];
  });

});

thisModule.addSlots(avocado.testCase.resultProto, function(add) {

  add.method('initialize', function () {
		this.failed = [];
		this.succeeded = [];
		this.timeToRun = {};
	});

  add.method('incrementTimeToRun', function (testCaseName, time) {
	    return this.timeToRun[testCaseName] = (this.timeToRun[testCaseName] || 0) + time;
	});

  add.method('setTimeToRun', function (testCaseName, time) {
	    return this.timeToRun[testCaseName] = time;
	});

  add.method('getTimeToRun', function (testCaseName) {
	    return this.timeToRun[testCaseName];
	});

  add.method('addSuccess', function (className, selector) {
		this.succeeded.push({
				classname: className,
				selector: selector});
	});

  add.method('addFailure', function (className, selector, error) {
	  // Better error message than the standard LK one.
	  var failure = {
				classname: className,
				selector: selector,
				err: error
		};
		failure.toString = this.failureDescription.bind(this, failure);
		this.failed.push(failure);
  });

  add.method('failureDescription', function (failure) {
    var s = avocado.stringBuffer.create(failure.selector).append(" failed ");
    if (failure.err.sourceURL !== undefined) {
      s.append("(").append(this.getFileNameFromError(failure.err));
      if (failure.err.line !== undefined) {
        s.append(":").append(failure.err.line);
      }
      s.append("): ");
    }
    s.append(typeof(failure.err.message) !== 'undefined' ? failure.err.message : failure.err);
    return s.toString();
  });

  add.method('runs', function () {
		if (!this.failed)
			return 0;
		return this.failed.length + this.succeeded.length;
	});

  add.method('toString', function () {
    return this.inspect();
	});

  add.method('getFileNameFromError', function (err) {
    if (!err.sourceURL) { return ""; }
    var path = err.sourceURL.split("/");
    return path[path.length - 1].split("?")[0];
	});

  add.method('failureList', function () {
		var result = this.failed.collect(function(ea) {
			return ea.classname + '.' + ea.selector + '\n  -->' + ea.err.constructor.name + ":" + ea.err.message  +
	            ' in ' + this.getFileNameFromError(ea.err) +
	            (ea.err.line ? ' ( Line '+ ea.err.line + ')' : "");
		}, this);
		return result;
	});

  add.method('successList', function () {
		return this.succeeded.collect(function(ea) { return ea.classname + '.' + ea.selector });
	});

  add.method('totalTimeToRun', function () {
    var total = 0;
    for (var name in this.timeToRun) {
      if (this.timeToRun.hasOwnProperty(name)) {
        var t = this.timeToRun[name];
        if (typeof(t) === 'number') {
          total += t;
        }
      }
    }
    return total;
  });

  add.method('inspect', function () {
    return this.testCase.name() + "(" + this.totalTimeToRun() + " ms)";
  });

  add.method('allPassed', function () {
    return this.failed.length === 0;
  });

  add.method('anyFailed', function () {
    return ! this.allPassed();
  });

  add.method('getErrorObjects', function (evt) {
    this.failed.each(function(f) {
      avocado.ui.grab(reflect(f.err), evt);
    });
  }, {category: ['user interface', 'commands']});

  add.method('commands', function () {
    var cmdList = avocado.command.list.create();
    cmdList.addItem({label: 'get error objects', go: this.getErrorObjects.bind(this), isApplicable: this.anyFailed.bind(this)});
    return cmdList;
  }, {category: ['user interface', 'commands']});

});

});
<|endoftext|>