summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordharcourt@chromium.org <dharcourt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-08 16:59:14 +0000
committerdharcourt@chromium.org <dharcourt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2012-10-08 16:59:14 +0000
commite58ee7751304b4d210a3dbeaf2cce611cb37316f (patch)
tree579e15d2dcb62db4ab3a94f17eba9be8b2d90a8c
parentc15f1a868de68bef581ee5fe3152734f4ac1530b (diff)
downloadchromium_src-e58ee7751304b4d210a3dbeaf2cce611cb37316f.zip
chromium_src-e58ee7751304b4d210a3dbeaf2cce611cb37316f.tar.gz
chromium_src-e58ee7751304b4d210a3dbeaf2cce611cb37316f.tar.bz2
Fixed calculator app decimal behavior and added tests.
Fixed a number of bugs with the calculator's handling of decimals by switching the model from using JavaScript numbers to using strings. Added a number of manual unit tests currently available through model_tests.html, to be converted to automated tests eventually. Added utilities to allow calculator tests to be written more easily. Fixed a few calculator UI bugs. BUG=150880 Review URL: https://chromiumcodereview.appspot.com/11030043 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@160666 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/common/extensions/docs/examples/apps/calculator/LICENSE27
-rw-r--r--chrome/common/extensions/docs/examples/apps/calculator/README.md5
-rw-r--r--chrome/common/extensions/docs/examples/apps/calculator/background.js (renamed from chrome/common/extensions/docs/examples/apps/calculator/main.js)3
-rw-r--r--chrome/common/extensions/docs/examples/apps/calculator/calculator.html69
-rw-r--r--chrome/common/extensions/docs/examples/apps/calculator/controller.js8
-rw-r--r--chrome/common/extensions/docs/examples/apps/calculator/manifest.json14
-rw-r--r--chrome/common/extensions/docs/examples/apps/calculator/model.js219
-rw-r--r--chrome/common/extensions/docs/examples/apps/calculator/style.css370
-rw-r--r--chrome/common/extensions/docs/examples/apps/calculator/tests/calculator_test.html36
-rw-r--r--chrome/common/extensions/docs/examples/apps/calculator/tests/calculator_test.js175
-rw-r--r--chrome/common/extensions/docs/examples/apps/calculator/tests/model_test_utilities.js275
-rw-r--r--chrome/common/extensions/docs/examples/apps/calculator/tests/model_tests.html27
-rw-r--r--chrome/common/extensions/docs/examples/apps/calculator/tests/model_tests.js144
-rw-r--r--chrome/common/extensions/docs/examples/apps/calculator/view.js293
14 files changed, 939 insertions, 726 deletions
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/LICENSE b/chrome/common/extensions/docs/examples/apps/calculator/LICENSE
new file mode 100644
index 0000000..e6c0d72
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/LICENSE
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+// * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+// * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+// * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/README.md b/chrome/common/extensions/docs/examples/apps/calculator/README.md
index 6d5fb7d..7ec1705 100644
--- a/chrome/common/extensions/docs/examples/apps/calculator/README.md
+++ b/chrome/common/extensions/docs/examples/apps/calculator/README.md
@@ -1,7 +1,7 @@
# Calculator
-A sample application that provides a simple calculator. Supports basic operations
-such as addition, multiplication, subtraction and division.
+A sample application that provides a simple calculator. Supports basic
+operations such as addition, multiplication, subtraction and division.
This sample also incorporates an MVC-style structure and requires jQuery for
DOM manipulation.
@@ -10,4 +10,3 @@ DOM manipulation.
* [Runtime](http://developer.chrome.com/trunk/apps/app.runtime.html)
* [Window](http://developer.chrome.com/trunk/apps/app.window.html)
-
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/main.js b/chrome/common/extensions/docs/examples/apps/calculator/background.js
index 4ea62c8..958f84b 100644
--- a/chrome/common/extensions/docs/examples/apps/calculator/main.js
+++ b/chrome/common/extensions/docs/examples/apps/calculator/background.js
@@ -4,9 +4,8 @@
* found in the LICENSE file.
**/
-
/**
- * Listens for the app launching then creates the window
+ * Listens for the app launching then creates the window.
*
* @see http://developer.chrome.com/trunk/apps/app.window.html
*/
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/calculator.html b/chrome/common/extensions/docs/examples/apps/calculator/calculator.html
index 5ed4709..3cfe4a4 100644
--- a/chrome/common/extensions/docs/examples/apps/calculator/calculator.html
+++ b/chrome/common/extensions/docs/examples/apps/calculator/calculator.html
@@ -1,20 +1,53 @@
<html>
- <head>
- <title>Calculator</title>
- <link rel="stylesheet" type="text/css" href="style.css" />
- <script src="jquery/jquery.min.js"></script>
- <script src="controller.js"></script>
- <script src="model.js"></script>
- <script src="view.js"></script>
- </head>
-
- <body>
- <div id="calc">
- <div class="edge-top"></div>
- <div class="edge"></div>
- <div id="display" class="calc-display">
- </div>
- <div id="buttons"></div>
- </div>
- </body>
+ <head>
+ <title>Calculator</title>
+ <link rel="stylesheet" type="text/css" href="style.css" />
+ <script src="jquery/jquery.min.js"></script>
+ <script src="model.js"></script>
+ <script src="view.js"></script>
+ <script src="controller.js"></script>
+ </head>
+ <body>
+ <div id="calculator">
+ <div id="top-edge"></div>
+ <div id="top-fade"></div>
+ <div id="display">
+ <div class="equation">
+ <div class="accumulator"></div>
+ <div class="operand">0</div>
+ <div class="operator"></div>
+ </div>
+ </div>
+ <div id="buttons">
+ <div>
+ <div class="button clear"></div>
+ <div class="button negate"></div>
+ <div class="button divide"></div>
+ <div class="button multiply"></div>
+ </div>
+ <div>
+ <div class="button seven"></div>
+ <div class="button eight"></div>
+ <div class="button nine"></div>
+ <div class="button subtract"></div>
+ </div>
+ <div>
+ <div class="button four"></div>
+ <div class="button five"></div>
+ <div class="button six"></div>
+ <div class="button add"></div>
+ </div>
+ <div>
+ <div class="button one"></div>
+ <div class="button two"></div>
+ <div class="button three"></div>
+ <div class="button equals"></div>
+ </div>
+ <div>
+ <div class="button zero"></div>
+ <div class="button point"></div>
+ </div>
+ </div>
+ </div>
+ </body>
</html>
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/controller.js b/chrome/common/extensions/docs/examples/apps/calculator/controller.js
index 73ead52..21c3ae9 100644
--- a/chrome/common/extensions/docs/examples/apps/calculator/controller.js
+++ b/chrome/common/extensions/docs/examples/apps/calculator/controller.js
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ **/
+
$(document).ready(function () {
- new View(new Calculator);
+ new View(new Model(8));
});
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/manifest.json b/chrome/common/extensions/docs/examples/apps/calculator/manifest.json
index b4b6cd0..9509dde 100644
--- a/chrome/common/extensions/docs/examples/apps/calculator/manifest.json
+++ b/chrome/common/extensions/docs/examples/apps/calculator/manifest.json
@@ -3,16 +3,8 @@
"description": "A simple calculator.",
"manifest_version": 2,
"minimum_chrome_version": "23",
- "version": "1.1.3",
+ "version": "1.2",
"offline_enabled": true,
- "app": {
- "background": {
- "scripts": ["main.js"]
- }
- },
-
- "icons": {
- "16": "assets/icon-128x128.png",
- "128": "assets/icon-128x128.png"
- }
+ "app": {"background": {"scripts": ["background.js"]}},
+ "icons": {"128": "assets/icon-128x128.png"}
}
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/model.js b/chrome/common/extensions/docs/examples/apps/calculator/model.js
index b341c7b..5845180 100644
--- a/chrome/common/extensions/docs/examples/apps/calculator/model.js
+++ b/chrome/common/extensions/docs/examples/apps/calculator/model.js
@@ -1,129 +1,122 @@
-function Calculator() {
- this.operatorNeedsReset = true;
- this.operandNeedsReset = true;
- this.accumulatorNeedsReset = true;
- this.decimal = -1;
- this.ResetRegisters();
+/**
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ **/
+
+function Model(precision) {
+ this.reset_({precision: precision});
}
-Calculator.prototype.DoOperation = function() {
- switch (this.operator) {
+/**
+ * Handles a calculator event, updating the calculator state accordingly and
+ * returning an object with 'accumulator', 'operator', and 'operand' properties
+ * representing that state.
+ *
+ * @private
+ */
+Model.prototype.handle = function (event) {
+ switch (event) {
case '+':
- this.accumulator += this.operand;
- break;
case '-':
- this.accumulator -= this.operand;
- break;
case '/':
- this.accumulator /= this.operand;
- break;
case '*':
- this.accumulator *= this.operand;
- break;
+ // For operations, ignore the last operator if no operand was entered,
+ // otherwise perform the current calculation before setting the new
+ // operator. In either case, clear the operand and the defaults.
+ var operator = this.operand && this.operator;
+ var result = this.calculate_(operator, this.operand);
+ return this.reset_({accumulator: result, operator: event});
+ case '=':
+ // For the equal sign, perform the current calculation and save the
+ // operator and operands used as defaults, or if there is no current
+ // operator, use the default operators and operands instead. In any case,
+ // clear the operator and operand and return a transient state with a '='
+ // operator.
+ var operator = this.operator || this.defaults.operator;
+ var operand = this.operator ? this.operand : this.defaults.operand;
+ var result = this.calculate_(operator, operand);
+ var defaults = {operator: operator, operand: this.operand};
+ return this.reset_({accumulator: result, defaults: defaults});
+ case 'AC':
+ return this.reset_({});
+ case 'C':
+ return this.operand ? this.set_({operand: null}) :
+ this.operator ? this.set_({operator: null}) :
+ this.handle('AC');
+ case 'back':
+ var length = (this.operand || '').length;
+ return (length > 1) ? this.set_({operand: this.operand.slice(0, -1)}) :
+ this.operand ? this.set_({operand: null}) :
+ this.set_({operator: null});
+ case '+ / -':
+ var initial = (this.operand || '0')[0];
+ return (initial === '-') ? this.set_({operand: this.operand.slice(1)}) :
+ (initial !== '0') ? this.set_({operand: '-' + this.operand}) :
+ this.set_({});
default:
- this.accumulator = this.operand;
- break;
+ var operand = (this.operand || '0') + event;
+ var duplicate = (operand.replace(/[^.]/g, '').length > 1);
+ var overflow = (operand.replace(/[^0-9]/g, '').length > this.precision);
+ return operand.match(/^0[0-9]/) ? this.set_({operand: operand[1]}) :
+ (!duplicate && !overflow) ? this.set_({operand: operand}) :
+ this.set_({});
}
- return this.accumulator;
}
-Calculator.prototype.SendAccumulatorByUDP = function() {
- var calcResult = this.accumulator;
+/**
+ * Reset the model's state to the passed in state.
+ *
+ * @private
+ */
+Model.prototype.reset_ = function (state) {
+ this.accumulator = this.operand = this.operator = null;
+ this.defaults = {operator: null, operand: null};
+ return this.set_(state);
}
-Calculator.prototype.ResetRegisters = function() {
- if (this.operatorNeedsReset) {
- this.operatorNeedsReset = false;
- this.operator = null;
- }
- if (this.operandNeedsReset) {
- this.operandNeedsReset = false;
- this.operand = 0;
- this.decimal = -1;
- }
- if (this.accumulatorNeedsReset) {
- this.accumulatorNeedsReset = false;
- this.accumulator = 0;
- }
+/**
+ * Selectively replace the model's state with the passed in state.
+ *
+ * @private
+ */
+Model.prototype.set_ = function (state) {
+ var ifDefined = function (x, y) { return (x !== undefined) ? x : y; };
+ var precision = (state && state.precision) || this.precision || 9;
+ this.precision = Math.min(Math.max(precision, 1), 9);
+ this.accumulator = ifDefined(state && state.accumulator, this.accumulator);
+ this.operator = ifDefined(state && state.operator, this.operator);
+ this.operand = ifDefined(state && state.operand, this.operand);
+ this.defaults = ifDefined(state && state.defaults, this.defaults);
+ return this;
}
-Calculator.prototype.HandleButtonClick = function(buttonValue) {
- var result;
-
- switch (buttonValue) {
- case '+':
- case '-':
- case '/':
- case '*':
- this.decimal = -1;
- if (this.operatorNeedsReset) {
- this.operatorNeedsReset = false;
- this.operator = null;
- this.operand = this.accumulator;
- }
- this.operandNeedsReset = true;
- result = this.DoOperation();
- this.operator = buttonValue;
- break;
- case '=':
- this.decimal = -1;
- this.operandNeedsReset = true;
- this.operatorNeedsReset = true;
- this.DoOperation();
- this.SendAccumulatorByUDP();
- break;
- case 'AC':
- this.decimal = -1;
- this.accumulatorNeedsReset = true;
- this.operandNeedsReset = true;
- this.operatorNeedsReset = true;
- this.ResetRegisters();
- break;
- case '.':
- if (this.decimal < 0)
- this.decimal = 0;
- this.operand = parseFloat(this.operand);
- result = this.operand;
- break;
- case '+ / -':
- this.operand *= -1;
- break;
- case 'back':
- this.accumulatorNeedsReset = false;
- this.ResetRegisters();
- if (this.operand == 0) {
- this.operator = 'back';
- this.operatorNeedsReset = true;
- }
- else {
- var operandStr = this.operand + '';
- operandStr = operandStr.slice(0, operandStr.length - 1);
- if (operandStr == '') this.operand = 0;
- else this.operand = parseFloat(operandStr);
- }
- break;
- default:
- this.ResetRegisters();
- if (this.decimal >= 0) {
- this.decimal += 1;
- this.operand += ( Math.pow(10, -1 * this.decimal)
- * parseInt(buttonValue));
- } else {
- this.operand *= 10;
- this.operand += parseInt(buttonValue);
- }
- result = this.operand;
- break;
- }
-
- if (result == null) {
- result = this.accumulator;
- }
+/**
+ * Performs a calculation based on the passed in operator and operand, updating
+ * the model's state with the operator and operand used but returning the result
+ * of the calculation instead of updating the model's state with it.
+ *
+ * @private
+ */
+Model.prototype.calculate_ = function (operator, operand) {
+ var x = Number(this.accumulator) || 0;
+ var y = operand ? Number(operand) : x;
+ this.set_({accumulator: String(x), operator: operator, operand: String(y)});
+ return (this.operator == '+') ? this.round_(x + y) :
+ (this.operator == '-') ? this.round_(x - y) :
+ (this.operator == '*') ? this.round_(x * y) :
+ (this.operator == '/') ? this.round_(x / y) :
+ this.round_(y);
+}
- var rstr_len = (result + '').length;
- if ((result >= 0 && rstr_len > 8) ||
- (result < 0 && rstr_len > 9)) {
- result = 'Overflow';
- }
- return [this.operator, this.operand, this.accumulator];
+/**
+ * Returns the string representation of the passed in value rounded to the
+ * model's precision, or "+Infinity" or "-Infinity" on overflow.
+ *
+ * @private
+ */
+Model.prototype.round_ = function (x) {
+ var rounded = String(Number(x.toFixed(this.precision - 1)));
+ var overflow = (rounded.replace(/[^0-9]/g, '').length > this.precision);
+ return (overflow || Math.abs(x) == Infinity) ? 'E' : rounded;
}
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/style.css b/chrome/common/extensions/docs/examples/apps/calculator/style.css
index 40ce70f..5015f37 100644
--- a/chrome/common/extensions/docs/examples/apps/calculator/style.css
+++ b/chrome/common/extensions/docs/examples/apps/calculator/style.css
@@ -1,28 +1,15 @@
-html {
- height: 380px;
- margin: 0;
- padding: 0;
- width: 244px;
- font-family: "Open Sans", "Hevatica Neue", "Helvetica", "Arial", sans-serif;
- font-weight: 700;
- font-size: 16px;
- overflow: hidden;
-}
+/**
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ **/
body {
margin: 0px;
padding: 0px;
}
-::-webkit-scrollbar {
- display: none;
-}
-
-::-webkit-scrollbar-thumb {
- display: none;
-}
-
-#calc {
+#calculator {
background-color: #fff;
border: 0;
margin: 0;
@@ -31,92 +18,7 @@ body {
padding: 0px;
}
-#calc #display,
-#calc #display:focus {
- border: none;
- bottom: 225px;
- letter-spacing: 1px;
- line-height: 20px;
- margin: 0px;
- min-width: 204px;
- overflow: scroll;
- padding: 20px;
- position: absolute;
- top: 0px;
-}
-
-#calc .edge-top {
- height: 5px;
- width: 244px;
- z-index: 99;
- position: absolute;
- background: #fff;
-}
-
-#calc .edge {
- background: -webkit-linear-gradient(top, rgba(255,255,255,1) 0%,
- rgba(255,255,255,0) 100%);
- background: linear-gradient(to bottom, rgba(255,255,255,1) 0%,
- rgba(255,255,255,0) 100%);
- height: 20px;
- position: absolute;
- top: 5px;
- width: 244px;
- z-index: 99;
-}
-
-.equation {
- width: 100%;
- position: relative;
- clear: both;
- height: 22px;
-}
-
-.equation .operator {
- color: #2c2c2c;
- width: 15px;
- float: right;
- padding-right: 5px;
- height: 22px;
- line-height: 16px;
- padding-top: 3px;
- padding-bottom: 3px;
-}
-
-.equation .operand {
- color: #2c2c2c;
- float: right;
- max-width: 80px;
- text-align: right;
- overflow: hidden;
- height: 16px;
- line-height: 16px;
- padding-top: 3px;
- padding-bottom: 3px;
-}
-
-.equation .accumulator {
- color: #888;
- float: left;
- font-size: 13px;
- width: 85px;
- max-width: 80px;
- text-align: left;
- overflow: hidden;
- height: 13px;
- line-height: 13px;
- padding-top: 6px;
- padding-bottom: 3px;
-}
-
-#display .hr {
- width: 100%;
- height: 0px;
- border-top: 1px solid #d9d9d9;
- position: relative;
-}
-
-#calc #buttons {
+#calculator #buttons {
height: 225px;
max-width: 244px;
min-width: 244px;
@@ -124,196 +26,298 @@ body {
bottom: 0px;
}
-.calc-button {
- height: 45px;
+#calculator #buttons .button {
width: 61px;
+ height: 45px;
float: left;
}
-.calc-button.AC {
- background: url('img/1x/normal/52112_calculator_ac_btn.png');
+#calculator #buttons .button.add {
+ background: url('img/1x/normal/52112_calculator_addition_btn.png');
background-size: 61px 45px;
}
-.calc-button.AC:active {
- background: url('img/1x/pressed/52112_calculator_ac_btn.png');
+#calculator #buttons .button.add:active {
+ background: url('img/1x/pressed/52112_calculator_addition_btn.png');
background-size: 61px 45px;
}
-.calc-button.plus-minus {
- background: url('img/1x/normal/52112_calculator_positivenegative_btn.png');
+#calculator #buttons .button.clear {
+ background: url('img/1x/normal/52112_calculator_ac_btn.png');
background-size: 61px 45px;
}
-.calc-button.plus-minus:active {
- background: url('img/1x/pressed/52112_calculator_positivenegative_btn.png');
+#calculator #buttons .button.clear:active {
+ background: url('img/1x/pressed/52112_calculator_ac_btn.png');
background-size: 61px 45px;
}
-.calc-button.div {
+#calculator #buttons .button.divide {
background: url('img/1x/normal/52112_calculator_division_btn.png');
background-size: 61px 45px;
}
-.calc-button.div:active {
+#calculator #buttons .button.divide:active {
background: url('img/1x/pressed/52112_calculator_division_btn.png');
background-size: 61px 45px;
}
-.calc-button.mult {
- background: url('img/1x/normal/52112_calculator_multiplication_btn.png');
+#calculator #buttons .button.eight {
+ background: url('img/1x/normal/52112_calculator_8_btn.png');
background-size: 61px 45px;
}
-.calc-button.mult:active {
- background: url('img/1x/pressed/52112_calculator_multiplication_btn.png');
+#calculator #buttons .button.eight:active {
+ background: url('img/1x/pressed/52112_calculator_8_btn.png');
background-size: 61px 45px;
}
-.calc-button.plus {
- background: url('img/1x/normal/52112_calculator_addition_btn.png');
- background-size: 61px 45px;
+#calculator #buttons .button.equals {
+ background:
+ url('img/1x/normal/52112_calculator_equals_btn.png') center no-repeat;
+ background-size: 61px 90px;
+ height: 90px;
}
-.calc-button.plus:active {
- background: url('img/1x/pressed/52112_calculator_addition_btn.png');
- background-size: 61px 45px;
+#calculator #buttons .button.equals:active {
+ background: url('img/1x/pressed/52112_calculator_equals_btn.png');
+ background-size: 61px 90px;
}
-.calc-button.minus {
- background: url('img/1x/normal/52112_calculator_subtraction_btn.png');
+#calculator #buttons .button.four {
+ background: url('img/1x/normal/52112_calculator_4_btn.png');
background-size: 61px 45px;
}
-.calc-button.minus:active {
- background: url('img/1x/pressed/52112_calculator_subtraction_btn.png');
+#calculator #buttons .button.five {
+ background: url('img/1x/normal/52112_calculator_5_btn.png');
background-size: 61px 45px;
}
-.calc-button.one {
- background: url('img/1x/normal/52112_calculator_1_btn.png');
+#calculator #buttons .button.five:active {
+ background: url('img/1x/pressed/52112_calculator_5_btn.png');
background-size: 61px 45px;
}
-.calc-button.one:active {
- background: url('img/1x/pressed/52112_calculator_1_btn.png');
+#calculator #buttons .button.four:active {
+ background: url('img/1x/pressed/52112_calculator_4_btn.png');
background-size: 61px 45px;
}
-.calc-button.two {
- background: url('img/1x/normal/52112_calculator_2_btn.png');
+#calculator #buttons .button.multiply {
+ background: url('img/1x/normal/52112_calculator_multiplication_btn.png');
background-size: 61px 45px;
}
-.calc-button.two:active {
- background: url('img/1x/pressed/52112_calculator_2_btn.png');
+#calculator #buttons .button.multiply:active {
+ background: url('img/1x/pressed/52112_calculator_multiplication_btn.png');
background-size: 61px 45px;
}
-.calc-button.three {
- background: url('img/1x/normal/52112_calculator_3_btn.png');
+#calculator #buttons .button.negate {
+ background: url('img/1x/normal/52112_calculator_positivenegative_btn.png');
background-size: 61px 45px;
}
-.calc-button.three:active {
- background: url('img/1x/pressed/52112_calculator_3_btn.png');
+#calculator #buttons .button.negate:active {
+ background: url('img/1x/pressed/52112_calculator_positivenegative_btn.png');
background-size: 61px 45px;
}
-.calc-button.four {
- background: url('img/1x/normal/52112_calculator_4_btn.png');
+#calculator #buttons .button.nine {
+ background: url('img/1x/normal/52112_calculator_9_btn.png');
background-size: 61px 45px;
}
-.calc-button.four:active {
- background: url('img/1x/pressed/52112_calculator_4_btn.png');
+#calculator #buttons .button.nine:active {
+ background: url('img/1x/pressed/52112_calculator_9_btn.png');
background-size: 61px 45px;
}
-.calc-button.five {
- background: url('img/1x/normal/52112_calculator_5_btn.png');
+#calculator #buttons .button.one {
+ background: url('img/1x/normal/52112_calculator_1_btn.png');
background-size: 61px 45px;
}
-.calc-button.five:active {
- background: url('img/1x/pressed/52112_calculator_5_btn.png');
+#calculator #buttons .button.one:active {
+ background: url('img/1x/pressed/52112_calculator_1_btn.png');
background-size: 61px 45px;
}
-.calc-button.six {
- background: url('img/1x/normal/52112_calculator_6_btn.png');
+#calculator #buttons .button.point {
+ background:
+ url('img/1x/normal/52112_calculator_point_btn.png') center no-repeat;
background-size: 61px 45px;
+ margin-top: -45px;
+ margin-left: 122px;
}
-.calc-button.six:active {
- background: url('img/1x/pressed/52112_calculator_6_btn.png');
+#calculator #buttons .button.point:active {
+ background:
+ url('img/1x/pressed/52112_calculator_point_btn.png') center no-repeat;
background-size: 61px 45px;
}
-.calc-button.seven {
+#calculator #buttons .button.seven {
background: url('img/1x/normal/52112_calculator_7_btn.png');
background-size: 61px 45px;
}
-.calc-button.seven:active {
+#calculator #buttons .button.seven:active {
background: url('img/1x/pressed/52112_calculator_7_btn.png');
background-size: 61px 45px;
}
-.calc-button.eight {
- background: url('img/1x/normal/52112_calculator_8_btn.png');
+#calculator #buttons .button.six {
+ background: url('img/1x/normal/52112_calculator_6_btn.png');
background-size: 61px 45px;
}
-.calc-button.eight:active {
- background: url('img/1x/pressed/52112_calculator_8_btn.png');
+#calculator #buttons .button.six:active {
+ background: url('img/1x/pressed/52112_calculator_6_btn.png');
background-size: 61px 45px;
}
-.calc-button.nine {
- background: url('img/1x/normal/52112_calculator_9_btn.png');
+#calculator #buttons .button.subtract {
+ background: url('img/1x/normal/52112_calculator_subtraction_btn.png');
background-size: 61px 45px;
}
-.calc-button.nine:active {
- background: url('img/1x/pressed/52112_calculator_9_btn.png');
+#calculator #buttons .button.subtract:active {
+ background: url('img/1x/pressed/52112_calculator_subtraction_btn.png');
background-size: 61px 45px;
}
-.calc-button.equals {
- background: url('img/1x/normal/52112_calculator_equals_btn.png') center no-repeat;
- background-size: 61px 90px;
- height: 90px;
+#calculator #buttons .button.three {
+ background: url('img/1x/normal/52112_calculator_3_btn.png');
+ background-size: 61px 45px;
}
-.calc-button.equals:active {
- background: url('img/1x/pressed/52112_calculator_equals_btn.png');
- background-size: 61px 90px;
+#calculator #buttons .button.three:active {
+ background: url('img/1x/pressed/52112_calculator_3_btn.png');
+ background-size: 61px 45px;
+}
+
+#calculator #buttons .button.two {
+ background: url('img/1x/normal/52112_calculator_2_btn.png');
+ background-size: 61px 45px;
+}
+
+#calculator #buttons .button.two:active {
+ background: url('img/1x/pressed/52112_calculator_2_btn.png');
+ background-size: 61px 45px;
}
-.calc-button.zero {
+#calculator #buttons .button.zero {
background: url('img/1x/normal/52112_calculator_0_btn.png') center no-repeat;
background-size: 122px 45px;
margin-top: -45px;
width: 122px;
}
-.calc-button.zero:active {
+#calculator #buttons .button.zero:active {
background: url('img/1x/pressed/52112_calculator_0_btn.png') center no-repeat;
background-size: 122px 45px;
}
-.calc-button.point {
- background: url('img/1x/normal/52112_calculator_point_btn.png') center no-repeat;
- background-size: 61px 45px;
- height: 45px;
- margin-top: -45px;
- margin-left: 122px;
+#calculator #display,
+#calculator #display:focus {
+ border: none;
+ bottom: 225px;
+ letter-spacing: 1px;
+ line-height: 20px;
+ margin: 0px;
+ min-width: 204px;
+ overflow: scroll;
+ padding: 20px;
+ position: absolute;
+ top: 0px;
}
-.calc-button.point:active {
- background: url('img/1x/pressed/52112_calculator_point_btn.png') center no-repeat;
- background-size: 61px 45px;
+#calculator #display .equation {
+ width: 100%;
+ position: relative;
+ clear: both;
+ height: 22px;
}
+#calculator #display .equation .accumulator {
+ color: #888;
+ font-size: 13px;
+ float: left;
+ text-align: left;
+ overflow: hidden;
+ width: 85px;
+ max-width: 72px;
+ height: 13px;
+ line-height: 13px;
+ padding-top: 6px;
+ padding-bottom: 3px;
+}
+
+#calculator #display .equation .operand {
+ color: #2c2c2c;
+ float: right;
+ text-align: right;
+ overflow: hidden;
+ max-width: 88px;
+ height: 16px;
+ line-height: 16px;
+ padding-top: 3px;
+ padding-bottom: 3px;
+}
+
+#calculator #display .equation .operator {
+ color: #2c2c2c;
+ float: right;
+ width: 15px;
+ height: 22px;
+ line-height: 16px;
+ padding-top: 3px;
+ padding-bottom: 3px;
+ padding-right: 5px;
+}
+
+#calculator #display .hr {
+ width: 100%;
+ height: 0px;
+ border-top: 1px solid #d9d9d9;
+ position: relative;
+}
+#calculator #top-edge {
+ width: 244px;
+ height: 5px;
+ z-index: 99;
+ position: absolute;
+ background: #fff;
+}
+
+#calculator #top-fade {
+ top: 5px;
+ width: 244px;
+ height: 20px;
+ z-index: 99;
+ position: absolute;
+ background: -webkit-linear-gradient(top, rgba(255,255,255,1) 0%,
+ rgba(255,255,255,0) 100%);
+}
+
+html {
+ height: 380px;
+ margin: 0;
+ padding: 0;
+ width: 244px;
+ font-family: "Open Sans", "Hevatica Neue", "Helvetica", "Arial", sans-serif;
+ font-weight: 700;
+ font-size: 16px;
+ overflow: hidden;
+}
+
+::-webkit-scrollbar {
+ display: none;
+}
+
+::-webkit-scrollbar-thumb {
+ display: none;
+}
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/tests/calculator_test.html b/chrome/common/extensions/docs/examples/apps/calculator/tests/calculator_test.html
deleted file mode 100644
index f6d2d21..0000000
--- a/chrome/common/extensions/docs/examples/apps/calculator/tests/calculator_test.html
+++ /dev/null
@@ -1,36 +0,0 @@
-<!DOCTYPE html>
-<html>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
- <title>Calculator Chrome App</title>
- <script type="text/javascript" src="../jquery/jquery.min.js"></script>
- <script type="text/javascript" src="http://code.jquery.com/qunit/git/qunit.js"></script>
- <script type="text/javascript" src="../view.js"></script>
- <script type="text/javascript" src="../model.js"></script>
- <script type="text/javascript" src="../controller.js"></script>
- <script type="text/javascript" src="calculator_test.js"></script>
- <link rel="stylesheet" type="text/css" href="http://code.jquery.com/qunit/git/qunit.css">
- </head>
-
- <body>
- <h1 id="qunit-header">Calculator Tests</h1>
- <h2 id="qunit-banner"></h2>
- <div id="qunit-testrunner-toolbar"></div>
- <h2 id="qunit-userAgent"></h2>
- <ol id="qunit-tests"></ol>
-
- <div id="qunit-fixture">
- <div id="calc">
- <div class="edge-top"></div>
- <div class="edge"></div>
- <div id="display" class="calc-display"></div>
- <div id="buttons"></div>
- </div>
- </div>
-
-</body>
-</html>
-
-
-
-
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/tests/calculator_test.js b/chrome/common/extensions/docs/examples/apps/calculator/tests/calculator_test.js
deleted file mode 100644
index 34e9810..0000000
--- a/chrome/common/extensions/docs/examples/apps/calculator/tests/calculator_test.js
+++ /dev/null
@@ -1,175 +0,0 @@
-$(document).ready(function() {
-
- module("Initialize and Reset Registers");
-
- test("Basic Initialization", function() {
- var calculator = new Calculator();
- ok(!calculator.operatorNeedsReset);
- equal(calculator.operator, null);
- equal(calculator.decimal, -1);
- ok(!calculator.operandNeedsReset);
- ok(!calculator.accumulatorNeedsReset);
- equal(calculator.operand, 0);
- equal(calculator.accumulator, 0);
- });
-
- test("Reset Operator", function() {
- var calculator = new Calculator();
- calculator.operator = '+';
- calculator.operatorNeedsReset = true;
- calculator.ResetRegisters();
- ok(!calculator.operatorNeedsReset);
- equal(calculator.operator, null);
- });
-
- test("Reset Operand", function() {
- var calculator = new Calculator();
- calculator.operand = 50;
- calculator.decimal = -5;
- calculator.operandNeedsReset = true;
- calculator.ResetRegisters();
- ok(!calculator.operatorNeedsReset);
- equal(calculator.operator, null);
- equal(calculator.decimal, -1);
- });
-
- test("Reset Accumulator", function() {
- var calculator = new Calculator();
- calculator.accumulator = 50;
- calculator.accumulatorNeedsReset = true;
- calculator.ResetRegisters();
- ok(!calculator.accumulatorNeedsReset);
- equal(calculator.accumulator, 0);
- });
-
- module("Do Operations");
-
- test("Plus", function() {
- var calculator = new Calculator();
- calculator.operator = '+';
- equal(calculator.accumulator, 0);
-
- calculator.operand = 5;
- calculator.DoOperation();
- equal(calculator.accumulator, 5);
-
- calculator.operand = -3;
- calculator.DoOperation();
- equal(calculator.accumulator, 2);
-
- calculator.operand = -2;
- calculator.DoOperation();
- equal(calculator.accumulator, 0);
-
- calculator.operand = -1;
- calculator.DoOperation();
- equal(calculator.accumulator, -1);
-
- calculator.operand = 3;
- calculator.DoOperation();
- equal(calculator.accumulator, 2);
-
- calculator.operand = 0;
- calculator.DoOperation();
- equal(calculator.accumulator, 2);
- });
-
- test("Minus", function() {
- var calculator = new Calculator();
- calculator.operator = '-';
- equal(calculator.accumulator, 0);
-
- calculator.operand = 5;
- calculator.DoOperation();
- equal(calculator.accumulator, -5);
-
- calculator.operand = -3;
- calculator.DoOperation();
- equal(calculator.accumulator, -2);
-
- calculator.operand = -2;
- calculator.DoOperation();
- equal(calculator.accumulator, 0);
-
- calculator.operand = -1;
- calculator.DoOperation();
- equal(calculator.accumulator, 1);
-
- calculator.operand = 3;
- calculator.DoOperation();
- equal(calculator.accumulator, -2);
-
- calculator.operand = 0;
- calculator.DoOperation();
- equal(calculator.accumulator, -2);
- });
-
- test("Multiplication", function() {
- var calculator = new Calculator();
- calculator.operator = '*';
- equal(calculator.accumulator, 0);
-
- calculator.operand = 5;
- calculator.DoOperation();
- equal(calculator.accumulator, 0);
-
- calculator.accumulator = 1;
- equal(calculator.accumulator, 1);
-
- calculator.operand = 5;
- calculator.DoOperation();
- equal(calculator.accumulator, 5);
-
- calculator.operand = -2;
- calculator.DoOperation();
- equal(calculator.accumulator, -10);
-
- calculator.operand = 1.5;
- calculator.DoOperation();
- equal(calculator.accumulator, -15);
-
- calculator.operand = -1;
- calculator.DoOperation();
- equal(calculator.accumulator, 15);
-
- calculator.operand = 0;
- calculator.DoOperation();
- equal(calculator.accumulator, 0);
- });
-
- test("Division", function() {
- var calculator = new Calculator();
- calculator.operator = '/';
- equal(calculator.accumulator, 0);
-
- calculator.operand = 5;
- calculator.DoOperation();
- equal(calculator.accumulator, 0);
-
- calculator.accumulator = 1;
- equal(calculator.accumulator, 1);
-
- calculator.operand = 5;
- calculator.DoOperation();
- equal(calculator.accumulator, 0.2);
-
- calculator.operand = -2;
- calculator.DoOperation();
- equal(calculator.accumulator, -0.1);
-
- calculator.operand = 0.1;
- calculator.DoOperation();
- equal(calculator.accumulator, -1);
-
- calculator.operand = -1;
- calculator.DoOperation();
- equal(calculator.accumulator, 1);
-
- calculator.operand = 0;
- calculator.DoOperation();
- equal(calculator.accumulator, 'Infinity');
- });
-
-
-
-});
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/tests/model_test_utilities.js b/chrome/common/extensions/docs/examples/apps/calculator/tests/model_test_utilities.js
new file mode 100644
index 0000000..c00ed6a
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/tests/model_test_utilities.js
@@ -0,0 +1,275 @@
+/**
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * The utility functions defined in this file allow tests like the following:
+ *
+ * test('Two Plus Two', function () {
+ * var model = new Model();
+ * deepEqual(model.handle('2'), [null, null, '2'], '2');
+ * deepEqual(model.handle('+'), ['2', '+', null], '+');
+ * deepEqual(model.handle('2'), ['2', '+', '2'], '2');
+ * deepEqual(model.handle('='), ['4', '=', null], '=');
+ * }
+ *
+ * to be more succinctly written as:
+ *
+ * testModel('Two Plus Two', '2 + 2 = [4]');
+ */
+
+/**
+ * Describes models, strings, numbers, and arrays used in tests.
+ */
+var describeTested = function(strings, object, suffix) {
+ if (object === null) {
+ strings.push('null');
+ } else if (Array.isArray(object)) {
+ strings.push('[');
+ for (var i = 0; i < object.length; ++i) {
+ describeTested(strings, object[i], (i + 1 < object.length) ? ', ' : ']');
+ }
+ } else if (typeof object === 'number') {
+ strings.push('#');
+ strings.push(String(object));
+ } else if (typeof object === 'String') {
+ strings.push('"');
+ strings.push(object);
+ strings.push('"');
+ } else if (typeof object === 'object') {
+ strings.push('(');
+ describeTested(strings, object.accumulator, ' ');
+ describeTested(strings, object.operator, ' ');
+ describeTested(strings, object.operand, ' | ');
+ describeTested(strings, object.defaults && object.defaults.operator, ' ');
+ describeTested(strings, object.defaults && object.defaults.operand, ')');
+ } else {
+ strings.push(String(object));
+ }
+ strings.push(suffix || '');
+ return strings;
+}
+
+/**
+ * Tests how a calculator model handles a single event, logging the state of the
+ * model before and after the test.
+ */
+var testEvent = function (model, event, expected) {
+ var before = describeTested([], model).join('');
+ var results = model.handle(event);
+ var accumulator = results.accumulator;
+ var operator = results.operator;
+ var operand = results.operand;
+ var actual = [accumulator, operator, operand && [operand]];
+ var after = describeTested(describeTested([], actual, ' '), model).join('');
+ deepEqual(actual, expected, event + ' ' + before + ' => ' + after);
+}
+
+/**
+ * Tests how a calculator model handles a sequence of digits and periods,
+ * updating the expected operand values before each digit and period event is
+ * tested according to these rules:
+ *
+ * - If the passed in expected values array has a null expected operand array,
+ * the expected operand used for the tests starts with the first digit or
+ * period of the sequence of number events and the following digits and
+ * periods of that sequence are appended before each new event's test.
+ *
+ * - If the passed in expected values array has an expected operand array of
+ * the form [operand], the expected operand used for the tests start with
+ * the first character of that array's element and one additional character
+ * of that element is added before each of the following digit and period
+ * event tests.
+ *
+ * - If the passed in expected values array has an expected operand array of
+ * the form [prefix, operand], the expected operand used for the tests
+ * starts with the first element in that array and the first character of
+ * the second element, and one character of that second element is added
+ * before each of the following digit and period event tests.
+ *
+ * - In all of these cases, leading zeros and occurences of the '=' character
+ * in the expected operand are ignored.
+ *
+ * For example the sequence of calls:
+ *
+ * testDigits(model, '00', [x, y, ['0=']])
+ * testDigits(model, '1.2.3', [x, y, ['1.2=3']])
+ * testDigits(model, '45', [x, y, ['1.23', '45']])
+ *
+ * would yield the following tests:
+ *
+ * deepEqual(model.handle('0'), [x, y, '0'], '0');
+ * deepEqual(model.handle('0'), [x, y, '0'], '0');
+ * deepEqual(model.handle('1'), [x, y, '1'], '1');
+ * deepEqual(model.handle('.'), [x, y, '1.'], '.');
+ * deepEqual(model.handle('2'), [x, y, '1.2'], '2');
+ * deepEqual(model.handle('.'), [x, y, '1.2'], '.');
+ * deepEqual(model.handle('3'), [x, y, '1.23'], '3');
+ * deepEqual(model.handle('4'), [x, y, '1.234'], '4');
+ * deepEqual(model.handle('5'), [x, y, '1.2345'], '5');
+ *
+ * and changes the expected value array to the following:
+ *
+ * [x, y, ['1.2345']]
+ */
+var testNumber = function (model, number, expected) {
+ var multiple = (expected[2] && expected[2].length > 1);
+ var prefix = multiple ? expected[2][0] : '';
+ var suffix = expected[2] ? expected[2][multiple ? 1 : 0] : number;
+ for (var i = 0; i < number.length; ++i) {
+ expected[2] = [prefix + suffix.slice(0, i + 1)];
+ expected[2] = [expected[2][0].replace(/^0+([0-9])/, '$1')];
+ expected[2] = [expected[2][0].replace(/=/g, '')];
+ this.testEvent(model, number[i], expected);
+ }
+};
+
+/**
+ * Tests how a new calculator model handles a sequence of numbers, operations,
+ * and commands, verifying that the model has expected accumulator, operator,
+ * and operand values after each event handled by the model.
+ *
+ * Within the sequence string, expected values must be specified as arrays of
+ * the form described below. The strings '~', '<', and 'A' will be interpreted
+ * as the commands '+ / -', 'back', and 'AC' respectively, and other strings
+ * will be interpreted as the corresponding digits, periods, operations, and
+ * commands.
+ *
+ * Expected value arrays must have one of the following forms:
+ *
+ * [accumulator]
+ * [accumulator operator]
+ * [accumulator operator [operand]]
+ * [accumulator operator [prefix operand]]
+ * [[operand]]
+ * [[prefix operand]]
+ *
+ * where |accumulator| is a number or |null|, |operator| one of the operator
+ * character or |null|, |prefix| a number, and |operand| also a number. Both
+ * |prefix| and |operand| numbers may contain leading zeros and their use is
+ * described in the comments for the |testNumber()| method above. Expected value
+ * arrays must be specified just after the sequence element which they are meant
+ * to test, like this:
+ *
+ * testCalculation(model, '12 - 34 = [-22]')
+ *
+ * When expected values are not specified for an element, the following rules
+ * are applied to obtain best guesses for the expected values for that
+ * element's tests:
+ *
+ * - The initial expected values are:
+ *
+ * [null, null, null].
+ *
+ * - If the element being tested is a number, the expected operand values are
+ * set and changed as described in the comments for the |testNumber()|
+ * method above.
+ *
+ * - If the element being tested is the '+ / -' operation the expected values
+ * will be changed as follows:
+ *
+ * [x, y, null] -> [x, y, null]
+ * [x, y, [z]] -> [x, y, [-z]]
+ * [x, y, [z1 z2]] -> [x, y, ['-' + z1 + z2]]
+ *
+ * - If the element |e| being tested is the '+', '-', '*', or '/' operation
+ * the expected values will be changed as follows:
+ *
+ * [null, y, null] -> [0, e, null]
+ * [x, y, null] -> [x, e, null]
+ * [x, y, [z]] -> [z, e, null]
+ * [x, y, [z1 z2]] -> ['' + z1 + z2, e, null]
+ *
+ * - If the element being tested is the '=' command, the expected values will
+ * be changed as for the '+' operation except that the expected operator
+ * value will be set to null.
+ *
+ * So for example this call:
+ *
+ * testModel('My Test', '12 + 34 - [46] 56 = [-10] 0')
+ *
+ * would yield the following tests:
+ *
+ * test('My Test', function () {
+ * var model = new Model();
+ * deepEqual(model.handle('1'), ['0', null, '1'], '1');
+ * deepEqual(model.handle('2'), ['0', null, '12'], '2');
+ * deepEqual(model.handle('+'), ['12', '+', null], '+');
+ * deepEqual(model.handle('3'), ['12', '+', '3'], '3');
+ * deepEqual(model.handle('4'), ['12', '+', '34'], '4');
+ * deepEqual(model.handle('-'), ['46', '-', null], '-');
+ * deepEqual(model.handle('2'), ['46', '-', '2']), '2';
+ * deepEqual(model.handle('1'), ['46', '-', '21'], '1');
+ * deepEqual(model.handle('='), ['25', '=', null], '=');
+ * deepEqual(model.handle('0'), ['25', null, '0'], '0');
+ * });
+ */
+var testModel = function (name, sequence) {
+ // Define constants and variables for matching.
+ var NUMBER = /(-?[\d.][\d.=]*)|(E)/g; // '2'
+ var OPERATION = /([+*/-])/g; // '+'
+ var COMMAND = /([=~<CA])/g; // '='
+ var VALUES = /(\[[^\[\]]*(\[[^\[\]]+\])?\])/g; // '[x, y, [z]]'
+ var ABBREVIATIONS = {'~': '+ / -', '<': 'back', 'A': 'AC'}; // '~'
+ var match, before, after, replacement;
+ // Parse the sequence into JSON array contents, so '1 - 2 = [-1]' becomes...
+ sequence = sequence.replace(NUMBER, ',$1$2,'); // ',2, + ,2, = [,4,]'
+ sequence = sequence.replace(OPERATION, ',$1,'); // ',2, ,+, ,2,= [,4,]'
+ sequence = sequence.replace(COMMAND, ',$1,'); // ',2, ,+, ,2, ,=, [,4,]'
+ sequence = sequence.replace(VALUES, ',$1,'); // ',2, ,+, ,2, ,=, ,[,4,],'
+ sequence = sequence.replace(/(null)/g, ',$1,'); // ',2, ,+, ,2, ,=, ,[,4,],'
+ sequence = sequence.replace(/\s+/g, ''); // ',2,,+,,2,,=,,[,4,],'
+ sequence = sequence.replace(/,,+/g, ','); // ',2,+,2,=,[,4,],'
+ sequence = sequence.replace(/\[,/g, '['); // ',2,+,2,=,[4,],'
+ sequence = sequence.replace(/,\]/g, ']'); // ',2,+,2,=,[4],'
+ sequence = sequence.replace(/(^,)|(,$)/g, ''); // '2,+,2,=,[4]'
+ sequence = sequence.replace(NUMBER, '"$1$2"'); // '"2",+,"2",=,["4"]'
+ sequence = sequence.replace(OPERATION, '"$1"'); // '"2","+","2",=,["4"]'
+ sequence = sequence.replace(COMMAND, '"$1"'); // '"2","+","2","=",["4"]'
+ // Fix some expected value characters parse errors. For example the original
+ // sequences '[[0=]]" and "[-1]' would have become '[["0","="]]' and
+ // '["-","1"]' and would need to be fixed to '[["0="]]' and '["-1"]'.
+ while ((match = VALUES.exec(sequence)) != null) {
+ before = sequence.slice(0, match.index);
+ after = sequence.slice(match.index + match[0].length);
+ replacement = match[0].replace(/","=/g, '=').replace(/=","/g, '=');
+ replacement = replacement.replace(/-","/g, '-');
+ sequence = before + replacement + after;
+ VALUES.lastIndex = match.index + replacement.length;
+ }
+ // Convert the sequence to an array and run the test.
+ sequence = JSON.parse('[' + sequence + ']'); // ['2','-','2','=',['4']]
+ test(name, function () {
+ var model = new Model();
+ var expected = [null, null, null];
+ for (var i = 0; i < sequence.length; ++i) {
+ // Peek ahead to get expected value, then test the current sequence
+ // element, skipping any expected values already processed.
+ var next = sequence[i + 1] && sequence[i + 1];
+ var current = sequence[i];
+ if (!Array.isArray(current)) {
+ // Apply expected value rules.
+ var operand = expected[2] && expected[2].join('');
+ if (current.match(OPERATION)) {
+ expected = [operand || expected[0] || '0', current, null];
+ } else if (current === '=') {
+ expected = [operand || expected[0] || '0', null, null];
+ } else if (current === '~' && operand) {
+ expected[2] = ['-' + operand];
+ }
+ // Adjust expected values with explicitly specified ones.
+ if (Array.isArray(next) && Array.isArray(next[0])) {
+ expected = expected.slice(0, 2).concat([next[0]]).slice(0, 3);
+ } else if (Array.isArray(next)) {
+ expected = next.concat(expected.slice(next.length)).slice(0, 3);
+ }
+ // Test.
+ if (current.match(NUMBER)) {
+ testNumber(model, current, expected);
+ } else {
+ this.testEvent(model, ABBREVIATIONS[current] || current, expected);
+ }
+ };
+ }
+ }.bind(this));
+}
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/tests/model_tests.html b/chrome/common/extensions/docs/examples/apps/calculator/tests/model_tests.html
new file mode 100644
index 0000000..a04e1bb
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/tests/model_tests.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <title>Calculator Chrome App</title>
+ <script type="text/javascript" src="../jquery/jquery.min.js"></script>
+ <script type="text/javascript"
+ src="http://code.jquery.com/qunit/git/qunit.js"></script>
+ <script type="text/javascript" src="../model.js"></script>
+ <script type="text/javascript" src="../view.js"></script>
+ <script type="text/javascript" src="model_test_utilities.js"></script>
+ <script type="text/javascript" src="model_tests.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="http://code.jquery.com/qunit/git/qunit.css">
+ </head>
+ <body>
+ <h1 id="qunit-header">Model Tests</h1>
+ <h2 id="qunit-banner"></h2>
+ <div id="qunit-testrunner-toolbar"></div>
+ <h2 id="qunit-userAgent"></h2>
+ <ol id="qunit-tests"></ol>
+ </body>
+</html>
+
+
+
+
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/tests/model_tests.js b/chrome/common/extensions/docs/examples/apps/calculator/tests/model_tests.js
new file mode 100644
index 0000000..57c9fca
--- /dev/null
+++ b/chrome/common/extensions/docs/examples/apps/calculator/tests/model_tests.js
@@ -0,0 +1,144 @@
+/**
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ **/
+
+$(document).ready(function() {
+
+ // TODO(dharcourt@chromium.org): Organize and beef up these tests.
+ // TODO(dharcourt@chromium.org): testModel("*", '~ = [-0]');
+ // TODO(dharcourt@chromium.org): testModel("*", '~42 [[-42]]');
+ // TODO(dharcourt@chromium.org): testModel("*", '1 / 3 * 3 = [[1]]');
+ // TODO(dharcourt@chromium.org): Test {nega,posi}tive {under,over}flows.
+
+ test("Initialization", function() {
+ var model = new Model();
+ equal(model.accumulator, null);
+ equal(model.operator, null);
+ equal(model.operand, null);
+ equal(model.defaults.operator, null);
+ equal(model.defaults.operand, null);
+ });
+
+ testModel("AC", '1 + 2 = [3] 4 A [null null null]');
+
+ testModel("back", '1 + 2 < [1 + null] < [1 null null] < [1 null null]');
+ // TODO(dharcourt@chromium.org): Test more AC, C, back
+
+ testModel("Miscellaneous Test A",
+ '2 [[2]] + [2 + null] = [4 null null]' +
+ ' + [4 + null] = [8 null null]' +
+ ' = [12 null null]');
+
+ testModel("Miscellaneous Test B", '2 * [2] = [4] * = [16] = [64]');
+
+ testModel("Miscellaneous Test C", '0.020202020 [[0.02020202=]]');
+
+ testModel("Miscellaneous Test D", '.2 [[0 .2]]');
+
+ testModel("Miscellaneous Test E", '0.000000014 [[0.00000001=]]');
+
+ testModel("Miscellaneous Test F", '0.100000004 [[0.10000000=]]');
+
+ testModel("Miscellaneous Test G", '0.123123124 [[0.12312312=]]');
+
+ testModel("Miscellaneous Test H", '1.231231234 [[1.23123123=]]');
+
+ testModel("Miscellaneous Test I", '123.1231234 [[123.123123=]]');
+
+ testModel("Miscellaneous Test J", '123123.1234 [[123123.123=]]');
+
+ testModel("Miscellaneous Test K", '12312312.34 [[12312312.3=]]');
+
+ testModel("Miscellaneous Test L", '12312312.04 [[12312312.0=]]');
+
+ testModel("Miscellaneous Test M", '1231231234 [[123123123=]]');
+
+ testModel("Miscellaneous Test N", '1 + 1 + [2] = [4]');
+
+ testModel("Miscellaneous Test O", '1 + 12 [1 + [12]]');
+
+ testModel("Positive + Positive", '82959 + 4 = [82963]');
+
+ testModel("Positive + Negative", '123 + 456~ = [-333]');
+
+ testModel("Negative + Positive", '502~ + 385 = [-117]');
+
+ testModel("Negative + Negative", '4296~ + 32~ = [-4328]');
+
+ testModel("Positive + Zero", '23650 + 0 = [23650]');
+
+ testModel("Negative + Zero", '489719~ + 0 = [-489719]');
+
+ testModel("Zero + Positive", '0 + 4296 = [4296]');
+
+ testModel("Zero + Negative", '0 + 322~ = [-322]');
+
+ testModel("Zero + Zero", '0 + 0 = [0]');
+
+ testModel("Addition Chain",
+ ' + [0] 5 + [5] 3~ + [2] 2~ + [0] 1~ + [-1] 3 + [2] 0 = [2]');
+
+ testModel("Positive - Positive", '4534 - 327 = [4207]');
+
+ testModel("Subtraction Chain",
+ '- [0] 5 - [-5] 3~ - [-2] 2~ - [0] 1~ - [1] 3 - [-2] 0 = [-2]');
+
+ testModel("Positive * Positive", '7459 * 660 = [4922940]');
+
+ testModel("Multiplication Chain",
+ '* [0] 5 = [0] 1 * [1] 5 * [5] 2~ ' +
+ '* [-10] 1.5 * [-15] 1~ * [15] 0 = [0]');
+
+ testModel("Positive / Positive", '181 / 778 = [0.23264781]');
+
+ testModel("Division Chain",
+ '/ [0] 5 = [0] 1 / [1] 5 / [0.2] 2~ ' +
+ '/ [-0.1] 0.1 / [-1] 1~ / [1] 0 = [E]');
+
+ testModel("Positive Decimal Plus Positive Decimal",
+ '7.48 + 8.2 = [15.68]');
+
+ testModel("Decimals with Leading Zeros",
+ '0.0023 + 0.082 = [0.0843]');
+
+ testModel("Addition and Subtraction Chain",
+ '4 + [4] 1055226 - [1055230] 198067 = [857163]');
+
+ testModel("Multiplication and Division Chain",
+ '580 * [580] 544 / [315520] 64 = [4930]');
+
+ testModel("Addition After Equals",
+ '5138 + 3351301 = [3356439] 550 + 62338 = [62888]');
+
+ testModel("Missing First Operand in Addition", '+ 9701389 = [9701389]');
+
+ testModel("Missing First Operand in Subtraction", '- 1770 = [-1770]');
+
+ testModel("Missing First Operand in Multiplication", '* 65146 = [0]');
+
+ testModel("Missing First Operand in Division", '/ 8 = [0]');
+
+ testModel("Missing Second Operand in Addition", '28637 + = [57274]');
+
+ testModel("Missing Second Operand in Subtraction", '52288 - = [0]');
+
+ testModel("Missing Second Operand in Multiplication", '17 * = [289]');
+
+ testModel("Missing Second Operand in Division", '47 / = [1]');
+
+ testModel("Missing Last Operand in Addition and Subtraction Chain",
+ '8449 + [8449] 4205138 - [4213587] = [0]');
+
+ testModel("Leading zeros should be collapsed",
+ '0 [null null [0]] 0 [null null [0]] 0 [null null [0]] ' +
+ '2 [null null [2]] 0 [null null [2 0]] + [20 + null] ' +
+ '0 [20 + [0]] 0 [20 + [0]] 0 [20 + [0]] ' +
+ '2 [20 + [2]] 0 [20 + [2 0]] = [40 null null]');
+
+ testModel("Test utilities should correctly predict zeros collapse",
+ '000 [[0==]] 20 [[20]] + 000 [[0==]] 20 [[20]] = [40]' +
+ '00020 + 00020 = [40]');
+
+});
diff --git a/chrome/common/extensions/docs/examples/apps/calculator/view.js b/chrome/common/extensions/docs/examples/apps/calculator/view.js
index c810038..7aa1604 100644
--- a/chrome/common/extensions/docs/examples/apps/calculator/view.js
+++ b/chrome/common/extensions/docs/examples/apps/calculator/view.js
@@ -1,202 +1,127 @@
-var operators = ['+', '-', '/', '*'];
-
-var values = { 'one' : 1,
- 'two' : 2,
- 'three' : 3,
- 'four' : 4,
- 'five' : 5,
- 'six' : 6,
- 'seven' : 7,
- 'eight' : 8,
- 'nine' : 9,
- 'zero' : 0,
- 'plus' : '+',
- 'minus' : '-',
- 'div' : '/',
- 'mult' : '*',
- 'equals': '=',
- 'point' : '.',
- 'AC' : 'AC',
- 'plus-minus' : '+ / -'
- }
-
-var keyboard = { 49 : 1,
- 50 : 2,
- 51 : 3,
- 52 : 4,
- 53 : 5,
- 54 : 6,
- 55 : 7,
- 56 : 8,
- 57 : 9,
- 48 : 0,
- 187 : '=',
- 13 : '=',
- 190 : '.',
- 189 : '-',
- 191 : '/',
- 67 : 'AC',
- 8 : 'back'
- };
-var shiftKeyboard = { 187 : '+',
- 56 : '*'
- };
-
-var shift = false;
-
-function View(calcModel) {
- this.calcElement = $('#calc');
- this.buttonsElement = $('#buttons');
- this.displayElement = $('#display');
- this.lastDisplayElement = null;
- this.BuildWidgets();
- var calc = this;
-
- $('.calc-button').click(function() {
- var clicked = values[$(this).attr('class').split(' ')[1]];
- var result = calcModel.HandleButtonClick(clicked);
- calc.buttonClicked(clicked, result);
+/**
+ * Copyright (c) 2012 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ **/
+
+function View(model) {
+ var view = this;
+ var model = model;
+ var events = this.defineEvents_();
+
+ this.buttonsElement = $('#calculator #buttons');
+ this.displayElement = $('#calculator #display');
+ this.keyPrefix = '';
+
+ $('#calculator #buttons .button').click(function (event) {
+ var button = $(event.target).attr('class').split(' ')[1];
+ view.handleEvent_(model, events.byButton[button]);
});
- $(document).keydown(function(event) {
- var clicked = null;
- if (event.which == 16)
- shift = true;
- else if (shift && event.which in shiftKeyboard)
- clicked = shiftKeyboard[event.which]
- else if (!shift && event.which in keyboard)
- clicked = keyboard[event.which]
- if (clicked != null) {
- var result = calcModel.HandleButtonClick(clicked);
- calc.buttonClicked(clicked, result);
+ $(document).keydown(function (event) {
+ var key = view.keyPrefix + event.which;
+ if (key === '16') {
+ view.keyPrefix = '^'
+ } else {
+ view.handleEvent_(model, events.byKey[key]);
}
});
- $(document).keyup(function(event) {
- if (event.which == 16)
- shift = false;
+ $(document).keyup(function (event) {
+ if (event.which === '16') {
+ view.keyPrefix = '';
+ }
});
-
}
-
-function displayNumber(number) {
- var digits = (number + '').length;
- if ((number >= 0 && digits > 8) || (number < 0 && digits > 9)) {
- if (number % 1 != 0) {
- number = parseFloat((number + '').slice(0, 8));
- if (number % 1 != 0) return number;
- }
- var pow = (number + '').length - 1;
- var extra_length = (pow + '').length + 2;
- number = number * Math.pow(10, -1*pow);
- number = (number + '').slice(0, 8 - extra_length) + 'e' + pow;
- }
- return number;
+/** @private */
+View.prototype.defineEvents_ = function () {
+ var events = {byName: {}, byButton: {}, byKey: {}};
+ this.defineEvent_(events, '0', 'zero', '48');
+ this.defineEvent_(events, '1', 'one', '49');
+ this.defineEvent_(events, '2', 'two', '50');
+ this.defineEvent_(events, '3', 'three', '51');
+ this.defineEvent_(events, '4', 'four', '52');
+ this.defineEvent_(events, '5', 'five', '53');
+ this.defineEvent_(events, '6', 'six', '54');
+ this.defineEvent_(events, '7', 'seven', '55');
+ this.defineEvent_(events, '8', 'eight', '56');
+ this.defineEvent_(events, '9', 'nine', '57');
+ this.defineEvent_(events, '.', 'point', '190');
+ this.defineEvent_(events, '+', 'add', '^187', true);
+ this.defineEvent_(events, '-', 'subtract', '189', true);
+ this.defineEvent_(events, '*', 'multiply', '^56', true);
+ this.defineEvent_(events, '/', 'divide', '191', true);
+ this.defineEvent_(events, '=', 'equals', '187');
+ this.defineEvent_(events, '=', ' ', '13');
+ this.defineEvent_(events, '+ / -', 'negate', '32');
+ this.defineEvent_(events, 'AC', 'clear', '67');
+ this.defineEvent_(events, 'back', ' ', '8');
+ return events;
}
-View.prototype.buttonClicked = function(clicked, result) {
- var operator = result[0];
- var operand = displayNumber(result[1]);
- var accumulator = displayNumber(result[2]);
- if (clicked == 'AC') {
- this.displayElement.text('');
- this.AddDisplayEquation('', 0, '');
- }
- else if (clicked == 'back' && operator == 'back') {
- this.UpdateDisplayEquation('', '', '');
- }
- else if (operators.indexOf(clicked) != -1) {
- if (this.lastDisplayElement)
- this.UpdateTotal(accumulator);
- operand = '';
- accumulator = '';
- this.AddDisplayEquation(operator, operand, accumulator);
- }
- else if (clicked == '=') {
- this.displayElement.append('<div class="hr"></div>');
- this.AddDisplayEquation('', accumulator, accumulator);
- this.lastDisplayElement = null;
- }
- else if (clicked == '+ / -') {
- this.UpdateDisplayEquation(operator, operand, '');
- }
- else if (this.lastDisplayElement) {
- accumulator = '';
- this.UpdateDisplayEquation(operator, operand, accumulator);
- }
- else {
- accumulator = '';
- operator = '';
- this.AddDisplayEquation(operator, operand, accumulator)
+/** @private */
+View.prototype.defineEvent_ = function (events, name, button, key, operator) {
+ var event = {name: name, button: button, key: key, operator: !!operator};
+ events.byButton[button] = event;
+ events.byKey[key] = event;
+};
+
+/** @private */
+View.prototype.handleEvent_ = function (model, event) {
+ if (event) {
+ var results = model.handle(event.name);
+ console.log('results:', JSON.stringify(results));
+ if (!results.accumulator && !results.operator && !results.operand) {
+ this.displayElement.empty();
+ this.addDisplayEquation_(results);
+ } else if (event.name === '=') {
+ results = {accumulator: results.accumulator, operand:results.accumulator};
+ this.displayElement.append('<div class="hr"></div>');
+ this.addDisplayEquation_(results);
+ } else if (event.operator) {
+ this.updateLastDisplayEquation_({accumulator: results.accumulator});
+ this.addDisplayEquation_({operator: results.operator});
+ } else {
+ results = {operator: results.operator, operand: results.operand};
+ if (!this.updateLastDisplayEquation_(results)) {
+ this.addDisplayEquation_(results);
+ }
+ }
}
}
-View.prototype.BuildWidgets = function() {
- this.AddButtons(this.calcElement);
- this.AddDisplayEquation('', 0, '');
-}
-
-View.prototype.UpdateTotal = function(accumulator) {
- $(this.lastDisplayElement).children('.accumulator').text(accumulator);
-}
-
-View.prototype.AddDisplayEquation = function(operator, operand, accumulator) {
+/** @private */
+View.prototype.addDisplayEquation_ = function (values) {
+ console.log('add:', JSON.stringify(values));
+ var zero = (!values.accumulator && !values.operator);
+ var operand = values.operand || (zero ? '0' : '');
this.displayElement.append(
- '<div class="equation">'
- + '<div class="operand">' + operand + '</div>'
- + '<div class="operator">' + operator + '</div>'
- + '<div class="accumulator">' + accumulator + '</div'
- + '</div>');
- this.lastDisplayElement = $('.equation').last();
- this.displayElement.scrollTop(this.displayElement[0].scrollHeight);
-}
-
-View.prototype.UpdateDisplayEquation = function(operator, operand, accumulator) {
- $(this.lastDisplayElement).children('.operator').text(operator);
- $(this.lastDisplayElement).children('.operand').text(operand);
- $(this.lastDisplayElement).children('.accumulator').text(accumulator);
+ '<div class="equation">' +
+ '<div class="operand">' + operand + '</div>' +
+ '<div class="operator">' + (values.operator || '') + '</div>' +
+ '<div class="accumulator">' + (values.accumulator || '') + '</div>' +
+ '</div>');
this.displayElement.scrollTop(this.displayElement[0].scrollHeight);
}
-View.prototype.AddButtons = function() {
- var row;
-
- row = this.AddRow();
- this.AddButton(row, 'AC', 'AC');
- this.AddButton(row, 'plus-minus', 'plus-minus');
- this.AddButton(row, 'div', 'div');
- this.AddButton(row, 'mult', 'mult');
-
- row = this.AddRow();
- this.AddButton(row, 7, 'seven');
- this.AddButton(row, 8, 'eight');
- this.AddButton(row, 9, 'nine');
- this.AddButton(row, 'minus', 'minus');
-
- row = this.AddRow();
- this.AddButton(row, 4, 'four');
- this.AddButton(row, 5, 'five');
- this.AddButton(row, 6, 'six');
- this.AddButton(row, 'plus', 'plus');
-
- row = this.AddRow();
- this.AddButton(row, 1, 'one');
- this.AddButton(row, 2, 'two');
- this.AddButton(row, 3, 'three');
- this.AddButton(row, 'equals', 'equals');
-
- row = this.AddRow();
- this.AddButton(row, 0, 'zero');
- this.AddButton(row, 'point', 'point')
-}
-
-View.prototype.AddRow = function() {
- var row = $('<div/>');
- this.buttonsElement.append(row);
- return row;
-}
-
-View.prototype.AddButton = function(row, value, button_value) {
- row.append('<div class="calc-button ' + button_value + '">' + '</div>');
+/** @private */
+View.prototype.updateLastDisplayEquation_ = function (values) {
+ var equation = this.displayElement.find('.equation').last();
+ var accumulator = equation.find('.accumulator');
+ var operator = equation.find('.operator');
+ var operand = equation.find('.operand');
+ var update = !accumulator.text();
+ if (update) {
+ if (values.accumulator !== undefined) {
+ accumulator.text(values.accumulator || '');
+ }
+ if (values.operator !== undefined) {
+ operator.text(values.operator || '');
+ }
+ if (values.operand !== undefined) {
+ operand.text(values.operand || (operator.text() ? '' : '0'));
+ }
+ }
+ return update;
}