diff options
author | dharcourt@chromium.org <dharcourt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-08 16:59:14 +0000 |
---|---|---|
committer | dharcourt@chromium.org <dharcourt@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-10-08 16:59:14 +0000 |
commit | e58ee7751304b4d210a3dbeaf2cce611cb37316f (patch) | |
tree | 579e15d2dcb62db4ab3a94f17eba9be8b2d90a8c | |
parent | c15f1a868de68bef581ee5fe3152734f4ac1530b (diff) | |
download | chromium_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
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; } |