diff --git a/test/.classpath b/test/.classpath new file mode 100644 index 00000000..ab564941 --- /dev/null +++ b/test/.classpath @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/test/.project b/test/.project new file mode 100644 index 00000000..24e3c941 --- /dev/null +++ b/test/.project @@ -0,0 +1,34 @@ + + + CordovaTest + + + Phonegapdemo + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/test/AndroidManifest.xml b/test/AndroidManifest.xml new file mode 100644 index 00000000..87da52fc --- /dev/null +++ b/test/AndroidManifest.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/assets/www/LICENSE b/test/assets/www/LICENSE new file mode 100644 index 00000000..5652ae1c --- /dev/null +++ b/test/assets/www/LICENSE @@ -0,0 +1,208 @@ +The MIT License + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2009-2011 Rob Ellis, Brock Whitten, Brian LeRoux + Copyright 2010-2011, IBM Corporation + Copyright 2011 Adobe + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + diff --git a/test/assets/www/README.md b/test/assets/www/README.md new file mode 100644 index 00000000..86e876c7 --- /dev/null +++ b/test/assets/www/README.md @@ -0,0 +1,18 @@ +## Mobile Spec Suite ## + +These specs are designed to run inside the mobile device that implements it - _it will fail in the DESKTOP browser_. + +These set of tests is designed to be used with Cordova. You should initialize a fresh Cordova repository for a target platform and then toss these files into the www folder, replacing the +contents. + +Make sure you include Cordova-\*.js in the www folder. You also need to edit Cordova.js to reference the Cordova-\*.js file you are testing. +For example, to test with Cordova-0.9.6.1, the Cordova.js file would be: + + document.write(''); + document.write(''); + +This is done so that you don't have to modify every HTML file when you want to test a new version of Cordova. + +The goal is to test mobile device functionality inside a mobile browser. +Where possible, the Cordova API lines up with HTML 5 spec. Maybe down +the road we could use this spec for parts of HTML 5, too :) diff --git a/test/assets/www/accelerometer/index.html b/test/assets/www/accelerometer/index.html new file mode 100755 index 00000000..07135e16 --- /dev/null +++ b/test/assets/www/accelerometer/index.html @@ -0,0 +1,138 @@ + + + + + + PhoneGap + + + + + + + + + +

Acceleration

+
+
Stopped
+
+ + + +
X: 
Y: 
Z: 
+
+ +

Action

+ Get Acceleration + Start Watch + Clear Watch +

 

Back + + diff --git a/test/assets/www/audio/index.html b/test/assets/www/audio/index.html new file mode 100755 index 00000000..ba1a1bf3 --- /dev/null +++ b/test/assets/www/audio/index.html @@ -0,0 +1,272 @@ + + + + + + PhoneGap + + + + + + + + + +

Audio

+
+ + + + +
Status: 
Duration:
Position:
+
+

Action

+ Play/Unpause Audio + Pause Playing Audio + Stop Playing Audio + Record Audio for 10 sec + Play/Unpause Recording +

 

Back + + + diff --git a/test/assets/www/autotest/index.html b/test/assets/www/autotest/index.html new file mode 100755 index 00000000..5349bdab --- /dev/null +++ b/test/assets/www/autotest/index.html @@ -0,0 +1,62 @@ + + + + + + + PhoneGap API Spec + + + + + + + + + + + + + + + + + + + + + + + + + + + + +

PhoneGap API Spec

+

+

+
    +
    + +
    + + diff --git a/test/assets/www/autotest/qunit.css b/test/assets/www/autotest/qunit.css new file mode 100644 index 00000000..78a7d7e3 --- /dev/null +++ b/test/assets/www/autotest/qunit.css @@ -0,0 +1,215 @@ +/** Font Family and Sizes */ + +#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { + font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial; +} + +#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } +#qunit-tests { font-size: smaller; } + + +/** Resets */ + +#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult { + margin: 0; + padding: 0; +} + + +/** Header */ + +#qunit-header { + padding: 0.5em 0 0.5em 1em; + + color: #8699a4; + background-color: #0d3349; + + font-size: 1.5em; + line-height: 1em; + font-weight: normal; + + border-radius: 15px 15px 0 0; + -moz-border-radius: 15px 15px 0 0; + -webkit-border-top-right-radius: 15px; + -webkit-border-top-left-radius: 15px; +} + +#qunit-header a { + text-decoration: none; + color: #c2ccd1; +} + +#qunit-header a:hover, +#qunit-header a:focus { + color: #fff; +} + +#qunit-banner { + height: 5px; +} + +#qunit-testrunner-toolbar { + padding: 0.5em 0 0.5em 2em; + color: #5E740B; + background-color: #eee; +} + +#qunit-userAgent { + padding: 0.5em 0 0.5em 2.5em; + background-color: #2b81af; + color: #fff; + text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; +} + + +/** Tests: Pass/Fail */ + +#qunit-tests { + list-style-position: inside; +} + +#qunit-tests li { + padding: 0.4em 0.5em 0.4em 2.5em; + border-bottom: 1px solid #fff; + list-style-position: inside; +} + +#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { + display: none; +} + +#qunit-tests li strong { + cursor: pointer; +} + +#qunit-tests li a { + padding: 0.5em; + color: #c2ccd1; + text-decoration: none; +} +#qunit-tests li a:hover, +#qunit-tests li a:focus { + color: #000; +} + +#qunit-tests ol { + margin-top: 0.5em; + padding: 0.5em; + + background-color: #fff; + + border-radius: 15px; + -moz-border-radius: 15px; + -webkit-border-radius: 15px; + + box-shadow: inset 0px 2px 13px #999; + -moz-box-shadow: inset 0px 2px 13px #999; + -webkit-box-shadow: inset 0px 2px 13px #999; +} + +#qunit-tests table { + border-collapse: collapse; + margin-top: .2em; +} + +#qunit-tests th { + text-align: right; + vertical-align: top; + padding: 0 .5em 0 0; +} + +#qunit-tests td { + vertical-align: top; +} + +#qunit-tests pre { + margin: 0; + white-space: pre-wrap; + word-wrap: break-word; +} + +#qunit-tests del { + background-color: #e0f2be; + color: #374e0c; + text-decoration: none; +} + +#qunit-tests ins { + background-color: #ffcaca; + color: #500; + text-decoration: none; +} + +/*** Test Counts */ + +#qunit-tests b.counts { color: black; } +#qunit-tests b.passed { color: #5E740B; } +#qunit-tests b.failed { color: #710909; } + +#qunit-tests li li { + margin: 0.5em; + padding: 0.4em 0.5em 0.4em 0.5em; + background-color: #fff; + border-bottom: none; + list-style-position: inside; +} + +/*** Passing Styles */ + +#qunit-tests li li.pass { + color: #5E740B; + background-color: #fff; + border-left: 26px solid #C6E746; +} + +#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } +#qunit-tests .pass .test-name { color: #366097; } + +#qunit-tests .pass .test-actual, +#qunit-tests .pass .test-expected { color: #999999; } + +#qunit-banner.qunit-pass { background-color: #C6E746; } + +/*** Failing Styles */ + +#qunit-tests li li.fail { + color: #710909; + background-color: #fff; + border-left: 26px solid #EE5757; +} + +#qunit-tests > li:last-child { + border-radius: 0 0 15px 15px; + -moz-border-radius: 0 0 15px 15px; + -webkit-border-bottom-right-radius: 15px; + -webkit-border-bottom-left-radius: 15px; +} + +#qunit-tests .fail { color: #000000; background-color: #EE5757; } +#qunit-tests .fail .test-name, +#qunit-tests .fail .module-name { color: #000000; } + +#qunit-tests .fail .test-actual { color: #EE5757; } +#qunit-tests .fail .test-expected { color: green; } + +#qunit-banner.qunit-fail { background-color: #EE5757; } + + +/** Result */ + +#qunit-testresult { + padding: 0.5em 0.5em 0.5em 2.5em; + + color: #2b81af; + background-color: #D2E0E6; + + border-bottom: 1px solid white; +} + +/** Fixture */ + +#qunit-fixture { + position: absolute; + top: -10000px; + left: -10000px; +} diff --git a/test/assets/www/autotest/qunit.js b/test/assets/www/autotest/qunit.js new file mode 100644 index 00000000..b7e8a1c9 --- /dev/null +++ b/test/assets/www/autotest/qunit.js @@ -0,0 +1,1434 @@ +/* + * QUnit - A JavaScript Unit Testing Framework + * + * http://docs.jquery.com/QUnit + * + * Copyright (c) 2011 John Resig, Jörn Zaefferer + * Dual licensed under the MIT (MIT-LICENSE.txt) + * or GPL (GPL-LICENSE.txt) licenses. + */ + +(function(window) { + +var defined = { + setTimeout: typeof window.setTimeout !== "undefined", + sessionStorage: (function() { + try { + return !!sessionStorage.getItem; + } catch(e){ + return false; + } + })() +}; + +var testId = 0; + +var Test = function(name, testName, expected, testEnvironmentArg, async, callback) { + this.name = name; + this.testName = testName; + this.expected = expected; + this.testEnvironmentArg = testEnvironmentArg; + this.async = async; + this.callback = callback; + this.assertions = []; +}; +Test.prototype = { + init: function() { + var tests = id("qunit-tests"); + if (tests) { + var b = document.createElement("strong"); + b.innerHTML = "Running " + this.name; + var li = document.createElement("li"); + li.appendChild( b ); + li.className = "running"; + li.id = this.id = "test-output" + testId++; + tests.appendChild( li ); + } + }, + setup: function() { + if (this.module != config.previousModule) { + if ( config.previousModule ) { + QUnit.moduleDone( { + name: config.previousModule, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all + } ); + } + config.previousModule = this.module; + config.moduleStats = { all: 0, bad: 0 }; + QUnit.moduleStart( { + name: this.module + } ); + } + + config.current = this; + this.testEnvironment = extend({ + setup: function() {}, + teardown: function() {} + }, this.moduleTestEnvironment); + if (this.testEnvironmentArg) { + extend(this.testEnvironment, this.testEnvironmentArg); + } + + QUnit.testStart( { + name: this.testName + } ); + + // allow utility functions to access the current test environment + // TODO why?? + QUnit.current_testEnvironment = this.testEnvironment; + + try { + if ( !config.pollution ) { + saveGlobal(); + } + + this.testEnvironment.setup.call(this.testEnvironment); + } catch(e) { + QUnit.ok( false, "Setup failed on " + this.testName + ": " + e.message ); + } + }, + run: function() { + if ( this.async ) { + QUnit.stop(); + } + + if ( config.notrycatch ) { + this.callback.call(this.testEnvironment); + return; + } + try { + this.callback.call(this.testEnvironment); + } catch(e) { + fail("Test " + this.testName + " died, exception and test follows", e, this.callback); + QUnit.ok( false, "Died on test #" + (this.assertions.length + 1) + ": " + e.message + " - " + QUnit.jsDump.parse(e) ); + // else next test will carry the responsibility + saveGlobal(); + + // Restart the tests if they're blocking + if ( config.blocking ) { + start(); + } + } + }, + teardown: function() { + try { + checkPollution(); + this.testEnvironment.teardown.call(this.testEnvironment); + } catch(e) { + QUnit.ok( false, "Teardown failed on " + this.testName + ": " + e.message ); + } + }, + finish: function() { + if ( this.expected && this.expected != this.assertions.length ) { + QUnit.ok( false, "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run" ); + } + + var good = 0, bad = 0, + tests = id("qunit-tests"); + + config.stats.all += this.assertions.length; + config.moduleStats.all += this.assertions.length; + + if ( tests ) { + var ol = document.createElement("ol"); + + for ( var i = 0; i < this.assertions.length; i++ ) { + var assertion = this.assertions[i]; + + var li = document.createElement("li"); + li.className = assertion.result ? "pass" : "fail"; + li.innerHTML = assertion.message || (assertion.result ? "okay" : "failed"); + ol.appendChild( li ); + + if ( assertion.result ) { + good++; + } else { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + + // store result when possible + QUnit.config.reorder && defined.sessionStorage && sessionStorage.setItem("qunit-" + this.testName, bad); + + if (bad == 0) { + ol.style.display = "none"; + } + + var b = document.createElement("strong"); + b.innerHTML = this.name + " (" + bad + ", " + good + ", " + this.assertions.length + ")"; + + var a = document.createElement("a"); + a.innerHTML = "Rerun"; + a.href = QUnit.url({ filter: getText([b]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); + + addEvent(b, "click", function() { + var next = b.nextSibling.nextSibling, + display = next.style.display; + next.style.display = display === "none" ? "block" : "none"; + }); + + addEvent(b, "dblclick", function(e) { + var target = e && e.target ? e.target : window.event.srcElement; + if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) { + target = target.parentNode; + } + if ( window.location && target.nodeName.toLowerCase() === "strong" ) { + window.location = QUnit.url({ filter: getText([target]).replace(/\([^)]+\)$/, "").replace(/(^\s*|\s*$)/g, "") }); + } + }); + + var li = id(this.id); + li.className = bad ? "fail" : "pass"; + li.removeChild( li.firstChild ); + li.appendChild( b ); + li.appendChild( a ); + li.appendChild( ol ); + + } else { + for ( var i = 0; i < this.assertions.length; i++ ) { + if ( !this.assertions[i].result ) { + bad++; + config.stats.bad++; + config.moduleStats.bad++; + } + } + } + + try { + QUnit.reset(); + } catch(e) { + fail("reset() failed, following Test " + this.testName + ", exception and reset fn follows", e, QUnit.reset); + } + + QUnit.testDone( { + name: this.testName, + failed: bad, + passed: this.assertions.length - bad, + total: this.assertions.length + } ); + }, + + queue: function() { + var test = this; + synchronize(function() { + test.init(); + }); + function run() { + // each of these can by async + synchronize(function() { + test.setup(); + }); + synchronize(function() { + test.run(); + }); + synchronize(function() { + test.teardown(); + }); + synchronize(function() { + test.finish(); + }); + } + // defer when previous test run passed, if storage is available + var bad = QUnit.config.reorder && defined.sessionStorage && +sessionStorage.getItem("qunit-" + this.testName); + if (bad) { + run(); + } else { + synchronize(run); + }; + } + +}; + +var QUnit = { + + // call on start of module test to prepend name to all tests + module: function(name, testEnvironment) { + config.currentModule = name; + config.currentModuleTestEnviroment = testEnvironment; + }, + + asyncTest: function(testName, expected, callback) { + if ( arguments.length === 2 ) { + callback = expected; + expected = 0; + } + + QUnit.test(testName, expected, callback, true); + }, + + test: function(testName, expected, callback, async) { + var name = '' + testName + '', testEnvironmentArg; + + if ( arguments.length === 2 ) { + callback = expected; + expected = null; + } + // is 2nd argument a testEnvironment? + if ( expected && typeof expected === 'object') { + testEnvironmentArg = expected; + expected = null; + } + + if ( config.currentModule ) { + name = '' + config.currentModule + ": " + name; + } + + if ( !validTest(config.currentModule + ": " + testName) ) { + return; + } + + var test = new Test(name, testName, expected, testEnvironmentArg, async, callback); + test.module = config.currentModule; + test.moduleTestEnvironment = config.currentModuleTestEnviroment; + test.queue(); + }, + + /** + * Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through. + */ + expect: function(asserts) { + config.current.expected = asserts; + }, + + /** + * Asserts true. + * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" ); + */ + ok: function(a, msg) { + a = !!a; + var details = { + result: a, + message: msg + }; + msg = escapeHtml(msg); + QUnit.log(details); + config.current.assertions.push({ + result: a, + message: msg + }); + }, + + /** + * Checks that the first two arguments are equal, with an optional message. + * Prints out both actual and expected values. + * + * Prefered to ok( actual == expected, message ) + * + * @example equal( format("Received {0} bytes.", 2), "Received 2 bytes." ); + * + * @param Object actual + * @param Object expected + * @param String message (optional) + */ + equal: function(actual, expected, message) { + QUnit.push(expected == actual, actual, expected, message); + }, + + notEqual: function(actual, expected, message) { + QUnit.push(expected != actual, actual, expected, message); + }, + + deepEqual: function(actual, expected, message) { + QUnit.push(QUnit.equiv(actual, expected), actual, expected, message); + }, + + notDeepEqual: function(actual, expected, message) { + QUnit.push(!QUnit.equiv(actual, expected), actual, expected, message); + }, + + strictEqual: function(actual, expected, message) { + QUnit.push(expected === actual, actual, expected, message); + }, + + notStrictEqual: function(actual, expected, message) { + QUnit.push(expected !== actual, actual, expected, message); + }, + + raises: function(block, expected, message) { + var actual, ok = false; + + if (typeof expected === 'string') { + message = expected; + expected = null; + } + + try { + block(); + } catch (e) { + actual = e; + } + + if (actual) { + // we don't want to validate thrown error + if (!expected) { + ok = true; + // expected is a regexp + } else if (QUnit.objectType(expected) === "regexp") { + ok = expected.test(actual); + // expected is a constructor + } else if (actual instanceof expected) { + ok = true; + // expected is a validation function which returns true is validation passed + } else if (expected.call({}, actual) === true) { + ok = true; + } + } + + QUnit.ok(ok, message); + }, + + start: function() { + config.semaphore--; + if (config.semaphore > 0) { + // don't start until equal number of stop-calls + return; + } + if (config.semaphore < 0) { + // ignore if start is called more often then stop + config.semaphore = 0; + } + // A slight delay, to avoid any current callbacks + if ( defined.setTimeout ) { + window.setTimeout(function() { + if ( config.timeout ) { + clearTimeout(config.timeout); + } + + config.blocking = false; + process(); + }, 250); + } else { + config.blocking = false; + process(); + } + }, + + stop: function(timeout) { + config.semaphore++; + config.blocking = true; + + if ( timeout && defined.setTimeout ) { + clearTimeout(config.timeout); + config.timeout = window.setTimeout(function() { + QUnit.ok( false, "Test timed out" ); + QUnit.start(); + }, timeout); + } + } +}; + +// Backwards compatibility, deprecated +QUnit.equals = QUnit.equal; +QUnit.same = QUnit.deepEqual; + +// Maintain internal state +var config = { + // The queue of tests to run + queue: [], + + // block until document ready + blocking: true, + + // by default, run previously failed tests first + // very useful in combination with "Hide passed tests" checked + reorder: true, + + noglobals: false, + notrycatch: false +}; + +// Load paramaters +(function() { + var location = window.location || { search: "", protocol: "file:" }, + params = location.search.slice( 1 ).split( "&" ), + length = params.length, + urlParams = {}, + current; + + if ( params[ 0 ] ) { + for ( var i = 0; i < length; i++ ) { + current = params[ i ].split( "=" ); + current[ 0 ] = decodeURIComponent( current[ 0 ] ); + // allow just a key to turn on a flag, e.g., test.html?noglobals + current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true; + urlParams[ current[ 0 ] ] = current[ 1 ]; + if ( current[ 0 ] in config ) { + config[ current[ 0 ] ] = current[ 1 ]; + } + } + } + + QUnit.urlParams = urlParams; + config.filter = urlParams.filter; + + // Figure out if we're running the tests from a server or not + QUnit.isLocal = !!(location.protocol === 'file:'); +})(); + +// Expose the API as global variables, unless an 'exports' +// object exists, in that case we assume we're in CommonJS +if ( typeof exports === "undefined" || typeof require === "undefined" ) { + extend(window, QUnit); + window.QUnit = QUnit; +} else { + extend(exports, QUnit); + exports.QUnit = QUnit; +} + +// define these after exposing globals to keep them in these QUnit namespace only +extend(QUnit, { + config: config, + + // Initialize the configuration options + init: function() { + extend(config, { + stats: { all: 0, bad: 0 }, + moduleStats: { all: 0, bad: 0 }, + started: +new Date, + updateRate: 1000, + blocking: false, + autostart: true, + autorun: false, + filter: "", + queue: [], + semaphore: 0 + }); + + var tests = id( "qunit-tests" ), + banner = id( "qunit-banner" ), + result = id( "qunit-testresult" ); + + if ( tests ) { + tests.innerHTML = ""; + } + + if ( banner ) { + banner.className = ""; + } + + if ( result ) { + result.parentNode.removeChild( result ); + } + + if ( tests ) { + result = document.createElement( "p" ); + result.id = "qunit-testresult"; + result.className = "result"; + tests.parentNode.insertBefore( result, tests ); + result.innerHTML = 'Running...
     '; + } + }, + + /** + * Resets the test setup. Useful for tests that modify the DOM. + * + * If jQuery is available, uses jQuery's html(), otherwise just innerHTML. + */ + reset: function() { + if ( window.jQuery ) { + jQuery( "#main, #qunit-fixture" ).html( config.fixture ); + } else { + var main = id( 'main' ) || id( 'qunit-fixture' ); + if ( main ) { + main.innerHTML = config.fixture; + } + } + }, + + /** + * Trigger an event on an element. + * + * @example triggerEvent( document.body, "click" ); + * + * @param DOMElement elem + * @param String type + */ + triggerEvent: function( elem, type, event ) { + if ( document.createEvent ) { + event = document.createEvent("MouseEvents"); + event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView, + 0, 0, 0, 0, 0, false, false, false, false, 0, null); + elem.dispatchEvent( event ); + + } else if ( elem.fireEvent ) { + elem.fireEvent("on"+type); + } + }, + + // Safe object type checking + is: function( type, obj ) { + return QUnit.objectType( obj ) == type; + }, + + objectType: function( obj ) { + if (typeof obj === "undefined") { + return "undefined"; + + // consider: typeof null === object + } + if (obj === null) { + return "null"; + } + + var type = Object.prototype.toString.call( obj ) + .match(/^\[object\s(.*)\]$/)[1] || ''; + + switch (type) { + case 'Number': + if (isNaN(obj)) { + return "nan"; + } else { + return "number"; + } + case 'String': + case 'Boolean': + case 'Array': + case 'Date': + case 'RegExp': + case 'Function': + return type.toLowerCase(); + } + if (typeof obj === "object") { + return "object"; + } + return undefined; + }, + + push: function(result, actual, expected, message) { + var details = { + result: result, + message: message, + actual: actual, + expected: expected + }; + + message = escapeHtml(message) || (result ? "okay" : "failed"); + message = '' + message + ""; + expected = escapeHtml(QUnit.jsDump.parse(expected)); + actual = escapeHtml(QUnit.jsDump.parse(actual)); + var output = message + ''; + if (actual != expected) { + output += ''; + output += ''; + } + if (!result) { + var source = sourceFromStacktrace(); + if (source) { + details.source = source; + output += ''; + } + } + output += "
    Expected:
    ' + expected + '
    Result:
    ' + actual + '
    Diff:
    ' + QUnit.diff(expected, actual) +'
    Source:
    ' + source +'
    "; + + QUnit.log(details); + + config.current.assertions.push({ + result: !!result, + message: output + }); + }, + + url: function( params ) { + params = extend( extend( {}, QUnit.urlParams ), params ); + var querystring = "?", + key; + for ( key in params ) { + querystring += encodeURIComponent( key ) + "=" + + encodeURIComponent( params[ key ] ) + "&"; + } + return window.location.pathname + querystring.slice( 0, -1 ); + }, + + // Logging callbacks; all receive a single argument with the listed properties + // run test/logs.html for any related changes + begin: function() {}, + // done: { failed, passed, total, runtime } + done: function() {}, + // log: { result, actual, expected, message } + log: function() {}, + // testStart: { name } + testStart: function() {}, + // testDone: { name, failed, passed, total } + testDone: function() {}, + // moduleStart: { name } + moduleStart: function() {}, + // moduleDone: { name, failed, passed, total } + moduleDone: function() {} +}); + +if ( typeof document === "undefined" || document.readyState === "complete" ) { + config.autorun = true; +} + +addEvent(window, "load", function() { + QUnit.begin({}); + + // Initialize the config, saving the execution queue + var oldconfig = extend({}, config); + QUnit.init(); + extend(config, oldconfig); + + config.blocking = false; + + var userAgent = id("qunit-userAgent"); + if ( userAgent ) { + userAgent.innerHTML = navigator.userAgent; + } + var banner = id("qunit-header"); + if ( banner ) { + banner.innerHTML = ' ' + banner.innerHTML + ' ' + + '' + + ''; + addEvent( banner, "change", function( event ) { + var params = {}; + params[ event.target.name ] = event.target.checked ? true : undefined; + window.location = QUnit.url( params ); + }); + } + + var toolbar = id("qunit-testrunner-toolbar"); + if ( toolbar ) { + var filter = document.createElement("input"); + filter.type = "checkbox"; + filter.id = "qunit-filter-pass"; + addEvent( filter, "click", function() { + var ol = document.getElementById("qunit-tests"); + if ( filter.checked ) { + ol.className = ol.className + " hidepass"; + } else { + var tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " "; + ol.className = tmp.replace(/ hidepass /, " "); + } + if ( defined.sessionStorage ) { + sessionStorage.setItem("qunit-filter-passed-tests", filter.checked ? "true" : ""); + } + }); + if ( defined.sessionStorage && sessionStorage.getItem("qunit-filter-passed-tests") ) { + filter.checked = true; + var ol = document.getElementById("qunit-tests"); + ol.className = ol.className + " hidepass"; + } + toolbar.appendChild( filter ); + + var label = document.createElement("label"); + label.setAttribute("for", "qunit-filter-pass"); + label.innerHTML = "Hide passed tests"; + toolbar.appendChild( label ); + } + + var main = id('main') || id('qunit-fixture'); + if ( main ) { + config.fixture = main.innerHTML; + } + + if (config.autostart) { + QUnit.start(); + } +}); + +function done() { + config.autorun = true; + + // Log the last module results + if ( config.currentModule ) { + QUnit.moduleDone( { + name: config.currentModule, + failed: config.moduleStats.bad, + passed: config.moduleStats.all - config.moduleStats.bad, + total: config.moduleStats.all + } ); + } + + var banner = id("qunit-banner"), + tests = id("qunit-tests"), + runtime = +new Date - config.started, + passed = config.stats.all - config.stats.bad, + html = [ + 'Tests completed in ', + runtime, + ' milliseconds.
    ', + '', + passed, + ' tests of ', + config.stats.all, + ' passed, ', + config.stats.bad, + ' failed.' + ].join(''); + + if ( banner ) { + banner.className = (config.stats.bad ? "qunit-fail" : "qunit-pass"); + } + + if ( tests ) { + id( "qunit-testresult" ).innerHTML = html; + } + + QUnit.done( { + failed: config.stats.bad, + passed: passed, + total: config.stats.all, + runtime: runtime + } ); +} + +function validTest( name ) { + var filter = config.filter, + run = false; + + if ( !filter ) { + return true; + } + + not = filter.charAt( 0 ) === "!"; + if ( not ) { + filter = filter.slice( 1 ); + } + + if ( name.indexOf( filter ) !== -1 ) { + return !not; + } + + if ( not ) { + run = true; + } + + return run; +} + +// so far supports only Firefox, Chrome and Opera (buggy) +// could be extended in the future to use something like https://github.com/csnover/TraceKit +function sourceFromStacktrace() { + try { + throw new Error(); + } catch ( e ) { + if (e.stacktrace) { + // Opera + return e.stacktrace.split("\n")[6]; + } else if (e.stack) { + // Firefox, Chrome + return e.stack.split("\n")[4]; + } + } +} + +function escapeHtml(s) { + if (!s) { + return ""; + } + s = s + ""; + return s.replace(/[\&"<>\\]/g, function(s) { + switch(s) { + case "&": return "&"; + case "\\": return "\\\\"; + case '"': return '\"'; + case "<": return "<"; + case ">": return ">"; + default: return s; + } + }); +} + +function synchronize( callback ) { + config.queue.push( callback ); + + if ( config.autorun && !config.blocking ) { + process(); + } +} + +function process() { + var start = (new Date()).getTime(); + + while ( config.queue.length && !config.blocking ) { + if ( config.updateRate <= 0 || (((new Date()).getTime() - start) < config.updateRate) ) { + config.queue.shift()(); + } else { + window.setTimeout( process, 250 ); + break; + } + } + if (!config.blocking && !config.queue.length) { + done(); + } +} + +function saveGlobal() { + config.pollution = []; + + if ( config.noglobals ) { + for ( var key in window ) { + config.pollution.push( key ); + } + } +} + +function checkPollution( name ) { + var old = config.pollution; + saveGlobal(); + + var newGlobals = diff( old, config.pollution ); + if ( newGlobals.length > 0 ) { + ok( false, "Introduced global variable(s): " + newGlobals.join(", ") ); + config.current.expected++; + } + + var deletedGlobals = diff( config.pollution, old ); + if ( deletedGlobals.length > 0 ) { + ok( false, "Deleted global variable(s): " + deletedGlobals.join(", ") ); + config.current.expected++; + } +} + +// returns a new Array with the elements that are in a but not in b +function diff( a, b ) { + var result = a.slice(); + for ( var i = 0; i < result.length; i++ ) { + for ( var j = 0; j < b.length; j++ ) { + if ( result[i] === b[j] ) { + result.splice(i, 1); + i--; + break; + } + } + } + return result; +} + +function fail(message, exception, callback) { + if ( typeof console !== "undefined" && console.error && console.warn ) { + console.error(message); + console.error(exception); + console.warn(callback.toString()); + + } else if ( window.opera && opera.postError ) { + opera.postError(message, exception, callback.toString); + } +} + +function extend(a, b) { + for ( var prop in b ) { + if ( b[prop] === undefined ) { + delete a[prop]; + } else { + a[prop] = b[prop]; + } + } + + return a; +} + +function addEvent(elem, type, fn) { + if ( elem.addEventListener ) { + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + elem.attachEvent( "on" + type, fn ); + } else { + fn(); + } +} + +function id(name) { + return !!(typeof document !== "undefined" && document && document.getElementById) && + document.getElementById( name ); +} + +// Test for equality any JavaScript type. +// Discussions and reference: http://philrathe.com/articles/equiv +// Test suites: http://philrathe.com/tests/equiv +// Author: Philippe Rathé +QUnit.equiv = function () { + + var innerEquiv; // the real equiv function + var callers = []; // stack to decide between skip/abort functions + var parents = []; // stack to avoiding loops from circular referencing + + // Call the o related callback with the given arguments. + function bindCallbacks(o, callbacks, args) { + var prop = QUnit.objectType(o); + if (prop) { + if (QUnit.objectType(callbacks[prop]) === "function") { + return callbacks[prop].apply(callbacks, args); + } else { + return callbacks[prop]; // or undefined + } + } + } + + var callbacks = function () { + + // for string, boolean, number and null + function useStrictEquality(b, a) { + if (b instanceof a.constructor || a instanceof b.constructor) { + // to catch short annotaion VS 'new' annotation of a declaration + // e.g. var i = 1; + // var j = new Number(1); + return a == b; + } else { + return a === b; + } + } + + return { + "string": useStrictEquality, + "boolean": useStrictEquality, + "number": useStrictEquality, + "null": useStrictEquality, + "undefined": useStrictEquality, + + "nan": function (b) { + return isNaN(b); + }, + + "date": function (b, a) { + return QUnit.objectType(b) === "date" && a.valueOf() === b.valueOf(); + }, + + "regexp": function (b, a) { + return QUnit.objectType(b) === "regexp" && + a.source === b.source && // the regex itself + a.global === b.global && // and its modifers (gmi) ... + a.ignoreCase === b.ignoreCase && + a.multiline === b.multiline; + }, + + // - skip when the property is a method of an instance (OOP) + // - abort otherwise, + // initial === would have catch identical references anyway + "function": function () { + var caller = callers[callers.length - 1]; + return caller !== Object && + typeof caller !== "undefined"; + }, + + "array": function (b, a) { + var i, j, loop; + var len; + + // b could be an object literal here + if ( ! (QUnit.objectType(b) === "array")) { + return false; + } + + len = a.length; + if (len !== b.length) { // safe and faster + return false; + } + + //track reference to avoid circular references + parents.push(a); + for (i = 0; i < len; i++) { + loop = false; + for(j=0;j= 0) { + type = "array"; + } else { + type = typeof obj; + } + return type; + }, + separator:function() { + return this.multiline ? this.HTML ? '
    ' : '\n' : this.HTML ? ' ' : ' '; + }, + indent:function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing + if ( !this.multiline ) + return ''; + var chr = this.indentChar; + if ( this.HTML ) + chr = chr.replace(/\t/g,' ').replace(/ /g,' '); + return Array( this._depth_ + (extra||0) ).join(chr); + }, + up:function( a ) { + this._depth_ += a || 1; + }, + down:function( a ) { + this._depth_ -= a || 1; + }, + setParser:function( name, parser ) { + this.parsers[name] = parser; + }, + // The next 3 are exposed so you can use them + quote:quote, + literal:literal, + join:join, + // + _depth_: 1, + // This is the list of parsers, to modify them, use jsDump.setParser + parsers:{ + window: '[Window]', + document: '[Document]', + error:'[ERROR]', //when no parser is found, shouldn't happen + unknown: '[Unknown]', + 'null':'null', + 'undefined':'undefined', + 'function':function( fn ) { + var ret = 'function', + name = 'name' in fn ? fn.name : (reName.exec(fn)||[])[1];//functions never have name in IE + if ( name ) + ret += ' ' + name; + ret += '('; + + ret = [ ret, QUnit.jsDump.parse( fn, 'functionArgs' ), '){'].join(''); + return join( ret, QUnit.jsDump.parse(fn,'functionCode'), '}' ); + }, + array: array, + nodelist: array, + arguments: array, + object:function( map ) { + var ret = [ ]; + QUnit.jsDump.up(); + for ( var key in map ) + ret.push( QUnit.jsDump.parse(key,'key') + ': ' + QUnit.jsDump.parse(map[key]) ); + QUnit.jsDump.down(); + return join( '{', ret, '}' ); + }, + node:function( node ) { + var open = QUnit.jsDump.HTML ? '<' : '<', + close = QUnit.jsDump.HTML ? '>' : '>'; + + var tag = node.nodeName.toLowerCase(), + ret = open + tag; + + for ( var a in QUnit.jsDump.DOMAttrs ) { + var val = node[QUnit.jsDump.DOMAttrs[a]]; + if ( val ) + ret += ' ' + a + '=' + QUnit.jsDump.parse( val, 'attribute' ); + } + return ret + close + open + '/' + tag + close; + }, + functionArgs:function( fn ) {//function calls it internally, it's the arguments part of the function + var l = fn.length; + if ( !l ) return ''; + + var args = Array(l); + while ( l-- ) + args[l] = String.fromCharCode(97+l);//97 is 'a' + return ' ' + args.join(', ') + ' '; + }, + key:quote, //object calls it internally, the key part of an item in a map + functionCode:'[code]', //function calls it internally, it's the content of the function + attribute:quote, //node calls it internally, it's an html attribute value + string:quote, + date:quote, + regexp:literal, //regex + number:literal, + 'boolean':literal + }, + DOMAttrs:{//attributes to dump from nodes, name=>realName + id:'id', + name:'name', + 'class':'className' + }, + HTML:false,//if true, entities are escaped ( <, >, \t, space and \n ) + indentChar:' ',//indentation unit + multiline:true //if true, items in a collection, are separated by a \n, else just a space. + }; + + return jsDump; +})(); + +// from Sizzle.js +function getText( elems ) { + var ret = "", elem; + + for ( var i = 0; elems[i]; i++ ) { + elem = elems[i]; + + // Get the text from text nodes and CDATA nodes + if ( elem.nodeType === 3 || elem.nodeType === 4 ) { + ret += elem.nodeValue; + + // Traverse everything else, except comment nodes + } else if ( elem.nodeType !== 8 ) { + ret += getText( elem.childNodes ); + } + } + + return ret; +}; + +/* + * Javascript Diff Algorithm + * By John Resig (http://ejohn.org/) + * Modified by Chu Alan "sprite" + * + * Released under the MIT license. + * + * More Info: + * http://ejohn.org/projects/javascript-diff-algorithm/ + * + * Usage: QUnit.diff(expected, actual) + * + * QUnit.diff("the quick brown fox jumped over", "the quick fox jumps over") == "the quick brown fox jumped jumps over" + */ +QUnit.diff = (function() { + function diff(o, n){ + var ns = new Object(); + var os = new Object(); + + for (var i = 0; i < n.length; i++) { + if (ns[n[i]] == null) + ns[n[i]] = { + rows: new Array(), + o: null + }; + ns[n[i]].rows.push(i); + } + + for (var i = 0; i < o.length; i++) { + if (os[o[i]] == null) + os[o[i]] = { + rows: new Array(), + n: null + }; + os[o[i]].rows.push(i); + } + + for (var i in ns) { + if (ns[i].rows.length == 1 && typeof(os[i]) != "undefined" && os[i].rows.length == 1) { + n[ns[i].rows[0]] = { + text: n[ns[i].rows[0]], + row: os[i].rows[0] + }; + o[os[i].rows[0]] = { + text: o[os[i].rows[0]], + row: ns[i].rows[0] + }; + } + } + + for (var i = 0; i < n.length - 1; i++) { + if (n[i].text != null && n[i + 1].text == null && n[i].row + 1 < o.length && o[n[i].row + 1].text == null && + n[i + 1] == o[n[i].row + 1]) { + n[i + 1] = { + text: n[i + 1], + row: n[i].row + 1 + }; + o[n[i].row + 1] = { + text: o[n[i].row + 1], + row: i + 1 + }; + } + } + + for (var i = n.length - 1; i > 0; i--) { + if (n[i].text != null && n[i - 1].text == null && n[i].row > 0 && o[n[i].row - 1].text == null && + n[i - 1] == o[n[i].row - 1]) { + n[i - 1] = { + text: n[i - 1], + row: n[i].row - 1 + }; + o[n[i].row - 1] = { + text: o[n[i].row - 1], + row: i - 1 + }; + } + } + + return { + o: o, + n: n + }; + } + + return function(o, n){ + o = o.replace(/\s+$/, ''); + n = n.replace(/\s+$/, ''); + var out = diff(o == "" ? [] : o.split(/\s+/), n == "" ? [] : n.split(/\s+/)); + + var str = ""; + + var oSpace = o.match(/\s+/g); + if (oSpace == null) { + oSpace = [" "]; + } + else { + oSpace.push(" "); + } + var nSpace = n.match(/\s+/g); + if (nSpace == null) { + nSpace = [" "]; + } + else { + nSpace.push(" "); + } + + if (out.n.length == 0) { + for (var i = 0; i < out.o.length; i++) { + str += '' + out.o[i] + oSpace[i] + ""; + } + } + else { + if (out.n[0].text == null) { + for (n = 0; n < out.o.length && out.o[n].text == null; n++) { + str += '' + out.o[n] + oSpace[n] + ""; + } + } + + for (var i = 0; i < out.n.length; i++) { + if (out.n[i].text == null) { + str += '' + out.n[i] + nSpace[i] + ""; + } + else { + var pre = ""; + + for (n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++) { + pre += '' + out.o[n] + oSpace[n] + ""; + } + str += " " + out.n[i].text + nSpace[i] + pre; + } + } + } + + return str; + }; +})(); + +})(this); diff --git a/test/assets/www/autotest/test-runner.js b/test/assets/www/autotest/test-runner.js new file mode 100644 index 00000000..0bd1aa2c --- /dev/null +++ b/test/assets/www/autotest/test-runner.js @@ -0,0 +1,9 @@ +// Prevent QUnit from running when the DOM load event fires +QUnit.config.autostart = false; +sessionStorage.clear(); + +// Timeout is 2 seconds to allow physical devices enough +// time to query the response. This is important for some +// Android devices. +var Tests = function() {}; +Tests.TEST_TIMEOUT = 2000; diff --git a/test/assets/www/autotest/tests/accelerometer.tests.js b/test/assets/www/autotest/tests/accelerometer.tests.js new file mode 100644 index 00000000..a99d459f --- /dev/null +++ b/test/assets/www/autotest/tests/accelerometer.tests.js @@ -0,0 +1,55 @@ +Tests.prototype.AccelerometerTests = function() { + module('Accelerometer (navigator.accelerometer)'); + test("should exist", function() { + expect(1); + ok(navigator.accelerometer != null, "navigator.accelerometer should not be null."); + }); + test("should contain a getCurrentAcceleration function", function() { + expect(2); + ok(typeof navigator.accelerometer.getCurrentAcceleration != 'undefined' && navigator.accelerometer.getCurrentAcceleration != null, "navigator.accelerometer.getCurrentAcceleration should not be null."); + ok(typeof navigator.accelerometer.getCurrentAcceleration == 'function', "navigator.accelerometer.getCurrentAcceleration should be a function."); + }); + test("getCurrentAcceleration success callback should be called with an Acceleration object", function() { + expect(7); + QUnit.stop(Tests.TEST_TIMEOUT); + var win = function(a) { + ok(typeof a == 'object', "Acceleration object returned in getCurrentAcceleration success callback should be of type 'object'."); + ok(a.x != null, "Acceleration object returned in getCurrentAcceleration success callback should have an 'x' property."); + ok(typeof a.x == 'number', "Acceleration object's 'x' property returned in getCurrentAcceleration success callback should be of type 'number'."); + ok(a.y != null, "Acceleration object returned in getCurrentAcceleration success callback should have a 'y' property."); + ok(typeof a.y == 'number', "Acceleration object's 'y' property returned in getCurrentAcceleration success callback should be of type 'number'."); + ok(a.z != null, "Acceleration object returned in getCurrentAcceleration success callback should have a 'z' property."); + ok(typeof a.z == 'number', "Acceleration object's 'z' property returned in getCurrentAcceleration success callback should be of type 'number'."); + start(); + }; + var fail = function() { start(); }; + navigator.accelerometer.getCurrentAcceleration(win, fail); + }); + test("should contain a watchAcceleration function", function() { + expect(2); + ok(typeof navigator.accelerometer.watchAcceleration != 'undefined' && navigator.accelerometer.watchAcceleration != null, "navigator.accelerometer.watchAcceleration should not be null."); + ok(typeof navigator.accelerometer.watchAcceleration == 'function', "navigator.accelerometer.watchAcceleration should be a function."); + }); + test("should contain a clearWatch function", function() { + expect(2); + ok(typeof navigator.accelerometer.clearWatch != 'undefined' && navigator.accelerometer.clearWatch != null, "navigator.accelerometer.clearWatch should not be null."); + ok(typeof navigator.accelerometer.clearWatch == 'function', "navigator.accelerometer.clearWatch should be a function!"); + }); + module('Acceleration model'); + test("should be able to define a new Acceleration object with x, y, z and timestamp properties.", function () { + expect(9); + var x = 1; + var y = 2; + var z = 3; + var a = new Acceleration(x, y, z); + ok(a != null, "new Acceleration object should not be null."); + ok(typeof a == 'object', "new Acceleration object should be of type 'object'."); + ok(a.x != null, "new Acceleration object should have an 'x' property."); + equals(a.x, x, "new Acceleration object should have 'x' property equal to first parameter passed in Acceleration constructor."); + ok(a.y != null, "new Acceleration object should have a 'y' property."); + equals(a.y, y, "new Acceleration object should have 'y' property equal to second parameter passed in Acceleration constructor."); + ok(a.z != null, "new Acceleration object should have a 'z' property."); + equals(a.z, z, "new Acceleration object should have 'z' property equal to third parameter passed in Acceleration constructor."); + ok(a.timestamp != null, "new Acceleration object should have a 'timestamp' property."); + }); +}; \ No newline at end of file diff --git a/test/assets/www/autotest/tests/battery.tests.js b/test/assets/www/autotest/tests/battery.tests.js new file mode 100644 index 00000000..bce3079f --- /dev/null +++ b/test/assets/www/autotest/tests/battery.tests.js @@ -0,0 +1,18 @@ +Tests.prototype.BatteryTests = function() { + module('Battery (navigator.battery)'); + test("should exist", function() { + expect(1); + ok(navigator.battery != null, "navigator.battery should not be null."); + }); + /** + * Tests add for Battery Status API spec + * http://www.w3.org/TR/2011/WD-battery-status-20111129/ + */ + test("should have properties", function() { + expect(4); + ok(typeof navigator.battery.charging != 'undefined' && navigator.battery.charging != null, "navigator.battery.charging should be a boolean value."); + ok(typeof navigator.battery.chargingTime != 'undefined' && navigator.battery.chargingTime != null, "navigator.battery.chargingTime should be a double value."); + ok(typeof navigator.battery.level != 'undefined' && navigator.battery.level != null, "navigator.battery.level should be a double value."); + ok(typeof navigator.battery.dischargingTime != 'undefined' && navigator.battery.dischargingTime != null, "navigator.battery.dischargingTime should be a double value."); + }); +}; \ No newline at end of file diff --git a/test/assets/www/autotest/tests/camera.tests.js b/test/assets/www/autotest/tests/camera.tests.js new file mode 100644 index 00000000..c52b7f56 --- /dev/null +++ b/test/assets/www/autotest/tests/camera.tests.js @@ -0,0 +1,40 @@ +Tests.prototype.CameraTests = function() { + module('Camera (navigator.camera)'); + test("should exist", function() { + expect(1); + ok(navigator.camera !== null, "navigator.camera should not be null."); + }); + test("should contain a getPicture function", function() { + expect(2); + ok(typeof navigator.camera.getPicture != 'undefined' && navigator.camera.getPicture !== null, "navigator.camera.getPicture should not be null."); + ok(typeof navigator.camera.getPicture == 'function', "navigator.camera.getPicture should be a function."); + }); + + module('Camera Constants (window.Camera)'); + test("should exist", function() { + expect(1); + ok(window.Camera !== null, "window.Camera should not be null."); + }); + test("should contain two DestinationType constants", function() { + expect(2); + equals(Camera.DestinationType.DATA_URL, 0, "DestinationType.DATA_URL should equal to 0"); + equals(Camera.DestinationType.FILE_URI, 1, "DestinationType.DATA_URL should equal to 1"); + }); + test("should contain two EncodingType constants", function() { + expect(2); + equals(Camera.EncodingType.JPEG, 0, "EncodingType.JPEG should equal to 0"); + equals(Camera.EncodingType.PNG, 1, "EncodingType.PNG should equal to 1"); + }); + test("should contain three MediaType constants", function() { + expect(3); + equals(Camera.MediaType.PICTURE, 0, 'MediaType.PICTURE should equal to 0'); + equals(Camera.MediaType.VIDEO, 1, 'MediaType.VIDEO should equal to 1'); + equals(Camera.MediaType.ALLMEDIA, 2, 'MediaType.ALLMEDIA should equal to 2'); + }); + test("should contain three PictureSourceType constants", function() { + expect(3); + equals(Camera.PictureSourceType.PHOTOLIBRARY, 0, 'PictureSourceType.PHOTOLIBRARY should equal to 0'); + equals(Camera.PictureSourceType.CAMERA, 1, 'PictureSourceType.CAMERA should equal to 1'); + equals(Camera.PictureSourceType.SAVEDPHOTOALBUM, 2, 'PictureSourceType.SAVEDPHOTOALBUM should equal to 2'); + }); +}; diff --git a/test/assets/www/autotest/tests/capture.tests.js b/test/assets/www/autotest/tests/capture.tests.js new file mode 100644 index 00000000..746b6f92 --- /dev/null +++ b/test/assets/www/autotest/tests/capture.tests.js @@ -0,0 +1,97 @@ +Tests.prototype.CaptureTests = function() { + module('Capture (navigator.device.capture)'); + test("should exist", function() { + expect(2); + ok(navigator.device !== null, "navigator.device should not be null."); + ok(navigator.device.capture !== null, "navigator.device.capture should not be null."); + }); + test("should have the correct properties ", function() { + expect(3); + ok(typeof navigator.device.capture.supportedAudioModes != 'undefined' && navigator.device.capture.supportedAudioModes !== null , "there should be a supported audio modes property"); + ok(typeof navigator.device.capture.supportedImageModes != 'undefined' && navigator.device.capture.supportedImageModes !== null , "there should be a supported image modes property"); + ok(typeof navigator.device.capture.supportedVideoModes != 'undefined' && navigator.device.capture.supportedVideoModes !== null , "there should be a supported video modes property"); + }); + test("should contain a captureAudio function", function() { + expect(2); + ok(typeof navigator.device.capture.captureAudio != 'undefined' && navigator.device.capture.captureAudio !== null, "navigator.device.capture.captureAudio should not be null."); + ok(typeof navigator.device.capture.captureAudio == 'function', "navigator.device.capture.captureAudio should be a function."); + }); + test("should contain a captureImage function", function() { + expect(2); + ok(typeof navigator.device.capture.captureImage != 'undefined' && navigator.device.capture.captureImage !== null, "navigator.device.capture.captureImage should not be null."); + ok(typeof navigator.device.capture.captureImage == 'function', "navigator.device.capture.captureImage should be a function."); + }); + test("should contain a captureVideo function", function() { + expect(2); + ok(typeof navigator.device.capture.captureVideo != 'undefined' && navigator.device.capture.captureVideo !== null, "navigator.device.capture.captureVideo should not be null."); + ok(typeof navigator.device.capture.captureVideo == 'function', "navigator.device.capture.captureVideo should be a function."); + }); + + module('CaptureAudioOptions'); + test("CaptureAudioOptions constructor should exist", function() { + expect(4); + var options = new CaptureAudioOptions(); + ok(options !== null, "CaptureAudioOptions object should not be null."); + ok(typeof options.limit !== 'undefined', "CaptureAudioOptions object should have a 'limit' property."); + ok(typeof options.duration !== 'undefined', "CaptureAudioOptions object should have a 'duration' property."); + ok(typeof options.mode !== 'undefined', "CaptureAudioOptions object should have a 'mode' property."); + }); + + module('CaptureImageOptions'); + test("CaptureImageOptions constructor should exist", function() { + expect(3); + var options = new CaptureImageOptions(); + ok(options !== null, "CaptureImageOptions object should not be null."); + ok(typeof options.limit !== 'undefined', "CaptureImageOptions object should have a 'limit' property."); + ok(typeof options.mode !== 'undefined', "CaptureImageOptions object should have a 'mode' property."); + }); + + module('CaptureVideoOptions'); + test("CaptureVideoOptions constructor should exist", function() { + expect(4); + var options = new CaptureVideoOptions(); + ok(options !== null, "CaptureVideoOptions object should not be null."); + ok(typeof options.limit !== 'undefined', "CaptureVideoOptions object should have a 'limit' property."); + ok(typeof options.duration !== 'undefined', "CaptureVideoOptions object should have a 'duration' property."); + ok(typeof options.mode !== 'undefined', "CaptureVideoOptions object should have a 'mode' property."); + }); + + module('CaptureError interface'); + test("CaptureError constants should be defined", function() { + expect(4); + equal(CaptureError.CAPTURE_INTERNAL_ERR, 0, "CaptureError.CAPTURE_INTERNAL_ERR should be defined"); + equal(CaptureError.CAPTURE_APPLICATION_BUSY, 1, "CaptureError.CAPTURE_APPLICATION_BUSY should be defined"); + equal(CaptureError.CAPTURE_INVALID_ARGUMENT, 2, "CaptureError.CAPTURE_INVALID_ARGUMENT should be defined"); + equal(CaptureError.CAPTURE_NO_MEDIA_FILES, 3, "CaptureError.CAPTURE_NO_MEDIA_FILES should be defined"); + }); + test("CaptureError properties should exist", function() { + expect(2); + var error = new CaptureError(); + ok(error !== null, "CaptureError object should not be null."); + ok(typeof error.code !== 'undefined', "CaptureError object should have a 'code' property."); + }); + + module('MediaFileData'); + test("MediaFileData constructor should exist", function() { + expect(6); + var fileData = new MediaFileData(); + ok(fileData !== null, "MediaFileData object should not be null."); + ok(typeof fileData.bitrate !== 'undefined', "MediaFileData object should have a 'bitrate' property."); + ok(typeof fileData.codecs !== 'undefined', "MediaFileData object should have a 'codecs' property."); + ok(typeof fileData.duration !== 'undefined', "MediaFileData object should have a 'duration' property."); + ok(typeof fileData.height !== 'undefined', "MediaFileData object should have a 'height' property."); + ok(typeof fileData.width !== 'undefined', "MediaFileData object should have a 'width' property."); + }); + + module('MediaFile'); + test("MediaFile constructor should exist", function() { + expect(6); + var fileData = new MediaFileData(); + ok(fileData !== null, "MediaFileData object should not be null."); + ok(typeof fileData.name !== 'undefined', "MediaFile object should have a 'name' property."); + ok(typeof fileData.fullPath !== 'undefined', "MediaFile object should have a 'fullPath' property."); + ok(typeof fileData.type !== 'undefined', "MediaFile object should have a 'type' property."); + ok(typeof fileData.lastModifiedDate !== 'undefined', "MediaFile object should have a 'lastModifiedDate' property."); + ok(typeof fileData.size !== 'undefined', "MediaFile object should have a 'size' property."); + }); +}; diff --git a/test/assets/www/autotest/tests/compass.tests.js b/test/assets/www/autotest/tests/compass.tests.js new file mode 100644 index 00000000..e0b1fa6d --- /dev/null +++ b/test/assets/www/autotest/tests/compass.tests.js @@ -0,0 +1,62 @@ +Tests.prototype.CompassTests = function() { + module('Compass (navigator.compass)'); + test("should exist", function() { + expect(1); + ok(navigator.compass !== null, "navigator.compass should not be null."); + }); + test("should contain a getCurrentHeading function", function() { + expect(2); + ok(typeof navigator.compass.getCurrentHeading != 'undefined' && navigator.compass.getCurrentHeading !== null, "navigator.compass.getCurrentHeading should not be null."); + ok(typeof navigator.compass.getCurrentHeading == 'function', "navigator.compass.getCurrentHeading should be a function."); + }); + test("getCurrentHeading success callback should be called with a Heading object", function() { + expect(9); + QUnit.stop(Tests.TEST_TIMEOUT); + var win = function(a) { + ok(typeof a == 'object', "Heading object returned in getCurrentHeading success callback should be of type 'object'."); + ok(a.magneticHeading !== null, "Heading object returned in getCurrentHeading success callback should have an 'magneticHeading' property."); + ok(typeof a.magneticHeading == 'number', "Heading object's 'magneticHeading' property returned in getCurrentHeading success callback should be of type 'number'."); + ok(a.trueHeading !== null, "Heading object returned in getCurrentHeading success callback should have a 'trueHeading' property."); + ok(typeof a.trueHeading == 'number', "Heading object's 'trueHeading' property returned in getCurrentHeading success callback should be of type 'number'."); + ok(a.headingAccuracy !== null, "Heading object returned in getCurrentHeading success callback should have a 'headingAccuracy' property."); + ok(typeof a.headingAccuracy == 'number', "Heading object's 'headingAccuracy' property returned in getCurrentHeading success callback should be of type 'number'."); + ok(a.timestamp !== null, "Heading object returned in getCurrentHeading success callback should have a 'timestamp' property."); + ok(a.timestamp instanceof Date, "Heading object's 'timestamp' property returned in getCurrentHeading success callback should be an instance of Date."); + QUnit.start(); + }; + var fail = function() { QUnit.start(); }; + navigator.compass.getCurrentHeading(win, fail); + }); + test("should contain a watchHeading function", function() { + expect(2); + ok(typeof navigator.compass.watchHeading != 'undefined' && navigator.compass.watchHeading !== null, "navigator.compass.watchHeading should not be null."); + ok(typeof navigator.compass.watchHeading == 'function', "navigator.compass.watchHeading should be a function."); + }); + test("should contain a clearWatch function", function() { + expect(2); + ok(typeof navigator.compass.clearWatch != 'undefined' && navigator.compass.clearWatch !== null, "navigator.compass.clearWatch should not be null."); + ok(typeof navigator.compass.clearWatch == 'function', "navigator.compass.clearWatch should be a function!"); + }); + + module('Compass Constants (window.CompassError)'); + test("CompassError globals should exist", function() { + expect(3); + ok(window.CompassError !== null, 'window.CompassError should not be null'); + equals(window.CompassError.COMPASS_INTERNAL_ERR, 0, 'window.CompassError.COMPASS_INTERNAL_ERR should be 0'); + equals(window.CompassError.COMPASS_NOT_SUPPORTED, 20, 'window.CompassError.COMPASS_NOT_SUPPORTED should be 20'); + }); + + module('Compass Heading model (CompassHeading)'); + test("CompassHeading function should exist", function() { + expect(1); + ok(typeof CompassHeading != 'undefined' && CompassHeading !== null, 'CompassHeading should not be null'); + }); + test("Creating a new CompassHeading instance", function() { + expect(4); + var h = new CompassHeading(); + equals(h.magneticHeading, null, "CompassHeading instance should have null magneticHeading property by default"); + equals(h.trueHeading, null, "CompassHeading instance should have null trueHeading property by default"); + equals(h.headingAccuracy, null, "CompassHeading instance should have null headingAccuracy property by default"); + ok(h.timestamp !== null, "CompassHeading instance should have timestamp that is not null by default"); + }); +}; diff --git a/test/assets/www/autotest/tests/contacts.tests.js b/test/assets/www/autotest/tests/contacts.tests.js new file mode 100644 index 00000000..b13eec33 --- /dev/null +++ b/test/assets/www/autotest/tests/contacts.tests.js @@ -0,0 +1,219 @@ +Tests.prototype.ContactsTests = function() { + module("Contacts (navigator.contacts)"); + test("should exist", function() { + expect(1); + ok(navigator.contacts != null, "navigator.contacts should not be null."); + }); + test("should contain a find function", function() { + expect(2); + ok(typeof navigator.contacts.find != 'undefined' && navigator.contacts.find != null, "navigator.contacts.find should not be null."); + ok(typeof navigator.contacts.find == 'function', "navigator.contacts.find should be a function."); + }); + test("contacts.find success callback should be called with an array", function() { + expect(2); + QUnit.stop(Tests.TEST_TIMEOUT); + var win = function(result) { + ok(typeof result == 'object', "Object returned in contacts.find success callback is of type 'object' (actually array)."); + ok(typeof result.length == 'number', "Object returned in contacts.find success callback has a length property which is numerical."); + QUnit.start(); + }; + var fail = function() { QUnit.start(); }; + var obj = new ContactFindOptions(); + obj.filter=""; + obj.multiple=true; + navigator.contacts.find(["displayName", "name", "phoneNumbers", "emails"], win, fail, obj); + }); + test("contacts.find success callback should not be null", function() { + expect(1); + var fail = function() {}; + var obj = new ContactFindOptions(); + obj.filter=""; + obj.multiple=true; + try { + navigator.contacts.find(["displayName", "name", "emails", "phoneNumbers"], null, fail, obj); + } catch(e) { + ok(true, "Trying to find with a null success call back should throw TypeError."); + } + }); + test("contacts.find error callback should be called when no fields are specified", function() { + expect(2); + QUnit.stop(Tests.TEST_TIMEOUT); + var win = function(result) { + QUnit.start(); + }; + var fail = function(result) { + ok(typeof result == 'object', "Object returned in contact.find failure callback is of type 'object' (actually ContactError)."); + ok(result.code == ContactError.INVALID_ARGUMENT_ERROR, "Object returned in contacts.find failure callback has a code property which equal to ContactError.INVALID_ARGUMENT_ERROR."); + QUnit.start(); + }; + var obj = new ContactFindOptions(); + obj.filter=""; + obj.multiple=true; + navigator.contacts.find([], win, fail, obj); + }); + test("should contain a create function", function() { + expect(2); + ok(typeof navigator.contacts.create != 'undefined' && navigator.contacts.create != null, "navigator.contacts.create should not be null."); + ok(typeof navigator.contacts.create == 'function', "navigator.contacts.create should be a function."); + }); + test("contacts.create should return a Contact object", function() { + expect(9); + var obj = navigator.contacts.create({"displayName": "test name", "gender": "male", "note": "my note", "name": {"formatted": "Mr. Test Name"}, "emails": [{"value": "here@there.com"}, {"value": "there@here.com"}]}); + ok(obj != 'undefined' && obj != null, "navigator.contacts.create should return a Contact object."); + ok(obj.displayName == 'test name', "Contact should contain a displayName property."); + ok(obj.gender == 'male', "Contact should contain a gender property."); + ok(obj.note == 'my note', "Contact should contain a note property."); + ok(obj.name.formatted == 'Mr. Test Name', "Contact should contain a name.formatted property."); + ok(obj.emails.length == 2, "Contact should contain and array of emails with 2 entries"); + ok(obj.emails[0].value == 'here@there.com', "Contact.emails[1] should contain a value."); + ok(obj.emails[1].value == 'there@here.com', "Contact.emails[2] should contain a value."); + ok(obj.birthday == null, "Contact object should not contain a birthday property."); + }); + module("Contact model"); + test("should be able to define a Contact object", function() { + expect(15); + var contact = new Contact("a", "b", new ContactName("a", "b", "c", "d", "e", "f"), "c", [], [], [], [], [], "f", "i", + [], [], []); + ok(contact != null, "new Contact() should not be null."); + ok(typeof contact.id != 'undefined' && contact.id != null && contact.id == "a", "new Contact() should include a 'id' property."); + ok(typeof contact.displayName != 'undefined' && contact.displayName != null && contact.displayName == "b", "new Contact() should include a 'displayName' property."); + ok(typeof contact.name != 'undefined' && contact.name != null && contact.name.formatted == "a", "new Contact() should include a 'name' property."); + ok(typeof contact.nickname != 'undefined' && contact.nickname != null && contact.nickname == "c", "new Contact() should include a 'nickname' property."); + ok(typeof contact.phoneNumbers != 'undefined' && contact.phoneNumbers != null, "new Contact() should include a 'phoneNumbers' property."); + ok(typeof contact.emails != 'undefined' && contact.emails != null, "new Contact() should include a 'emails' property."); + ok(typeof contact.addresses != 'undefined' && contact.addresses != null, "new Contact() should include a 'addresses' property."); + ok(typeof contact.ims != 'undefined' && contact.ims != null, "new Contact() should include a 'ims' property."); + ok(typeof contact.organizations != 'undefined' && contact.organizations != null, "new Contact() should include a 'organizations' property."); + ok(typeof contact.birthday != 'undefined' && contact.birthday != null && contact.birthday == "f", "new Contact() should include a 'birthday' property."); + ok(typeof contact.note != 'undefined' && contact.note != null && contact.note == "i", "new Contact() should include a 'note' property."); + ok(typeof contact.photos != 'undefined' && contact.photos != null, "new Contact() should include a 'photos' property."); + ok(typeof contact.categories != 'undefined' && contact.categories != null, "new Contact() should include a 'categories' property."); + ok(typeof contact.urls != 'undefined' && contact.urls != null, "new Contact() should include a 'urls' property."); + }); + test("should be able to define a ContactName object", function() { + expect(7); + var contactName = new ContactName("Dr. First Last Jr.", "Last", "First", "Middle", "Dr.", "Jr."); + ok(contactName != null, "new ContactName() should not be null."); + ok(typeof contactName.formatted != 'undefined' && contactName.formatted != null && contactName.formatted == "Dr. First Last Jr.", "new ContactName() should include a 'formatted' property."); + ok(typeof contactName.familyName != 'undefined' && contactName.familyName != null && contactName.familyName == "Last", "new ContactName() should include a 'familyName' property."); + ok(typeof contactName.givenName != 'undefined' && contactName.givenName != null && contactName.givenName == "First", "new ContactName() should include a 'givenName' property."); + ok(typeof contactName.middleName != 'undefined' && contactName.middleName != null && contactName.middleName == "Middle", "new ContactName() should include a 'middleName' property."); + ok(typeof contactName.honorificPrefix != 'undefined' && contactName.honorificPrefix != null && contactName.honorificPrefix == "Dr.", "new ContactName() should include a 'honorificPrefix' property."); + ok(typeof contactName.honorificSuffix != 'undefined' && contactName.honorificSuffix != null && contactName.honorificSuffix == "Jr.", "new ContactName() should include a 'honorificSuffix' property."); + }); + test("should be able to define a ContactField object", function() { + expect(4); + var contactField = new ContactField("home", "8005551212", true); + ok(contactField != null, "new ContactField() should not be null."); + ok(typeof contactField.type != 'undefined' && contactField.type != null && contactField.type == "home", "new ContactField() should include a 'type' property."); + ok(typeof contactField.value != 'undefined' && contactField.value != null && contactField.value == "8005551212", "new ContactField() should include a 'value' property."); + ok(typeof contactField.pref != 'undefined' && contactField.pref != null && contactField.pref == true, "new ContactField() should include a 'pref' property."); + }); + test("should be able to define a ContactAddress object", function() { + expect(9); + var contactAddress = new ContactAddress(true, "home", "a","b","c","d","e","f"); + ok(contactAddress != null, "new ContactAddress() should not be null."); + ok(typeof contactAddress.pref != 'undefined' && contactAddress.pref != null && contactAddress.pref == true, "new ContactAddress() should include a 'pref' property."); + ok(typeof contactAddress.type != 'undefined' && contactAddress.type != null && contactAddress.type == "home", "new ContactAddress() should include a 'type' property."); + ok(typeof contactAddress.formatted != 'undefined' && contactAddress.formatted != null && contactAddress.formatted == "a", "new ContactAddress() should include a 'formatted' property."); + ok(typeof contactAddress.streetAddress != 'undefined' && contactAddress.streetAddress != null && contactAddress.streetAddress == "b", "new ContactAddress() should include a 'streetAddress' property."); + ok(typeof contactAddress.locality != 'undefined' && contactAddress.locality != null && contactAddress.locality == "c", "new ContactAddress() should include a 'locality' property."); + ok(typeof contactAddress.region != 'undefined' && contactAddress.region != null && contactAddress.region == "d", "new ContactAddress() should include a 'region' property."); + ok(typeof contactAddress.postalCode != 'undefined' && contactAddress.postalCode != null && contactAddress.postalCode == "e", "new ContactAddress() should include a 'postalCode' property."); + ok(typeof contactAddress.country != 'undefined' && contactAddress.country != null && contactAddress.country == "f", "new ContactAddress() should include a 'country' property."); + }); + test("should be able to define a ContactOrganization object", function() { + expect(6); + var contactOrg = new ContactOrganization(true, "home", "a","b","c","d","e","f","g"); + ok(contactOrg != null, "new ContactOrganization() should not be null."); + ok(typeof contactOrg.pref != 'undefined' && contactOrg.pref != null && contactOrg.pref == true, "new ContactOrganization() should include a 'pref' property."); + ok(typeof contactOrg.type != 'undefined' && contactOrg.type != null && contactOrg.type == "home", "new ContactOrganization() should include a 'type' property."); + ok(typeof contactOrg.name != 'undefined' && contactOrg.name != null && contactOrg.name == "a", "new ContactOrganization() should include a 'name' property."); + ok(typeof contactOrg.department != 'undefined' && contactOrg.department != null && contactOrg.department == "b", "new ContactOrganization() should include a 'department' property."); + ok(typeof contactOrg.title != 'undefined' && contactOrg.title != null && contactOrg.title == "c", "new ContactOrganization() should include a 'title' property."); + }); + test("should be able to define a ContactFindOptions object", function() { + expect(3); + var contactFindOptions = new ContactFindOptions("a", true, "b"); + ok(contactFindOptions != null, "new ContactFindOptions() should not be null."); + ok(typeof contactFindOptions.filter != 'undefined' && contactFindOptions.filter != null && contactFindOptions.filter == "a", "new ContactFindOptions() should include a 'filter' property."); + ok(typeof contactFindOptions.multiple != 'undefined' && contactFindOptions.multiple != null && contactFindOptions.multiple == true, "new ContactFindOptions() should include a 'multiple' property."); + }); + module("Contact Object"); + test("should contain a clone function", function() { + expect(2); + var contact = new Contact(); + ok(typeof contact.clone != 'undefined' && contact.clone != null, "contact.clone should not be null."); + ok(typeof contact.clone == 'function', "contact.clone should be a function."); + }); + test("clone function should make deep copy of Contact Object", function() { + expect(8); + var contact = new Contact(); + contact.id=1; + contact.displayName="Test Name"; + contact.nickname="Testy"; + contact.gender="male"; + contact.note="note to be cloned"; + contact.name = new ContactName("Mr. Test Name"); + + var clonedContact = contact.clone(); + + ok(contact.id == 1, "contact.id should be 1."); + ok(clonedContact.id == null, "clonedContact.id should be null."); + ok(clonedContact.displayName == contact.displayName, "displayName's should be equal"); + ok(clonedContact.nickname == contact.nickname, "nickname's should be equal"); + ok(clonedContact.gender == contact.gender, "gender's should be equal"); + ok(clonedContact.note == contact.note, "note's should be equal"); + ok(clonedContact.name.formatted == contact.name.formatted, "name.formatted's should be equal"); + ok(clonedContact.connected == contact.connected, "connected's should be equal (null)"); + }); + test("should contain a remove function", function() { + expect(2); + var contact = new Contact(); + ok(typeof contact.remove != 'undefined' && contact.remove != null, "contact.remove should not be null."); + ok(typeof contact.remove == 'function', "contact.remove should be a function."); + }); + test("calling remove on a contact has an id of null should return ContactError.UNKNOWN_ERROR", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + var win = function(result) { + }; + var fail = function(result) { + ok(typeof result == 'object', "Object returned in contact.remove failure callback is of type 'object' (actually ContactError)."); + ok(result.code == ContactError.UNKNOWN_ERROR, "Object returned in contacts.remove failure callback has a code property which equal to ContactError.UNKNOWN_ERROR."); + QUnit.start(); + }; + var rmContact = new Contact(); + rmContact.remove(win, fail); + }); + test("calling remove on a contact that does not exist should return ContactError.UNKNOWN_ERROR", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + var win = function(result) { + }; + var fail = function(result) { + ok(typeof result == 'object', "Object returned in contact.remove failure callback is of type 'object' (actually ContactError)."); + ok(result.code == ContactError.UNKNOWN_ERROR, "Object returned in contacts.remove failure callback has a code property which equal to ContactError.UNKNOWN_ERROR."); + QUnit.start(); + }; + var contact = new Contact(99); + contact.remove(win, fail); + }); + test("should contain a save function", function() { + expect(2); + var contact = new Contact(); + ok(typeof contact.save != 'undefined' && contact.save != null, "contact.save should not be null."); + ok(typeof contact.save == 'function', "contact.save should be a function."); + }); + module('ContactError interface'); + test("ContactError constants should be defined", function() { + expect(7); + equal(ContactError.UNKNOWN_ERROR, 0, "ContactError.UNKNOWN_ERROR should be defined"); + equal(ContactError.INVALID_ARGUMENT_ERROR, 1, "ContactError.INVALID_ARGUMENT_ERROR should be defined"); + equal(ContactError.TIMEOUT_ERROR, 2, "ContactError.TIMEOUT_ERROR should be defined"); + equal(ContactError.PENDING_OPERATION_ERROR, 3, "ContactError.PENDING_OPERATION_ERROR should be defined"); + equal(ContactError.IO_ERROR, 4, "ContactError.IO_ERROR should be defined"); + equal(ContactError.NOT_SUPPORTED_ERROR, 5, "ContactError.NOT_SUPPORTED_ERROR should be defined"); + equal(ContactError.PERMISSION_DENIED_ERROR, 20, "ContactError.PERMISSION_DENIED_ERROR should be defined"); + }); +}; diff --git a/test/assets/www/autotest/tests/device.tests.js b/test/assets/www/autotest/tests/device.tests.js new file mode 100644 index 00000000..537aedad --- /dev/null +++ b/test/assets/www/autotest/tests/device.tests.js @@ -0,0 +1,36 @@ +Tests.prototype.DeviceTests = function() { + module('Device Information (window.device)'); + test("should exist", function() { + expect(1); + ok(window.device != null, "window.device should not be null."); + }); + test("should contain a platform specification that is a string", function() { + expect(2); + ok(typeof window.device.platform != 'undefined' && window.device.platform != null, "window.device.platform should not be null.") + ok((new String(window.device.platform)).length > 0, "window.device.platform should contain some sort of description.") + }); + test("should contain a version specification that is a string", function() { + expect(2); + ok(typeof window.device.version != 'undefined' && window.device.version != null, "window.device.version should not be null.") + ok((new String(window.device.version)).length > 0, "window.device.version should contain some kind of description.") + }); + test("should contain a name specification that is a string", function() { + expect(2); + ok(typeof window.device.name != 'undefined' && window.device.name != null, "window.device.name should not be null.") + ok((new String(window.device.name)).length > 0, "window.device.name should contain some kind of description.") + }); + test("should contain a UUID specification that is a string or a number", function() { + expect(2); + ok(typeof window.device.uuid != 'undefined' && window.device.uuid != null, "window.device.uuid should not be null.") + if (typeof window.device.uuid == 'string' || typeof window.device.uuid == 'object') { + ok((new String(window.device.uuid)).length > 0, "window.device.uuid, as a string, should have at least one character.") + } else { + ok(window.device.uuid > 0, "window.device.uuid, as a number, should be greater than 0. (should it, even?)") + } + }); + test("should contain a phonegap specification that is a string", function() { + expect(2); + ok(typeof window.device.phonegap != 'undefined' && window.device.phonegap != null, "window.device.phonegap should not be null.") + ok((new String(window.device.phonegap)).length > 0, "window.device.phonegap should contain some kind of description.") + }); +}; \ No newline at end of file diff --git a/test/assets/www/autotest/tests/file.tests.js b/test/assets/www/autotest/tests/file.tests.js new file mode 100644 index 00000000..57f93c9f --- /dev/null +++ b/test/assets/www/autotest/tests/file.tests.js @@ -0,0 +1,2664 @@ +/** + * Retrieves root file system entries once, so they don't have to be + * repeated for every test (file system shouldn't change during test run). + */ +var getFileSystemRoot = (function() { + + // private + var temp_root, persistent_root; + + var onError = function(error) { + console.log('unable to retrieve file system: ' + error.code); + }; + + // one-time retrieval of the root file system entry + var init = function() { + window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, + function(fileSystem) { + persistent_root = fileSystem.root; + }, onError); + window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, + function(fileSystem) { + temp_root = fileSystem.root; + }, onError); + }; + document.addEventListener("deviceready", init, true); + + // public function returns private root entry + return function() { + // When testing, it is a good idea to run the test suite once for each + // file system type. Just change the return value from this function. + //return temp_root; + return persistent_root; + }; +}()); // execute immediately + +Tests.prototype.FileTests = function() { + module('FileError interface'); + test("FileError constants should be defined", function() { + expect(12); + equal(FileError.NOT_FOUND_ERR, 1, "FileError.NOT_FOUND_ERR should be defined"); + equal(FileError.SECURITY_ERR, 2, "FileError.SECURITY_ERR should be defined"); + equal(FileError.ABORT_ERR, 3, "FileError.ABORT should be defined"); + equal(FileError.NOT_READABLE_ERR, 4, "FileError.NOT_READABLE_ERR should be defined"); + equal(FileError.ENCODING_ERR, 5, "FileError.ENCODING_ERR should be defined"); + equal(FileError.NO_MODIFICATION_ALLOWED_ERR, 6, "FileError.NO_MODIFICATION_ALLOWED_ERR should be defined"); + equal(FileError.INVALID_STATE_ERR, 7, "FileError.INVALID_STATE_ERR should be defined"); + equal(FileError.SYNTAX_ERR, 8, "FileError.SYNTAX_ERR should be defined"); + equal(FileError.INVALID_MODIFICATION_ERR, 9, "FileError.INVALID_MODIFICATION_ERR should be defined"); + equal(FileError.QUOTA_EXCEEDED_ERR, 10, "FileError.QUOTA_EXCEEDED_ERR should be defined"); + equal(FileError.TYPE_MISMATCH_ERR, 11, "FileError.TYPE_MISMATCH_ERR should be defined"); + equal(FileError.PATH_EXISTS_ERR, 12, "FileError.PATH_EXISTS_ERR should be defined"); + }); + + module('LocalFileSystem interface'); + test("window.requestFileSystem function should be defined", function() { + expect(1); + ok(typeof window.requestFileSystem === 'function', "window.requestFileSystem should be a function."); + }); + test("window.resolveLocalFileSystemURI function should be defined", function() { + expect(1); + ok(typeof window.resolveLocalFileSystemURI === 'function', "window.resolveLocalFileSystemURI should be a function."); + }); + test("File system types should be defined", function() { + expect(2); + equal(LocalFileSystem.TEMPORARY, 0, "LocalFileSystem.TEMPORARY should be defined"); + equal(LocalFileSystem.PERSISTENT, 1, "LocalFileSystem.PERSISTENT should be defined"); + }); + test("retrieve PERSISTENT file system", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(4); + + var testPersistent = function(fileSystem) { + ok(typeof fileSystem !== 'undefined' && fileSystem !== null, "window.requestFileSystem should return an object."); + ok(typeof fileSystem.name !== 'undefined' && fileSystem.name !== null, "filesystem should include a 'name' property."); + equal(fileSystem.name, "persistent", "file system 'name' attribute should be set properly"); + ok(typeof fileSystem.root !== 'undefined' && fileSystem.root !== null, "filesystem should include a 'root' property."); + QUnit.start(); + }; + + // retrieve PERSISTENT file system + window.requestFileSystem(LocalFileSystem.PERSISTENT, 0, testPersistent, + function(error) { + console.log('error retrieving file system: ' + error.code); + }); + }); + test("retrieve TEMPORARY file system", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(4); + + var testTemporary = function(fileSystem) { + ok(typeof fileSystem !== 'undefined' && fileSystem !== null, "window.requestFileSystem should return an object."); + ok(typeof fileSystem.name !== 'undefined' && fileSystem.name !== null, "filesystem should include a 'name' property."); + equal(fileSystem.name, "temporary", "file system 'name' attribute should be set properly"); + ok(typeof fileSystem.root !== 'undefined' && fileSystem.root !== null, "filesystem should include a 'root' property."); + QUnit.start(); + }; + + // Request the file system + window.requestFileSystem(LocalFileSystem.TEMPORARY, 0, testTemporary, + function(error) { + console.log('error retrieving file system: ' + error.code); + }); + }); + test("request a file system that is too large", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var failFS = function(error) { + ok(error !== null, "error should not be null."); + equal(error.code, FileError.QUOTA_EXCEEDED_ERR, "Shoud receive error code FileError.QUOTA_EXCEEDED_ERR"); + QUnit.start(); + }; + + // Request the file system + window.requestFileSystem(LocalFileSystem.TEMPORARY, 1000000000000000, null, failFS); + }); + test("request a file system that does not exist", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var failFS = function(error) { + ok(typeof error !== 'undefined' && error !== null, "error should not be null."); + equal(error.code, FileError.SYNTAX_ERR, "Shoud receive error code FileError.SYNTAX_ERR"); + QUnit.start(); + }; + + // Request the file system + window.requestFileSystem(-1, 0, null, failFS); + }); + test("resolve invalid file name", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var failURI = function(error) { + ok(typeof error !== 'undefined' && error !== null, "error should not be null."); + equal(error.code, FileError.NOT_FOUND_ERR, "Shoud receive error code FileError.NOT_FOUND_ERR"); + QUnit.start(); + }; + + // lookup file system entry + window.resolveLocalFileSystemURI("file:///this.is.not.a.valid.file.txt", null, failURI); + }); + test("resolve invalid URI", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var failURI = function(error) { + ok(typeof error !== 'undefined' && error !== null, "error should not be null."); + equal(error.code, FileError.ENCODING_ERR, "Shoud receive an error code FileError.ENCODING_ERR"); + QUnit.start(); + }; + + // lookup file system entry + window.resolveLocalFileSystemURI("/this.is.not.a.valid.url", null, failURI); + }); + + module('Metadata interface'); + test("Metadata constructor should exist", function() { + expect(2); + var metadata = new Metadata(); + ok(metadata !== null, "Metadata object should not be null."); + ok(typeof metadata.modificationTime !== 'undefined', "Metadata object should have a 'modificationTime' property."); + }); + module('Flags interface'); + test("Flags constructor should exist", function() { + expect(5); + var flags = new Flags(false, true); + ok(flags !== null, "Flags object should not be null."); + ok(typeof flags.create !== 'undefined' && flags.create !== null, "Flags object should have a 'create' property."); + equal(flags.create, false, "Flags.create should be set properly"); + ok(typeof flags.exclusive !== 'undefined' && flags.exclusive !== null, "Flags object should have an 'exclusive' property."); + equal(flags.exclusive, true, "flags.exclusive should be set properly") + }); + module('FileSystem interface'); + test("FileSystem root should be a DirectoryEntry", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(15); + + var root = getFileSystemRoot(), + testFSRoot = function(entry) { + ok(typeof entry !== 'undefined' && entry !== null, "entry should be non-null"); + equal(entry.isFile, false, "entry.isFile should be false"); + equal(entry.isDirectory, true, "entry.isDirectory should be true"); + ok(typeof entry.name !== 'undefined' && entry.name !== null, "entry should include a 'name' property."); + ok(typeof entry.fullPath !== 'undefined' && entry.fullPath !== null, "entry should include a 'fullPath' property."); + ok(typeof entry.getMetadata === 'function', "entry object should have a 'getMetadata' function."); + ok(typeof entry.moveTo === 'function', "entry object should have a 'moveTo' function."); + ok(typeof entry.copyTo === 'function', "entry object should have a 'copyTo' function."); + ok(typeof entry.toURI === 'function', "entry object should have a 'toURI' function."); + ok(typeof entry.remove === 'function', "entry object should have a 'remove' function."); + ok(typeof entry.getParent === 'function', "entry object should have a 'getParent' function."); + ok(typeof entry.createReader === 'function', "entry object should have a 'createReader' function."); + ok(typeof entry.getFile === 'function', "entry object should have a 'getFile' function."); + ok(typeof entry.getDirectory === 'function', "entry object should have a 'getDirectory' function."); + ok(typeof entry.removeRecursively === 'function', "entry object should have a 'removeRecursively' function."); + QUnit.start(); + }; + + window.resolveLocalFileSystemURI(root.toURI(), testFSRoot, null); + + }); + module('DirectoryEntry interface', { + // setup function will run before each test + setup: function() { + this.root = getFileSystemRoot(); + this.fail = function(error) { + console.log('file error: ' + error.code); + }; + this.unexpectedSuccess = function() { + console.log('!!! success function called when not expected !!!'); + }; + } + }); + test("DirectoryEntry.getFile: get Entry for file that does not exist", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var fileName = "de.no.file", + filePath = this.root.fullPath + '/' + fileName, + that = this, + testFile = function(error) { + ok(typeof error !== 'undefined' && error !== null, "retrieving a file that does not exist is an error"); + equal(error.code, FileError.NOT_FOUND_ERR, "error code should be FileError.NOT_FOUND_ERR"); + + // cleanup + QUnit.start(); + }; + + // create:false, exclusive:false, file does not exist + this.root.getFile(fileName, {create:false}, null, testFile); + }); + test("DirectoryEntry.getFile: create new file", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(5); + + var fileName = "de.create.file", + filePath = this.root.fullPath + '/' + fileName, + that = this, + testFile = function(entry) { + ok(typeof entry !== 'undefined' && entry !== null, "file entry should not be null"); + equal(entry.isFile, true, "entry 'isFile' attribute should be true"); + equal(entry.isDirectory, false, "entry 'isDirectory' attribute should be false"); + equal(entry.name, fileName, "entry 'name' attribute should be set"); + equal(entry.fullPath, filePath, "entry 'fullPath' attribute should be set"); + + // cleanup + entry.remove(null, that.fail); + QUnit.start(); + }; + + // create:true, exclusive:false, file does not exist + this.root.getFile(fileName, {create: true}, testFile, this.fail); + }); + test("DirectoryEntry.getFile: create new file (exclusive)", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(5); + + var fileName = "de.create.exclusive.file", + filePath = this.root.fullPath + '/' + fileName, + that = this, + testFile = function(entry) { + ok(typeof entry !== 'undefined' && entry !== null, "file entry should not be null"); + equal(entry.isFile, true, "entry 'isFile' attribute should be true"); + equal(entry.isDirectory, false, "entry 'isDirectory' attribute should be false"); + equal(entry.name, fileName, "entry 'name' attribute should be set"); + equal(entry.fullPath, filePath, "entry 'fullPath' attribute should be set"); + + // cleanup + entry.remove(null, that.fail); + QUnit.start(); + }; + + // create:true, exclusive:true, file does not exist + this.root.getFile(fileName, {create: true, exclusive:true}, testFile, this.fail); + }); + test("DirectoryEntry.getFile: create file that already exists", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(5); + + var fileName = "de.create.existing.file", + filePath = this.root.fullPath + '/' + fileName, + that = this, + getFile = function(file) { + // create:true, exclusive:false, file exists + that.root.getFile(fileName, {create:true}, testFile, that.fail); + }, + testFile = function(entry) { + ok(typeof entry !== 'undefined' && entry !== null, "file entry should not be null"); + equal(entry.isFile, true, "entry 'isFile' attribute should be true"); + equal(entry.isDirectory, false, "entry 'isDirectory' attribute should be false"); + equal(entry.name, fileName, "entry 'name' attribute should be set"); + equal(entry.fullPath, filePath, "entry 'fullPath' attribute should be set"); + + // cleanup + entry.remove(null, that.fail); + QUnit.start(); + }; + + // create file to kick off test + this.root.getFile(fileName, {create:true}, getFile, this.fail); + }); + test("DirectoryEntry.getFile: create file that already exists (exclusive)", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var fileName = "de.create.exclusive.existing.file", + filePath = this.root.fullPath + '/' + fileName, + that = this, + existingFile, + getFile = function(file) { + existingFile = file; + // create:true, exclusive:true, file exists + that.root.getFile(fileName, {create:true, exclusive:true}, null, testFile); + }, + testFile = function(error) { + ok(typeof error !== 'undefined' && error !== null, "creating exclusive file that already exists is an error"); + equal(error.code, FileError.PATH_EXISTS_ERR, "error code should be FileError.PATH_EXISTS_ERR"); + + // cleanup + existingFile.remove(null, that.fail); + QUnit.start(); + }; + + // create file to kick off test + this.root.getFile(fileName, {create:true}, getFile, this.fail); + }); + test("DirectoryEntry.getFile: get Entry for existing file", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(5); + + var fileName = "de.get.file", + filePath = this.root.fullPath + '/' + fileName, + that = this, + getFile = function(file) { + // create:false, exclusive:false, file exists + that.root.getFile(fileName, {create:false}, testFile, that.fail); + }, + testFile = function(entry) { + ok(typeof entry !== 'undefined' && entry !== null, "file entry should not be null"); + equal(entry.isFile, true, "entry 'isFile' attribute should be true"); + equal(entry.isDirectory, false, "entry 'isDirectory' attribute should be false"); + equal(entry.name, fileName, "entry 'name' attribute should be set"); + equal(entry.fullPath, filePath, "entry 'fullPath' attribute should be set"); + + // cleanup + entry.remove(null, that.fail); + QUnit.start(); + }; + + // create file to kick off test + this.root.getFile(fileName, {create:true}, getFile, this.fail); + }); + test("DirectoryEntry.getFile: get FileEntry for invalid path", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var fileName = "de:invalid:path", + that = this, + testFile = function(error) { + ok(typeof error !== 'undefined' && error !== null, "retrieving a file using an invalid path is an error"); + equal(error.code, FileError.ENCODING_ERR, "error code should be FileError.ENCODING_ERR"); + + // cleanup + QUnit.start(); + }; + + // create:false, exclusive:false, invalid path + this.root.getFile(fileName, {create:false}, null, testFile); + }); + test("DirectoryEntry.getDirectory: get Entry for directory that does not exist", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var dirName = "de.no.dir", + dirPath = this.root.fullPath + '/' + dirName, + that = this, + testDir = function(error) { + ok(typeof error !== 'undefined' && error !== null, "retrieving a directory that does not exist is an error"); + equal(error.code, FileError.NOT_FOUND_ERR, "error code should be FileError.NOT_FOUND_ERR"); + + // cleanup + QUnit.start(); + }; + + // create:false, exclusive:false, directory does not exist + this.root.getDirectory(dirName, {create:false}, null, testDir); + }); + test("DirectoryEntry.getDirectory: create new dir with space then resolveFileSystemURI", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(5); + + var dirName = "de create dir", + dirPath = this.root.fullPath + '/' + dirName, + that = this, + getDir = function(dirEntry) { + + var dirURI = dirEntry.toURI(); + // now encode URI and try to resolve + window.resolveLocalFileSystemURI(dirURI, testDirFromURI, that.fail); + + }, + testDirFromURI = function(directory) { + ok(typeof directory !== 'undefined' && directory !== null, "directory entry should not be null"); + equal(directory.isFile, false, "directory 'isFile' attribute should be false"); + equal(directory.isDirectory, true, "directory 'isDirectory' attribute should be true"); + equal(directory.name, dirName, "directory 'name' attribute should be set"); + equal(directory.fullPath, dirPath, "directory 'fullPath' attribute should be set"); + + // cleanup + directory.remove(null, that.fail); + QUnit.start(); + }; + + // create:true, exclusive:false, directory does not exist + this.root.getDirectory(dirName, {create: true}, getDir, this.fail); + }); + test("DirectoryEntry.getDirectory: create new dir with space resolveFileSystemURI with encoded URI", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(5); + + var dirName = "de create dir", + dirPath = this.root.fullPath + '/' + dirName, + that = this, + getDir = function(dirEntry) { + + var dirURI = dirEntry.toURI(); + // now encode URI and try to resolve + window.resolveLocalFileSystemURI(encodeURI(dirURI), testDirFromURI, that.fail); + + }, + testDirFromURI = function(directory) { + ok(typeof directory !== 'undefined' && directory !== null, "directory entry should not be null"); + equal(directory.isFile, false, "directory 'isFile' attribute should be false"); + equal(directory.isDirectory, true, "directory 'isDirectory' attribute should be true"); + equal(directory.name, dirName, "directory 'name' attribute should be set"); + equal(directory.fullPath, dirPath, "directory 'fullPath' attribute should be set"); + + // cleanup + directory.remove(null, that.fail); + QUnit.start(); + }; + + // create:true, exclusive:false, directory does not exist + this.root.getDirectory(dirName, {create: true}, getDir, this.fail); + }); + + test("DirectoryEntry.getDirectory: create new directory", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(5); + + var dirName = "de.create.dir", + dirPath = this.root.fullPath + '/' + dirName, + that = this, + testDir = function(directory) { + ok(typeof directory !== 'undefined' && directory !== null, "directory entry should not be null"); + equal(directory.isFile, false, "directory 'isFile' attribute should be false"); + equal(directory.isDirectory, true, "directory 'isDirectory' attribute should be true"); + equal(directory.name, dirName, "directory 'name' attribute should be set"); + equal(directory.fullPath, dirPath, "directory 'fullPath' attribute should be set"); + + // cleanup + directory.remove(null, that.fail); + QUnit.start(); + }; + + // create:true, exclusive:false, directory does not exist + this.root.getDirectory(dirName, {create: true}, testDir, this.fail); + }); + + test("DirectoryEntry.getDirectory: create new directory (exclusive)", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(5); + + var dirName = "de.create.exclusive.dir", + dirPath = this.root.fullPath + '/' + dirName, + that = this, + testDir = function(directory) { + ok(typeof directory !== 'undefined' && directory !== null, "directory entry should not be null"); + equal(directory.isFile, false, "directory 'isFile' attribute should be false"); + equal(directory.isDirectory, true, "directory 'isDirectory' attribute should be true"); + equal(directory.name, dirName, "directory 'name' attribute should be set"); + equal(directory.fullPath, dirPath, "directory 'fullPath' attribute should be set"); + + // cleanup + directory.remove(null, that.fail); + QUnit.start(); + }; + + // create:true, exclusive:true, directory does not exist + this.root.getDirectory(dirName, {create: true, exclusive:true}, testDir, this.fail); + }); + test("DirectoryEntry.getDirectory: create directory that already exists", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(5); + + var dirName = "de.create.existing.dir", + dirPath = this.root.fullPath + '/' + dirName, + that = this, + getDir = function(directory) { + // create:true, exclusive:false, directory exists + that.root.getDirectory(dirName, {create:true}, testDir, that.fail); + }, + testDir = function(directory) { + ok(typeof directory !== 'undefined' && directory !== null, "directory entry should not be null"); + equal(directory.isFile, false, "directory 'isFile' attribute should be false"); + equal(directory.isDirectory, true, "directory 'isDirectory' attribute should be true"); + equal(directory.name, dirName, "directory 'name' attribute should be set"); + equal(directory.fullPath, dirPath, "directory 'fullPath' attribute should be set"); + + // cleanup + directory.remove(null, that.fail); + QUnit.start(); + }; + + // create directory to kick off test + this.root.getDirectory(dirName, {create:true}, getDir, this.fail); + }); + test("DirectoryEntry.getDirectory: create directory that already exists (exclusive)", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var dirName = "de.create.exclusive.existing.dir", + dirPath = this.root.fullPath + '/' + dirName, + that = this, + existingDir, + getDir = function(directory) { + existingDir = directory; + // create:true, exclusive:true, directory exists + that.root.getDirectory(dirName, {create:true, exclusive:true}, null, testDir); + }, + testDir = function(error) { + ok(typeof error !== 'undefined' && error !== null, "creating exclusive directory that already exists is an error"); + equal(error.code, FileError.PATH_EXISTS_ERR, "error code should be FileError.PATH_EXISTS_ERR"); + + // cleanup + existingDir.remove(null, that.fail); + QUnit.start(); + }; + + // create directory to kick off test + this.root.getDirectory(dirName, {create:true}, getDir, this.fail); + }); + test("DirectoryEntry.getDirectory: get Entry for existing directory", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(5); + + var dirName = "de.get.dir", + dirPath = this.root.fullPath + '/' + dirName, + that = this, + getDir = function(directory) { + // create:false, exclusive:false, directory exists + that.root.getDirectory(dirName, {create:false}, testDir, that.fail); + }, + testDir = function(directory) { + ok(typeof directory !== 'undefined' && directory !== null, "directory entry should not be null"); + equal(directory.isFile, false, "directory 'isFile' attribute should be false"); + equal(directory.isDirectory, true, "directory 'isDirectory' attribute should be true"); + equal(directory.name, dirName, "directory 'name' attribute should be set"); + equal(directory.fullPath, dirPath, "directory 'fullPath' attribute should be set"); + + // cleanup + directory.remove(null, that.fail); + QUnit.start(); + }; + + // create directory to kick off test + this.root.getDirectory(dirName, {create:true}, getDir, this.fail); + }); + test("DirectoryEntry.getDirectory: get DirectoryEntry for invalid path", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var dirName = "de:invalid:path", + that = this, + testDir = function(error) { + ok(typeof error !== 'undefined' && error !== null, "retrieving a directory using an invalid path is an error"); + equal(error.code, FileError.ENCODING_ERR, "error code should be FileError.ENCODING_ERR"); + + // cleanup + QUnit.start(); + }; + + // create:false, exclusive:false, invalid path + this.root.getDirectory(dirName, {create:false}, null, testDir); + }); + test("DirectoryEntry.getDirectory: get DirectoryEntry for existing file", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var fileName = "de.existing.file", + existingFile, + filePath = this.root.fullPath + '/' + fileName, + that = this, + getDir = function(file) { + existingFile = file; + // create:false, exclusive:false, existing file + that.root.getDirectory(fileName, {create:false}, null, testDir); + }, + testDir = function(error) { + ok(typeof error !== 'undefined' && error !== null, "retrieving directory for existing file is an error"); + equal(error.code, FileError.TYPE_MISMATCH_ERR, "error code should be FileError.TYPE_MISMATCH_ERR"); + + // cleanup + existingFile.remove(null, that.fail); + QUnit.start(); + }; + + // create file to kick off test + this.root.getFile(fileName, {create:true}, getDir, this.fail); + }); + test("DirectoryEntry.getFile: get FileEntry for existing directory", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var dirName = "de.existing.dir", + existingDir, + dirPath = this.root.fullPath + '/' + dirName, + that = this, + getFile = function(directory) { + existingDir = directory; + // create:false, exclusive:false, existing directory + that.root.getFile(dirName, {create:false}, null, testFile); + }, + testFile = function(error) { + ok(typeof error !== 'undefined' && error !== null, "retrieving file for existing directory is an error"); + equal(error.code, FileError.TYPE_MISMATCH_ERR, "error code should be FileError.TYPE_MISMATCH_ERR"); + + // cleanup + existingDir.remove(null, that.fail); + QUnit.start(); + }; + + // create directory to kick off test + this.root.getDirectory(dirName, {create:true}, getFile, this.fail); + }); + test("DirectoryEntry.removeRecursively on directory", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var dirName = "de.removeRecursively", + subDirName = "dir", + dirPath = this.root.fullPath + '/' + dirName, + //subDirPath = this.root.fullPath + '/' + subDirName, + subDirPath = dirPath + '/' + subDirName, + that = this, + entryCallback = function(entry) { + // delete directory + var deleteDirectory = function(directory) { + entry.removeRecursively(testRemove, that.fail); + }; + // create a sub-directory within directory + entry.getDirectory(subDirName, {create: true}, deleteDirectory, that.fail); + }, + testRemove = function() { + // test that removed directory no longer exists + that.root.getDirectory(dirName, {create:false}, null, testDirExists); + }, + testDirExists = function(error){ + ok(typeof error !== 'undefined' && error !== null, "removed directory should not exist"); + equal(error.code, FileError.NOT_FOUND_ERR, "error code should be FileError.NOT_FOUND_ERR"); + QUnit.start(); + }; + + // create a new directory entry to kick off test + this.root.getDirectory(dirName, {create:true}, entryCallback, this.fail); + }); + test("DirectoryEntry.createReader: create reader on existing directory", function() { + expect(2); + + // create reader for root directory + var reader = this.root.createReader(); + ok(typeof reader !== 'undefined' && reader !== null, "reader object should not be null"); + ok(typeof reader.readEntries === 'function', "reader object should have a 'readEntries' method"); + }); + module('DirectoryReader interface', { + // setup function will run before each test + setup: function() { + this.root = getFileSystemRoot(); + this.fail = function(error) { + console.log('file error: ' + error.code); + }; + } + }); + test("DirectoryReader.readEntries: read contents of existing directory", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(4); + + var reader, + testEntries = function(entries) { + ok(typeof entries !== 'undefined' && entries !== null, "directory entries should not be null"); + ok(entries.constructor === Array, "readEntries should return an array of entries"); + QUnit.start(); + }; + + // create reader for root directory + reader = this.root.createReader(); + ok(typeof reader !== 'undefined' && reader !== null, "reader object should not be null"); + ok(typeof reader.readEntries === 'function', "reader object should have a 'readEntries' method"); + + // read entries + reader.readEntries(testEntries, this.fail); + }); + test("DirectoryReader.readEntries: read contents of directory that has been removed", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(4); + + var dirName = "de.createReader.notfound", + dirPath = this.root.fullPath + '/' + dirName, + that = this, + entryCallback = function(directory) { + // read entries + var readEntries = function() { + var reader = directory.createReader(); + reader.readEntries(null, testReader); + }; + // delete directory + directory.removeRecursively(readEntries, that.fail); + }, + testReader = function(error) { + var testDirectoryExists = function(error) { + ok(typeof error !== 'undefined' && error !== null, "reading entries on a directory that does not exist is an error") + equal(error.code, FileError.NOT_FOUND_ERR, "removed directory should not exist"); + QUnit.start(); + }; + ok(typeof error !== 'undefined' && error !== null, "reading entries on a directory that does not exist is an error") + equal(error.code, FileError.NOT_FOUND_ERR, "error code should be FileError.NOT_FOUND_ERR"); + that.root.getDirectory(dirName, {create:false}, null, testDirectoryExists); + }; + + // create a new directory entry to kick off test + this.root.getDirectory(dirName, {create:true}, entryCallback, this.fail); + }); + test("DirectoryEntry.removeRecursively on root file system", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var testRemove = function(error) { + ok(typeof error !== 'undefined' && error !== null, "removing root file system should generate an error"); + equal(error.code, FileError.NO_MODIFICATION_ALLOWED_ERR, "error code should be FileError.NO_MODIFICATION_ALLOWED_ERR"); + QUnit.start(); + }; + + // remove root file system + this.root.removeRecursively(null, testRemove); + }); + module('File interface'); + test("File constructor should be defined", function() { + expect(1); + ok(typeof File === 'function', "File constructor should be a function."); + }); + test("File attributes should be defined", function() { + expect(5); + var file = new File(); + ok(typeof file.name !== 'undefined', "File object should have a 'name' attribute"); + ok(typeof file.fullPath !== 'undefined', "File object should have a 'fullPath' attribute"); + ok(typeof file.type !== 'undefined', "File object should have a 'type' attribute"); + ok(typeof file.lastModifiedDate !== 'undefined', "File object should have a 'lastModifiedDate' attribute"); + ok(typeof file.size !== 'undefined', "File object should have a 'size' attribute"); + }); + module('FileEntry interface', { + // setup function will run before each test + setup: function() { + this.root = getFileSystemRoot(); + this.fail = function(error) { + console.log('file error: ' + error.code); + }; + } + }); + test("FileEntry methods should be defined", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(3); + + var fileName = "fe.methods", + that = this, + testFileEntry = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "FileEntry should not be null"); + ok(typeof fileEntry.createWriter === 'function', "FileEntry should have a 'createWriter' method"); + ok(typeof fileEntry.file === 'function', "FileEntry should have a 'file' method"); + + // cleanup + fileEntry.remove(null, that.fail); + QUnit.start(); + }; + + // create a new file entry to kick off test + this.root.getFile(fileName, {create:true}, testFileEntry, this.fail); + }); + test("FileEntry.createWriter should return a FileWriter object", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var fileName = "fe.createWriter", + that = this, + testFile, + entryCallback = function(fileEntry) { + testFile = fileEntry; + fileEntry.createWriter(testWriter, that.fail); + }, + testWriter = function(writer) { + ok(typeof writer !== 'undefined' && writer !== null, "FileWriter object should not be null"); + ok(writer.constructor === FileWriter, "writer should be a FileWriter object"); + + // cleanup + testFile.remove(null, that.fail); + QUnit.start(); + }; + + // create a new file entry to kick off test + this.root.getFile(fileName, {create:true}, entryCallback, this.fail); + }); + test("FileEntry.file should return a File object", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var fileName = "fe.file", + that = this, + newFile, + entryCallback = function(fileEntry) { + newFile = fileEntry; + fileEntry.file(testFile, that.fail); + }, + testFile = function(file) { + ok(typeof file !== 'undefined' && file !== null, "File object should not be null"); + ok(file.constructor === File, "File object should be a File"); + + // cleanup + newFile.remove(null, that.fail); + QUnit.start(); + }; + + // create a new file entry to kick off test + this.root.getFile(fileName, {create:true}, entryCallback, this.fail); + }); + test("FileEntry.file: on File that has been removed", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var fileName = "fe.no.file", + that = this, + entryCallback = function(fileEntry) { + // create File object + var getFile = function() { + fileEntry.file(null, testFile); + }; + // delete file + fileEntry.remove(getFile, that.fail); + }, + testFile = function(error) { + ok(typeof error !== 'undefined' && error !== null, "invoking FileEntry.file on a file that does not exist is an error"); + equal(error.code, FileError.NOT_FOUND_ERR, "error code should be FileError.NOT_FOUND_ERR"); + QUnit.start(); + }; + + // create a new file entry to kick off test + this.root.getFile(fileName, {create:true}, entryCallback, this.fail); + }); + module('Entry interface', { + // setup function will run before each test + setup: function() { + var that = this; + this.root = getFileSystemRoot(); + this.fail = function(error) { + console.log('file error: ' + error.code); + }; + this.unexpectedSuccess = function() { + console.log('!!! success function called when not expected !!!'); + }; + // deletes specified file or directory + this.deleteEntry = function(name, success, error) { + // deletes entry, if it exists + window.resolveLocalFileSystemURI(that.root.toURI() + '/' + name, + function(entry) { + console.log('Deleting: ' + entry.fullPath); + if (entry.isDirectory === true) { + entry.removeRecursively(success, error); + } + else { + entry.remove(success, error); + } + }, + // doesn't exist + success); + }; + // deletes and re-creates the specified file + this.createFile = function(fileName, success, error) { + that.deleteEntry(fileName, function() { + console.log('Creating file: ' + that.root.fullPath + '/' + fileName); + that.root.getFile(fileName, {create: true}, success, error); + }, error); + }; + // deletes and re-creates the specified directory + this.createDirectory = function(dirName, success, error) { + that.deleteEntry(dirName, function() { + console.log('Creating directory: ' + that.root.fullPath + '/' + dirName); + that.root.getDirectory(dirName, {create: true}, success, error); + }, error); + }; + } + }); + test("Entry object", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(13); + + var fileName = "entry", + that = this, + fullPath = this.root.fullPath + '/' + fileName, + testEntry = function(entry) { + ok(typeof entry !== 'undefined' && entry !== null, "entry should not be null."); + equal(entry.isFile, true, "entry.isFile should be true"); + equal(entry.isDirectory, false, "entry.isDirectory should be false"); + equal(entry.name, fileName, "entry object 'name' property should be set"); + equal(entry.fullPath, fullPath, "entry object 'fullPath' property should be set"); + ok(typeof entry.getMetadata === 'function', "entry object should have a 'getMetadata' function."); + ok(typeof entry.moveTo === 'function', "entry object should have a 'moveTo' function."); + ok(typeof entry.copyTo === 'function', "entry object should have a 'copyTo' function."); + ok(typeof entry.toURI === 'function', "entry object should have a 'toURI' function."); + ok(typeof entry.remove === 'function', "entry object should have a 'remove' function."); + ok(typeof entry.getParent === 'function', "entry object should have a 'getParent' function."); + ok(typeof entry.createWriter === 'function', "entry object should have a 'createWriter' function."); + ok(typeof entry.file === 'function', "entry object should have a 'file' function."); + + // cleanup + that.deleteEntry(fileName); + QUnit.start(); + }; + + // create a new file entry + this.createFile(fileName, testEntry, this.fail); + }); + test("Entry.getMetadata on file", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var fileName = "entry.metadata.file", + that = this, + entryCallback = function(entry) { + entry.getMetadata(testMetadata, this.fail); + }, + testMetadata = function(metadata) { + ok(typeof metadata !== 'undefined' && metadata !== null, "metadata should not be null."); + ok(metadata.modificationTime instanceof Date, "metadata.modificationTime should be Date object"); + + // cleanup + that.deleteEntry(fileName); + QUnit.start(); + }; + + // create a new file entry + this.createFile(fileName, entryCallback, this.fail); + }); + test("Entry.getMetadata on directory", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var dirName = "entry.metadata.dir", + that = this, + entryCallback = function(entry) { + entry.getMetadata(testMetadata, this.fail); + }, + testMetadata = function(metadata) { + ok(typeof metadata !== 'undefined' && metadata !== null, "metadata should not be null."); + ok(metadata.modificationTime instanceof Date, "metadata.modificationTime should be Date object"); + + // cleanup + that.deleteEntry(dirName); + QUnit.start(); + }; + + // create a new directory entry + this.createDirectory(dirName, entryCallback, this.fail); + }); + test("Entry.getParent on file in root file system", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var fileName = "entry.parent.file", + that = this, + rootPath = this.root.fullPath, + entryCallback = function(entry) { + entry.getParent(testParent, this.fail); + }, + testParent = function(parent) { + ok(typeof parent !== 'undefined' && parent !== null, "parent directory should not be null."); + equal(parent.fullPath, rootPath, "parent fullPath should be root file system"); + + // cleanup + that.deleteEntry(fileName); + QUnit.start(); + }; + + // create a new file entry + this.createFile(fileName, entryCallback, this.fail); + }); + test("Entry.getParent on directory in root file system", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var dirName = "entry.parent.dir", + that = this, + rootPath = this.root.fullPath, + entryCallback = function(entry) { + entry.getParent(testParent, this.fail); + }, + testParent = function(parent) { + ok(typeof parent !== 'undefined' && parent !== null, "parent directory should not be null."); + equal(parent.fullPath, rootPath, "parent fullPath should be root file system"); + + // cleanup + that.deleteEntry(dirName); + QUnit.start(); + }; + + // create a new directory entry + this.createDirectory(dirName, entryCallback, this.fail); + }); + test("Entry.getParent on root file system", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var rootPath = this.root.fullPath, + testParent = function(parent) { + ok(typeof parent !== 'undefined' && parent !== null, "parent directory should not be null."); + equal(parent.fullPath, rootPath, "parent fullPath should be root file system"); + QUnit.start(); + }; + + // create a new directory entry + this.root.getParent(testParent, this.fail); + }); + test("Entry.toURI on file", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var fileName = "entry.uri.file", + that = this, + rootPath = this.root.fullPath, + testURI = function(entry) { + var uri = entry.toURI(); + ok(typeof uri !== 'undefined' && uri !== null, "URI should not be null."); + ok(uri.indexOf(rootPath) !== -1, "URI should contain root file system path"); + + // cleanup + that.deleteEntry(fileName); + QUnit.start(); + }; + + // create a new file entry + this.createFile(fileName, testURI, this.fail); + }); + test("Entry.toURI on directory", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var dirName = "entry.uri.dir", + that = this, + rootPath = this.root.fullPath, + testURI = function(entry) { + var uri = entry.toURI(); + ok(typeof uri !== 'undefined' && uri !== null, "URI should not be null."); + ok(uri.indexOf(rootPath) !== -1, "URI should contain root file system path"); + + // cleanup + that.deleteEntry(dirName); + QUnit.start(); + }; + + // create a new directory entry + this.createDirectory(dirName, testURI, this.fail); + }); + test("Entry.remove on file", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(3); + + var fileName = "entry.rm.file", + that = this, + fullPath = this.root.fullPath + '/' + fileName, + entryCallback = function(entry) { + var checkRemove = function() { + that.root.getFile(fileName, null, that.unexpectedSuccess, testRemove); + }; + ok(typeof entry !== 'undefined' && entry !== null, "entry should not be null."); + entry.remove(checkRemove, that.fail); + }, + testRemove = function(error) { + ok(typeof error !== 'undefined' && error !== null, "file should not exist"); + equal(error.code, FileError.NOT_FOUND_ERR, "error code should be FileError.NOT_FOUND_ERR"); + // cleanup + that.deleteEntry(fileName); + QUnit.start(); + }; + + // create a new file entry + this.createFile(fileName, entryCallback, this.fail); + }); + test("Entry.remove on empty directory", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(3); + + var dirName = "entry.rm.dir", + that = this, + fullPath = this.root.fullPath + '/' + dirName, + entryCallback = function(entry) { + var checkRemove = function() { + that.root.getDirectory(dirName, null, that.unexpectedSuccess, testRemove); + }; + ok(typeof entry !== 'undefined' && entry !== null, "entry should not be null."); + entry.remove(checkRemove, that.fail); + }, + testRemove = function(error) { + ok(typeof error !== 'undefined' && error !== null, "directory should not exist"); + equal(error.code, FileError.NOT_FOUND_ERR, "error code should be FileError.NOT_FOUND_ERR"); + // cleanup + that.deleteEntry(dirName); + QUnit.start(); + }; + + // create a new directory entry + this.createDirectory(dirName, entryCallback, this.fail); + }); + test("Entry.remove on non-empty directory", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(4); + + var dirName = "entry.rm.dir.not.empty", + that = this, + fullPath = this.root.fullPath + '/' + dirName, + fileName = "remove.txt", + entryCallback = function(entry) { + var checkFile = function(error) { + ok(typeof error !== 'undefined' && error !== null, "removing non-empty directory should generate an error"); + equal(error.code, FileError.INVALID_MODIFICATION_ERR, "error code should be FileError.INVALID_MODIFICATION_ERR"); + // verify that dir still exists + that.root.getDirectory(dirName, null, testRemove, that.fail); + }; + // delete directory + var deleteDirectory = function(fileEntry) { + entry.remove(that.unexpectedSuccess, checkFile); + }; + // create a file within directory, then try to delete directory + entry.getFile(fileName, {create: true}, deleteDirectory, that.fail); + }, + testRemove = function(entry) { + ok(typeof entry !== 'undefined' && entry !== null, "entry should not be null."); + equal(entry.fullPath, fullPath, "dir entry should still exisit"); + // cleanup + that.deleteEntry(dirName); + QUnit.start(); + }; + + // create a new directory entry + this.createDirectory(dirName, entryCallback, this.fail); + }); + test("Entry.remove on root file system", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var testRemove = function(error) { + ok(typeof error !== 'undefined' && error !== null, "removing root file system should generate an error"); + equal(error.code, FileError.NO_MODIFICATION_ALLOWED_ERR, "error code should be FileError.NO_MODIFICATION_ALLOWED_ERR"); + QUnit.start(); + }; + + // remove entry that doesn't exist + this.root.remove(null, testRemove); + }); + test("Entry.copyTo: file", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(10); + + var file1 = "entry.copy.file1", + file2 = "entry.copy.file2", + that = this, + fullPath = this.root.fullPath + '/' + file2, + entryCallback = function(entry) { + // copy file1 to file2 + entry.copyTo(that.root, file2, testCopy, that.fail); + }, + testCopy = function(entry) { + + ok(typeof entry !== 'undefined' && entry !== null, "copied file entry should not be null"); + equals(entry.isFile, true, "entry 'isFile' attribute should be set to true"); + equals(entry.isDirectory, false, "entry 'isDirectory' attribute should be set to false"); + equals(entry.fullPath, fullPath, "entry 'fullPath' should be set correctly"); + equals(entry.name, file2, "entry 'name' attribute should be set correctly"); + that.root.getFile(file2, {create:false}, testFileExists, null); + + }, + testFileExists = function(entry2) { + // a bit redundant since copy returned this entry already + ok(typeof entry2 !== 'undefined' && entry2 !== null, "copied file entry should not be null"); + equals(entry2.isFile, true, "entry 'isFile' attribute should be set to true"); + equals(entry2.isDirectory, false, "entry 'isDirectory' attribute should be set to false"); + equals(entry2.fullPath, fullPath, "entry 'fullPath' should be set correctly"); + equals(entry2.name, file2, "entry 'name' attribute should be set correctly"); + + // cleanup + that.deleteEntry(file1); + that.deleteEntry(file2); + QUnit.start(); + }; + + // create a new file entry to kick off test + this.createFile(file1, entryCallback, this.fail); + }); + test("Entry.copyTo: file onto itself", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var file1 = "entry.copy.fos.file1", + that = this, + entryCallback = function(entry) { + // copy file1 onto itself + entry.copyTo(that.root, null, null, testCopy); + }, + testCopy = function(error) { + ok(typeof error !== 'undefined' && error !== null, "it is an error to copy an entry into its parent if a different name is not specified"); + equal(error.code, FileError.INVALID_MODIFICATION_ERR, "error code should be FileError.INVALID_MODIFICATION_ERR"); + + // cleanup + that.deleteEntry(file1); + QUnit.start(); + }; + + // create a new file entry to kick off test + this.createFile(file1, entryCallback, this.fail); + }); + test("Entry.copyTo: directory", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(15); + + var file1 = "file1", + srcDir = "entry.copy.srcDir", + dstDir = "entry.copy.dstDir", + dstPath = this.root.fullPath + '/' + dstDir, + filePath = dstPath + '/' + file1, + that = this, + entryCallback = function(directory) { + var copyDir = function(fileEntry) { + // copy srcDir to dstDir + directory.copyTo(that.root, dstDir, testCopy, that.fail); + }; + // create a file within new directory + directory.getFile(file1, {create: true}, copyDir, that.fail); + }, + testCopy = function(directory) { + + ok(typeof directory !== 'undefined' && directory !== null, "copied directory entry should not be null"); + equals(directory.isFile, false, "entry 'isFile' attribute should be false"); + equals(directory.isDirectory, true, "entry 'isDirectory' attribute should be true"); + equals(directory.fullPath, dstPath, "entry 'fullPath' should be set correctly"); + equals(directory.name, dstDir, "entry 'name' attribute should be set correctly"); + + that.root.getDirectory(dstDir, {create:false}, testDirExists, that.fail); + }, + testDirExists = function(dirEntry) { + ok(typeof dirEntry !== 'undefined' && dirEntry !== null, "copied directory entry should not be null"); + equals(dirEntry.isFile, false, "entry 'isFile' attribute should be false"); + equals(dirEntry.isDirectory, true, "entry 'isDirectory' attribute should be true"); + equals(dirEntry.fullPath, dstPath, "entry 'fullPath' should be set correctly"); + equals(dirEntry.name, dstDir, "entry 'name' attribute should be set correctly"); + + dirEntry.getFile(file1, {create:false}, testFileExists, that.fail); + + }; + testFileExists = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "copied directory entry should not be null"); + equals(fileEntry.isFile, true, "entry 'isFile' attribute should be true"); + equals(fileEntry.isDirectory, false, "entry 'isDirectory' attribute should be false"); + equals(fileEntry.fullPath, filePath, "entry 'fullPath' should be set correctly"); + equals(fileEntry.name, file1, "entry 'name' attribute should be set correctly"); + + + // cleanup + that.deleteEntry(srcDir); + that.deleteEntry(dstDir); + QUnit.start(); + }; + + // create a new directory entry to kick off test + this.createDirectory(srcDir, entryCallback, this.fail); + }); + test("Entry.copyTo: directory to backup at same root directory", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(15); + + var file1 = "file1", + srcDir = "entry.copy.srcDir", + dstDir = "entry.copy.srcDir-backup", + dstPath = this.root.fullPath + '/' + dstDir, + filePath = dstPath + '/' + file1, + that = this, + entryCallback = function(directory) { + var copyDir = function(fileEntry) { + // copy srcDir to dstDir + directory.copyTo(that.root, dstDir, testCopy, that.fail); + }; + // create a file within new directory + directory.getFile(file1, {create: true}, copyDir, that.fail); + }, + testCopy = function(directory) { + + ok(typeof directory !== 'undefined' && directory !== null, "copied directory entry should not be null"); + equals(directory.isFile, false, "entry 'isFile' attribute should be false"); + equals(directory.isDirectory, true, "entry 'isDirectory' attribute should be true"); + equals(directory.fullPath, dstPath, "entry 'fullPath' should be set correctly"); + equals(directory.name, dstDir, "entry 'name' attribute should be set correctly"); + + that.root.getDirectory(dstDir, {create:false}, testDirExists, that.fail); + }, + testDirExists = function(dirEntry) { + ok(typeof dirEntry !== 'undefined' && dirEntry !== null, "copied directory entry should not be null"); + equals(dirEntry.isFile, false, "entry 'isFile' attribute should be false"); + equals(dirEntry.isDirectory, true, "entry 'isDirectory' attribute should be true"); + equals(dirEntry.fullPath, dstPath, "entry 'fullPath' should be set correctly"); + equals(dirEntry.name, dstDir, "entry 'name' attribute should be set correctly"); + + dirEntry.getFile(file1, {create:false}, testFileExists, that.fail); + + }; + testFileExists = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "copied directory entry should not be null"); + equals(fileEntry.isFile, true, "entry 'isFile' attribute should be true"); + equals(fileEntry.isDirectory, false, "entry 'isDirectory' attribute should be false"); + equals(fileEntry.fullPath, filePath, "entry 'fullPath' should be set correctly"); + equals(fileEntry.name, file1, "entry 'name' attribute should be set correctly"); + + + // cleanup + that.deleteEntry(srcDir); + that.deleteEntry(dstDir); + QUnit.start(); + }; + + // create a new directory entry to kick off test + this.createDirectory(srcDir, entryCallback, this.fail); + }); + test("Entry.copyTo: directory onto itself", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(6); + + var file1 = "file1", + srcDir = "entry.copy.dos.srcDir", + srcPath = this.root.fullPath + '/' + srcDir, + filePath = srcPath + '/' + file1, + that = this, + entryCallback = function(directory) { + var copyDir = function(fileEntry) { + // copy srcDir onto itself + directory.copyTo(that.root, null, null, testCopy); + }; + // create a file within new directory + directory.getFile(file1, {create: true}, copyDir, that.fail); + }, + testCopy = function(error) { + ok(typeof error !== 'undefined' && error !== null, "it is an error to copy an entry into its parent if a different name is not specified"); + equal(error.code, FileError.INVALID_MODIFICATION_ERR, "error code should be FileError.INVALID_MODIFICATION_ERR"); + + that.root.getDirectory(srcDir, {create:false}, testDirectoryExists, null); + }, + testDirectoryExists = function(dirEntry) { + // returning confirms existence so just check fullPath entry + ok(typeof dirEntry !== 'undefined' && dirEntry !== null, "original directory should exist."); + equals(dirEntry.fullPath, srcPath, "entry 'fullPath' should be set correctly"); + + dirEntry.getFile(file1, {create:false}, testFileExists, null); + }, + testFileExists = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "original directory contents should exist."); + equals(fileEntry.fullPath, filePath, "entry 'fullPath' should be set correctly"); + + // cleanup + that.deleteEntry(srcDir); + QUnit.start(); + }; + + // create a new directory entry to kick off test + this.createDirectory(srcDir, entryCallback, this.fail); + }); + test("Entry.copyTo: directory into itself", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(4); + + var srcDir = "entry.copy.dis.srcDir", + dstDir = "entry.copy.dis.dstDir", + srcPath = this.root.fullPath + '/' + srcDir, + that = this, + entryCallback = function(directory) { + // copy source directory into itself + directory.copyTo(directory, dstDir, null, testCopy); + }, + testCopy = function(error) { + ok(typeof error !== 'undefined' && error !== null, "it is an error to copy a directory into itself"); + equal(error.code, FileError.INVALID_MODIFICATION_ERR, "error code should be FileError.INVALID_MODIFICATION_ERR"); + + that.root.getDirectory(srcDir, {create:false}, testDirectoryExists, null); + }, + testDirectoryExists = function(dirEntry) { + // returning confirms existence so just check fullPath entry + ok(typeof dirEntry !== 'undefined' && dirEntry !== null, "original directory should exist."); + equals(dirEntry.fullPath, srcPath, "entry 'fullPath' should be set correctly"); + + // cleanup + that.deleteEntry(srcDir); + QUnit.start(); + }; + + // create a new directory entry to kick off test + this.createDirectory(srcDir, entryCallback, this.fail); + }); + test("Entry.copyTo: directory that does not exist", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(4); + + var file1 = "entry.copy.dnf.file1", + dstDir = "entry.copy.dnf.dstDir", + filePath = this.root.fullPath + '/' + file1, + dstPath = this.root.fullPath + '/' + dstDir, + that = this, + entryCallback = function(entry) { + // copy file to target directory that does not exist + directory = new DirectoryEntry(); + directory.fullPath = dstPath; + entry.copyTo(directory, null, null, testCopy); + }, + testCopy = function(error) { + ok(typeof error !== 'undefined' && error !== null, "it is an error to copy to a directory that does not exist"); + equal(error.code, FileError.NOT_FOUND_ERR, "error code should be FileError.NOT_FOUND_ERR"); + that.root.getFile(file1, {create: false}, testFileExists, null); + }, + testFileExists = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "original file should exist"); + equals(fileEntry.fullPath, filePath, "entry 'fullPath' should be set correctly"); + + // cleanup + that.deleteEntry(file1); + QUnit.start(); + }; + + // create a new file entry to kick off test + this.createFile(file1, entryCallback, this.fail); + }); + test("Entry.copyTo: invalid target name", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var file1 = "entry.copy.itn.file1", + file2 = "bad:file:name", + that = this, + filePath = this.root.fullPath + '/' + file1, + entryCallback = function(entry) { + // copy file1 to file2 + entry.copyTo(that.root, file2, null, testCopy); + }, + testCopy = function(error) { + ok(typeof error !== 'undefined' && error !== null, "invalid file name should result in error"); + equal(error.code, FileError.ENCODING_ERR, "error code should be FileError.ENCODING_ERR"); + + // cleanup + that.deleteEntry(file1); + QUnit.start(); + }; + + // create a new file entry + this.createFile(file1, entryCallback, this.fail); + }); + test("Entry.moveTo: file to same parent", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(9); + + var file1 = "entry.move.fsp.file1", + file2 = "entry.move.fsp.file2", + that = this, + srcPath = this.root.fullPath + '/' + file1, + dstPath = this.root.fullPath + '/' + file2, + entryCallback = function(entry) { + // move file1 to file2 + entry.moveTo(that.root, file2, testMove, that.fail); + }, + testMove = function(entry) { + ok(typeof entry !== 'undefined' && entry !== null, "file entry should not be null"); + equals(entry.isFile, true, "entry 'isFile' attribute should be set to true"); + equals(entry.isDirectory, false, "entry 'isDirectory' attribute should be set to false"); + equals(entry.fullPath, dstPath, "entry 'fullPath' should be set correctly"); + equals(entry.name, file2, "entry 'name' attribute should be set correctly"); + + that.root.getFile(file2, {create:false}, testMovedExists, null); + }, + testMovedExists = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "moved file should exist"); + equals(fileEntry.fullPath, dstPath, "entry 'fullPath' should be set correctly"); + + that.root.getFile(file1, {create:false}, null, testOrig); + }, + testOrig = function(error) { + //ok(navigator.fileMgr.testFileExists(srcPath) === false, "original file should not exist."); + ok(typeof error !== 'undefined' && error !== null, "it is an error if original file exists after a move"); + equal(error.code, FileError.NOT_FOUND_ERR, "error code should be FileError.NOT_FOUND_ERR"); + + + // cleanup + that.deleteEntry(file1); + that.deleteEntry(file2); + QUnit.start(); + }; + + // create a new file entry to kick off test + this.createFile(file1, entryCallback, this.fail); + }); + test("Entry.moveTo: file to new parent", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(9); + + var file1 = "entry.move.fnp.file1", + dir = "entry.move.fnp.dir", + that = this, + srcPath = this.root.fullPath + '/' + file1, + dstPath = this.root.fullPath + '/' + dir + '/' + file1, + entryCallback = function(entry) { + // move file1 to new directory + var moveFile = function(directory) { + + var testMove = function(entry) { + ok(typeof entry !== 'undefined' && entry !== null, "file entry should not be null"); + equals(entry.isFile, true, "entry 'isFile' attribute should be set to true"); + equals(entry.isDirectory, false, "entry 'isDirectory' attribute should be set to false"); + equals(entry.fullPath, dstPath, "entry 'fullPath' should be set correctly"); + equals(entry.name, file1, "entry 'name' attribute should be set correctly"); + // test the moved file exists + directory.getFile(file1, {create:false}, testMovedExists, null); + }; + // move the file + entry.moveTo(directory, null, testMove, that.fail); + }; + + // create a parent directory to move file to + that.root.getDirectory(dir, {create: true}, moveFile, that.fail); + }, + testMovedExists = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "moved file should exist"); + equals(fileEntry.fullPath, dstPath, "entry 'fullPath' should be set correctly"); + + that.root.getFile(file1, {create:false}, null, testOrig); + }, + testOrig = function(error) { + ok(typeof error !== 'undefined' && error !== null, "it is an error if original file exists after a move"); + equal(error.code, FileError.NOT_FOUND_ERR, "error code should be FileError.NOT_FOUND_ERR"); + + // cleanup + that.deleteEntry(file1); + that.deleteEntry(dir); + QUnit.start(); + }; + + // ensure destination directory is cleaned up before test + this.deleteEntry(dir, function() { + // create a new file entry to kick off test + that.createFile(file1, entryCallback, that.fail); + }, this.fail); + }); + test("Entry.moveTo: directory to same parent", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(9); + + var file1 = "file1", + srcDir = "entry.move.dsp.srcDir", + dstDir = "entry.move.dsp.dstDir", + srcPath = this.root.fullPath + '/' + srcDir, + dstPath = this.root.fullPath + '/' + dstDir, + filePath = dstPath + '/' + file1, + that = this, + entryCallback = function(directory) { + var moveDir = function(fileEntry) { + // move srcDir to dstDir + directory.moveTo(that.root, dstDir, testMove, that.fail); + }; + // create a file within directory + directory.getFile(file1, {create: true}, moveDir, that.fail); + }, + testMove = function(directory) { + ok(typeof directory !== 'undefined' && directory !== null, "new directory entry should not be null"); + equals(directory.isFile, false, "entry 'isFile' attribute should be false"); + equals(directory.isDirectory, true, "entry 'isDirectory' attribute should be true"); + equals(directory.fullPath, dstPath, "entry 'fullPath' should be set correctly"); + equals(directory.name, dstDir, "entry 'name' attribute should be set correctly"); + // test that moved file exists in destination dir + directory.getFile(file1, {create:false}, testMovedExists, null); + }, + testMovedExists = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "moved file should exist within moved directory"); + equals(fileEntry.fullPath, filePath, "entry 'fullPath' should be set correctly"); + // test that the moved file no longer exists in original dir + that.root.getFile(file1, {create:false}, null, testOrig); + }, + testOrig = function(error) { + ok(typeof error !== 'undefined' && error !== null, "it is an error if original file exists after a move"); + equal(error.code, FileError.NOT_FOUND_ERR, "error code should be FileError.NOT_FOUND_ERR"); + + // cleanup + that.deleteEntry(srcDir); + that.deleteEntry(dstDir); + QUnit.start(); + }; + + // ensure destination directory is cleaned up before test + this.deleteEntry(dstDir, function() { + // create a new directory entry to kick off test + that.createDirectory(srcDir, entryCallback, that.fail); + }, this.fail); + }); + test("Entry.moveTo: directory to same parent with same name", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(9); + + var file1 = "file1", + srcDir = "entry.move.dsp.srcDir", + dstDir = "entry.move.dsp.srcDir-backup", + srcPath = this.root.fullPath + '/' + srcDir, + dstPath = this.root.fullPath + '/' + dstDir, + filePath = dstPath + '/' + file1, + that = this, + entryCallback = function(directory) { + var moveDir = function(fileEntry) { + // move srcDir to dstDir + directory.moveTo(that.root, dstDir, testMove, that.fail); + }; + // create a file within directory + directory.getFile(file1, {create: true}, moveDir, that.fail); + }, + testMove = function(directory) { + ok(typeof directory !== 'undefined' && directory !== null, "new directory entry should not be null"); + equals(directory.isFile, false, "entry 'isFile' attribute should be false"); + equals(directory.isDirectory, true, "entry 'isDirectory' attribute should be true"); + equals(directory.fullPath, dstPath, "entry 'fullPath' should be set correctly"); + equals(directory.name, dstDir, "entry 'name' attribute should be set correctly"); + // test that moved file exists in destination dir + directory.getFile(file1, {create:false}, testMovedExists, null); + }, + testMovedExists = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "moved file should exist within moved directory"); + equals(fileEntry.fullPath, filePath, "entry 'fullPath' should be set correctly"); + // test that the moved file no longer exists in original dir + that.root.getFile(file1, {create:false}, null, testOrig); + }, + testOrig = function(error) { + ok(typeof error !== 'undefined' && error !== null, "it is an error if original file exists after a move"); + equal(error.code, FileError.NOT_FOUND_ERR, "error code should be FileError.NOT_FOUND_ERR"); + + // cleanup + that.deleteEntry(srcDir); + that.deleteEntry(dstDir); + QUnit.start(); + }; + + // ensure destination directory is cleaned up before test + this.deleteEntry(dstDir, function() { + // create a new directory entry to kick off test + that.createDirectory(srcDir, entryCallback, that.fail); + }, this.fail); + }); + test("Entry.moveTo: directory to new parent", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(9); + + var file1 = "file1", + srcDir = "entry.move.dnp.srcDir", + dstDir = "entry.move.dnp.dstDir", + srcPath = this.root.fullPath + '/' + srcDir, + dstPath = this.root.fullPath + '/' + dstDir, + filePath = dstPath + '/' + file1, + that = this, + entryCallback = function(directory) { + var moveDir = function(fileEntry) { + // move srcDir to dstDir + directory.moveTo(that.root, dstDir, testMove, that.fail); + }; + // create a file within directory + directory.getFile(file1, {create: true}, moveDir, that.fail); + }, + testMove = function(directory) { + ok(typeof directory !== 'undefined' && directory !== null, "new directory entry should not be null"); + equals(directory.isFile, false, "entry 'isFile' attribute should be false"); + equals(directory.isDirectory, true, "entry 'isDirectory' attribute should be true"); + equals(directory.fullPath, dstPath, "entry 'fullPath' should be set correctly"); + equals(directory.name, dstDir, "entry 'name' attribute should be set correctly"); + // test that moved file exists in destination dir + directory.getFile(file1, {create:false}, testMovedExists, null); + }, + testMovedExists = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "moved file should exist within moved directory"); + equals(fileEntry.fullPath, filePath, "entry 'fullPath' should be set correctly"); + // test that the moved file no longer exists in original dir + that.root.getFile(file1, {create:false}, null, testOrig); + }, + testOrig = function(error) { + ok(typeof error !== 'undefined' && error !== null, "it is an error if original file exists after a move"); + equal(error.code, FileError.NOT_FOUND_ERR, "error code should be FileError.NOT_FOUND_ERR"); + + // cleanup + that.deleteEntry(srcDir); + that.deleteEntry(dstDir); + QUnit.start(); + }; + + // ensure destination directory is cleaned up before test + this.deleteEntry(dstDir, function() { + // create a new directory entry to kick off test + that.createDirectory(srcDir, entryCallback, that.fail); + }, this.fail); + }); + test("Entry.moveTo: directory onto itself", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(6); + + var file1 = "file1", + srcDir = "entry.move.dos.srcDir", + srcPath = this.root.fullPath + '/' + srcDir, + filePath = srcPath + '/' + file1, + that = this, + entryCallback = function(directory) { + var moveDir = function(fileEntry) { + // move srcDir onto itself + directory.moveTo(that.root, null, null, testMove); + }; + // create a file within new directory + directory.getFile(file1, {create: true}, moveDir, that.fail); + }, + testMove = function(error) { + ok(typeof error !== 'undefined' && error !== null, "it is an error to move an entry into its parent if a different name is not specified"); + equal(error.code, FileError.INVALID_MODIFICATION_ERR, "error code should be FileError.INVALID_MODIFICATION_ERR"); + + // test that original dir still exists + that.root.getDirectory(srcDir, {create:false}, testDirectoryExists, null); + }, + testDirectoryExists = function(dirEntry) { + // returning confirms existence so just check fullPath entry + ok(typeof dirEntry !== 'undefined' && dirEntry !== null, "original directory should exist."); + equals(dirEntry.fullPath, srcPath, "entry 'fullPath' should be set correctly"); + + dirEntry.getFile(file1, {create:false}, testFileExists, null); + }, + testFileExists = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "original directory contents should exist."); + equals(fileEntry.fullPath, filePath, "entry 'fullPath' should be set correctly"); + + // cleanup + that.deleteEntry(srcDir); + QUnit.start(); + }; + + // create a new directory entry to kick off test + this.createDirectory(srcDir, entryCallback, this.fail); + }); + test("Entry.moveTo: directory into itself", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(4); + + var srcDir = "entry.move.dis.srcDir", + dstDir = "entry.move.dis.dstDir", + srcPath = this.root.fullPath + '/' + srcDir, + that = this, + entryCallback = function(directory) { + // move source directory into itself + directory.moveTo(directory, dstDir, null, testMove); + }, + testMove = function(error) { + ok(typeof error !== 'undefined' && error !== null, "it is an error to move a directory into itself"); + equal(error.code, FileError.INVALID_MODIFICATION_ERR, "error code should be FileError.INVALID_MODIFICATION_ERR"); + // make sure original directory still exists + that.root.getDirectory(srcDir, {create:false}, testDirectoryExists, null); + }, + testDirectoryExists = function(entry) { + ok(typeof entry !== 'undefined' && entry !== null, "original directory should exist."); + equals(entry.fullPath, srcPath, "entry 'fullPath' should be set correctly"); + + // cleanup + that.deleteEntry(srcDir); + QUnit.start(); + }; + + // create a new directory entry to kick off test + this.createDirectory(srcDir, entryCallback, this.fail); + }); + test("Entry.moveTo: file onto itself", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(4); + + var file1 = "entry.move.fos.file1", + filePath = this.root.fullPath + '/' + file1, + that = this, + entryCallback = function(entry) { + // move file1 onto itself + entry.moveTo(that.root, null, null, testMove); + }, + testMove = function(error) { + ok(typeof error !== 'undefined' && error !== null, "it is an error to move an entry into its parent if a different name is not specified"); + equal(error.code, FileError.INVALID_MODIFICATION_ERR, "error code should be FileError.INVALID_MODIFICATION_ERR"); + + //test that original file still exists + that.root.getFile(file1, {create:false}, testFileExists, null); + }, + testFileExists = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "original directory contents should exist."); + equals(fileEntry.fullPath, filePath, "entry 'fullPath' should be set correctly"); + + // cleanup + that.deleteEntry(file1); + QUnit.start(); + }; + + // create a new file entry to kick off test + this.createFile(file1, entryCallback, this.fail); + }); + test("Entry.moveTo: file onto existing directory", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(6); + + var file1 = "entry.move.fod.file1", + dstDir = "entry.move.fod.dstDir", + subDir = "subDir", + dirPath = this.root.fullPath + '/' + dstDir + '/' + subDir, + filePath = this.root.fullPath + '/' + file1, + that = this, + entryCallback = function(entry) { + var createSubDirectory = function(directory) { + var moveFile = function(subDirectory) { + var testMove = function(error) { + ok(typeof error !== 'undefined' && error !== null, "it is an error to move a file onto an existing directory"); + equal(error.code, FileError.INVALID_MODIFICATION_ERR, "error code should be FileError.INVALID_MODIFICATION_ERR"); + // test that original dir still exists + directory.getDirectory(subDir, {create:false}, testDirectoryExists, null); + }; + // move file1 onto sub-directory + entry.moveTo(directory, subDir, null, testMove); + }; + // create sub-directory + directory.getDirectory(subDir, {create: true}, moveFile, that.fail); + }; + // create top level directory + that.root.getDirectory(dstDir, {create: true}, createSubDirectory, that.fail); + }, + testDirectoryExists = function(dirEntry) { + ok(typeof dirEntry !== 'undefined' && dirEntry !== null, "original directory contents should exist."); + equals(dirEntry.fullPath, dirPath, "entry 'fullPath' should be set correctly"); + // test that original file still exists + that.root.getFile(file1, {create:false},testFileExists, null); + }, + testFileExists = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "original directory contents should exist."); + equals(fileEntry.fullPath, filePath, "entry 'fullPath' should be set correctly"); + + // cleanup + that.deleteEntry(file1); + that.deleteEntry(dstDir); + QUnit.start(); + }; + + // ensure destination directory is cleaned up before test + this.deleteEntry(dstDir, function() { + // create a new file entry to kick off test + that.createFile(file1, entryCallback, that.fail); + }, this.fail); + }); + test("Entry.moveTo: directory onto existing file", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(6); + + var file1 = "entry.move.dof.file1", + srcDir = "entry.move.dof.srcDir", + dirPath = this.root.fullPath + '/' + srcDir, + filePath = this.root.fullPath + '/' + file1, + that = this, + entryCallback = function(entry) { + var moveDir = function(fileEntry) { + // move directory onto file + entry.moveTo(that.root, file1, null, testMove); + }; + // create file + that.root.getFile(file1, {create: true}, moveDir, that.fail); + }, + testMove = function(error) { + ok(typeof error !== 'undefined' && error !== null, "it is an error to move a directory onto an existing file"); + equal(error.code, FileError.INVALID_MODIFICATION_ERR, "error code should be FileError.INVALID_MODIFICATION_ERR"); + // test that original directory exists + that.root.getDirectory(srcDir, {create:false}, testDirectoryExists, null); + }, + testDirectoryExists = function(dirEntry) { + // returning confirms existence so just check fullPath entry + ok(typeof dirEntry !== 'undefined' && dirEntry !== null, "original directory should exist."); + equals(dirEntry.fullPath, dirPath, "entry 'fullPath' should be set correctly"); + // test that original file exists + that.root.getFile(file1, {create:false}, testFileExists, null); + }, + testFileExists = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "original directory contents should exist."); + equals(fileEntry.fullPath, filePath, "entry 'fullPath' should be set correctly"); + + + // cleanup + that.deleteEntry(file1); + that.deleteEntry(srcDir); + QUnit.start(); + }; + + // create a new directory entry to kick off test + this.createDirectory(srcDir, entryCallback, this.fail); + }); + test("Entry.copyTo: directory onto existing file", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(6); + + var file1 = "entry.copy.dof.file1", + srcDir = "entry.copy.dof.srcDir", + dirPath = this.root.fullPath + '/' + srcDir, + filePath = this.root.fullPath + '/' + file1, + that = this, + entryCallback = function(entry) { + var copyDir = function(fileEntry) { + // move directory onto file + entry.copyTo(that.root, file1, null, testMove); + }; + // create file + that.root.getFile(file1, {create: true}, copyDir, that.fail); + }, + testMove = function(error) { + ok(typeof error !== 'undefined' && error !== null, "it is an error to copy a directory onto an existing file"); + equal(error.code, FileError.INVALID_MODIFICATION_ERR, "error code should be FileError.INVALID_MODIFICATION_ERR"); + //test that original dir still exists + that.root.getDirectory(srcDir, {create:false}, testDirectoryExists, null); + }, + testDirectoryExists = function(dirEntry) { + // returning confirms existence so just check fullPath entry + ok(typeof dirEntry !== 'undefined' && dirEntry !== null, "original directory should exist."); + equals(dirEntry.fullPath, dirPath, "entry 'fullPath' should be set correctly"); + // test that original file still exists + that.root.getFile(file1, {create:false}, testFileExists, null); + }, + testFileExists = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "original directory contents should exist."); + equals(fileEntry.fullPath, filePath, "entry 'fullPath' should be set correctly"); + + // cleanup + that.deleteEntry(file1); + that.deleteEntry(srcDir); + QUnit.start(); + }; + + // create a new directory entry to kick off test + this.createDirectory(srcDir, entryCallback, this.fail); + }); + test("Entry.moveTo: directory onto directory that is not empty", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(6); + + var srcDir = "entry.move.dod.srcDir", + dstDir = "entry.move.dod.dstDir", + subDir = "subDir", + srcPath = this.root.fullPath + '/' + srcDir, + dstPath = this.root.fullPath + '/' + dstDir + '/' + subDir, + that = this, + entryCallback = function(entry) { + var createSubDirectory = function(directory) { + var moveDir = function(subDirectory) { + // move srcDir onto dstDir (not empty) + entry.moveTo(that.root, dstDir, null, testMove); + }; + var testMove = function(error) { + ok(typeof error !== 'undefined' && error !== null, "it is an error to move a directory onto a directory that is not empty"); + equal(error.code, FileError.INVALID_MODIFICATION_ERR, "error code should be FileError.INVALID_MODIFICATION_ERR"); + + // test that destination directory still exists + directory.getDirectory(subDir, {create:false}, testDirectoryExists, null); + }; + // create sub-directory + directory.getDirectory(subDir, {create: true}, moveDir, that.fail); + }; + // create top level directory + that.root.getDirectory(dstDir, {create: true}, createSubDirectory, that.fail); + }, + testDirectoryExists = function(dirEntry) { + // returning confirms existence so just check fullPath entry + ok(typeof dirEntry !== 'undefined' && dirEntry !== null, "original directory should exist."); + equals(dirEntry.fullPath, dstPath, "entry 'fullPath' should be set correctly"); + // test that source directory exists + that.root.getDirectory(srcDir,{create:false}, testSrcDirectoryExists, null); + }, + testSrcDirectoryExists = function(srcEntry){ + ok(typeof srcEntry !== 'undefined' && srcEntry !== null, "original directory should exist."); + equals(srcEntry.fullPath, srcPath, "entry 'fullPath' should be set correctly"); + // cleanup + that.deleteEntry(srcDir); + that.deleteEntry(dstDir); + QUnit.start(); + }; + + // ensure destination directory is cleaned up before test + this.deleteEntry(dstDir, function() { + // create a new file entry to kick off test + that.createDirectory(srcDir, entryCallback, that.fail); + }, this.fail); + }); + test("Entry.moveTo: file replace existing file", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(9); + + var file1 = "entry.move.frf.file1", + file2 = "entry.move.frf.file2", + file1Path = this.root.fullPath + '/' + file1, + file2Path = this.root.fullPath + '/' + file2, + that = this, + entryCallback = function(entry) { + var moveFile = function(fileEntry) { + // replace file2 with file1 + entry.moveTo(that.root, file2, testMove, that.fail); + }; + // create file + that.root.getFile(file2, {create: true}, moveFile, that.fail); + }, + testMove = function(entry) { + ok(typeof entry !== 'undefined' && entry !== null, "file entry should not be null") + equals(entry.isFile, true, "entry 'isFile' attribute should be true"); + equals(entry.isDirectory, false, "entry 'isDirectory' attribute should be false"); + equals(entry.fullPath, file2Path, "entry 'fullPath' should be set correctly"); + equals(entry.name, file2, "entry 'name' attribute should be set correctly"); + + // test that old file does not exists + that.root.getFile(file1, {create:false}, null, testFileMoved); + }, + testFileMoved = function(error){ + ok(typeof error !== 'undefined' && error !== null, "it is an error if original file exists after a move"); + equal(error.code, FileError.NOT_FOUND_ERR, "error code should be FileError.NOT_FOUND_ERR"); + // test that new file exists + that.root.getFile(file2, {create:false}, testFileExists, null); + }, + testFileExists = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "original directory contents should exist."); + equals(fileEntry.fullPath, file2Path, "entry 'fullPath' should be set correctly"); + + // cleanup + that.deleteEntry(file1); + that.deleteEntry(file2); + QUnit.start(); + }; + + // create a new directory entry to kick off test + this.createFile(file1, entryCallback, this.fail); + }); + test("Entry.moveTo: directory replace empty directory", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(9); + + var file1 = "file1", + srcDir = "entry.move.drd.srcDir", + dstDir = "entry.move.drd.dstDir", + srcPath = this.root.fullPath + '/' + srcDir, + dstPath = this.root.fullPath + '/' + dstDir, + filePath = dstPath + '/' + file1, + that = this, + entryCallback = function(directory) { + var mkdir = function(fileEntry) { + // create destination directory + that.root.getDirectory(dstDir, {create: true}, moveDir, that.fail); + }; + var moveDir = function(fileEntry) { + // move srcDir to dstDir + directory.moveTo(that.root, dstDir, testMove, that.fail); + }; + // create a file within source directory + directory.getFile(file1, {create: true}, mkdir, that.fail); + }, + testMove = function(directory) { + ok(typeof directory !== 'undefined' && directory !== null, "new directory entry should not be null"); + equals(directory.isFile, false, "entry 'isFile' attribute should be false"); + equals(directory.isDirectory, true, "entry 'isDirectory' attribute should be true"); + equals(directory.fullPath, dstPath, "entry 'fullPath' should be set correctly"); + equals(directory.name, dstDir, "entry 'name' attribute should be set correctly"); + // test that old directory contents have been moved + directory.getFile(file1, {create:false}, testFileExists, null); + }, + testFileExists = function(fileEntry) { + ok(typeof fileEntry !== 'undefined' && fileEntry !== null, "original directory contents should exist."); + equals(fileEntry.fullPath, filePath, "entry 'fullPath' should be set correctly"); + + // test that old directory no longer exists + that.root.getDirectory(srcDir, {create:false}, null, testRemoved); + }, + testRemoved = function(error){ + ok(typeof error !== 'undefined' && error !== null, "it is an error if original file exists after a move"); + equal(error.code, FileError.NOT_FOUND_ERR, "error code should be FileError.NOT_FOUND_ERR"); + + // cleanup + that.deleteEntry(srcDir); + that.deleteEntry(dstDir); + QUnit.start(); + }; + + // ensure destination directory is cleaned up before test + this.deleteEntry(dstDir, function() { + // create a new directory entry to kick off test + that.createDirectory(srcDir, entryCallback, that.fail); + }, this.fail); + }); + test("Entry.moveTo: directory that does not exist", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var file1 = "entry.move.dnf.file1", + dstDir = "entry.move.dnf.dstDir", + filePath = this.root.fullPath + '/' + file1, + dstPath = this.root.fullPath + '/' + dstDir, + that = this, + entryCallback = function(entry) { + // move file to directory that does not exist + directory = new DirectoryEntry(); + directory.fullPath = dstPath; + entry.moveTo(directory, null, null, testMove); + }, + testMove = function(error) { + ok(typeof error !== 'undefined' && error !== null, "it is an error to move to a directory that does not exist"); + equal(error.code, FileError.NOT_FOUND_ERR, "error code should be FileError.NOT_FOUND_ERR"); + + // cleanup + that.deleteEntry(file1); + QUnit.start(); + }; + + // create a new file entry to kick off test + this.createFile(file1, entryCallback, this.fail); + }); + test("Entry.moveTo: invalid target name", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var file1 = "entry.move.itn.file1", + file2 = "bad:file:name", + that = this, + filePath = this.root.fullPath + '/' + file1, + entryCallback = function(entry) { + // move file1 to file2 + entry.moveTo(that.root, file2, null, testMove); + }, + testMove = function(error) { + ok(typeof error !== 'undefined' && error !== null, "invalid file name should result in error"); + equal(error.code, FileError.ENCODING_ERR, "error code should be FileError.ENCODING_ERR"); + + // cleanup + that.deleteEntry(file1); + QUnit.start(); + }; + + // create a new file entry to kick off test + this.createFile(file1, entryCallback, this.fail); + }); + module('FileReader model'); + test("FileReader object should have correct methods", function() { + expect(6); + var reader = new FileReader(); + ok(reader !== null, "new FileReader() should not be null."); + ok(typeof reader.readAsBinaryString === 'function', "FileReader object should have a readAsBinaryString function."); + ok(typeof reader.readAsDataURL === 'function', "FileReader object should have a readAsDataURL function."); + ok(typeof reader.readAsText === 'function', "FileReader object should have a readAsText function."); + ok(typeof reader.readAsArrayBuffer === 'function', "FileReader object should have a readAsArrayBuffer function."); + ok(typeof reader.abort === 'function', "FileReader object should have an abort function."); + }); + module('FileReader read', { + setup: function() { + this.root = getFileSystemRoot(); + this.fail = function(error) { + console.log('file error: ' + error.code); + }; + } + }); + test("should read file properly, File object", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(1); + + // path of file + var fileName = "reader.txt", + filePath = this.root.fullPath + '/' + fileName; + // file content + rule = "There is an exception to every rule. Except this one.", + // creates a FileWriter object + create_writer = function(fileEntry) { + fileEntry.createWriter(write_file, this.fail); + }, + // writes file and reads it back in + write_file = function(writer) { + writer.onwriteend = read_file; + writer.write(rule); + }, + // reads file and compares content to what was written + read_file = function(evt) { + var reader = new FileReader(); + reader.onloadend = function(evt) { + console.log("read success"); + console.log(evt.target.result); + ok(evt.target.result === rule, "reader.result should be equal to the text written."); + QUnit.start(); + }; + var myFile = new File(); + myFile.fullPath = filePath; + reader.readAsText(myFile); + }; + + // create a file, write to it, and read it in again + this.root.getFile(fileName, {create: true}, create_writer, this.fail); + }); + test("should read empty file properly", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(1); + + // path of file + var fileName = "empty.txt", + filePath = this.root.fullPath + '/' + fileName; + // file content + rule = "", + // reads file and compares content to what was written + read_file = function(evt) { + var reader = new FileReader(); + reader.onloadend = function(evt) { + console.log("read success"); + console.log(evt.target.result); + ok(evt.target.result === rule, "reader.result should be equal to the empty string."); + QUnit.start(); + }; + var myFile = new File(); + myFile.fullPath = filePath; + reader.readAsText(myFile); + }; + + // create a file, write to it, and read it in again + this.root.getFile(fileName, {create: true}, read_file, this.fail); + }); + test("should error out on non-existent file", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(1); + + var reader = new FileReader(); + reader.onerror = function(evt) { + console.log("Properly got a file error as no file exists."); + ok(evt.target.error.code === 1, "Should throw a NOT_FOUND_ERR."); + QUnit.start(); + } + var myFile = new File(); + myFile.fullPath = this.root.fullPath + '/' + "doesnotexist.err"; + reader.readAsText(myFile); + }); + test("should read file properly, Data URL", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(1); + + // path of file + var fileName = "reader.txt", + filePath = this.root.fullPath + '/' + fileName; + // file content + rule = "There is an exception to every rule. Except this one.", + // creates a FileWriter object + create_writer = function(fileEntry) { + fileEntry.createWriter(write_file, this.fail); + }, + // writes file and reads it back in + write_file = function(writer) { + writer.onwriteend = read_file; + writer.write(rule); + }, + // reads file and compares content to what was written + read_file = function(evt) { + var reader = new FileReader(); + reader.onloadend = function(evt) { + console.log("read success"); + console.log(evt.target.result); + ok(evt.target.result.substr(0,23) === "data:text/plain;base64,", "reader.result should be base64 encoded."); + QUnit.start(); + }; + var myFile = new File(); + myFile.fullPath = filePath; + reader.readAsDataURL(myFile); + }; + + // create a file, write to it, and read it in again + this.root.getFile(fileName, {create: true}, create_writer, this.fail); + }); + module('FileWriter model', { + // setup function will run before each test + setup: function() { + var that = this; + this.root = getFileSystemRoot(); + this.fail = function(error) { + console.log('file error: ' + error.code); + }; + // deletes file, if it exists, then invokes callback + this.deleteFile = function(fileName, callback) { + that.root.getFile(fileName, null, + // remove file system entry + function(entry) { + entry.remove(callback, that.fail); + }, + // doesn't exist + callback); + }; + // deletes and re-creates the specified file, then invokes callback + this.createFile = function(fileName, callback) { + // creates file + var create_file = function() { + that.root.getFile(fileName, {create: true}, callback, that.fail); + }; + + // deletes file, then re-creates it + that.deleteFile(fileName, create_file); + }; + } + }); + test("FileWriter object should have correct methods", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(5); + + // retrieve a FileWriter object + var fileName = "writer.methods", + that = this, + test_writer = function(fileEntry) { + fileEntry.createWriter(function(writer) { + ok(typeof writer !== 'undefined' && writer !== null, "FileEntry.createWriter should return a FileWriter object."); + ok(typeof writer.write === 'function', "FileWriter object should have a write function."); + ok(typeof writer.seek === 'function', "FileWriter object should have a seek function."); + ok(typeof writer.truncate === 'function', "FileWriter object should have a truncate function."); + ok(typeof writer.abort === 'function', "FileWriter object should have an abort function."); + + // cleanup + that.deleteFile(fileName); + QUnit.start(); + }, this.fail); + }; + + // test FileWriter + this.root.getFile(fileName, {create: true}, test_writer, this.fail); + }); + test("should be able to write and append to file, createWriter", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(4); + + var that = this, + fileName = "writer.append", + filePath = this.root.fullPath + '/' + fileName, + // file content + rule = "There is an exception to every rule.", + // for testing file length + length = rule.length, + // writes initial file content + write_file = function(fileEntry) { + fileEntry.createWriter(function(writer) { + writer.onwriteend = function(evt) { + ok(writer.length === length, "should have written " + length + " bytes"); + ok(writer.position === length, "position should be at " + length); + append_file(writer); + }; + writer.write(rule); + }, that.fail); + }, + // appends to file + append_file = function(writer) { + var exception = " Except this one."; + writer.onwriteend = function(evt) { + ok(writer.length === length, "file length should be " + length); + ok(writer.position === length, "position should be at " + length); + + // cleanup + that.deleteFile(fileName); + QUnit.start(); + }; + length += exception.length; + writer.seek(writer.length); + writer.write(exception); + }; + + // create file, then write and append to it + this.createFile(fileName, write_file); + }); + test("should be able to write and append to file, File object", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(4); + + var that = this, + fileName = "writer.append", + filePath = this.root.fullPath + '/' + fileName, + // file content + rule = "There is an exception to every rule.", + // for testing file length + length = rule.length, + // writes initial file content + write_file = function(file) { + var writer = new FileWriter(file); + writer.onwriteend = function(evt) { + ok(writer.length === length, "should have written " + length + " bytes"); + ok(writer.position === length, "position should be at " + length); + append_file(writer); + }; + writer.write(rule); + }, + // appends to file + append_file = function(writer) { + var exception = " Except this one."; + writer.onwriteend = function(evt) { + ok(writer.length === length, "file length should be " + length); + ok(writer.position === length, "position should be at " + length); + + // cleanup + that.deleteFile(fileName); + QUnit.start(); + }; + length += exception.length; + writer.seek(writer.length); + writer.write(exception); + }; + + // create file, then write and append to it + var file = new File(); + file.fullPath = filePath; + write_file(file); + }); + test("should be able to seek to the middle of the file and write more data than file.length", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(4); + + var that = this, + fileName = "writer.seek.write", + filePath = this.root.fullPath + '/' + fileName, + // file content + rule = "This is our sentence.", + // for testing file length + length = rule.length, + // writes initial file content + write_file = function(fileEntry) { + fileEntry.createWriter(function(writer) { + writer.onwriteend = function(evt) { + ok(writer.length === length, "should have written " + length + " bytes"); + ok(writer.position === length, "position should be at " + length); + append_file(writer); + }; + writer.write(rule); + }, that.fail); + }, + // appends to file + append_file = function(writer) { + var exception = "newer sentence."; + writer.onwriteend = function(evt) { + ok(writer.length === length, "file length should be " + length); + ok(writer.position === length, "position should be at " + length); + + // cleanup + that.deleteFile(fileName); + QUnit.start(); + }; + length = 12 + exception.length; + writer.seek(12); + writer.write(exception); + }; + + // create file, then write and append to it + this.createFile(fileName, write_file); + }); + test("should be able to seek to the middle of the file and write less data than file.length", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(4); + + var that = this, + fileName = "writer.seek.write2", + filePath = this.root.fullPath + '/' + fileName, + // file content + rule = "This is our sentence.", + // for testing file length + length = rule.length, + // writes initial file content + write_file = function(fileEntry) { + fileEntry.createWriter(function(writer) { + writer.onwriteend = function(evt) { + ok(writer.length === length, "should have written " + length + " bytes"); + ok(writer.position === length, "position should be at " + length); + append_file(writer); + }; + writer.write(rule); + }, that.fail); + }, + // appends to file + append_file = function(writer) { + var exception = "new."; + writer.onwriteend = function(evt) { + ok(writer.length === length, "file length should be " + length); + ok(writer.position === length, "position should be at " + length); + + // cleanup + that.deleteFile(fileName); + QUnit.start(); + }; + length = 8 + exception.length; + writer.seek(8); + writer.write(exception); + }; + + // create file, then write and append to it + this.createFile(fileName, write_file); + }); + test("should be able to write XML data", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var that = this, + fileName = "writer.xml", + filePath = this.root.fullPath + '/' + fileName, + // file content + rule = '\n\nData\n\n', + // for testing file length + length = rule.length, + // writes file content + write_file = function(fileEntry) { + fileEntry.createWriter(function(writer) { + writer.onwriteend = function(evt) { + ok(writer.length === length, "should have written " + length + " bytes"); + ok(writer.position === length, "position should be at " + length); + + // cleanup + that.deleteFile(fileName); + QUnit.start(); + }; + writer.write(rule); + }, that.fail); + }; + + // creates file, then write XML data + this.createFile(fileName, write_file); + }); + test("should be able to write JSON data", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var that = this, + fileName = "writer.json", + filePath = this.root.fullPath + '/' + fileName, + // file content + rule = '{ "name": "Guy Incognito", "email": "here@there.com" }', + // for testing file length + length = rule.length, + // writes file content + write_file = function(fileEntry) { + fileEntry.createWriter(function(writer) { + writer.onwriteend = function(evt) { + ok(writer.length === length, "should have written " + length + " bytes"); + ok(writer.position === length, "position should be at " + length); + + // cleanup + that.deleteFile(fileName); + QUnit.start(); + }; + writer.write(rule); + }, that.fail); + }; + + // creates file, then write JSON content + this.createFile(fileName, write_file); + }); + test("should write and read special characters", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(1); + + var that = this, + // path of file + fileName = "reader.txt", + filePath = this.root.fullPath + '/' + fileName, + // file content + rule = "H\u00EBll\u00F5 Euro \u20AC\u00A1", + // creates a FileWriter object + create_writer = function(fileEntry) { + fileEntry.createWriter(write_file, this.fail); + }, + // writes file and reads it back in + write_file = function(writer) { + writer.onwriteend = read_file; + writer.write(rule); + }, + // reads file and compares content to what was written + read_file = function(evt) { + var reader = new FileReader(); + reader.onloadend = function(evt) { + console.log("read success"); + console.log(evt.target.result); + ok(evt.target.result === rule, "reader.result should be equal to the text written."); + // cleanup + that.deleteFile(fileName); + QUnit.start(); + }; + var myFile = new File(); + myFile.fullPath = filePath; + reader.readAsText(myFile); + }; + + // create a file, write to it, and read it in again + this.createFile(fileName, create_writer, this.fail); + }); + test("should be able to seek", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(5); + + var that = this, + fileName = "writer.seek", + // file content + rule = "There is an exception to every rule. Except this one.", + // for testing file length + length = rule.length, + // writes file content and tests writer.seek + seek_file = function(fileEntry) { + fileEntry.createWriter(function(writer) { + writer.onwriteend = function(evt) { + ok(writer.position == length, "position should be at " + length); + writer.seek(-5); + ok(writer.position == (length-5), "position should be at " + (length-5)); + writer.seek(100); + ok(writer.position == length, "position should be at " + length); + writer.seek(10); + ok(writer.position == 10, "position should be at 10"); + + // cleanup + that.deleteFile(fileName); + QUnit.start(); + }; + writer.seek(-100); + ok(writer.position == 0, "position should be at 0"); + writer.write(rule); + }, that.fail); + }; + + // creates file, then write JSON content + this.createFile(fileName, seek_file); + }); + test("should be able to truncate", function() { + QUnit.stop(Tests.TEST_TIMEOUT); + expect(2); + + var that = this, + fileName = "writer.truncate", + rule = "There is an exception to every rule. Except this one.", + // writes file content + write_file = function(fileEntry) { + fileEntry.createWriter(function(writer) { + writer.onwriteend = function(evt) { + truncate_file(writer); + }; + writer.write(rule); + }, that.fail); + }, + // and tests writer.truncate + truncate_file = function(writer) { + writer.onwriteend = function(evt) { + ok(writer.length == 36, "file length should be 36"); + ok(writer.position == 36, "position should be at 36"); + + // cleanup + that.deleteFile(fileName); + QUnit.start(); + }; + writer.truncate(36); + }; + + // creates file, writes to it, then truncates it + this.createFile(fileName, write_file); + }); +}; diff --git a/test/assets/www/autotest/tests/geolocation.tests.js b/test/assets/www/autotest/tests/geolocation.tests.js new file mode 100644 index 00000000..17f37ce4 --- /dev/null +++ b/test/assets/www/autotest/tests/geolocation.tests.js @@ -0,0 +1,57 @@ +Tests.prototype.GeoLocationTests = function() { + module('Geolocation (navigator.geolocation)'); + test("should exist", function() { + expect(1); + ok(navigator.geolocation != null, "navigator.geolocation should not be null."); + }); + test("should contain a getCurrentPosition function", function() { + expect(2); + ok(typeof navigator.geolocation.getCurrentPosition != 'undefined' && navigator.geolocation.getCurrentPosition != null, "navigator.geolocation.getCurrentPosition should not be null."); + ok(typeof navigator.geolocation.getCurrentPosition == 'function', "navigator.geolocation.getCurrentPosition should be a function."); + }); + test("should contain a watchPosition function", function() { + expect(2); + ok(typeof navigator.geolocation.watchPosition != 'undefined' && navigator.geolocation.watchPosition != null, "navigator.geolocation.watchPosition should not be null."); + ok(typeof navigator.geolocation.watchPosition == 'function', "navigator.geolocation.watchPosition should be a function."); + }); + test("should contain a clearWatch function", function() { + expect(2); + ok(typeof navigator.geolocation.clearWatch != 'undefined' && navigator.geolocation.clearWatch != null, "navigator.geolocation.watchPosition should not be null."); + ok(typeof navigator.geolocation.clearWatch == 'function', "navigator.geolocation.clearWatch should be a function."); + }); + test("getCurrentPosition success callback should be called with a Position object", function() { + expect(2); + QUnit.stop(Tests.TEST_TIMEOUT); + var win = function(p) { + ok(p.coords != null, "Position object returned in getCurrentPosition success callback has a 'coords' property."); + ok(p.timestamp != null, "Position object returned in getCurrentPosition success callback has a 'timestamp' property."); + start(); + }; + var fail = function() { start(); }; + navigator.geolocation.getCurrentPosition(win, fail); + }); + // TODO: Need to test error callback... how? + // TODO: Need to test watchPosition success callback, test that makes sure clearPosition works (how to test that a timer is getting cleared?) + // TODO: Need to test options object passed in. Members that need to be tested so far include: + // - options.frequency: this is also labelled differently on some implementations (internval on iPhone/BlackBerry currently). + module('Geolocation model'); + test("should be able to define a Position object with coords and timestamp properties", function() { + expect(3); + var pos = new Position({}, new Date()); + ok(pos != null, "new Position() should not be null."); + ok(typeof pos.coords != 'undefined' && pos.coords != null, "new Position() should include a 'coords' property."); + ok(typeof pos.timestamp != 'undefined' && pos.timestamp != null, "new Position() should include a 'timestamp' property."); + }); + test("should be able to define a Coordinates object with latitude, longitude, accuracy, altitude, heading, speed and altitudeAccuracy properties", function() { + expect(8); + var coords = new Coordinates(1,2,3,4,5,6,7); + ok(coords != null, "new Coordinates() should not be null."); + ok(typeof coords.latitude != 'undefined' && coords.latitude != null, "new Coordinates() should include a 'latitude' property."); + ok(typeof coords.longitude != 'undefined' && coords.longitude != null, "new Coordinates() should include a 'longitude' property."); + ok(typeof coords.accuracy != 'undefined' && coords.accuracy != null, "new Coordinates() should include a 'accuracy' property."); + ok(typeof coords.altitude != 'undefined' && coords.altitude != null, "new Coordinates() should include a 'altitude' property."); + ok(typeof coords.heading != 'undefined' && coords.heading != null, "new Coordinates() should include a 'heading' property."); + ok(typeof coords.speed != 'undefined' && coords.speed != null, "new Coordinates() should include a 'speed' property."); + ok(typeof coords.altitudeAccuracy != 'undefined' && coords.altitudeAccuracy != null, "new Coordinates() should include a 'altitudeAccuracy' property."); + }); +}; \ No newline at end of file diff --git a/test/assets/www/autotest/tests/media.tests.js b/test/assets/www/autotest/tests/media.tests.js new file mode 100644 index 00000000..43f9b1c3 --- /dev/null +++ b/test/assets/www/autotest/tests/media.tests.js @@ -0,0 +1,31 @@ +// +// @TODO Update to Latest HTML5 Audio Element Spec +// @see http://www.whatwg.org/specs/web-apps/current-work/multipage/video.html#audio +// +Tests.prototype.MediaTests = function() { + module('Media (Audio)'); + test("should exist", function() { + expect(1); + ok(typeof Audio === "function" || typeof Audio === "object", "'Audio' should be defined as a function in global scope."); + }); + test("should define constants for Media errors", function() { + expect(5); + ok(MediaError != null && typeof MediaError != 'undefined', "MediaError object exists in global scope."); + equals(MediaError.MEDIA_ERR_ABORTED, 1, "MediaError.MEDIA_ERR_ABORTED is equal to 1."); + equals(MediaError.MEDIA_ERR_NETWORK, 2, "MediaError.MEDIA_ERR_NETWORK is equal to 2."); + equals(MediaError.MEDIA_ERR_DECODE, 3, "MediaError.MEDIA_ERR_DECODE is equal to 3."); + equals(MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED, 4, "MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED is equal to 4."); + }); + test("should contain 'src', 'loop' and 'error' properties", function() { + expect(7); + var audioSrc = '/test.mp3'; + var audio = new Audio(audioSrc); + ok(typeof audio == "object", "Instantiated 'Audio' object instance should be of type 'object.'"); + ok(audio.src != null && typeof audio.src != 'undefined', "Instantiated 'Audio' object's 'src' property should not be null or undefined."); + ok(audio.src.indexOf(audioSrc) >= 0, "Instantiated 'Audio' object's 'src' property should match constructor parameter."); + ok(audio.loop != null && typeof audio.loop != 'undefined', "Instantiated 'Audio' object's 'loop' property should not be null or undefined."); + ok(audio.loop == false, "Instantiated 'Audio' object's 'loop' property should initially be false."); + ok(typeof audio.error != 'undefined', "Instantiated 'Audio' object's 'error' property should not undefined."); + ok(audio.error == null, "Instantiated 'Audio' object's 'error' should initially be null."); + }); +}; \ No newline at end of file diff --git a/test/assets/www/autotest/tests/network.tests.js b/test/assets/www/autotest/tests/network.tests.js new file mode 100644 index 00000000..af87d731 --- /dev/null +++ b/test/assets/www/autotest/tests/network.tests.js @@ -0,0 +1,26 @@ +Tests.prototype.NetworkTests = function() { + module('Network (navigator.network)'); + test("should exist", function() { + expect(1); + ok(navigator.network != null, "navigator.network should not be null."); + }); + module('Network Information API'); + test("connection should exist", function() { + expect(1); + ok(navigator.network.connection != null, "navigator.network.connection should not be null."); + }); + test("should contain connection properties", function() { + expect(1); + ok(typeof navigator.network.connection.type != 'undefined', "navigator.network.connection.type is defined."); + }); + test("should define constants for connection status", function() { + expect(7); + equals(Connection.UNKNOWN, "unknown", "Connection.UNKNOWN is equal to 'unknown'."); + equals(Connection.ETHERNET, "ethernet", "Connection.ETHERNET is equal to 'ethernet'."); + equals(Connection.WIFI, "wifi", "Connection.WIFI is equal to 'wifi'."); + equals(Connection.CELL_2G, "2g", "Connection.CELL_2G is equal to '2g'."); + equals(Connection.CELL_3G, "3g", "Connection.CELL_3G is equal to '3g'."); + equals(Connection.CELL_4G, "4g", "Connection.CELL_4G is equal to '4g'."); + equals(Connection.NONE, "none", "Connection.NONE is equal to 'none'."); + }); +}; \ No newline at end of file diff --git a/test/assets/www/autotest/tests/notification.tests.js b/test/assets/www/autotest/tests/notification.tests.js new file mode 100644 index 00000000..03e3885e --- /dev/null +++ b/test/assets/www/autotest/tests/notification.tests.js @@ -0,0 +1,22 @@ +Tests.prototype.NotificationTests = function() { + module('Notification (navigator.notification)'); + test("should exist", function() { + expect(1); + ok(navigator.notification != null, "navigator.notification should not be null."); + }); + test("should contain a vibrate function", function() { + expect(2); + ok(typeof navigator.notification.vibrate != 'undefined' && navigator.notification.vibrate != null, "navigator.notification.vibrate should not be null."); + ok(typeof navigator.notification.vibrate == 'function', "navigator.notification.vibrate should be a function."); + }); + test("should contain a beep function", function() { + expect(2); + ok(typeof navigator.notification.beep != 'undefined' && navigator.notification.beep != null, "navigator.notification.beep should not be null."); + ok(typeof navigator.notification.beep == 'function', "navigator.notification.beep should be a function."); + }); + test("should contain a alert function", function() { + expect(2); + ok(typeof navigator.notification.alert != 'undefined' && navigator.notification.alert != null, "navigator.notification.alert should not be null."); + ok(typeof navigator.notification.alert == 'function', "navigator.notification.alert should be a function."); + }); +}; \ No newline at end of file diff --git a/test/assets/www/autotest/tests/orientation.tests.js b/test/assets/www/autotest/tests/orientation.tests.js new file mode 100644 index 00000000..5dcfcf1e --- /dev/null +++ b/test/assets/www/autotest/tests/orientation.tests.js @@ -0,0 +1,34 @@ +Tests.prototype.OrientationTests = function() { + module('Orientation (navigator.orientation)'); + test("should exist", function() { + expect(1); + ok(navigator.orientation != null, "navigator.orientation should not be null."); + }); + test("should have an initially null lastPosition property", function() { + expect(1); + ok(typeof navigator.orientation.currentOrientation != 'undefined' && navigator.orientation.currentOrientation == null, "navigator.orientation.currentOrientation should be initially null."); + }); + test("should contain a getCurrentOrientation function", function() { + expect(2); + ok(typeof navigator.orientation.getCurrentOrientation != 'undefined' && navigator.orientation.getCurrentOrientation != null, "navigator.orientation.getCurrentOrientation should not be null."); + ok(typeof navigator.orientation.getCurrentOrientation == 'function', "navigator.orientation.getCurrentOrientation should be a function."); + }); + test("should contain a watchOrientation function", function() { + expect(2); + ok(typeof navigator.orientation.watchOrientation != 'undefined' && navigator.orientation.watchOrientation != null, "navigator.orientation.watchOrientation should not be null."); + ok(typeof navigator.orientation.watchOrientation == 'function', "navigator.orientation.watchOrientation should be a function."); + }); + // TODO: add tests for DisplayOrientation constants? + test("getCurrentOrientation success callback should be called with an Orientation enumeration", function() { + expect(2); + QUnit.stop(Tests.TEST_TIMEOUT); + var win = function(orient) { + ok(0 <= orient <= 6, "Position object returned in getCurrentPosition success callback is a valid DisplayOrientation value."); + equals(orient, navigator.orientation.currentOrientation, "Orientation value returned in getCurrentOrientation success callback equals navigator.orientation.currentOrientation."); + start(); + }; + var fail = function() { start(); }; + navigator.orientation.getCurrentOrientation(win, fail); + }); + // TODO: Need to test watchPosition success callback, test that makes sure clearPosition works (how to test that a timer is getting cleared?) +}; \ No newline at end of file diff --git a/test/assets/www/autotest/tests/storage.tests.js b/test/assets/www/autotest/tests/storage.tests.js new file mode 100644 index 00000000..41476d84 --- /dev/null +++ b/test/assets/www/autotest/tests/storage.tests.js @@ -0,0 +1,170 @@ +Tests.prototype.StorageTests = function() +{ + module("Session Storage"); + test("should exist", function() { + expect(7); + ok(window.sessionStorage != null, "sessionStorage is defined"); + ok(typeof window.sessionStorage.length != 'undefined', "sessionStorage.length is defined"); + ok(typeof(window.sessionStorage.key) == "function", "sessionStorage.key is defined"); + ok(typeof(window.sessionStorage.getItem) == "function", "sessionStorage.getItem is defined"); + ok(typeof(window.sessionStorage.setItem) == "function", "sessionStorage.setItem is defined"); + ok(typeof(window.sessionStorage.removeItem) == "function", "sessionStorage.removeItem is defined"); + ok(typeof(window.sessionStorage.clear) == "function", "sessionStorage.clear is defined"); + }); + test("check length", function() { + expect(3); + ok(window.sessionStorage.length == 0, "length should be 0"); + window.sessionStorage.setItem("key","value"); + ok(window.sessionStorage.length == 1, "length should be 1"); + window.sessionStorage.removeItem("key"); + ok(window.sessionStorage.length == 0, "length should be 0"); + }); + test("check key", function() { + expect(3); + ok(window.sessionStorage.key(0) == null, "key should be null"); + window.sessionStorage.setItem("test","value"); + ok(window.sessionStorage.key(0) == "test", "key should be 'test'"); + window.sessionStorage.removeItem("test"); + ok(window.sessionStorage.key(0) == null, "key should be null"); + }); + test("check getItem", function() { + expect(3); + ok(window.sessionStorage.getItem("item") == null, "item should be null"); + window.sessionStorage.setItem("item","value"); + ok(window.sessionStorage.getItem("item") == "value", "The value of the item should be 'value'"); + window.sessionStorage.removeItem("item"); + ok(window.sessionStorage.getItem("item") == null, "item should be null"); + }); + test("check setItem", function() { + expect(4); + ok(window.sessionStorage.getItem("item") == null, "item should be null"); + window.sessionStorage.setItem("item","value"); + ok(window.sessionStorage.getItem("item") == "value", "The value of the item should be 'value'"); + window.sessionStorage.setItem("item","newval"); + ok(window.sessionStorage.getItem("item") == "newval", "The value of the item should be 'newval'"); + window.sessionStorage.removeItem("item"); + ok(window.sessionStorage.getItem("item") == null, "item should be null"); + }); + test("check removeItem", function() { + expect(3); + ok(window.sessionStorage.getItem("item") == null, "item should be null"); + window.sessionStorage.setItem("item","value"); + ok(window.sessionStorage.getItem("item") == "value", "The value of the item should be 'value'"); + window.sessionStorage.removeItem("item"); + ok(window.sessionStorage.getItem("item") == null, "item should be null"); + }); + test("check clear", function() { + expect(11); + ok(window.sessionStorage.getItem("item1") == null, "item1 should be null"); + ok(window.sessionStorage.getItem("item2") == null, "item2 should be null"); + ok(window.sessionStorage.getItem("item3") == null, "item3 should be null"); + window.sessionStorage.setItem("item1","value"); + window.sessionStorage.setItem("item2","value"); + window.sessionStorage.setItem("item3","value"); + ok(window.sessionStorage.getItem("item1") == "value", "item1 should be null"); + ok(window.sessionStorage.getItem("item2") == "value", "item2 should be null"); + ok(window.sessionStorage.getItem("item3") == "value", "item3 should be null"); + ok(window.sessionStorage.length == 3, "length should be 3"); + window.sessionStorage.clear(); + ok(window.sessionStorage.length == 0, "length should be 0"); + ok(window.sessionStorage.getItem("item1") == null, "item1 should be null"); + ok(window.sessionStorage.getItem("item2") == null, "item2 should be null"); + ok(window.sessionStorage.getItem("item3") == null, "item3 should be null"); + }); + test("check dot notation", function() { + expect(3); + ok(window.sessionStorage.item == null, "item should be null"); + window.sessionStorage.item = "value"; + ok(window.sessionStorage.item == "value", "The value of the item should be 'value'"); + window.sessionStorage.removeItem("item"); + ok(window.sessionStorage.item == null, "item should be null"); + }); + module("Local Storage"); + test("should exist", function() { + expect(7); + ok(window.localStorage != null, "localStorage is defined"); + ok(typeof window.localStorage.length != 'undefined', "localStorage.length is defined"); + ok(typeof(window.localStorage.key) == "function", "localStorage.key is defined"); + ok(typeof(window.localStorage.getItem) == "function", "localStorage.getItem is defined"); + ok(typeof(window.localStorage.setItem) == "function", "localStorage.setItem is defined"); + ok(typeof(window.localStorage.removeItem) == "function", "localStorage.removeItem is defined"); + ok(typeof(window.localStorage.clear) == "function", "localStorage.clear is defined"); + }); + test("check length", function() { + expect(3); + ok(window.localStorage.length == 0, "length should be 0"); + window.localStorage.setItem("key","value"); + ok(window.localStorage.length == 1, "length should be 1"); + window.localStorage.removeItem("key"); + ok(window.localStorage.length == 0, "length should be 0"); + }); + test("check key", function() { + expect(3); + ok(window.localStorage.key(0) == null, "key should be null"); + window.localStorage.setItem("test","value"); + ok(window.localStorage.key(0) == "test", "key should be 'test'"); + window.localStorage.removeItem("test"); + ok(window.localStorage.key(0) == null, "key should be null"); + }); + test("check getItem", function() { + expect(3); + ok(window.localStorage.getItem("item") == null, "item should be null"); + window.localStorage.setItem("item","value"); + ok(window.localStorage.getItem("item") == "value", "The value of the item should be 'value'"); + window.localStorage.removeItem("item"); + ok(window.localStorage.getItem("item") == null, "item should be null"); + }); + test("check setItem", function() { + expect(4); + ok(window.localStorage.getItem("item") == null, "item should be null"); + window.localStorage.setItem("item","value"); + ok(window.localStorage.getItem("item") == "value", "The value of the item should be 'value'"); + window.localStorage.setItem("item","newval"); + ok(window.localStorage.getItem("item") == "newval", "The value of the item should be 'newval'"); + window.localStorage.removeItem("item"); + ok(window.localStorage.getItem("item") == null, "item should be null"); + }); + test("check removeItem", function() { + expect(3); + ok(window.localStorage.getItem("item") == null, "item should be null"); + window.localStorage.setItem("item","value"); + ok(window.localStorage.getItem("item") == "value", "The value of the item should be 'value'"); + window.localStorage.removeItem("item"); + ok(window.localStorage.getItem("item") == null, "item should be null"); + }); + test("check clear", function() { + expect(11); + ok(window.localStorage.getItem("item1") == null, "item1 should be null"); + ok(window.localStorage.getItem("item2") == null, "item2 should be null"); + ok(window.localStorage.getItem("item3") == null, "item3 should be null"); + window.localStorage.setItem("item1","value"); + window.localStorage.setItem("item2","value"); + window.localStorage.setItem("item3","value"); + ok(window.localStorage.getItem("item1") == "value", "item1 should be null"); + ok(window.localStorage.getItem("item2") == "value", "item2 should be null"); + ok(window.localStorage.getItem("item3") == "value", "item3 should be null"); + ok(window.localStorage.length == 3, "length should be 3"); + window.localStorage.clear(); + ok(window.localStorage.length == 0, "length should be 0"); + ok(window.localStorage.getItem("item1") == null, "item1 should be null"); + ok(window.localStorage.getItem("item2") == null, "item2 should be null"); + ok(window.localStorage.getItem("item3") == null, "item3 should be null"); + }); + test("check dot notation", function() { + expect(3); + ok(window.localStorage.item == null, "item should be null"); + window.localStorage.item = "value"; + ok(window.localStorage.item == "value", "The value of the item should be 'value'"); + window.localStorage.removeItem("item"); + ok(window.localStorage.item == null, "item should be null"); + }); + module("HTML 5 Storage"); + test("should exist", function() { + expect(1); + ok(typeof(window.openDatabase) == "function", "Database is defined"); + }); + test("Should open a database", function() { + var db = openDatabase("Database", "1.0", "HTML5 Database API example", 200000); + ok(db != null, "Database should be opened"); + }); +} diff --git a/test/assets/www/autotest/tests/system.tests.js b/test/assets/www/autotest/tests/system.tests.js new file mode 100644 index 00000000..c76fdb5e --- /dev/null +++ b/test/assets/www/autotest/tests/system.tests.js @@ -0,0 +1,261 @@ +Tests.prototype.SystemTests = function() { + module('System Information (navigator.system)'); + test("should exist", function() { + expect(1); + ok(navigator.system != null, "navigator.system should not be null."); + }); + test("should contain a get function", function() { + expect(2); + ok(typeof navigator.system.get != 'undefined' && navigator.system.get != null, "navigator.system.get should not be null."); + ok(typeof navigator.system.get == 'function', "navigator.system.get should be a function."); + }); + test("should contain a has function", function() { + expect(2); + ok(typeof navigator.system.has != 'undefined' && navigator.system.has != null, "navigator.system.has should not be null."); + ok(typeof navigator.system.has == 'function', "navigator.system.has should be a function."); + }); + test("should contain a monitor function", function() { + expect(2); + ok(typeof navigator.system.monitor != 'undefined' && navigator.system.monitor != null, "navigator.system.monitor should not be null."); + ok(typeof navigator.system.monitor == 'function', "navigator.system.monitor should be a function."); + }); + module('System Information Options'); + test("should be able to define a SystemInfoOptions object", function() { + expect(6); + var systemInfoOptions = new SystemInfoOptions(0.0, 0.0, "a", 0, "b"); + ok(systemInfoOptions != null, "new SystemInfoOptions() should not be null."); + ok(typeof systemInfoOptions.highThreshold != 'undefined' && systemInfoOptions.highThreshold != null && systemInfoOptions.highThreshold == 0.0, "new SystemInfoOptions() should include a 'highThreshold' property."); + ok(typeof systemInfoOptions.lowThreshold != 'undefined' && systemInfoOptions.lowThreshold != null && systemInfoOptions.lowThreshold == 0.0, "new SystemInfoOptions() should include a 'lowThreshold' property."); + ok(typeof systemInfoOptions.thresholdTarget != 'undefined' && systemInfoOptions.thresholdTarget != null && systemInfoOptions.thresholdTarget == "a", "new SystemInfoOptions() should include a 'thresholdTarget' property."); + ok(typeof systemInfoOptions.timeout != 'undefined' && systemInfoOptions.timeout != null && systemInfoOptions.timeout == 0, "new SystemInfoOptions() should include a 'timeout' property."); + ok(typeof systemInfoOptions.id != 'undefined' && systemInfoOptions.id != null && systemInfoOptions.id == "b", "new SystemInfoOptions() should include a 'id' property."); + }); + module('Power Property'); + test("should be able to define a Power Property object", function() { + expect(7); + var power = new PowerAttributes("a","b",0.0,0,true,false); + ok(power != null, "new PowerAttributes() should not be null."); + ok(typeof power.info != 'undefined' && power.info != null && power.info == "a", "new PowerAttributes() should include a 'info' property."); + ok(typeof power.id != 'undefined' && power.id != null && power.id == "b", "new PowerAttributes() should include a 'id' property."); + ok(typeof power.level != 'undefined' && power.level != null && power.level == 0.0, "new PowerAttributes() should include a 'level' property."); + ok(typeof power.timeRemaining != 'undefined' && power.timeRemaining != null && power.timeRemaining == 0, "new PowerAttributes() should include a 'timeRemaining' property."); + ok(typeof power.isBattery != 'undefined' && power.isBattery != null && power.isBattery == true, "new PowerAttributes() should include a 'isBattery' property."); + ok(typeof power.isCharging != 'undefined' && power.isCharging != null && power.isCharging == false, "new PowerAttributes() should include a 'isCharging' property."); + }); + module('CPU Property'); + test("should be able to define a CPU Property object", function() { + expect(4); + var cpu = new CPUAttributes("a", "b", 0.0); + ok(cpu != null, "new CPUAttributes() should not be null."); + ok(typeof cpu.info != 'undefined' && cpu.info != null && cpu.info == "a", "new CPUAttributes() should include a 'info' property."); + ok(typeof cpu.id != 'undefined' && cpu.id != null && cpu.id == "b", "new CPUAttributes() should include a 'id' property."); + ok(typeof cpu.usage != 'undefined' && cpu.usage != null && cpu.usage == 0.0, "new CPUAttributes() should include a 'usage' property."); + }); + module('Thermal Property'); + test("should be able to define a Thermal Property object", function() { + expect(4); + var thermal = new ThermalAttributes("a", "b", 0.0); + ok(thermal != null, "new ThermalAttributes() should not be null."); + ok(typeof thermal.info != 'undefined' && thermal.info != null && thermal.info == "a", "new ThermalAttributes() should include a 'info' property."); + ok(typeof thermal.id != 'undefined' && thermal.id != null && thermal.id == "b", "new ThermalAttributes() should include a 'id' property."); + ok(typeof thermal.state != 'undefined' && thermal.state != null && thermal.state == 0.0, "new ThermalAttributes() should include a 'state' property."); + }); + module('Network Property'); + test("should be able to define a Network Property object", function() { + expect(4); + var network = new NetworkAttributes("a", "b", []); + ok(network != null, "new NetworkAttributes() should not be null."); + ok(typeof network.info != 'undefined' && network.info != null && network.info == "a", "new NetworkAttributes() should include a 'info' property."); + ok(typeof network.id != 'undefined' && network.id != null && network.id == "b", "new NetworkAttributes() should include a 'id' property."); + ok(typeof network.activeConnections != 'undefined' && network.activeConnections != null, "new NetworkAttributes() should include a 'activeConnections' property."); + }); + module('Connection Type Property'); + test("should be able to define a display Type Property object", function() { + expect(10); + var connection = new ConnectionAttributes('a', 'b', ConnectionType.UNKNOWN, 0, 0, 0, 0, 0.0, false); + ok(connection != null, "new displayAttributes() should not be null."); + ok(typeof connection.info != 'undefined' && connection.info != null && connection.info == "a", "new ConnectionAttributes() should include a 'info' property."); + ok(typeof connection.id != 'undefined' && connection.id != null && connection.id == "b", "new ConnectionAttributes() should include a 'id' property."); + ok(typeof connection.type != 'undefined' && connection.type != null && connection.type == 'unknown', "new ConnectionAttributes() should include a 'type' property."); + ok(typeof connection.currentDownloadBandwidth != 'undefined' && connection.currentDownloadBandwidth != null && connection.currentDownloadBandwidth == 0, "new ConnectionAttributes() should include a 'currentDownloadBandwidth' property."); + ok(typeof connection.currentUploadBandwidth != 'undefined' && connection.currentUploadBandwidth != null && connection.currentUploadBandwidth == 0, "new ConnectionAttributes() should include a 'currentUploadBandwidth' property."); + ok(typeof connection.maxDownloadBandwidth != 'undefined' && connection.maxDownloadBandwidth != null && connection.maxDownloadBandwidth == 0, "new ConnectionAttributes() should include a 'maxDownloadBandwidth' property."); + ok(typeof connection.maxUploadBandwidth != 'undefined' && connection.maxUploadBandwidth != null && connection.maxUploadBandwidth == 0, "new ConnectionAttributes() should include a 'maxUploadBandwidth' property."); + ok(typeof connection.currentSignalStrength != 'undefined' && connection.currentSignalStrength != null && connection.currentSignalStrength == 0.0, "new ConnectionAttributes() should include a 'currentSignalStrength' property."); + ok(typeof connection.roaming != 'undefined' && connection.roaming != null && connection.roaming == false, "new ConnectionAttributes() should include a 'roaming' property."); + }); + module('Sensor Property'); + test("should be able to define a Sensor Property object", function() { + expect(5); + var sensor = new SensorAttributes(0.0,0.0,0.0,0.0); + ok(sensor != null, "new SensorAttributes() should not be null."); + ok(typeof sensor.value != 'undefined' && sensor.value != null && sensor.value == 0.0, "new SensorAttributes() should include a 'value' property."); + ok(typeof sensor.min != 'undefined' && sensor.min != null && sensor.min == 0.0, "new SensorAttributes() should include a 'min' property."); + ok(typeof sensor.max != 'undefined' && sensor.max != null && sensor.max == 0.0, "new SensorAttributes() should include a 'max' property."); + ok(typeof sensor.normalizedValue != 'undefined' && sensor.normalizedValue != null && sensor.normalizedValue == 0.0, "new SensorAttributes() should include a 'normalizedValue' property."); + }); + module('AVCodecs Property'); + test("should be able to define a AVCodecs Property object", function() { + expect(5); + var avcodecs = new AVCodecsAttributes("a", "b", [], []); + ok(avcodecs != null, "new AVCodecsAttributes() should not be null."); + ok(typeof avcodecs.info != 'undefined' && avcodecs.info != null && avcodecs.info == "a", "new AVCodecsAttributes() should include a 'info' property."); + ok(typeof avcodecs.id != 'undefined' && avcodecs.id != null && avcodecs.id == "b", "new AVCodecsAttributes() should include a 'id' property."); + ok(typeof avcodecs.audioCodecs != 'undefined' && avcodecs.audioCodecs != null, "new AVCodecsAttributes() should include a 'audioCodecs' property."); + ok(typeof avcodecs.videoCodecs != 'undefined' && avcodecs.videoCodecs != null, "new AVCodecsAttributes() should include a 'videoCodecs' property."); + }); + module('Audio Codec Property'); + test("should be able to define a Audio Codec Property object", function() { + expect(6); + var codec = new AudioCodecAttributes("a", "b", 'a',true,true); + ok(codec != null, "new AudioCodecAttributes() should not be null."); + ok(typeof codec.info != 'undefined' && codec.info != null && codec.info == "a", "new AudioCodecAttributes() should include a 'info' property."); + ok(typeof codec.id != 'undefined' && codec.id != null && codec.id == "b", "new AudioCodecAttributes() should include a 'id' property."); + ok(typeof codec.compFormats != 'undefined' && codec.compFormats != null && codec.compFormats == 'a', "new AudioCodecAttributes() should include a 'compFormats' property."); + ok(typeof codec.encode != 'undefined' && codec.encode != null && codec.encode == true, "new AudioCodecAttributes() should include a 'encode' property."); + ok(typeof codec.decode != 'undefined' && codec.decode != null && codec.decode == true, "new AudioCodecAttributes() should include a 'decode' property."); + }); + module('Video Codec Property'); + test("should be able to define a Video Codec Property object", function() { + expect(9); + var codec = new VideoCodecAttributes("a", "b", [],[],[],[],[],[]); + ok(codec != null, "new VideoCodecAttributes() should not be null."); + ok(typeof codec.info != 'undefined' && codec.info != null && codec.info == "a", "new VideoCodecAttributes() should include a 'info' property."); + ok(typeof codec.id != 'undefined' && codec.id != null && codec.id == "b", "new VideoCodecAttributes() should include a 'id' property."); + ok(typeof codec.compFormats != 'undefined' && codec.compFormats != null, "new VideoCodecAttributes() should include a 'compFormats' property."); + ok(typeof codec.containerFormats != 'undefined' && codec.containerFormats != null, "new VideoCodecAttributes() should include a 'containerFormats' property."); + ok(typeof codec.hwAccel != 'undefined' && codec.hwAccel != null, "new VideoCodecAttributes() should include a 'hwAccel' property."); + ok(typeof codec.profiles != 'undefined' && codec.profiles != null, "new VideoCodecAttributes() should include a 'profiles' property."); + ok(typeof codec.frameTypes != 'undefined' && codec.frameTypes != null, "new VideoCodecAttributes() should include a 'frameTypes' property."); + ok(typeof codec.rateTypes != 'undefined' && codec.rateTypes != null, "new VideoCodecAttributes() should include a 'rateTypes' property."); + }); + module('Storage Unit Property'); + test("should be able to define a Storage Property object", function() { + expect(8); + var storage = new StorageUnitAttributes('a','b',0,true,0,0,true); + ok(storage != null, "new StorageUnitAttributes() should not be null."); + ok(typeof storage.info != 'undefined' && storage.info != null && storage.info == "a", "new StorageUnitAttributes() should include a 'info' property."); + ok(typeof storage.id != 'undefined' && storage.id != null && storage.id == "b", "new StorageUnitAttributes() should include a 'id' property."); + ok(typeof storage.type != 'undefined' && storage.type != null && storage.type == 0, "new StorageUnitAttributes() should include a 'type' property."); + ok(typeof storage.isWritable != 'undefined' && storage.isWritable != null && storage.isWritable == true, "new StorageUnitAttributes() should include a 'isWritable' property."); + ok(typeof storage.capacity != 'undefined' && storage.capacity != null && storage.capacity == 0, "new StorageUnitAttributes() should include a 'capacity' property."); + ok(typeof storage.availableCapacity != 'undefined' && storage.availableCapacity != null && storage.availableCapacity == 0, "new StorageUnitAttributes() should include a 'availableCapacity' property."); + ok(typeof storage.isRemoveable != 'undefined' && storage.isRemoveable != null && storage.isRemoveable == true, "new StorageUnitAttributes() should include a 'isRemoveable' property."); + }); + module('Output Devices Property'); + test("should be able to define a Input Devices Property object", function() { + expect(11); + var output = new OutputDevicesAttributes('a','b',[],[],[],"a",[],"a",[],[]); + ok(output != null, "new OutputDevicesAttributes() should not be null."); + ok(typeof output.info != 'undefined' && output.info != null && output.info == "a", "new OutputDevicesAttributes() should include a 'info' property."); + ok(typeof output.id != 'undefined' && output.id != null && output.id == "b", "new OutputDevicesAttributes() should include a 'id' property."); + ok(typeof output.displayDevices != 'undefined' && output.displayDevices != null, "new OutputDevicesAttributes() should include a 'displayDevices' property."); + ok(typeof output.activeDisplayDevices != 'undefined' && output.activeDisplayDevices != null, "new OutputDevicesAttributes() should include a 'activeDisplayDevices' property."); + ok(typeof output.printingDevices != 'undefined' && output.printingDevices != null, "new OutputDevicesAttributes() should include a 'printingDevices' property."); + ok(typeof output.activePrintingDevice != 'undefined' && output.activePrintingDevice != null && output.activePrintingDevice == "a", "new OutputDevicesAttributes() should include a 'activePrintingDevice' property."); + ok(typeof output.brailleDevices != 'undefined' && output.brailleDevices != null, "new OutputDevicesAttributes() should include a 'brailleDevices' property."); + ok(typeof output.activeBrailleDevice != 'undefined' && output.activeBrailleDevice != null && output.activeBrailleDevice == "a", "new OutputDevicesAttributes() should include a 'activeBrailleDevice' property."); + ok(typeof output.audioDevices != 'undefined' && output.audioDevices != null, "new OutputDevicesAttributes() should include a 'audioDevices' property."); + ok(typeof output.activeAudioDevices != 'undefined' && output.activeAudioDevices != null, "new OutputDevicesAttributes() should include a 'activeAudioDevices' property."); + }); + module('Display Device Type Property'); + test("should be able to define a Display Device Property object", function() { + expect(10); + var display = new DisplayDeviceAttributes(0,0.0,0.0,true,0,0,0.0,0.0,"a"); + ok(display != null, "new DisplayDeviceAttributes() should not be null."); + ok(typeof display.orientation != 'undefined' && display.orientation != null && display.orientation == 0, "new DisplayDeviceAttributes() should include a 'orientation' property."); + ok(typeof display.brightness != 'undefined' && display.brightness != null && display.brightness == 0.0, "new DisplayDeviceAttributes() should include a 'brightness' property."); + ok(typeof display.contrast != 'undefined' && display.contrast != null && display.contrast == 0.0, "new DisplayDeviceAttributes() should include a 'contrast' property."); + ok(typeof display.blanked != 'undefined' && display.blanked != null && display.blanked == true, "new DisplayDeviceAttributes() should include a 'blanked' property."); + ok(typeof display.dotsPerInchW != 'undefined' && display.dotsPerInchW != null && display.dotsPerInchW == 0, "new DisplayDeviceAttributes() should include a 'dotsPerInchW' property."); + ok(typeof display.dotsPerInchH != 'undefined' && display.dotsPerInchH != null && display.dotsPerInchH == 0, "new DisplayDeviceAttributes() should include a 'dotsPerInchH' property."); + ok(typeof display.physicalWidth != 'undefined' && display.physicalWidth != null && display.physicalWidth == 0.0, "new DisplayDeviceAttributes() should include a 'physicalWidth' property."); + ok(typeof display.physicalHeight != 'undefined' && display.physicalHeight != null && display.physicalHeight == 0.0, "new DisplayDeviceAttributes() should include a 'physicalHeight' property."); + ok(typeof display.info != 'undefined' && display.info != null && display.info == 'a', "new DisplayDeviceAttributes() should include a 'info' property."); + }); + module('Audio Device Type Property'); + test("should be able to define a Audio Device Property object", function() { + expect(6); + var audio = new AudioDeviceAttributes(0,0,0,0,"a"); + ok(audio != null, "new AudioDeviceAttributes() should not be null."); + ok(typeof audio.type != 'undefined' && audio.type != null && audio.type == 0, "new AudioDeviceAttributes() should include a 'type' property."); + ok(typeof audio.freqRangeLow != 'undefined' && audio.freqRangeLow != null && audio.freqRangeLow == 0, "new AudioDeviceAttributes() should include a 'freqRangeLow' property."); + ok(typeof audio.freqRangeHigh != 'undefined' && audio.freqRangeHigh != null && audio.freqRangeHigh == 0, "new AudioDeviceAttributes() should include a 'freqRangeHigh' property."); + ok(typeof audio.volumeLevel != 'undefined' && audio.volumeLevel != null && audio.volumeLevel == 0, "new AudioDeviceAttributes() should include a 'volumeLevel' property."); + ok(typeof audio.info != 'undefined' && audio.info != null && audio.info == "a", "new AudioDeviceAttributes() should include a 'info' property."); + }); + module('Printing Device Type Property'); + test("should be able to define a Printing Device Property object", function() { + expect(5); + var printer = new PrintingDeviceAttributes(0,0,0,"a"); + ok(printer != null, "new PrintingDeviceAttributes() should not be null."); + ok(typeof printer.type != 'undefined' && printer.type != null && printer.type == 0, "new PrintingDeviceAttributes() should include a 'type' property."); + ok(typeof printer.resolution != 'undefined' && printer.resolution != null && printer.resolution == 0, "new PrintingDeviceAttributes() should include a 'resolution' property."); + ok(typeof printer.color != 'undefined' && printer.color != null && printer.color == 0, "new PrintingDeviceAttributes() should include a 'color' property."); + ok(typeof printer.info != 'undefined' && printer.info != null && printer.info == "a", "new PrintingDeviceAttributes() should include a 'info' property."); + }); + module('Braille Device Type Property'); + test("should be able to define a Printing Device Property object", function() { + expect(3); + var braille = new BrailleDeviceAttributes(0,"a"); + ok(braille != null, "new BrailleDeviceAttributes() should not be null."); + ok(typeof braille.nbCells != 'undefined' && braille.nbCells != null && braille.nbCells == 0, "new BrailleDeviceAttributes() should include a 'nbCells' property."); + ok(typeof braille.info != 'undefined' && braille.info != null && braille.info == "a", "new BrailleDeviceAttributes() should include a 'info' property."); + }); + module('Input Devices Property'); + test("should be able to define a Input Devices Property object", function() { + expect(11); + var input = new InputDevicesAttributes('a','b',[],[],[],[],[],[],[],[]); + ok(input != null, "new InputDevicesAttributes() should not be null."); + ok(typeof input.info != 'undefined' && input.info != null && input.info == "a", "new InputDevicesAttributes() should include a 'info' property."); + ok(typeof input.id != 'undefined' && input.id != null && input.id == "b", "new InputDevicesAttributes() should include a 'id' property."); + ok(typeof input.pointingDevices != 'undefined' && input.pointingDevices != null, "new InputDevicesAttributes() should include a 'pointingDevices' property."); + ok(typeof input.activePointingDevices != 'undefined' && input.activePointingDevices != null, "new InputDevicesAttributes() should include a 'activePointingDevices' property."); + ok(typeof input.keyboards != 'undefined' && input.keyboards != null, "new InputDevicesAttributes() should include a 'keyboards' property."); + ok(typeof input.activeKeyboards != 'undefined' && input.activeKeyboards != null, "new InputDevicesAttributes() should include a 'activeKeyboards' property."); + ok(typeof input.cameras != 'undefined' && input.cameras != null, "new InputDevicesAttributes() should include a 'cameras' property."); + ok(typeof input.activeCameras != 'undefined' && input.activeCameras != null, "new InputDevicesAttributes() should include a 'activeCameras' property."); + ok(typeof input.microphones != 'undefined' && input.microphones != null, "new InputDevicesAttributes() should include a 'microphones' property."); + ok(typeof input.activeMicrophones != 'undefined' && input.activeMicrophones != null, "new InputDevicesAttributes() should include a 'activeMicrophones' property."); + }); + module('Pointer Property'); + test("should be able to define a Pointer Property object", function() { + expect(4); + var pointer = new PointerAttributes(0,true,"a"); + ok(pointer != null, "new PointerAttributes() should not be null."); + ok(typeof pointer.type != 'undefined' && pointer.type != null && pointer.type == 0, "new PointerAttributes() should include a 'type' property."); + ok(typeof pointer.supportsMultiTouch != 'undefined' && pointer.supportsMultiTouch != null && pointer.supportsMultiTouch == true, "new PointerAttributes() should include a 'supportsMultiTouch' property."); + ok(typeof pointer.info != 'undefined' && pointer.info != null && pointer.info == "a", "new PointerAttributes() should include a 'info' property."); + }); + module('Keyboard Property'); + test("should be able to define a Keyboard Property object", function() { + expect(4); + var keyboard = new KeyboardAttributes(0,true,"a"); + ok(keyboard != null, "new KeyboardAttributes() should not be null."); + ok(typeof keyboard.type != 'undefined' && keyboard.type != null && keyboard.type == 0, "new KeyboardAttributes() should include a 'type' property."); + ok(typeof keyboard.isHardware != 'undefined' && keyboard.isHardware != null && keyboard.isHardware == true, "new KeyboardAttributes() should include a 'isHardware' property."); + ok(typeof keyboard.info != 'undefined' && keyboard.info != null && keyboard.info == "a", "new KeyboardAttributes() should include a 'info' property."); + }); + module('Camera Property'); + test("should be able to define a Camera Property object", function() { + expect(5); + var camera = new CameraAttributes(true,true,0,0.0); + ok(camera != null, "new CameraAttributes() should not be null."); + ok(typeof camera.supportsVideo != 'undefined' && camera.supportsVideo != null && camera.supportsVideo == true, "new CameraAttributes() should include a 'supportsVideo' property."); + ok(typeof camera.hasFlash != 'undefined' && camera.hasFlash != null && camera.hasFlash == true, "new CameraAttributes() should include a 'hasFlash' property."); + ok(typeof camera.sensorPixels != 'undefined' && camera.sensorPixels != null && camera.sensorPixels == 0, "new CameraAttributes() should include a 'sensorPixels' property."); + ok(typeof camera.maxZoomFactor != 'undefined' && camera.maxZoomFactor != null && camera.maxZoomFactor == 0.0, "new CameraAttributes() should include a 'maxZoomFactor' property."); + }); + module('Microphone Property'); + test("should be able to define a Microphone Property object", function() { + expect(7); + var mic = new MicrophoneAttributes(0,0,0,"a","b",[]); + ok(mic != null, "new MicrophoneAttributes() should not be null."); + ok(typeof mic.type != 'undefined' && mic.type != null && mic.type == 0, "new MicrophoneAttributes() should include a 'type' property."); + ok(typeof mic.freqRangeLow != 'undefined' && mic.freqRangeLow != null && mic.freqRangeLow == 0, "new MicrophoneAttributes() should include a 'freqRangeLow' property."); + ok(typeof mic.freqRangeHigh != 'undefined' && mic.freqRangeHigh != null && mic.freqRangeHigh == 0, "new MicrophoneAttributes() should include a 'freqRangeHigh' property."); + ok(typeof mic.info != 'undefined' && mic.info != null && mic.info == "a", "new MicrophoneAttributes() should include a 'info' property."); + ok(typeof mic.name != 'undefined' && mic.name != null && mic.name == "b", "new MicrophoneAttributes() should include a 'name' property."); + ok(typeof mic.types != 'undefined' && mic.types != null, "new MicrophoneAttributes() should include a 'types' property."); + }); +}; diff --git a/test/assets/www/battery/index.html b/test/assets/www/battery/index.html new file mode 100644 index 00000000..298bf193 --- /dev/null +++ b/test/assets/www/battery/index.html @@ -0,0 +1,96 @@ + + + + + + PhoneGap + + + + + + + + + +

    Battery

    +
    + Status:
    + Level:
    + Plugged:
    + Low:
    + Critical:
    +
    +

    Action

    + Add "batterystatus" listener + Remove "batterystatus" listener + Add "batterylow" listener + Remove "batterylow" listener + Add "batterycritical" listener + Remove "batterycritical" listener +

     

    Back + + diff --git a/test/assets/www/camera/index.html b/test/assets/www/camera/index.html new file mode 100755 index 00000000..1c8fd076 --- /dev/null +++ b/test/assets/www/camera/index.html @@ -0,0 +1,96 @@ + + + + + + PhoneGap + + + + + + + + + +

    Camera

    +
    + Status:
    + +
    +

    Action

    + Take Picture + Select Image from Library +

     

    Back + + diff --git a/test/assets/www/compass/index.html b/test/assets/www/compass/index.html new file mode 100755 index 00000000..74b817a5 --- /dev/null +++ b/test/assets/www/compass/index.html @@ -0,0 +1,128 @@ + + + + + + PhoneGap + + + + + + + + + +

    Compass

    +
    + Status: Stopped + + +
    Heading:  
    +
    +

    Action

    + Get Compass + Start Watching Compass + Stop Watching Compass +

     

    Back + + diff --git a/test/assets/www/contacts/index.html b/test/assets/www/contacts/index.html new file mode 100755 index 00000000..e6ce44b3 --- /dev/null +++ b/test/assets/www/contacts/index.html @@ -0,0 +1,112 @@ + + + + + + PhoneGap + + + + + + + + + +

    Contacts

    +
    + Results:
    +   +
    +

    Action

    + Get phone's contacts + Add a new contact 'Dooney Evans' +

     

    Back + + diff --git a/test/assets/www/cordova-1.4.1.js b/test/assets/www/cordova-1.4.1.js new file mode 100644 index 00000000..edbbb47b --- /dev/null +++ b/test/assets/www/cordova-1.4.1.js @@ -0,0 +1,5510 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +if (typeof Cordova === "undefined") { + +/** + * The order of events during page load and Cordova startup is as follows: + * + * onDOMContentLoaded Internal event that is received when the web page is loaded and parsed. + * window.onload Body onload event. + * onNativeReady Internal event that indicates the Cordova native side is ready. + * onCordovaInit Internal event that kicks off creation of all Cordova JavaScript objects (runs constructors). + * onCordovaReady Internal event fired when all Cordova JavaScript objects have been created + * onCordovaInfoReady Internal event fired when device properties are available + * onDeviceReady User event fired to indicate that Cordova is ready + * onResume User event fired to indicate a start/resume lifecycle event + * onPause User event fired to indicate a pause lifecycle event + * onDestroy Internal event fired when app is being destroyed (User should use window.onunload event, not this one). + * + * The only Cordova events that user code should register for are: + * deviceready Cordova native code is initialized and Cordova APIs can be called from JavaScript + * pause App has moved to background + * resume App has returned to foreground + * + * Listeners can be registered as: + * document.addEventListener("deviceready", myDeviceReadyListener, false); + * document.addEventListener("resume", myResumeListener, false); + * document.addEventListener("pause", myPauseListener, false); + * + * The DOM lifecycle events should be used for saving and restoring state + * window.onload + * window.onunload + */ + +/** + * This represents the Cordova API itself, and provides a global namespace for accessing + * information about the state of Cordova. + * @class + */ +var Cordova = { + documentEventHandler: {}, // Collection of custom document event handlers + windowEventHandler: {} // Collection of custom window event handlers +}; + +/** + * List of resource files loaded by Cordova. + * This is used to ensure JS and other files are loaded only once. + */ +Cordova.resources = {base: true}; + +/** + * Determine if resource has been loaded by Cordova + * + * @param name + * @return + */ +Cordova.hasResource = function(name) { + return Cordova.resources[name]; +}; + +/** + * Add a resource to list of loaded resources by Cordova + * + * @param name + */ +Cordova.addResource = function(name) { + Cordova.resources[name] = true; +}; + +/** + * Custom pub-sub channel that can have functions subscribed to it + * @constructor + */ +Cordova.Channel = function (type) +{ + this.type = type; + this.handlers = {}; + this.guid = 0; + this.fired = false; + this.enabled = true; +}; + +/** + * Subscribes the given function to the channel. Any time that + * Channel.fire is called so too will the function. + * Optionally specify an execution context for the function + * and a guid that can be used to stop subscribing to the channel. + * Returns the guid. + */ +Cordova.Channel.prototype.subscribe = function(f, c, g) { + // need a function to call + if (f === null) { return; } + + var func = f; + if (typeof c === "object" && typeof f === "function") { func = Cordova.close(c, f); } + + g = g || func.observer_guid || f.observer_guid || this.guid++; + func.observer_guid = g; + f.observer_guid = g; + this.handlers[g] = func; + return g; +}; + +/** + * Like subscribe but the function is only called once and then it + * auto-unsubscribes itself. + */ +Cordova.Channel.prototype.subscribeOnce = function(f, c) { + var g = null; + var _this = this; + var m = function() { + f.apply(c || null, arguments); + _this.unsubscribe(g); + }; + if (this.fired) { + if (typeof c === "object" && typeof f === "function") { f = Cordova.close(c, f); } + f.apply(this, this.fireArgs); + } else { + g = this.subscribe(m); + } + return g; +}; + +/** + * Unsubscribes the function with the given guid from the channel. + */ +Cordova.Channel.prototype.unsubscribe = function(g) { + if (typeof g === "function") { g = g.observer_guid; } + this.handlers[g] = null; + delete this.handlers[g]; +}; + +/** + * Calls all functions subscribed to this channel. + */ +Cordova.Channel.prototype.fire = function(e) { + if (this.enabled) { + var fail = false; + var item, handler, rv; + for (item in this.handlers) { + if (this.handlers.hasOwnProperty(item)) { + handler = this.handlers[item]; + if (typeof handler === "function") { + rv = (handler.apply(this, arguments) === false); + fail = fail || rv; + } + } + } + this.fired = true; + this.fireArgs = arguments; + return !fail; + } + return true; +}; + +/** + * Calls the provided function only after all of the channels specified + * have been fired. + */ +Cordova.Channel.join = function(h, c) { + var i = c.length; + var f = function() { + if (!(--i)) { + h(); + } + }; + var len = i; + var j; + for (j=0; j + * + * @param name The plugin name + * @param obj The plugin object + */ +Cordova.addPlugin = function(name, obj) { + if (!window.plugins[name]) { + window.plugins[name] = obj; + } + else { + console.log("Error: Plugin "+name+" already exists."); + } +}; + +/** + * onDOMContentLoaded channel is fired when the DOM content + * of the page has been parsed. + */ +Cordova.onDOMContentLoaded = new Cordova.Channel('onDOMContentLoaded'); + +/** + * onNativeReady channel is fired when the Cordova native code + * has been initialized. + */ +Cordova.onNativeReady = new Cordova.Channel('onNativeReady'); + +/** + * onCordovaInit channel is fired when the web page is fully loaded and + * Cordova native code has been initialized. + */ +Cordova.onCordovaInit = new Cordova.Channel('onCordovaInit'); + +/** + * onCordovaReady channel is fired when the JS Cordova objects have been created. + */ +Cordova.onCordovaReady = new Cordova.Channel('onCordovaReady'); + +/** + * onCordovaInfoReady channel is fired when the Cordova device properties + * has been set. + */ +Cordova.onCordovaInfoReady = new Cordova.Channel('onCordovaInfoReady'); + +/** + * onCordovaConnectionReady channel is fired when the Cordova connection properties + * has been set. + */ +Cordova.onCordovaConnectionReady = new Cordova.Channel('onCordovaConnectionReady'); + +/** + * onDestroy channel is fired when the Cordova native code + * is destroyed. It is used internally. + * Window.onunload should be used by the user. + */ +Cordova.onDestroy = new Cordova.Channel('onDestroy'); +Cordova.onDestroy.subscribeOnce(function() { + Cordova.shuttingDown = true; +}); +Cordova.shuttingDown = false; + +// _nativeReady is global variable that the native side can set +// to signify that the native code is ready. It is a global since +// it may be called before any Cordova JS is ready. +if (typeof _nativeReady !== 'undefined') { Cordova.onNativeReady.fire(); } + +/** + * onDeviceReady is fired only after all Cordova objects are created and + * the device properties are set. + */ +Cordova.onDeviceReady = new Cordova.Channel('onDeviceReady'); + + +// Array of channels that must fire before "deviceready" is fired +Cordova.deviceReadyChannelsArray = [ Cordova.onCordovaReady, Cordova.onCordovaInfoReady, Cordova.onCordovaConnectionReady]; + +// Hashtable of user defined channels that must also fire before "deviceready" is fired +Cordova.deviceReadyChannelsMap = {}; + +/** + * Indicate that a feature needs to be initialized before it is ready to be used. + * This holds up Cordova's "deviceready" event until the feature has been initialized + * and Cordova.initComplete(feature) is called. + * + * @param feature {String} The unique feature name + */ +Cordova.waitForInitialization = function(feature) { + if (feature) { + var channel = new Cordova.Channel(feature); + Cordova.deviceReadyChannelsMap[feature] = channel; + Cordova.deviceReadyChannelsArray.push(channel); + } +}; + +/** + * Indicate that initialization code has completed and the feature is ready to be used. + * + * @param feature {String} The unique feature name + */ +Cordova.initializationComplete = function(feature) { + var channel = Cordova.deviceReadyChannelsMap[feature]; + if (channel) { + channel.fire(); + } +}; + +/** + * Create all Cordova objects once page has fully loaded and native side is ready. + */ +Cordova.Channel.join(function() { + + // Start listening for XHR callbacks + setTimeout(function() { + if (Cordova.UsePolling) { + Cordova.JSCallbackPolling(); + } + else { + var polling = prompt("usePolling", "gap_callbackServer:"); + Cordova.UsePolling = polling; + if (polling == "true") { + Cordova.UsePolling = true; + Cordova.JSCallbackPolling(); + } + else { + Cordova.UsePolling = false; + Cordova.JSCallback(); + } + } + }, 1); + + // Run Cordova constructors + Cordova.onCordovaInit.fire(); + + // Fire event to notify that all objects are created + Cordova.onCordovaReady.fire(); + + // Fire onDeviceReady event once all constructors have run and Cordova info has been + // received from native side, and any user defined initialization channels. + Cordova.Channel.join(function() { + // Let native code know we are inited on JS side + prompt("", "gap_init:"); + + Cordova.onDeviceReady.fire(); + }, Cordova.deviceReadyChannelsArray); + +}, [ Cordova.onDOMContentLoaded, Cordova.onNativeReady ]); + +// Listen for DOMContentLoaded and notify our channel subscribers +document.addEventListener('DOMContentLoaded', function() { + Cordova.onDOMContentLoaded.fire(); +}, false); + +// Intercept calls to document.addEventListener and watch for deviceready +Cordova.m_document_addEventListener = document.addEventListener; + +// Intercept calls to window.addEventListener +Cordova.m_window_addEventListener = window.addEventListener; + +/** + * Add a custom window event handler. + * + * @param {String} event The event name that callback handles + * @param {Function} callback The event handler + */ +Cordova.addWindowEventHandler = function(event, callback) { + Cordova.windowEventHandler[event] = callback; +}; + +/** + * Add a custom document event handler. + * + * @param {String} event The event name that callback handles + * @param {Function} callback The event handler + */ +Cordova.addDocumentEventHandler = function(event, callback) { + Cordova.documentEventHandler[event] = callback; +}; + +/** + * Intercept adding document event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +document.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + if (e === 'deviceready') { + Cordova.onDeviceReady.subscribeOnce(handler); + } + else { + // If subscribing to Android backbutton + if (e === 'backbutton') { + Cordova.exec(null, null, "App", "overrideBackbutton", [true]); + } + + // If subscribing to an event that is handled by a plugin + else if (typeof Cordova.documentEventHandler[e] !== "undefined") { + if (Cordova.documentEventHandler[e](e, handler, true)) { + return; // Stop default behavior + } + } + + Cordova.m_document_addEventListener.call(document, evt, handler, capture); + } +}; + +/** + * Intercept adding window event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +window.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If subscribing to an event that is handled by a plugin + if (typeof Cordova.windowEventHandler[e] !== "undefined") { + if (Cordova.windowEventHandler[e](e, handler, true)) { + return; // Stop default behavior + } + } + + Cordova.m_window_addEventListener.call(window, evt, handler, capture); +}; + +// Intercept calls to document.removeEventListener and watch for events that +// are generated by Cordova native code +Cordova.m_document_removeEventListener = document.removeEventListener; + +// Intercept calls to window.removeEventListener +Cordova.m_window_removeEventListener = window.removeEventListener; + +/** + * Intercept removing document event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +document.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If unsubscribing to Android backbutton + if (e === 'backbutton') { + Cordova.exec(null, null, "App", "overrideBackbutton", [false]); + } + + // If unsubcribing from an event that is handled by a plugin + if (typeof Cordova.documentEventHandler[e] !== "undefined") { + if (Cordova.documentEventHandler[e](e, handler, false)) { + return; // Stop default behavior + } + } + + Cordova.m_document_removeEventListener.call(document, evt, handler, capture); +}; + +/** + * Intercept removing window event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +window.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If unsubcribing from an event that is handled by a plugin + if (typeof Cordova.windowEventHandler[e] !== "undefined") { + if (Cordova.windowEventHandler[e](e, handler, false)) { + return; // Stop default behavior + } + } + + Cordova.m_window_removeEventListener.call(window, evt, handler, capture); +}; + +/** + * Method to fire document event + * + * @param {String} type The event type to fire + * @param {Object} data Data to send with event + */ +Cordova.fireDocumentEvent = function(type, data) { + var e = document.createEvent('Events'); + e.initEvent(type); + if (data) { + for (var i in data) { + e[i] = data[i]; + } + } + document.dispatchEvent(e); +}; + +/** + * Method to fire window event + * + * @param {String} type The event type to fire + * @param {Object} data Data to send with event + */ +Cordova.fireWindowEvent = function(type, data) { + var e = document.createEvent('Events'); + e.initEvent(type); + if (data) { + for (var i in data) { + e[i] = data[i]; + } + } + window.dispatchEvent(e); +}; + +/** + * Does a deep clone of the object. + * + * @param obj + * @return {Object} + */ +Cordova.clone = function(obj) { + var i, retVal; + if(!obj) { + return obj; + } + + if(obj instanceof Array){ + retVal = []; + for(i = 0; i < obj.length; ++i){ + retVal.push(Cordova.clone(obj[i])); + } + return retVal; + } + + if (typeof obj === "function") { + return obj; + } + + if(!(obj instanceof Object)){ + return obj; + } + + if (obj instanceof Date) { + return obj; + } + + retVal = {}; + for(i in obj){ + if(!(i in retVal) || retVal[i] !== obj[i]) { + retVal[i] = Cordova.clone(obj[i]); + } + } + return retVal; +}; + +Cordova.callbackId = 0; +Cordova.callbacks = {}; +Cordova.callbackStatus = { + NO_RESULT: 0, + OK: 1, + CLASS_NOT_FOUND_EXCEPTION: 2, + ILLEGAL_ACCESS_EXCEPTION: 3, + INSTANTIATION_EXCEPTION: 4, + MALFORMED_URL_EXCEPTION: 5, + IO_EXCEPTION: 6, + INVALID_ACTION: 7, + JSON_EXCEPTION: 8, + ERROR: 9 + }; + + +/** + * Execute a Cordova command. It is up to the native side whether this action is synch or async. + * The native side can return: + * Synchronous: PluginResult object as a JSON string + * Asynchrounous: Empty string "" + * If async, the native side will Cordova.callbackSuccess or Cordova.callbackError, + * depending upon the result of the action. + * + * @param {Function} success The success callback + * @param {Function} fail The fail callback + * @param {String} service The name of the service to use + * @param {String} action Action to be run in Cordova + * @param {Array.} [args] Zero or more arguments to pass to the method + */ +Cordova.exec = function(success, fail, service, action, args) { + try { + var callbackId = service + Cordova.callbackId++; + if (success || fail) { + Cordova.callbacks[callbackId] = {success:success, fail:fail}; + } + + var r = prompt(JSON.stringify(args), "gap:"+JSON.stringify([service, action, callbackId, true])); + + // If a result was returned + if (r.length > 0) { + eval("var v="+r+";"); + + // If status is OK, then return value back to caller + if (v.status === Cordova.callbackStatus.OK) { + + // If there is a success callback, then call it now with + // returned value + if (success) { + try { + success(v.message); + } catch (e) { + console.log("Error in success callback: " + callbackId + " = " + e); + } + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete Cordova.callbacks[callbackId]; + } + } + return v.message; + } + + // If no result + else if (v.status === Cordova.callbackStatus.NO_RESULT) { + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete Cordova.callbacks[callbackId]; + } + } + + // If error, then display error + else { + console.log("Error: Status="+v.status+" Message="+v.message); + + // If there is a fail callback, then call it now with returned value + if (fail) { + try { + fail(v.message); + } + catch (e1) { + console.log("Error in error callback: "+callbackId+" = "+e1); + } + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete Cordova.callbacks[callbackId]; + } + } + return null; + } + } + } catch (e2) { + console.log("Error: "+e2); + } +}; + +/** + * Called by native code when returning successful result from an action. + * + * @param callbackId + * @param args + */ +Cordova.callbackSuccess = function(callbackId, args) { + if (Cordova.callbacks[callbackId]) { + + // If result is to be sent to callback + if (args.status === Cordova.callbackStatus.OK) { + try { + if (Cordova.callbacks[callbackId].success) { + Cordova.callbacks[callbackId].success(args.message); + } + } + catch (e) { + console.log("Error in success callback: "+callbackId+" = "+e); + } + } + + // Clear callback if not expecting any more results + if (!args.keepCallback) { + delete Cordova.callbacks[callbackId]; + } + } +}; + +/** + * Called by native code when returning error result from an action. + * + * @param callbackId + * @param args + */ +Cordova.callbackError = function(callbackId, args) { + if (Cordova.callbacks[callbackId]) { + try { + if (Cordova.callbacks[callbackId].fail) { + Cordova.callbacks[callbackId].fail(args.message); + } + } + catch (e) { + console.log("Error in error callback: "+callbackId+" = "+e); + } + + // Clear callback if not expecting any more results + if (!args.keepCallback) { + delete Cordova.callbacks[callbackId]; + } + } +}; + +Cordova.JSCallbackPort = null; +Cordova.JSCallbackToken = null; + +/** + * This is only for Android. + * + * Internal function that uses XHR to call into Cordova Java code and retrieve + * any JavaScript code that needs to be run. This is used for callbacks from + * Java to JavaScript. + */ +Cordova.JSCallback = function() { + + // Exit if shutting down app + if (Cordova.shuttingDown) { + return; + } + + // If polling flag was changed, start using polling from now on + if (Cordova.UsePolling) { + Cordova.JSCallbackPolling(); + return; + } + + var xmlhttp = new XMLHttpRequest(); + + // Callback function when XMLHttpRequest is ready + xmlhttp.onreadystatechange=function(){ + if(xmlhttp.readyState === 4){ + + // Exit if shutting down app + if (Cordova.shuttingDown) { + return; + } + + // If callback has JavaScript statement to execute + if (xmlhttp.status === 200) { + + // Need to url decode the response + var msg = decodeURIComponent(xmlhttp.responseText); + setTimeout(function() { + try { + var t = eval(msg); + } + catch (e) { + // If we're getting an error here, seeing the message will help in debugging + console.log("JSCallback: Message from Server: " + msg); + console.log("JSCallback Error: "+e); + } + }, 1); + setTimeout(Cordova.JSCallback, 1); + } + + // If callback ping (used to keep XHR request from timing out) + else if (xmlhttp.status === 404) { + setTimeout(Cordova.JSCallback, 10); + } + + // If security error + else if (xmlhttp.status === 403) { + console.log("JSCallback Error: Invalid token. Stopping callbacks."); + } + + // If server is stopping + else if (xmlhttp.status === 503) { + console.log("JSCallback Server Closed: Stopping callbacks."); + } + + // If request wasn't GET + else if (xmlhttp.status === 400) { + console.log("JSCallback Error: Bad request. Stopping callbacks."); + } + + // If error, revert to polling + else { + console.log("JSCallback Error: Request failed."); + Cordova.UsePolling = true; + Cordova.JSCallbackPolling(); + } + } + }; + + if (Cordova.JSCallbackPort === null) { + Cordova.JSCallbackPort = prompt("getPort", "gap_callbackServer:"); + } + if (Cordova.JSCallbackToken === null) { + Cordova.JSCallbackToken = prompt("getToken", "gap_callbackServer:"); + } + xmlhttp.open("GET", "http://127.0.0.1:"+Cordova.JSCallbackPort+"/"+Cordova.JSCallbackToken , true); + xmlhttp.send(); +}; + +/** + * The polling period to use with JSCallbackPolling. + * This can be changed by the application. The default is 50ms. + */ +Cordova.JSCallbackPollingPeriod = 50; + +/** + * Flag that can be set by the user to force polling to be used or force XHR to be used. + */ +Cordova.UsePolling = false; // T=use polling, F=use XHR + +/** + * This is only for Android. + * + * Internal function that uses polling to call into Cordova Java code and retrieve + * any JavaScript code that needs to be run. This is used for callbacks from + * Java to JavaScript. + */ +Cordova.JSCallbackPolling = function() { + + // Exit if shutting down app + if (Cordova.shuttingDown) { + return; + } + + // If polling flag was changed, stop using polling from now on + if (!Cordova.UsePolling) { + Cordova.JSCallback(); + return; + } + + var msg = prompt("", "gap_poll:"); + if (msg) { + setTimeout(function() { + try { + var t = eval(""+msg); + } + catch (e) { + console.log("JSCallbackPolling: Message from Server: " + msg); + console.log("JSCallbackPolling Error: "+e); + } + }, 1); + setTimeout(Cordova.JSCallbackPolling, 1); + } + else { + setTimeout(Cordova.JSCallbackPolling, Cordova.JSCallbackPollingPeriod); + } +}; + +/** + * Create a UUID + * + * @return {String} + */ +Cordova.createUUID = function() { + return Cordova.UUIDcreatePart(4) + '-' + + Cordova.UUIDcreatePart(2) + '-' + + Cordova.UUIDcreatePart(2) + '-' + + Cordova.UUIDcreatePart(2) + '-' + + Cordova.UUIDcreatePart(6); +}; + +Cordova.UUIDcreatePart = function(length) { + var uuidpart = ""; + var i, uuidchar; + for (i=0; i frequency + 10 sec + Cordova.exec( + function(timeout) { + if (timeout < (frequency + 10000)) { + Cordova.exec(null, null, "Accelerometer", "setTimeout", [frequency + 10000]); + } + }, + function(e) { }, "Accelerometer", "getTimeout", []); + + // Start watch timer + var id = Cordova.createUUID(); + navigator.accelerometer.timers[id] = setInterval(function() { + Cordova.exec(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); + }, (frequency ? frequency : 1)); + + return id; +}; + +/** + * Clears the specified accelerometer watch. + * + * @param {String} id The id of the watch returned from #watchAcceleration. + */ +Accelerometer.prototype.clearWatch = function(id) { + + // Stop javascript timer & remove from timer list + if (id && navigator.accelerometer.timers[id] !== undefined) { + clearInterval(navigator.accelerometer.timers[id]); + delete navigator.accelerometer.timers[id]; + } +}; + +Cordova.addConstructor(function() { + if (typeof navigator.accelerometer === "undefined") { + navigator.accelerometer = new Accelerometer(); + } +}); +} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +if (!Cordova.hasResource("app")) { +Cordova.addResource("app"); +(function() { + +/** + * Constructor + * @constructor + */ +var App = function() {}; + +/** + * Clear the resource cache. + */ +App.prototype.clearCache = function() { + Cordova.exec(null, null, "App", "clearCache", []); +}; + +/** + * Load the url into the webview or into new browser instance. + * + * @param url The URL to load + * @param props Properties that can be passed in to the activity: + * wait: int => wait msec before loading URL + * loadingDialog: "Title,Message" => display a native loading dialog + * loadUrlTimeoutValue: int => time in msec to wait before triggering a timeout error + * clearHistory: boolean => clear webview history (default=false) + * openExternal: boolean => open in a new browser (default=false) + * + * Example: + * navigator.app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000}); + */ +App.prototype.loadUrl = function(url, props) { + Cordova.exec(null, null, "App", "loadUrl", [url, props]); +}; + +/** + * Cancel loadUrl that is waiting to be loaded. + */ +App.prototype.cancelLoadUrl = function() { + Cordova.exec(null, null, "App", "cancelLoadUrl", []); +}; + +/** + * Clear web history in this web view. + * Instead of BACK button loading the previous web page, it will exit the app. + */ +App.prototype.clearHistory = function() { + Cordova.exec(null, null, "App", "clearHistory", []); +}; + +/** + * Go to previous page displayed. + * This is the same as pressing the backbutton on Android device. + */ +App.prototype.backHistory = function() { + Cordova.exec(null, null, "App", "backHistory", []); +}; + +/** + * Exit and terminate the application. + */ +App.prototype.exitApp = function() { + return Cordova.exec(null, null, "App", "exitApp", []); +}; + +Cordova.addConstructor(function() { + navigator.app = new App(); +}); +}()); +} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +if (!Cordova.hasResource("battery")) { +Cordova.addResource("battery"); + +/** + * This class contains information about the current battery status. + * @constructor + */ +var Battery = function() { + this._level = null; + this._isPlugged = null; + this._batteryListener = []; + this._lowListener = []; + this._criticalListener = []; +}; + +/** + * Registers as an event producer for battery events. + * + * @param {Object} eventType + * @param {Object} handler + * @param {Object} add + */ +Battery.prototype.eventHandler = function(eventType, handler, add) { + var me = navigator.battery; + if (add) { + // If there are no current registered event listeners start the battery listener on native side. + if (me._batteryListener.length === 0 && me._lowListener.length === 0 && me._criticalListener.length === 0) { + Cordova.exec(me._status, me._error, "Battery", "start", []); + } + + // Register the event listener in the proper array + if (eventType === "batterystatus") { + if (me._batteryListener.indexOf(handler) === -1) { + me._batteryListener.push(handler); + } + } else if (eventType === "batterylow") { + if (me._lowListener.indexOf(handler) === -1) { + me._lowListener.push(handler); + } + } else if (eventType === "batterycritical") { + if (me._criticalListener.indexOf(handler) === -1) { + me._criticalListener.push(handler); + } + } + } else { + var pos = -1; + // Remove the event listener from the proper array + if (eventType === "batterystatus") { + pos = me._batteryListener.indexOf(handler); + if (pos > -1) { + me._batteryListener.splice(pos, 1); + } + } else if (eventType === "batterylow") { + pos = me._lowListener.indexOf(handler); + if (pos > -1) { + me._lowListener.splice(pos, 1); + } + } else if (eventType === "batterycritical") { + pos = me._criticalListener.indexOf(handler); + if (pos > -1) { + me._criticalListener.splice(pos, 1); + } + } + + // If there are no more registered event listeners stop the battery listener on native side. + if (me._batteryListener.length === 0 && me._lowListener.length === 0 && me._criticalListener.length === 0) { + Cordova.exec(null, null, "Battery", "stop", []); + } + } +}; + +/** + * Callback for battery status + * + * @param {Object} info keys: level, isPlugged + */ +Battery.prototype._status = function(info) { + if (info) { + var me = this; + var level = info.level; + if (me._level !== level || me._isPlugged !== info.isPlugged) { + // Fire batterystatus event + Cordova.fireWindowEvent("batterystatus", info); + + // Fire low battery event + if (level === 20 || level === 5) { + if (level === 20) { + Cordova.fireWindowEvent("batterylow", info); + } + else { + Cordova.fireWindowEvent("batterycritical", info); + } + } + } + me._level = level; + me._isPlugged = info.isPlugged; + } +}; + +/** + * Error callback for battery start + */ +Battery.prototype._error = function(e) { + console.log("Error initializing Battery: " + e); +}; + +Cordova.addConstructor(function() { + if (typeof navigator.battery === "undefined") { + navigator.battery = new Battery(); + Cordova.addWindowEventHandler("batterystatus", navigator.battery.eventHandler); + Cordova.addWindowEventHandler("batterylow", navigator.battery.eventHandler); + Cordova.addWindowEventHandler("batterycritical", navigator.battery.eventHandler); + } +}); +} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +if (!Cordova.hasResource("camera")) { +Cordova.addResource("camera"); + +/** + * This class provides access to the device camera. + * + * @constructor + */ +var Camera = function() { + this.successCallback = null; + this.errorCallback = null; + this.options = null; +}; + +/** + * Format of image that returned from getPicture. + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) + */ +Camera.DestinationType = { + DATA_URL: 0, // Return base64 encoded string + FILE_URI: 1 // Return file uri (content://media/external/images/media/2 for Android) +}; +Camera.prototype.DestinationType = Camera.DestinationType; + +/** + * Encoding of image returned from getPicture. + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * sourceType: Camera.PictureSourceType.CAMERA, + * encodingType: Camera.EncodingType.PNG}) +*/ +Camera.EncodingType = { + JPEG: 0, // Return JPEG encoded image + PNG: 1 // Return PNG encoded image +}; +Camera.prototype.EncodingType = Camera.EncodingType; + +/** + * Type of pictures to select from. Only applicable when + * PictureSourceType is PHOTOLIBRARY or SAVEDPHOTOALBUM + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * sourceType: Camera.PictureSourceType.PHOTOLIBRARY, + * mediaType: Camera.MediaType.PICTURE}) + */ +Camera.MediaType = { + PICTURE: 0, // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType + VIDEO: 1, // allow selection of video only, ONLY RETURNS URL + ALLMEDIA : 2 // allow selection from all media types +}; +Camera.prototype.MediaType = Camera.MediaType; + + +/** + * Source to getPicture from. + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) + */ +Camera.PictureSourceType = { + PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) + CAMERA : 1, // Take picture from camera + SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android) +}; +Camera.prototype.PictureSourceType = Camera.PictureSourceType; + +/** + * Gets a picture from source defined by "options.sourceType", and returns the + * image as defined by the "options.destinationType" option. + + * The defaults are sourceType=CAMERA and destinationType=DATA_URL. + * + * @param {Function} successCallback + * @param {Function} errorCallback + * @param {Object} options + */ +Camera.prototype.getPicture = function(successCallback, errorCallback, options) { + + // successCallback required + if (typeof successCallback !== "function") { + console.log("Camera Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback !== "function")) { + console.log("Camera Error: errorCallback is not a function"); + return; + } + + if (options === null || typeof options === "undefined") { + options = {}; + } + if (options.quality === null || typeof options.quality === "undefined") { + options.quality = 80; + } + if (options.maxResolution === null || typeof options.maxResolution === "undefined") { + options.maxResolution = 0; + } + if (options.destinationType === null || typeof options.destinationType === "undefined") { + options.destinationType = Camera.DestinationType.FILE_URI; + } + if (options.sourceType === null || typeof options.sourceType === "undefined") { + options.sourceType = Camera.PictureSourceType.CAMERA; + } + if (options.encodingType === null || typeof options.encodingType === "undefined") { + options.encodingType = Camera.EncodingType.JPEG; + } + if (options.mediaType === null || typeof options.mediaType === "undefined") { + options.mediaType = Camera.MediaType.PICTURE; + } + if (options.targetWidth === null || typeof options.targetWidth === "undefined") { + options.targetWidth = -1; + } + else if (typeof options.targetWidth === "string") { + var width = new Number(options.targetWidth); + if (isNaN(width) === false) { + options.targetWidth = width.valueOf(); + } + } + if (options.targetHeight === null || typeof options.targetHeight === "undefined") { + options.targetHeight = -1; + } + else if (typeof options.targetHeight === "string") { + var height = new Number(options.targetHeight); + if (isNaN(height) === false) { + options.targetHeight = height.valueOf(); + } + } + + Cordova.exec(successCallback, errorCallback, "Camera", "takePicture", [options]); +}; + +Cordova.addConstructor(function() { + if (typeof navigator.camera === "undefined") { + navigator.camera = new Camera(); + } +}); +} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +if (!Cordova.hasResource("capture")) { +Cordova.addResource("capture"); + +/** + * Represents a single file. + * + * name {DOMString} name of the file, without path information + * fullPath {DOMString} the full path of the file, including the name + * type {DOMString} mime type + * lastModifiedDate {Date} last modified date + * size {Number} size of the file in bytes + */ +var MediaFile = function(name, fullPath, type, lastModifiedDate, size){ + this.name = name || null; + this.fullPath = fullPath || null; + this.type = type || null; + this.lastModifiedDate = lastModifiedDate || null; + this.size = size || 0; +}; + +/** + * Launch device camera application for recording video(s). + * + * @param {Function} successCB + * @param {Function} errorCB + */ +MediaFile.prototype.getFormatData = function(successCallback, errorCallback){ + Cordova.exec(successCallback, errorCallback, "Capture", "getFormatData", [this.fullPath, this.type]); +}; + +/** + * MediaFileData encapsulates format information of a media file. + * + * @param {DOMString} codecs + * @param {long} bitrate + * @param {long} height + * @param {long} width + * @param {float} duration + */ +var MediaFileData = function(codecs, bitrate, height, width, duration){ + this.codecs = codecs || null; + this.bitrate = bitrate || 0; + this.height = height || 0; + this.width = width || 0; + this.duration = duration || 0; +}; + +/** + * The CaptureError interface encapsulates all errors in the Capture API. + */ +var CaptureError = function(){ + this.code = null; +}; + +// Capture error codes +CaptureError.CAPTURE_INTERNAL_ERR = 0; +CaptureError.CAPTURE_APPLICATION_BUSY = 1; +CaptureError.CAPTURE_INVALID_ARGUMENT = 2; +CaptureError.CAPTURE_NO_MEDIA_FILES = 3; +CaptureError.CAPTURE_NOT_SUPPORTED = 20; + +/** + * The Capture interface exposes an interface to the camera and microphone of the hosting device. + */ +var Capture = function(){ + this.supportedAudioModes = []; + this.supportedImageModes = []; + this.supportedVideoModes = []; +}; + +/** + * Launch audio recorder application for recording audio clip(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureAudioOptions} options + */ +Capture.prototype.captureAudio = function(successCallback, errorCallback, options){ + Cordova.exec(successCallback, errorCallback, "Capture", "captureAudio", [options]); +}; + +/** + * Launch camera application for taking image(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureImageOptions} options + */ +Capture.prototype.captureImage = function(successCallback, errorCallback, options){ + Cordova.exec(successCallback, errorCallback, "Capture", "captureImage", [options]); +}; + +/** + * Launch camera application for taking image(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureImageOptions} options + */ +Capture.prototype._castMediaFile = function(pluginResult){ + var mediaFiles = []; + var i; + for (i = 0; i < pluginResult.message.length; i++) { + var mediaFile = new MediaFile(); + mediaFile.name = pluginResult.message[i].name; + mediaFile.fullPath = pluginResult.message[i].fullPath; + mediaFile.type = pluginResult.message[i].type; + mediaFile.lastModifiedDate = pluginResult.message[i].lastModifiedDate; + mediaFile.size = pluginResult.message[i].size; + mediaFiles.push(mediaFile); + } + pluginResult.message = mediaFiles; + return pluginResult; +}; + +/** + * Launch device camera application for recording video(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureVideoOptions} options + */ +Capture.prototype.captureVideo = function(successCallback, errorCallback, options){ + Cordova.exec(successCallback, errorCallback, "Capture", "captureVideo", [options]); +}; + +/** + * Encapsulates a set of parameters that the capture device supports. + */ +var ConfigurationData = function(){ + // The ASCII-encoded string in lower case representing the media type. + this.type = null; + // The height attribute represents height of the image or video in pixels. + // In the case of a sound clip this attribute has value 0. + this.height = 0; + // The width attribute represents width of the image or video in pixels. + // In the case of a sound clip this attribute has value 0 + this.width = 0; +}; + +/** + * Encapsulates all image capture operation configuration options. + */ +var CaptureImageOptions = function(){ + // Upper limit of images user can take. Value must be equal or greater than 1. + this.limit = 1; + // The selected image mode. Must match with one of the elements in supportedImageModes array. + this.mode = null; +}; + +/** + * Encapsulates all video capture operation configuration options. + */ +var CaptureVideoOptions = function(){ + // Upper limit of videos user can record. Value must be equal or greater than 1. + this.limit = 1; + // Maximum duration of a single video clip in seconds. + this.duration = 0; + // The selected video mode. Must match with one of the elements in supportedVideoModes array. + this.mode = null; +}; + +/** + * Encapsulates all audio capture operation configuration options. + */ +var CaptureAudioOptions = function(){ + // Upper limit of sound clips user can record. Value must be equal or greater than 1. + this.limit = 1; + // Maximum duration of a single sound clip in seconds. + this.duration = 0; + // The selected audio mode. Must match with one of the elements in supportedAudioModes array. + this.mode = null; +}; + +Cordova.addConstructor(function(){ + if (typeof navigator.device.capture === "undefined") { + navigator.device.capture = window.device.capture = new Capture(); + } +}); +} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +if (!Cordova.hasResource("compass")) { +Cordova.addResource("compass"); + +var CompassError = function(){ + this.code = null; +}; + +// Capture error codes +CompassError.COMPASS_INTERNAL_ERR = 0; +CompassError.COMPASS_NOT_SUPPORTED = 20; + +var CompassHeading = function() { + this.magneticHeading = null; + this.trueHeading = null; + this.headingAccuracy = null; + this.timestamp = null; +}; + +/** + * This class provides access to device Compass data. + * @constructor + */ +var Compass = function() { + /** + * The last known Compass position. + */ + this.lastHeading = null; + + /** + * List of compass watch timers + */ + this.timers = {}; +}; + +Compass.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"]; + +/** + * Asynchronously aquires the current heading. + * + * @param {Function} successCallback The function to call when the heading data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) + * @param {PositionOptions} options The options for getting the heading data such as timeout. (OPTIONAL) + */ +Compass.prototype.getCurrentHeading = function(successCallback, errorCallback, options) { + + // successCallback required + if (typeof successCallback !== "function") { + console.log("Compass Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback !== "function")) { + console.log("Compass Error: errorCallback is not a function"); + return; + } + + // Get heading + Cordova.exec(successCallback, errorCallback, "Compass", "getHeading", []); +}; + +/** + * Asynchronously aquires the heading repeatedly at a given interval. + * + * @param {Function} successCallback The function to call each time the heading data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) + * @param {HeadingOptions} options The options for getting the heading data such as timeout and the frequency of the watch. (OPTIONAL) + * @return String The watch id that must be passed to #clearWatch to stop watching. + */ +Compass.prototype.watchHeading= function(successCallback, errorCallback, options) { + + // Default interval (100 msec) + var frequency = (options !== undefined) ? options.frequency : 100; + + // successCallback required + if (typeof successCallback !== "function") { + console.log("Compass Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback !== "function")) { + console.log("Compass Error: errorCallback is not a function"); + return; + } + + // Make sure compass timeout > frequency + 10 sec + Cordova.exec( + function(timeout) { + if (timeout < (frequency + 10000)) { + Cordova.exec(null, null, "Compass", "setTimeout", [frequency + 10000]); + } + }, + function(e) { }, "Compass", "getTimeout", []); + + // Start watch timer to get headings + var id = Cordova.createUUID(); + navigator.compass.timers[id] = setInterval( + function() { + Cordova.exec(successCallback, errorCallback, "Compass", "getHeading", []); + }, (frequency ? frequency : 1)); + + return id; +}; + + +/** + * Clears the specified heading watch. + * + * @param {String} id The ID of the watch returned from #watchHeading. + */ +Compass.prototype.clearWatch = function(id) { + + // Stop javascript timer & remove from timer list + if (id && navigator.compass.timers[id]) { + clearInterval(navigator.compass.timers[id]); + delete navigator.compass.timers[id]; + } +}; + +Compass.prototype._castDate = function(pluginResult) { + if (pluginResult.message.timestamp) { + var timestamp = new Date(pluginResult.message.timestamp); + pluginResult.message.timestamp = timestamp; + } + return pluginResult; +}; + +Cordova.addConstructor(function() { + if (typeof navigator.compass === "undefined") { + navigator.compass = new Compass(); + } +}); +} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +if (!Cordova.hasResource("contact")) { +Cordova.addResource("contact"); + +/** +* Contains information about a single contact. +* @constructor +* @param {DOMString} id unique identifier +* @param {DOMString} displayName +* @param {ContactName} name +* @param {DOMString} nickname +* @param {Array.} phoneNumbers array of phone numbers +* @param {Array.} emails array of email addresses +* @param {Array.} addresses array of addresses +* @param {Array.} ims instant messaging user ids +* @param {Array.} organizations +* @param {DOMString} birthday contact's birthday +* @param {DOMString} note user notes about contact +* @param {Array.} photos +* @param {Array.} categories +* @param {Array.} urls contact's web sites +*/ +var Contact = function (id, displayName, name, nickname, phoneNumbers, emails, addresses, + ims, organizations, birthday, note, photos, categories, urls) { + this.id = id || null; + this.rawId = null; + this.displayName = displayName || null; + this.name = name || null; // ContactName + this.nickname = nickname || null; + this.phoneNumbers = phoneNumbers || null; // ContactField[] + this.emails = emails || null; // ContactField[] + this.addresses = addresses || null; // ContactAddress[] + this.ims = ims || null; // ContactField[] + this.organizations = organizations || null; // ContactOrganization[] + this.birthday = birthday || null; + this.note = note || null; + this.photos = photos || null; // ContactField[] + this.categories = categories || null; // ContactField[] + this.urls = urls || null; // ContactField[] +}; + +/** + * ContactError. + * An error code assigned by an implementation when an error has occurreds + * @constructor + */ +var ContactError = function() { + this.code=null; +}; + +/** + * Error codes + */ +ContactError.UNKNOWN_ERROR = 0; +ContactError.INVALID_ARGUMENT_ERROR = 1; +ContactError.TIMEOUT_ERROR = 2; +ContactError.PENDING_OPERATION_ERROR = 3; +ContactError.IO_ERROR = 4; +ContactError.NOT_SUPPORTED_ERROR = 5; +ContactError.PERMISSION_DENIED_ERROR = 20; + +/** +* Removes contact from device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.remove = function(successCB, errorCB) { + if (this.id === null) { + var errorObj = new ContactError(); + errorObj.code = ContactError.UNKNOWN_ERROR; + errorCB(errorObj); + } + else { + Cordova.exec(successCB, errorCB, "Contacts", "remove", [this.id]); + } +}; + +/** +* Creates a deep copy of this Contact. +* With the contact ID set to null. +* @return copy of this Contact +*/ +Contact.prototype.clone = function() { + var clonedContact = Cordova.clone(this); + var i; + clonedContact.id = null; + clonedContact.rawId = null; + // Loop through and clear out any id's in phones, emails, etc. + if (clonedContact.phoneNumbers) { + for (i = 0; i < clonedContact.phoneNumbers.length; i++) { + clonedContact.phoneNumbers[i].id = null; + } + } + if (clonedContact.emails) { + for (i = 0; i < clonedContact.emails.length; i++) { + clonedContact.emails[i].id = null; + } + } + if (clonedContact.addresses) { + for (i = 0; i < clonedContact.addresses.length; i++) { + clonedContact.addresses[i].id = null; + } + } + if (clonedContact.ims) { + for (i = 0; i < clonedContact.ims.length; i++) { + clonedContact.ims[i].id = null; + } + } + if (clonedContact.organizations) { + for (i = 0; i < clonedContact.organizations.length; i++) { + clonedContact.organizations[i].id = null; + } + } + if (clonedContact.tags) { + for (i = 0; i < clonedContact.tags.length; i++) { + clonedContact.tags[i].id = null; + } + } + if (clonedContact.photos) { + for (i = 0; i < clonedContact.photos.length; i++) { + clonedContact.photos[i].id = null; + } + } + if (clonedContact.urls) { + for (i = 0; i < clonedContact.urls.length; i++) { + clonedContact.urls[i].id = null; + } + } + return clonedContact; +}; + +/** +* Persists contact to device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.save = function(successCB, errorCB) { + Cordova.exec(successCB, errorCB, "Contacts", "save", [this]); +}; + +/** +* Contact name. +* @constructor +* @param formatted +* @param familyName +* @param givenName +* @param middle +* @param prefix +* @param suffix +*/ +var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) { + this.formatted = formatted || null; + this.familyName = familyName || null; + this.givenName = givenName || null; + this.middleName = middle || null; + this.honorificPrefix = prefix || null; + this.honorificSuffix = suffix || null; +}; + +/** +* Generic contact field. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code +* @param type +* @param value +* @param pref +*/ +var ContactField = function(type, value, pref) { + this.id = null; + this.type = type || null; + this.value = value || null; + this.pref = pref || null; +}; + +/** +* Contact address. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code +* @param formatted +* @param streetAddress +* @param locality +* @param region +* @param postalCode +* @param country +*/ +var ContactAddress = function(pref, type, formatted, streetAddress, locality, region, postalCode, country) { + this.id = null; + this.pref = pref || null; + this.type = type || null; + this.formatted = formatted || null; + this.streetAddress = streetAddress || null; + this.locality = locality || null; + this.region = region || null; + this.postalCode = postalCode || null; + this.country = country || null; +}; + +/** +* Contact organization. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code +* @param name +* @param dept +* @param title +* @param startDate +* @param endDate +* @param location +* @param desc +*/ +var ContactOrganization = function(pref, type, name, dept, title) { + this.id = null; + this.pref = pref || null; + this.type = type || null; + this.name = name || null; + this.department = dept || null; + this.title = title || null; +}; + +/** +* Represents a group of Contacts. +* @constructor +*/ +var Contacts = function() { + this.inProgress = false; + this.records = []; +}; +/** +* Returns an array of Contacts matching the search criteria. +* @param fields that should be searched +* @param successCB success callback +* @param errorCB error callback +* @param {ContactFindOptions} options that can be applied to contact searching +* @return array of Contacts matching search criteria +*/ +Contacts.prototype.find = function(fields, successCB, errorCB, options) { + if (successCB === null) { + throw new TypeError("You must specify a success callback for the find command."); + } + if (fields === null || fields === "undefined" || fields.length === "undefined" || fields.length <= 0) { + if (typeof errorCB === "function") { + errorCB({"code": ContactError.INVALID_ARGUMENT_ERROR}); + } + } else { + Cordova.exec(successCB, errorCB, "Contacts", "search", [fields, options]); + } +}; + +/** +* This function creates a new contact, but it does not persist the contact +* to device storage. To persist the contact to device storage, invoke +* contact.save(). +* @param properties an object who's properties will be examined to create a new Contact +* @returns new Contact object +*/ +Contacts.prototype.create = function(properties) { + var i; + var contact = new Contact(); + for (i in properties) { + if (contact[i] !== 'undefined') { + contact[i] = properties[i]; + } + } + return contact; +}; + +/** +* This function returns and array of contacts. It is required as we need to convert raw +* JSON objects into concrete Contact objects. Currently this method is called after +* navigator.contacts.find but before the find methods success call back. +* +* @param jsonArray an array of JSON Objects that need to be converted to Contact objects. +* @returns an array of Contact objects +*/ +Contacts.prototype.cast = function(pluginResult) { + var contacts = []; + var i; + for (i=0; i][;base64], + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsDataURL = function(file) { + this.fileName = ""; + if (typeof file.fullPath === "undefined") { + this.fileName = file; + } else { + this.fileName = file.fullPath; + } + + // LOADING state + this.readyState = FileReader.LOADING; + + // If loadstart callback + if (typeof this.onloadstart === "function") { + this.onloadstart({"type":"loadstart", "target":this}); + } + + var me = this; + + // Read file + Cordova.exec( + // Success callback + function(r) { + var evt; + + // If DONE (cancelled), then don't do anything + if (me.readyState === FileReader.DONE) { + return; + } + + // Save result + me.result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload({"type":"load", "target":me}); + } + + // DONE state + me.readyState = FileReader.DONE; + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend({"type":"loadend", "target":me}); + } + }, + // Error callback + function(e) { + var evt; + // If DONE (cancelled), then don't do anything + if (me.readyState === FileReader.DONE) { + return; + } + + // Save error + me.error = e; + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror({"type":"error", "target":me}); + } + + // DONE state + me.readyState = FileReader.DONE; + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend({"type":"loadend", "target":me}); + } + }, "File", "readAsDataURL", [this.fileName]); +}; + +/** + * Read file and return data as a binary data. + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsBinaryString = function(file) { + // TODO - Can't return binary data to browser. + this.fileName = file; +}; + +/** + * Read file and return data as a binary data. + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsArrayBuffer = function(file) { + // TODO - Can't return binary data to browser. + this.fileName = file; +}; + +//----------------------------------------------------------------------------- +// File Writer +//----------------------------------------------------------------------------- + +/** + * This class writes to the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To write to the SD card, the file name is "sdcard/my_file.txt" + * + * @constructor + * @param file {File} File object containing file properties + * @param append if true write to the end of the file, otherwise overwrite the file + */ +var FileWriter = function(file) { + this.fileName = ""; + this.length = 0; + if (file) { + this.fileName = file.fullPath || file; + this.length = file.size || 0; + } + // default is to write at the beginning of the file + this.position = 0; + + this.readyState = 0; // EMPTY + + this.result = null; + + // Error + this.error = null; + + // Event handlers + this.onwritestart = null; // When writing starts + this.onprogress = null; // While writing the file, and reporting partial file data + this.onwrite = null; // When the write has successfully completed. + this.onwriteend = null; // When the request has completed (either in success or failure). + this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method. + this.onerror = null; // When the write has failed (see errors). +}; + +// States +FileWriter.INIT = 0; +FileWriter.WRITING = 1; +FileWriter.DONE = 2; + +/** + * Abort writing file. + */ +FileWriter.prototype.abort = function() { + // check for invalid state + if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) { + throw FileError.INVALID_STATE_ERR; + } + + // set error + var error = new FileError(), evt; + error.code = error.ABORT_ERR; + this.error = error; + + // If error callback + if (typeof this.onerror === "function") { + this.onerror({"type":"error", "target":this}); + } + // If abort callback + if (typeof this.onabort === "function") { + this.onabort({"type":"abort", "target":this}); + } + + this.readyState = FileWriter.DONE; + + // If write end callback + if (typeof this.onwriteend === "function") { + this.onwriteend({"type":"writeend", "target":this}); + } +}; + +/** + * Writes data to the file + * + * @param text to be written + */ +FileWriter.prototype.write = function(text) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw FileError.INVALID_STATE_ERR; + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart === "function") { + me.onwritestart({"type":"writestart", "target":me}); + } + + // Write file + Cordova.exec( + // Success callback + function(r) { + var evt; + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // position always increases by bytes written because file would be extended + me.position += r; + // The length of the file is now where we are done writing. + me.length = me.position; + + // If onwrite callback + if (typeof me.onwrite === "function") { + me.onwrite({"type":"write", "target":me}); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend({"type":"writeend", "target":me}); + } + }, + // Error callback + function(e) { + var evt; + + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // Save error + me.error = e; + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror({"type":"error", "target":me}); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend({"type":"writeend", "target":me}); + } + }, "File", "write", [this.fileName, text, this.position]); +}; + +/** + * Moves the file pointer to the location specified. + * + * If the offset is a negative number the position of the file + * pointer is rewound. If the offset is greater than the file + * size the position is set to the end of the file. + * + * @param offset is the location to move the file pointer to. + */ +FileWriter.prototype.seek = function(offset) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw FileError.INVALID_STATE_ERR; + } + + if (!offset) { + return; + } + + // See back from end of file. + if (offset < 0) { + this.position = Math.max(offset + this.length, 0); + } + // Offset is bigger then file size so set position + // to the end of the file. + else if (offset > this.length) { + this.position = this.length; + } + // Offset is between 0 and file size so set the position + // to start writing. + else { + this.position = offset; + } +}; + +/** + * Truncates the file to the size specified. + * + * @param size to chop the file at. + */ +FileWriter.prototype.truncate = function(size) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw FileError.INVALID_STATE_ERR; + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart === "function") { + me.onwritestart({"type":"writestart", "target":this}); + } + + // Write file + Cordova.exec( + // Success callback + function(r) { + var evt; + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // Update the length of the file + me.length = r; + me.position = Math.min(me.position, r); + + // If onwrite callback + if (typeof me.onwrite === "function") { + me.onwrite({"type":"write", "target":me}); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend({"type":"writeend", "target":me}); + } + }, + // Error callback + function(e) { + var evt; + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // Save error + me.error = e; + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror({"type":"error", "target":me}); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend({"type":"writeend", "target":me}); + } + }, "File", "truncate", [this.fileName, size]); +}; + +/** + * Information about the state of the file or directory + * + * @constructor + * {Date} modificationTime (readonly) + */ +var Metadata = function() { + this.modificationTime=null; +}; + +/** + * Supplies arguments to methods that lookup or create files and directories + * + * @constructor + * @param {boolean} create file or directory if it doesn't exist + * @param {boolean} exclusive if true the command will fail if the file or directory exists + */ +var Flags = function(create, exclusive) { + this.create = create || false; + this.exclusive = exclusive || false; +}; + +/** + * An interface representing a file system + * + * @constructor + * {DOMString} name the unique name of the file system (readonly) + * {DirectoryEntry} root directory of the file system (readonly) + */ +var FileSystem = function() { + this.name = null; + this.root = null; +}; + +/** + * An interface that lists the files and directories in a directory. + * @constructor + */ +var DirectoryReader = function(fullPath){ + this.fullPath = fullPath || null; +}; + +/** + * Returns a list of entries from a directory. + * + * @param {Function} successCallback is called with a list of entries + * @param {Function} errorCallback is called with a FileError + */ +DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) { + Cordova.exec(successCallback, errorCallback, "File", "readEntries", [this.fullPath]); +}; + +/** + * An interface representing a directory on the file system. + * + * @constructor + * {boolean} isFile always false (readonly) + * {boolean} isDirectory always true (readonly) + * {DOMString} name of the directory, excluding the path leading to it (readonly) + * {DOMString} fullPath the absolute full path to the directory (readonly) + * {FileSystem} filesystem on which the directory resides (readonly) + */ +var DirectoryEntry = function() { + this.isFile = false; + this.isDirectory = true; + this.name = null; + this.fullPath = null; + this.filesystem = null; +}; + +/** + * Copies a directory to a new location + * + * @param {DirectoryEntry} parent the directory to which to copy the entry + * @param {DOMString} newName the new name of the entry, defaults to the current name + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) { + Cordova.exec(successCallback, errorCallback, "File", "copyTo", [this.fullPath, parent, newName]); +}; + +/** + * Looks up the metadata of the entry + * + * @param {Function} successCallback is called with a Metadata object + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getMetadata = function(successCallback, errorCallback) { + Cordova.exec(successCallback, errorCallback, "File", "getMetadata", [this.fullPath]); +}; + +/** + * Gets the parent of the entry + * + * @param {Function} successCallback is called with a parent entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getParent = function(successCallback, errorCallback) { + Cordova.exec(successCallback, errorCallback, "File", "getParent", [this.fullPath]); +}; + +/** + * Moves a directory to a new location + * + * @param {DirectoryEntry} parent the directory to which to move the entry + * @param {DOMString} newName the new name of the entry, defaults to the current name + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) { + Cordova.exec(successCallback, errorCallback, "File", "moveTo", [this.fullPath, parent, newName]); +}; + +/** + * Removes the entry + * + * @param {Function} successCallback is called with no parameters + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.remove = function(successCallback, errorCallback) { + Cordova.exec(successCallback, errorCallback, "File", "remove", [this.fullPath]); +}; + +/** + * Returns a URI that can be used to identify this entry. + * + * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI. + * @return uri + */ +DirectoryEntry.prototype.toURI = function(mimeType) { + return "file://" + this.fullPath; +}; + +/** + * Creates a new DirectoryReader to read entries from this directory + */ +DirectoryEntry.prototype.createReader = function(successCallback, errorCallback) { + return new DirectoryReader(this.fullPath); +}; + +/** + * Creates or looks up a directory + * + * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory + * @param {Flags} options to create or excluively create the directory + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) { + Cordova.exec(successCallback, errorCallback, "File", "getDirectory", [this.fullPath, path, options]); +}; + +/** + * Creates or looks up a file + * + * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file + * @param {Flags} options to create or excluively create the file + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) { + Cordova.exec(successCallback, errorCallback, "File", "getFile", [this.fullPath, path, options]); +}; + +/** + * Deletes a directory and all of it's contents + * + * @param {Function} successCallback is called with no parameters + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) { + Cordova.exec(successCallback, errorCallback, "File", "removeRecursively", [this.fullPath]); +}; + +/** + * An interface representing a directory on the file system. + * + * @constructor + * {boolean} isFile always true (readonly) + * {boolean} isDirectory always false (readonly) + * {DOMString} name of the file, excluding the path leading to it (readonly) + * {DOMString} fullPath the absolute full path to the file (readonly) + * {FileSystem} filesystem on which the directory resides (readonly) + */ +var FileEntry = function() { + this.isFile = true; + this.isDirectory = false; + this.name = null; + this.fullPath = null; + this.filesystem = null; +}; + +/** + * Copies a file to a new location + * + * @param {DirectoryEntry} parent the directory to which to copy the entry + * @param {DOMString} newName the new name of the entry, defaults to the current name + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) { + Cordova.exec(successCallback, errorCallback, "File", "copyTo", [this.fullPath, parent, newName]); +}; + +/** + * Looks up the metadata of the entry + * + * @param {Function} successCallback is called with a Metadata object + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.getMetadata = function(successCallback, errorCallback) { + Cordova.exec(successCallback, errorCallback, "File", "getMetadata", [this.fullPath]); +}; + +/** + * Gets the parent of the entry + * + * @param {Function} successCallback is called with a parent entry + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.getParent = function(successCallback, errorCallback) { + Cordova.exec(successCallback, errorCallback, "File", "getParent", [this.fullPath]); +}; + +/** + * Moves a directory to a new location + * + * @param {DirectoryEntry} parent the directory to which to move the entry + * @param {DOMString} newName the new name of the entry, defaults to the current name + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) { + Cordova.exec(successCallback, errorCallback, "File", "moveTo", [this.fullPath, parent, newName]); +}; + +/** + * Removes the entry + * + * @param {Function} successCallback is called with no parameters + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.remove = function(successCallback, errorCallback) { + Cordova.exec(successCallback, errorCallback, "File", "remove", [this.fullPath]); +}; + +/** + * Returns a URI that can be used to identify this entry. + * + * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI. + * @return uri + */ +FileEntry.prototype.toURI = function(mimeType) { + return "file://" + this.fullPath; +}; + +/** + * Creates a new FileWriter associated with the file that this FileEntry represents. + * + * @param {Function} successCallback is called with the new FileWriter + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.createWriter = function(successCallback, errorCallback) { + this.file(function(filePointer) { + var writer = new FileWriter(filePointer); + + if (writer.fileName === null || writer.fileName === "") { + if (typeof errorCallback === "function") { + errorCallback({ + "code": FileError.INVALID_STATE_ERR + }); + } + } + + if (typeof successCallback === "function") { + successCallback(writer); + } + }, errorCallback); +}; + +/** + * Returns a File that represents the current state of the file that this FileEntry represents. + * + * @param {Function} successCallback is called with the new File object + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.file = function(successCallback, errorCallback) { + Cordova.exec(successCallback, errorCallback, "File", "getFileMetadata", [this.fullPath]); +}; + +/** @constructor */ +var LocalFileSystem = function() { +}; + +// File error codes +LocalFileSystem.TEMPORARY = 0; +LocalFileSystem.PERSISTENT = 1; +LocalFileSystem.RESOURCE = 2; +LocalFileSystem.APPLICATION = 3; + +/** + * Requests a filesystem in which to store application data. + * + * @param {int} type of file system being requested + * @param {Function} successCallback is called with the new FileSystem + * @param {Function} errorCallback is called with a FileError + */ +LocalFileSystem.prototype.requestFileSystem = function(type, size, successCallback, errorCallback) { + if (type < 0 || type > 3) { + if (typeof errorCallback === "function") { + errorCallback({ + "code": FileError.SYNTAX_ERR + }); + } + } + else { + Cordova.exec(successCallback, errorCallback, "File", "requestFileSystem", [type, size]); + } +}; + +/** + * + * @param {DOMString} uri referring to a local file in a filesystem + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +LocalFileSystem.prototype.resolveLocalFileSystemURI = function(uri, successCallback, errorCallback) { + Cordova.exec(successCallback, errorCallback, "File", "resolveLocalFileSystemURI", [uri]); +}; + +/** +* This function returns and array of contacts. It is required as we need to convert raw +* JSON objects into concrete Contact objects. Currently this method is called after +* navigator.service.contacts.find but before the find methods success call back. +* +* @param a JSON Objects that need to be converted to DirectoryEntry or FileEntry objects. +* @returns an entry +*/ +LocalFileSystem.prototype._castFS = function(pluginResult) { + var entry = null; + entry = new DirectoryEntry(); + entry.isDirectory = pluginResult.message.root.isDirectory; + entry.isFile = pluginResult.message.root.isFile; + entry.name = pluginResult.message.root.name; + entry.fullPath = pluginResult.message.root.fullPath; + pluginResult.message.root = entry; + return pluginResult; +}; + +LocalFileSystem.prototype._castEntry = function(pluginResult) { + var entry = null; + if (pluginResult.message.isDirectory) { + entry = new DirectoryEntry(); + } + else if (pluginResult.message.isFile) { + entry = new FileEntry(); + } + entry.isDirectory = pluginResult.message.isDirectory; + entry.isFile = pluginResult.message.isFile; + entry.name = pluginResult.message.name; + entry.fullPath = pluginResult.message.fullPath; + pluginResult.message = entry; + return pluginResult; +}; + +LocalFileSystem.prototype._castEntries = function(pluginResult) { + var entries = pluginResult.message; + var retVal = []; + for (var i=0; i + * + * @param name The plugin name + * @param obj The plugin object + */ +PhoneGap.addPlugin = function(name, obj) { + if (!window.plugins[name]) { + window.plugins[name] = obj; + } + else { + console.log("Error: Plugin "+name+" already exists."); + } +}; + +/** + * onDOMContentLoaded channel is fired when the DOM content + * of the page has been parsed. + */ +PhoneGap.onDOMContentLoaded = new PhoneGap.Channel('onDOMContentLoaded'); + +/** + * onNativeReady channel is fired when the PhoneGap native code + * has been initialized. + */ +PhoneGap.onNativeReady = new PhoneGap.Channel('onNativeReady'); + +/** + * onPhoneGapInit channel is fired when the web page is fully loaded and + * PhoneGap native code has been initialized. + */ +PhoneGap.onPhoneGapInit = new PhoneGap.Channel('onPhoneGapInit'); + +/** + * onPhoneGapReady channel is fired when the JS PhoneGap objects have been created. + */ +PhoneGap.onPhoneGapReady = new PhoneGap.Channel('onPhoneGapReady'); + +/** + * onPhoneGapInfoReady channel is fired when the PhoneGap device properties + * has been set. + */ +PhoneGap.onPhoneGapInfoReady = new PhoneGap.Channel('onPhoneGapInfoReady'); + +/** + * onPhoneGapConnectionReady channel is fired when the PhoneGap connection properties + * has been set. + */ +PhoneGap.onPhoneGapConnectionReady = new PhoneGap.Channel('onPhoneGapConnectionReady'); + +/** + * onDestroy channel is fired when the PhoneGap native code + * is destroyed. It is used internally. + * Window.onunload should be used by the user. + */ +PhoneGap.onDestroy = new PhoneGap.Channel('onDestroy'); +PhoneGap.onDestroy.subscribeOnce(function() { + PhoneGap.shuttingDown = true; +}); +PhoneGap.shuttingDown = false; + +// _nativeReady is global variable that the native side can set +// to signify that the native code is ready. It is a global since +// it may be called before any PhoneGap JS is ready. +if (typeof _nativeReady !== 'undefined') { PhoneGap.onNativeReady.fire(); } + +/** + * onDeviceReady is fired only after all PhoneGap objects are created and + * the device properties are set. + */ +PhoneGap.onDeviceReady = new PhoneGap.Channel('onDeviceReady'); + + +// Array of channels that must fire before "deviceready" is fired +PhoneGap.deviceReadyChannelsArray = [ PhoneGap.onPhoneGapReady, PhoneGap.onPhoneGapInfoReady, PhoneGap.onPhoneGapConnectionReady]; + +// Hashtable of user defined channels that must also fire before "deviceready" is fired +PhoneGap.deviceReadyChannelsMap = {}; + +/** + * Indicate that a feature needs to be initialized before it is ready to be used. + * This holds up PhoneGap's "deviceready" event until the feature has been initialized + * and PhoneGap.initComplete(feature) is called. + * + * @param feature {String} The unique feature name + */ +PhoneGap.waitForInitialization = function(feature) { + if (feature) { + var channel = new PhoneGap.Channel(feature); + PhoneGap.deviceReadyChannelsMap[feature] = channel; + PhoneGap.deviceReadyChannelsArray.push(channel); + } +}; + +/** + * Indicate that initialization code has completed and the feature is ready to be used. + * + * @param feature {String} The unique feature name + */ +PhoneGap.initializationComplete = function(feature) { + var channel = PhoneGap.deviceReadyChannelsMap[feature]; + if (channel) { + channel.fire(); + } +}; + +/** + * Create all PhoneGap objects once page has fully loaded and native side is ready. + */ +PhoneGap.Channel.join(function() { + + // Start listening for XHR callbacks + setTimeout(function() { + if (PhoneGap.UsePolling) { + PhoneGap.JSCallbackPolling(); + } + else { + var polling = prompt("usePolling", "gap_callbackServer:"); + PhoneGap.UsePolling = polling; + if (polling == "true") { + PhoneGap.UsePolling = true; + PhoneGap.JSCallbackPolling(); + } + else { + PhoneGap.UsePolling = false; + PhoneGap.JSCallback(); + } + } + }, 1); + + // Run PhoneGap constructors + PhoneGap.onPhoneGapInit.fire(); + + // Fire event to notify that all objects are created + PhoneGap.onPhoneGapReady.fire(); + + // Fire onDeviceReady event once all constructors have run and PhoneGap info has been + // received from native side, and any user defined initialization channels. + PhoneGap.Channel.join(function() { + // Let native code know we are inited on JS side + prompt("", "gap_init:"); + + PhoneGap.onDeviceReady.fire(); + }, PhoneGap.deviceReadyChannelsArray); + +}, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]); + +// Listen for DOMContentLoaded and notify our channel subscribers +document.addEventListener('DOMContentLoaded', function() { + PhoneGap.onDOMContentLoaded.fire(); +}, false); + +// Intercept calls to document.addEventListener and watch for deviceready +PhoneGap.m_document_addEventListener = document.addEventListener; + +// Intercept calls to window.addEventListener +PhoneGap.m_window_addEventListener = window.addEventListener; + +/** + * Add a custom window event handler. + * + * @param {String} event The event name that callback handles + * @param {Function} callback The event handler + */ +PhoneGap.addWindowEventHandler = function(event, callback) { + PhoneGap.windowEventHandler[event] = callback; +}; + +/** + * Add a custom document event handler. + * + * @param {String} event The event name that callback handles + * @param {Function} callback The event handler + */ +PhoneGap.addDocumentEventHandler = function(event, callback) { + PhoneGap.documentEventHandler[event] = callback; +}; + +/** + * Intercept adding document event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +document.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + if (e === 'deviceready') { + PhoneGap.onDeviceReady.subscribeOnce(handler); + } + else { + // If subscribing to Android backbutton + if (e === 'backbutton') { + PhoneGap.exec(null, null, "App", "overrideBackbutton", [true]); + } + + // If subscribing to an event that is handled by a plugin + else if (typeof PhoneGap.documentEventHandler[e] !== "undefined") { + if (PhoneGap.documentEventHandler[e](e, handler, true)) { + return; // Stop default behavior + } + } + + PhoneGap.m_document_addEventListener.call(document, evt, handler, capture); + } +}; + +/** + * Intercept adding window event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +window.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If subscribing to an event that is handled by a plugin + if (typeof PhoneGap.windowEventHandler[e] !== "undefined") { + if (PhoneGap.windowEventHandler[e](e, handler, true)) { + return; // Stop default behavior + } + } + + PhoneGap.m_window_addEventListener.call(window, evt, handler, capture); +}; + +// Intercept calls to document.removeEventListener and watch for events that +// are generated by PhoneGap native code +PhoneGap.m_document_removeEventListener = document.removeEventListener; + +// Intercept calls to window.removeEventListener +PhoneGap.m_window_removeEventListener = window.removeEventListener; + +/** + * Intercept removing document event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +document.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If unsubscribing to Android backbutton + if (e === 'backbutton') { + PhoneGap.exec(null, null, "App", "overrideBackbutton", [false]); + } + + // If unsubcribing from an event that is handled by a plugin + if (typeof PhoneGap.documentEventHandler[e] !== "undefined") { + if (PhoneGap.documentEventHandler[e](e, handler, false)) { + return; // Stop default behavior + } + } + + PhoneGap.m_document_removeEventListener.call(document, evt, handler, capture); +}; + +/** + * Intercept removing window event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +window.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If unsubcribing from an event that is handled by a plugin + if (typeof PhoneGap.windowEventHandler[e] !== "undefined") { + if (PhoneGap.windowEventHandler[e](e, handler, false)) { + return; // Stop default behavior + } + } + + PhoneGap.m_window_removeEventListener.call(window, evt, handler, capture); +}; + +/** + * Method to fire document event + * + * @param {String} type The event type to fire + * @param {Object} data Data to send with event + */ +PhoneGap.fireDocumentEvent = function(type, data) { + var e = document.createEvent('Events'); + e.initEvent(type); + if (data) { + for (var i in data) { + e[i] = data[i]; + } + } + document.dispatchEvent(e); +}; + +/** + * Method to fire window event + * + * @param {String} type The event type to fire + * @param {Object} data Data to send with event + */ +PhoneGap.fireWindowEvent = function(type, data) { + var e = document.createEvent('Events'); + e.initEvent(type); + if (data) { + for (var i in data) { + e[i] = data[i]; + } + } + window.dispatchEvent(e); +}; + +/** + * Does a deep clone of the object. + * + * @param obj + * @return {Object} + */ +PhoneGap.clone = function(obj) { + var i, retVal; + if(!obj) { + return obj; + } + + if(obj instanceof Array){ + retVal = []; + for(i = 0; i < obj.length; ++i){ + retVal.push(PhoneGap.clone(obj[i])); + } + return retVal; + } + + if (typeof obj === "function") { + return obj; + } + + if(!(obj instanceof Object)){ + return obj; + } + + if (obj instanceof Date) { + return obj; + } + + retVal = {}; + for(i in obj){ + if(!(i in retVal) || retVal[i] !== obj[i]) { + retVal[i] = PhoneGap.clone(obj[i]); + } + } + return retVal; +}; + +PhoneGap.callbackId = 0; +PhoneGap.callbacks = {}; +PhoneGap.callbackStatus = { + NO_RESULT: 0, + OK: 1, + CLASS_NOT_FOUND_EXCEPTION: 2, + ILLEGAL_ACCESS_EXCEPTION: 3, + INSTANTIATION_EXCEPTION: 4, + MALFORMED_URL_EXCEPTION: 5, + IO_EXCEPTION: 6, + INVALID_ACTION: 7, + JSON_EXCEPTION: 8, + ERROR: 9 + }; + + +/** + * Execute a PhoneGap command. It is up to the native side whether this action is synch or async. + * The native side can return: + * Synchronous: PluginResult object as a JSON string + * Asynchrounous: Empty string "" + * If async, the native side will PhoneGap.callbackSuccess or PhoneGap.callbackError, + * depending upon the result of the action. + * + * @param {Function} success The success callback + * @param {Function} fail The fail callback + * @param {String} service The name of the service to use + * @param {String} action Action to be run in PhoneGap + * @param {Array.} [args] Zero or more arguments to pass to the method + */ +PhoneGap.exec = function(success, fail, service, action, args) { + try { + var callbackId = service + PhoneGap.callbackId++; + if (success || fail) { + PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; + } + + var r = prompt(JSON.stringify(args), "gap:"+JSON.stringify([service, action, callbackId, true])); + + // If a result was returned + if (r.length > 0) { + eval("var v="+r+";"); + + // If status is OK, then return value back to caller + if (v.status === PhoneGap.callbackStatus.OK) { + + // If there is a success callback, then call it now with + // returned value + if (success) { + try { + success(v.message); + } catch (e) { + console.log("Error in success callback: " + callbackId + " = " + e); + } + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } + return v.message; + } + + // If no result + else if (v.status === PhoneGap.callbackStatus.NO_RESULT) { + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } + + // If error, then display error + else { + console.log("Error: Status="+v.status+" Message="+v.message); + + // If there is a fail callback, then call it now with returned value + if (fail) { + try { + fail(v.message); + } + catch (e1) { + console.log("Error in error callback: "+callbackId+" = "+e1); + } + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } + return null; + } + } + } catch (e2) { + console.log("Error: "+e2); + } +}; + +/** + * Called by native code when returning successful result from an action. + * + * @param callbackId + * @param args + */ +PhoneGap.callbackSuccess = function(callbackId, args) { + if (PhoneGap.callbacks[callbackId]) { + + // If result is to be sent to callback + if (args.status === PhoneGap.callbackStatus.OK) { + try { + if (PhoneGap.callbacks[callbackId].success) { + PhoneGap.callbacks[callbackId].success(args.message); + } + } + catch (e) { + console.log("Error in success callback: "+callbackId+" = "+e); + } + } + + // Clear callback if not expecting any more results + if (!args.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } +}; + +/** + * Called by native code when returning error result from an action. + * + * @param callbackId + * @param args + */ +PhoneGap.callbackError = function(callbackId, args) { + if (PhoneGap.callbacks[callbackId]) { + try { + if (PhoneGap.callbacks[callbackId].fail) { + PhoneGap.callbacks[callbackId].fail(args.message); + } + } + catch (e) { + console.log("Error in error callback: "+callbackId+" = "+e); + } + + // Clear callback if not expecting any more results + if (!args.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } +}; + +PhoneGap.JSCallbackPort = null; +PhoneGap.JSCallbackToken = null; + +/** + * This is only for Android. + * + * Internal function that uses XHR to call into PhoneGap Java code and retrieve + * any JavaScript code that needs to be run. This is used for callbacks from + * Java to JavaScript. + */ +PhoneGap.JSCallback = function() { + + // Exit if shutting down app + if (PhoneGap.shuttingDown) { + return; + } + + // If polling flag was changed, start using polling from now on + if (PhoneGap.UsePolling) { + PhoneGap.JSCallbackPolling(); + return; + } + + var xmlhttp = new XMLHttpRequest(); + + // Callback function when XMLHttpRequest is ready + xmlhttp.onreadystatechange=function(){ + if(xmlhttp.readyState === 4){ + + // Exit if shutting down app + if (PhoneGap.shuttingDown) { + return; + } + + // If callback has JavaScript statement to execute + if (xmlhttp.status === 200) { + + // Need to url decode the response + var msg = decodeURIComponent(xmlhttp.responseText); + setTimeout(function() { + try { + var t = eval(msg); + } + catch (e) { + // If we're getting an error here, seeing the message will help in debugging + console.log("JSCallback: Message from Server: " + msg); + console.log("JSCallback Error: "+e); + } + }, 1); + setTimeout(PhoneGap.JSCallback, 1); + } + + // If callback ping (used to keep XHR request from timing out) + else if (xmlhttp.status === 404) { + setTimeout(PhoneGap.JSCallback, 10); + } + + // If security error + else if (xmlhttp.status === 403) { + console.log("JSCallback Error: Invalid token. Stopping callbacks."); + } + + // If server is stopping + else if (xmlhttp.status === 503) { + console.log("JSCallback Server Closed: Stopping callbacks."); + } + + // If request wasn't GET + else if (xmlhttp.status === 400) { + console.log("JSCallback Error: Bad request. Stopping callbacks."); + } + + // If error, revert to polling + else { + console.log("JSCallback Error: Request failed."); + PhoneGap.UsePolling = true; + PhoneGap.JSCallbackPolling(); + } + } + }; + + if (PhoneGap.JSCallbackPort === null) { + PhoneGap.JSCallbackPort = prompt("getPort", "gap_callbackServer:"); + } + if (PhoneGap.JSCallbackToken === null) { + PhoneGap.JSCallbackToken = prompt("getToken", "gap_callbackServer:"); + } + xmlhttp.open("GET", "http://127.0.0.1:"+PhoneGap.JSCallbackPort+"/"+PhoneGap.JSCallbackToken , true); + xmlhttp.send(); +}; + +/** + * The polling period to use with JSCallbackPolling. + * This can be changed by the application. The default is 50ms. + */ +PhoneGap.JSCallbackPollingPeriod = 50; + +/** + * Flag that can be set by the user to force polling to be used or force XHR to be used. + */ +PhoneGap.UsePolling = false; // T=use polling, F=use XHR + +/** + * This is only for Android. + * + * Internal function that uses polling to call into PhoneGap Java code and retrieve + * any JavaScript code that needs to be run. This is used for callbacks from + * Java to JavaScript. + */ +PhoneGap.JSCallbackPolling = function() { + + // Exit if shutting down app + if (PhoneGap.shuttingDown) { + return; + } + + // If polling flag was changed, stop using polling from now on + if (!PhoneGap.UsePolling) { + PhoneGap.JSCallback(); + return; + } + + var msg = prompt("", "gap_poll:"); + if (msg) { + setTimeout(function() { + try { + var t = eval(""+msg); + } + catch (e) { + console.log("JSCallbackPolling: Message from Server: " + msg); + console.log("JSCallbackPolling Error: "+e); + } + }, 1); + setTimeout(PhoneGap.JSCallbackPolling, 1); + } + else { + setTimeout(PhoneGap.JSCallbackPolling, PhoneGap.JSCallbackPollingPeriod); + } +}; + +/** + * Create a UUID + * + * @return {String} + */ +PhoneGap.createUUID = function() { + return PhoneGap.UUIDcreatePart(4) + '-' + + PhoneGap.UUIDcreatePart(2) + '-' + + PhoneGap.UUIDcreatePart(2) + '-' + + PhoneGap.UUIDcreatePart(2) + '-' + + PhoneGap.UUIDcreatePart(6); +}; + +PhoneGap.UUIDcreatePart = function(length) { + var uuidpart = ""; + var i, uuidchar; + for (i=0; i + + + + + PhoneGap + + + + + + + + + +

    Events

    +
    + Results:
    + +
    + +

    Action

    + Intercept backbutton + Stop intercept of backbutton + Intercept menubutton + Stop intercept of menubutton + Intercept searchbutton + Stop intercept of searchbutton +

     

    Back + + diff --git a/test/assets/www/index.html b/test/assets/www/index.html new file mode 100755 index 00000000..b6f7e134 --- /dev/null +++ b/test/assets/www/index.html @@ -0,0 +1,36 @@ + + + + + + PhoneGap + + + + + + +

    PhoneGap Tests

    +
    +

    Platform:  

    +

    Version:  

    +

    UUID:  

    +

    Name:  

    +

    Width:  , Height:   + , Color Depth:

    +
    + Automatic Test + Accelerometer + Audio Play/Record + Battery + Camera + Compass + Contacts + Events + Location + Misc Content + Notification + Web SQL + Local Storage + + diff --git a/test/assets/www/location/index.html b/test/assets/www/location/index.html new file mode 100755 index 00000000..5c4e3781 --- /dev/null +++ b/test/assets/www/location/index.html @@ -0,0 +1,123 @@ + + + + + + PhoneGap + + + + + + + + + +

    Location

    +
    + Status: Stopped + + + +
    Latitude: 
    Longitude: 
    +
    +

    Action

    + Get Location + Start Watching Location + Stop Watching Location +

     

    Back + + diff --git a/test/assets/www/main.js b/test/assets/www/main.js new file mode 100755 index 00000000..ae447aa9 --- /dev/null +++ b/test/assets/www/main.js @@ -0,0 +1,140 @@ +var deviceInfo = function() { + document.getElementById("platform").innerHTML = device.platform; + document.getElementById("version").innerHTML = device.version; + document.getElementById("uuid").innerHTML = device.uuid; + document.getElementById("name").innerHTML = device.name; + document.getElementById("width").innerHTML = screen.width; + document.getElementById("height").innerHTML = screen.height; + document.getElementById("colorDepth").innerHTML = screen.colorDepth; +}; + +var getLocation = function() { + var suc = function(p) { + alert(p.coords.latitude + " " + p.coords.longitude); + }; + var locFail = function() { + }; + navigator.geolocation.getCurrentPosition(suc, locFail); +}; + +var beep = function() { + navigator.notification.beep(2); +}; + +var vibrate = function() { + navigator.notification.vibrate(0); +}; + +function roundNumber(num) { + var dec = 3; + var result = Math.round(num * Math.pow(10, dec)) / Math.pow(10, dec); + return result; +} + +var accelerationWatch = null; + +function updateAcceleration(a) { + document.getElementById('x').innerHTML = roundNumber(a.x); + document.getElementById('y').innerHTML = roundNumber(a.y); + document.getElementById('z').innerHTML = roundNumber(a.z); +} + +var toggleAccel = function() { + if (accelerationWatch !== null) { + navigator.accelerometer.clearWatch(accelerationWatch); + updateAcceleration({ + x : "", + y : "", + z : "" + }); + accelerationWatch = null; + } else { + var options = {}; + options.frequency = 1000; + accelerationWatch = navigator.accelerometer.watchAcceleration( + updateAcceleration, function(ex) { + alert("accel fail (" + ex.name + ": " + ex.message + ")"); + }, options); + } +}; + +var preventBehavior = function(e) { + e.preventDefault(); +}; + +function dump_pic(data) { + var viewport = document.getElementById('viewport'); + console.log(data); + viewport.style.display = ""; + viewport.style.position = "absolute"; + viewport.style.top = "10px"; + viewport.style.left = "10px"; + document.getElementById("test_img").src = "data:image/jpeg;base64," + data; +} + +function fail(msg) { + alert(msg); +} + +function show_pic() { + navigator.camera.getPicture(dump_pic, fail, { + quality : 50 + }); +} + +function close() { + var viewport = document.getElementById('viewport'); + viewport.style.position = "relative"; + viewport.style.display = "none"; +} + +// This is just to do this. +function readFile() { + navigator.file.read('/sdcard/phonegap.txt', fail, fail); +} + +function writeFile() { + navigator.file.write('foo.txt', "This is a test of writing to a file", + fail, fail); +} + +function contacts_success(contacts) { + alert(contacts.length + + ' contacts returned.' + + (contacts[2] && contacts[2].name ? (' Third contact is ' + contacts[2].name.formatted) + : '')); +} + +function get_contacts() { + var obj = new ContactFindOptions(); + obj.filter = ""; + obj.multiple = true; + obj.limit = 5; + navigator.service.contacts.find( + [ "displayName", "name" ], contacts_success, + fail, obj); +} + +var networkReachableCallback = function(reachability) { + // There is no consistency on the format of reachability + var networkState = reachability.code || reachability; + + var currentState = {}; + currentState[NetworkStatus.NOT_REACHABLE] = 'No network connection'; + currentState[NetworkStatus.REACHABLE_VIA_CARRIER_DATA_NETWORK] = 'Carrier data connection'; + currentState[NetworkStatus.REACHABLE_VIA_WIFI_NETWORK] = 'WiFi connection'; + + confirm("Connection type:\n" + currentState[networkState]); +}; + +function check_network() { + navigator.network.isReachable("www.mobiledevelopersolutions.com", + networkReachableCallback, {}); +} + +function init() { + // the next line makes it impossible to see Contacts on the HTC Evo since it + // doesn't have a scroll button + // document.addEventListener("touchmove", preventBehavior, false); + document.addEventListener("deviceready", deviceInfo, true); +} diff --git a/test/assets/www/master.css b/test/assets/www/master.css new file mode 100755 index 00000000..e1ad7c69 --- /dev/null +++ b/test/assets/www/master.css @@ -0,0 +1,110 @@ + body { + background:#222 none repeat scroll 0 0; + color:#666; + font-family:Helvetica; + font-size:72%; + line-height:1.5em; + margin:0; + border-top:1px solid #393939; + } + + #info{ + background:#ffa; + border: 1px solid #ffd324; + -webkit-border-radius: 5px; + border-radius: 5px; + clear:both; + margin:15px 6px 0; + width:295px; + padding:4px 0px 2px 10px; + } + + #info > h4{ + font-size:.95em; + margin:5px 0; + } + + #stage.theme{ + padding-top:3px; + } + + /* Definition List */ + #stage.theme > dl{ + padding-top:10px; + clear:both; + margin:0; + list-style-type:none; + padding-left:10px; + overflow:auto; + } + + #stage.theme > dl > dt{ + font-weight:bold; + float:left; + margin-left:5px; + } + + #stage.theme > dl > dd{ + width:45px; + float:left; + color:#a87; + font-weight:bold; + } + + /* Content Styling */ + #stage.theme > h1, #stage.theme > h2, #stage.theme > p{ + margin:1em 0 .5em 13px; + } + + #stage.theme > h1{ + color:#eee; + font-size:1.6em; + text-align:center; + margin:0; + margin-top:15px; + padding:0; + } + + #stage.theme > h2{ + clear:both; + margin:0; + padding:3px; + font-size:1em; + text-align:center; + } + + /* Stage Buttons */ + #stage.theme a.btn{ + border: 1px solid #555; + -webkit-border-radius: 5px; + border-radius: 5px; + text-align:center; + display:block; + float:left; + background:#444; + width:150px; + color:#9ab; + font-size:1.1em; + text-decoration:none; + padding:1.2em 0; + margin:3px 0px 3px 5px; + } + #stage.theme a.btn.large{ + width:308px; + padding:1.2em 0; + } + #stage.theme a.backBtn{ + border: 1px solid #555; + -webkit-border-radius: 5px; + border-radius: 5px; + text-align:center; + display:block; + float:right; + background:#666; + width:75px; + color:#9ab; + font-size:1.1em; + text-decoration:none; + padding:1.2em 0; + margin:3px 5px 3px 5px; + } diff --git a/test/assets/www/misc/index.html b/test/assets/www/misc/index.html new file mode 100755 index 00000000..512e6c76 --- /dev/null +++ b/test/assets/www/misc/index.html @@ -0,0 +1,59 @@ + + + + + + PhoneGap + + + + + + + + + +

    Display Other Content

    +
    +
    +

    Action

    + Call 411 + Send Mail + Send SMS + Load Web Site + + + Load another PhoneGap page +

    Android Only

    + Map IBM + Search Android market + View image app + +

     

    Back + + diff --git a/test/assets/www/misc/page2.html b/test/assets/www/misc/page2.html new file mode 100755 index 00000000..407af4a6 --- /dev/null +++ b/test/assets/www/misc/page2.html @@ -0,0 +1,25 @@ + + + + + + PhoneGap + + + + + + +

    Page2 App

    +

    This is page 2 of a PhoneGap app

    +
    +

    Platform:  

    +

    Version:  

    +

    UUID:  

    +

    Name:  

    +

    Width:  , Height:   + , Color Depth:

    +
    +
    + + diff --git a/test/assets/www/notification/index.html b/test/assets/www/notification/index.html new file mode 100755 index 00000000..39e8b1ed --- /dev/null +++ b/test/assets/www/notification/index.html @@ -0,0 +1,81 @@ + + + + + + PhoneGap + + + + + + + + + +

    Notifications and Dialogs

    +
    +
    + +

    Action

    + Beep + Vibrate + Alert Dialog + Confirm Dialog + Built-in Alert Dialog + Built-in Confirm Dialog + Built-in Prompt Dialog +

     

    Back + + diff --git a/test/assets/www/phonegap-1.4.0.js b/test/assets/www/phonegap-1.4.0.js new file mode 100644 index 00000000..df49a46c --- /dev/null +++ b/test/assets/www/phonegap-1.4.0.js @@ -0,0 +1,5554 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// Version 1.2.0 + +if (typeof PhoneGap === "undefined") { + +/** + * The order of events during page load and PhoneGap startup is as follows: + * + * onDOMContentLoaded Internal event that is received when the web page is loaded and parsed. + * window.onload Body onload event. + * onNativeReady Internal event that indicates the PhoneGap native side is ready. + * onPhoneGapInit Internal event that kicks off creation of all PhoneGap JavaScript objects (runs constructors). + * onPhoneGapReady Internal event fired when all PhoneGap JavaScript objects have been created + * onPhoneGapInfoReady Internal event fired when device properties are available + * onDeviceReady User event fired to indicate that PhoneGap is ready + * onResume User event fired to indicate a start/resume lifecycle event + * onPause User event fired to indicate a pause lifecycle event + * onDestroy Internal event fired when app is being destroyed (User should use window.onunload event, not this one). + * + * The only PhoneGap events that user code should register for are: + * deviceready PhoneGap native code is initialized and PhoneGap APIs can be called from JavaScript + * pause App has moved to background + * resume App has returned to foreground + * + * Listeners can be registered as: + * document.addEventListener("deviceready", myDeviceReadyListener, false); + * document.addEventListener("resume", myResumeListener, false); + * document.addEventListener("pause", myPauseListener, false); + * + * The DOM lifecycle events should be used for saving and restoring state + * window.onload + * window.onunload + */ + +/** + * This represents the PhoneGap API itself, and provides a global namespace for accessing + * information about the state of PhoneGap. + * @class + */ +var PhoneGap = { + documentEventHandler: {}, // Collection of custom document event handlers + windowEventHandler: {} // Collection of custom window event handlers +}; + +/** + * List of resource files loaded by PhoneGap. + * This is used to ensure JS and other files are loaded only once. + */ +PhoneGap.resources = {base: true}; + +/** + * Determine if resource has been loaded by PhoneGap + * + * @param name + * @return + */ +PhoneGap.hasResource = function(name) { + return PhoneGap.resources[name]; +}; + +/** + * Add a resource to list of loaded resources by PhoneGap + * + * @param name + */ +PhoneGap.addResource = function(name) { + PhoneGap.resources[name] = true; +}; + +/** + * Custom pub-sub channel that can have functions subscribed to it + * @constructor + */ +PhoneGap.Channel = function (type) +{ + this.type = type; + this.handlers = {}; + this.guid = 0; + this.fired = false; + this.enabled = true; +}; + +/** + * Subscribes the given function to the channel. Any time that + * Channel.fire is called so too will the function. + * Optionally specify an execution context for the function + * and a guid that can be used to stop subscribing to the channel. + * Returns the guid. + */ +PhoneGap.Channel.prototype.subscribe = function(f, c, g) { + // need a function to call + if (f === null) { return; } + + var func = f; + if (typeof c === "object" && typeof f === "function") { func = PhoneGap.close(c, f); } + + g = g || func.observer_guid || f.observer_guid || this.guid++; + func.observer_guid = g; + f.observer_guid = g; + this.handlers[g] = func; + return g; +}; + +/** + * Like subscribe but the function is only called once and then it + * auto-unsubscribes itself. + */ +PhoneGap.Channel.prototype.subscribeOnce = function(f, c) { + var g = null; + var _this = this; + var m = function() { + f.apply(c || null, arguments); + _this.unsubscribe(g); + }; + if (this.fired) { + if (typeof c === "object" && typeof f === "function") { f = PhoneGap.close(c, f); } + f.apply(this, this.fireArgs); + } else { + g = this.subscribe(m); + } + return g; +}; + +/** + * Unsubscribes the function with the given guid from the channel. + */ +PhoneGap.Channel.prototype.unsubscribe = function(g) { + if (typeof g === "function") { g = g.observer_guid; } + this.handlers[g] = null; + delete this.handlers[g]; +}; + +/** + * Calls all functions subscribed to this channel. + */ +PhoneGap.Channel.prototype.fire = function(e) { + if (this.enabled) { + var fail = false; + var item, handler, rv; + for (item in this.handlers) { + if (this.handlers.hasOwnProperty(item)) { + handler = this.handlers[item]; + if (typeof handler === "function") { + rv = (handler.apply(this, arguments) === false); + fail = fail || rv; + } + } + } + this.fired = true; + this.fireArgs = arguments; + return !fail; + } + return true; +}; + +/** + * Calls the provided function only after all of the channels specified + * have been fired. + */ +PhoneGap.Channel.join = function(h, c) { + var i = c.length; + var f = function() { + if (!(--i)) { + h(); + } + }; + var len = i; + var j; + for (j=0; j + * + * @param name The plugin name + * @param obj The plugin object + */ +PhoneGap.addPlugin = function(name, obj) { + if (!window.plugins[name]) { + window.plugins[name] = obj; + } + else { + console.log("Error: Plugin "+name+" already exists."); + } +}; + +/** + * onDOMContentLoaded channel is fired when the DOM content + * of the page has been parsed. + */ +PhoneGap.onDOMContentLoaded = new PhoneGap.Channel('onDOMContentLoaded'); + +/** + * onNativeReady channel is fired when the PhoneGap native code + * has been initialized. + */ +PhoneGap.onNativeReady = new PhoneGap.Channel('onNativeReady'); + +/** + * onPhoneGapInit channel is fired when the web page is fully loaded and + * PhoneGap native code has been initialized. + */ +PhoneGap.onPhoneGapInit = new PhoneGap.Channel('onPhoneGapInit'); + +/** + * onPhoneGapReady channel is fired when the JS PhoneGap objects have been created. + */ +PhoneGap.onPhoneGapReady = new PhoneGap.Channel('onPhoneGapReady'); + +/** + * onPhoneGapInfoReady channel is fired when the PhoneGap device properties + * has been set. + */ +PhoneGap.onPhoneGapInfoReady = new PhoneGap.Channel('onPhoneGapInfoReady'); + +/** + * onPhoneGapConnectionReady channel is fired when the PhoneGap connection properties + * has been set. + */ +PhoneGap.onPhoneGapConnectionReady = new PhoneGap.Channel('onPhoneGapConnectionReady'); + +/** + * onDestroy channel is fired when the PhoneGap native code + * is destroyed. It is used internally. + * Window.onunload should be used by the user. + */ +PhoneGap.onDestroy = new PhoneGap.Channel('onDestroy'); +PhoneGap.onDestroy.subscribeOnce(function() { + PhoneGap.shuttingDown = true; +}); +PhoneGap.shuttingDown = false; + +// _nativeReady is global variable that the native side can set +// to signify that the native code is ready. It is a global since +// it may be called before any PhoneGap JS is ready. +if (typeof _nativeReady !== 'undefined') { PhoneGap.onNativeReady.fire(); } + +/** + * onDeviceReady is fired only after all PhoneGap objects are created and + * the device properties are set. + */ +PhoneGap.onDeviceReady = new PhoneGap.Channel('onDeviceReady'); + + +// Array of channels that must fire before "deviceready" is fired +PhoneGap.deviceReadyChannelsArray = [ PhoneGap.onPhoneGapReady, PhoneGap.onPhoneGapInfoReady, PhoneGap.onPhoneGapConnectionReady]; + +// Hashtable of user defined channels that must also fire before "deviceready" is fired +PhoneGap.deviceReadyChannelsMap = {}; + +/** + * Indicate that a feature needs to be initialized before it is ready to be used. + * This holds up PhoneGap's "deviceready" event until the feature has been initialized + * and PhoneGap.initComplete(feature) is called. + * + * @param feature {String} The unique feature name + */ +PhoneGap.waitForInitialization = function(feature) { + if (feature) { + var channel = new PhoneGap.Channel(feature); + PhoneGap.deviceReadyChannelsMap[feature] = channel; + PhoneGap.deviceReadyChannelsArray.push(channel); + } +}; + +/** + * Indicate that initialization code has completed and the feature is ready to be used. + * + * @param feature {String} The unique feature name + */ +PhoneGap.initializationComplete = function(feature) { + var channel = PhoneGap.deviceReadyChannelsMap[feature]; + if (channel) { + channel.fire(); + } +}; + +/** + * Create all PhoneGap objects once page has fully loaded and native side is ready. + */ +PhoneGap.Channel.join(function() { + + // Start listening for XHR callbacks + setTimeout(function() { + if (PhoneGap.UsePolling) { + PhoneGap.JSCallbackPolling(); + } + else { + var polling = prompt("usePolling", "gap_callbackServer:"); + PhoneGap.UsePolling = polling; + if (polling == "true") { + PhoneGap.UsePolling = true; + PhoneGap.JSCallbackPolling(); + } + else { + PhoneGap.UsePolling = false; + PhoneGap.JSCallback(); + } + } + }, 1); + + // Run PhoneGap constructors + PhoneGap.onPhoneGapInit.fire(); + + // Fire event to notify that all objects are created + PhoneGap.onPhoneGapReady.fire(); + + // Fire onDeviceReady event once all constructors have run and PhoneGap info has been + // received from native side, and any user defined initialization channels. + PhoneGap.Channel.join(function() { + // Let native code know we are inited on JS side + prompt("", "gap_init:"); + + PhoneGap.onDeviceReady.fire(); + }, PhoneGap.deviceReadyChannelsArray); + +}, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]); + +// Listen for DOMContentLoaded and notify our channel subscribers +document.addEventListener('DOMContentLoaded', function() { + PhoneGap.onDOMContentLoaded.fire(); +}, false); + +// Intercept calls to document.addEventListener and watch for deviceready +PhoneGap.m_document_addEventListener = document.addEventListener; + +// Intercept calls to window.addEventListener +PhoneGap.m_window_addEventListener = window.addEventListener; + +/** + * Add a custom window event handler. + * + * @param {String} event The event name that callback handles + * @param {Function} callback The event handler + */ +PhoneGap.addWindowEventHandler = function(event, callback) { + PhoneGap.windowEventHandler[event] = callback; +}; + +/** + * Add a custom document event handler. + * + * @param {String} event The event name that callback handles + * @param {Function} callback The event handler + */ +PhoneGap.addDocumentEventHandler = function(event, callback) { + PhoneGap.documentEventHandler[event] = callback; +}; + +/** + * Intercept adding document event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +document.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + if (e === 'deviceready') { + PhoneGap.onDeviceReady.subscribeOnce(handler); + } + else { + // If subscribing to Android backbutton + if (e === 'backbutton') { + PhoneGap.exec(null, null, "App", "overrideBackbutton", [true]); + } + + // If subscribing to an event that is handled by a plugin + else if (typeof PhoneGap.documentEventHandler[e] !== "undefined") { + if (PhoneGap.documentEventHandler[e](e, handler, true)) { + return; // Stop default behavior + } + } + + PhoneGap.m_document_addEventListener.call(document, evt, handler, capture); + } +}; + +/** + * Intercept adding window event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +window.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If subscribing to an event that is handled by a plugin + if (typeof PhoneGap.windowEventHandler[e] !== "undefined") { + if (PhoneGap.windowEventHandler[e](e, handler, true)) { + return; // Stop default behavior + } + } + + PhoneGap.m_window_addEventListener.call(window, evt, handler, capture); +}; + +// Intercept calls to document.removeEventListener and watch for events that +// are generated by PhoneGap native code +PhoneGap.m_document_removeEventListener = document.removeEventListener; + +// Intercept calls to window.removeEventListener +PhoneGap.m_window_removeEventListener = window.removeEventListener; + +/** + * Intercept removing document event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +document.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If unsubscribing to Android backbutton + if (e === 'backbutton') { + PhoneGap.exec(null, null, "App", "overrideBackbutton", [false]); + } + + // If unsubcribing from an event that is handled by a plugin + if (typeof PhoneGap.documentEventHandler[e] !== "undefined") { + if (PhoneGap.documentEventHandler[e](e, handler, false)) { + return; // Stop default behavior + } + } + + PhoneGap.m_document_removeEventListener.call(document, evt, handler, capture); +}; + +/** + * Intercept removing window event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +window.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If unsubcribing from an event that is handled by a plugin + if (typeof PhoneGap.windowEventHandler[e] !== "undefined") { + if (PhoneGap.windowEventHandler[e](e, handler, false)) { + return; // Stop default behavior + } + } + + PhoneGap.m_window_removeEventListener.call(window, evt, handler, capture); +}; + +/** + * Method to fire document event + * + * @param {String} type The event type to fire + * @param {Object} data Data to send with event + */ +PhoneGap.fireDocumentEvent = function(type, data) { + var e = document.createEvent('Events'); + e.initEvent(type); + if (data) { + for (var i in data) { + e[i] = data[i]; + } + } + document.dispatchEvent(e); +}; + +/** + * Method to fire window event + * + * @param {String} type The event type to fire + * @param {Object} data Data to send with event + */ +PhoneGap.fireWindowEvent = function(type, data) { + var e = document.createEvent('Events'); + e.initEvent(type); + if (data) { + for (var i in data) { + e[i] = data[i]; + } + } + window.dispatchEvent(e); +}; + +/** + * Does a deep clone of the object. + * + * @param obj + * @return {Object} + */ +PhoneGap.clone = function(obj) { + var i, retVal; + if(!obj) { + return obj; + } + + if(obj instanceof Array){ + retVal = []; + for(i = 0; i < obj.length; ++i){ + retVal.push(PhoneGap.clone(obj[i])); + } + return retVal; + } + + if (typeof obj === "function") { + return obj; + } + + if(!(obj instanceof Object)){ + return obj; + } + + if (obj instanceof Date) { + return obj; + } + + retVal = {}; + for(i in obj){ + if(!(i in retVal) || retVal[i] !== obj[i]) { + retVal[i] = PhoneGap.clone(obj[i]); + } + } + return retVal; +}; + +PhoneGap.callbackId = 0; +PhoneGap.callbacks = {}; +PhoneGap.callbackStatus = { + NO_RESULT: 0, + OK: 1, + CLASS_NOT_FOUND_EXCEPTION: 2, + ILLEGAL_ACCESS_EXCEPTION: 3, + INSTANTIATION_EXCEPTION: 4, + MALFORMED_URL_EXCEPTION: 5, + IO_EXCEPTION: 6, + INVALID_ACTION: 7, + JSON_EXCEPTION: 8, + ERROR: 9 + }; + + +/** + * Execute a PhoneGap command. It is up to the native side whether this action is synch or async. + * The native side can return: + * Synchronous: PluginResult object as a JSON string + * Asynchrounous: Empty string "" + * If async, the native side will PhoneGap.callbackSuccess or PhoneGap.callbackError, + * depending upon the result of the action. + * + * @param {Function} success The success callback + * @param {Function} fail The fail callback + * @param {String} service The name of the service to use + * @param {String} action Action to be run in PhoneGap + * @param {Array.} [args] Zero or more arguments to pass to the method + */ +PhoneGap.exec = function(success, fail, service, action, args) { + try { + var callbackId = service + PhoneGap.callbackId++; + if (success || fail) { + PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; + } + + var r = prompt(JSON.stringify(args), "gap:"+JSON.stringify([service, action, callbackId, true])); + + // If a result was returned + if (r.length > 0) { + eval("var v="+r+";"); + + // If status is OK, then return value back to caller + if (v.status === PhoneGap.callbackStatus.OK) { + + // If there is a success callback, then call it now with + // returned value + if (success) { + try { + success(v.message); + } catch (e) { + console.log("Error in success callback: " + callbackId + " = " + e); + } + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } + return v.message; + } + + // If no result + else if (v.status === PhoneGap.callbackStatus.NO_RESULT) { + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } + + // If error, then display error + else { + console.log("Error: Status="+v.status+" Message="+v.message); + + // If there is a fail callback, then call it now with returned value + if (fail) { + try { + fail(v.message); + } + catch (e1) { + console.log("Error in error callback: "+callbackId+" = "+e1); + } + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } + return null; + } + } + } catch (e2) { + console.log("Error: "+e2); + } +}; + +/** + * Called by native code when returning successful result from an action. + * + * @param callbackId + * @param args + */ +PhoneGap.callbackSuccess = function(callbackId, args) { + if (PhoneGap.callbacks[callbackId]) { + + // If result is to be sent to callback + if (args.status === PhoneGap.callbackStatus.OK) { + try { + if (PhoneGap.callbacks[callbackId].success) { + PhoneGap.callbacks[callbackId].success(args.message); + } + } + catch (e) { + console.log("Error in success callback: "+callbackId+" = "+e); + } + } + + // Clear callback if not expecting any more results + if (!args.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } +}; + +/** + * Called by native code when returning error result from an action. + * + * @param callbackId + * @param args + */ +PhoneGap.callbackError = function(callbackId, args) { + if (PhoneGap.callbacks[callbackId]) { + try { + if (PhoneGap.callbacks[callbackId].fail) { + PhoneGap.callbacks[callbackId].fail(args.message); + } + } + catch (e) { + console.log("Error in error callback: "+callbackId+" = "+e); + } + + // Clear callback if not expecting any more results + if (!args.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } +}; + +PhoneGap.JSCallbackPort = null; +PhoneGap.JSCallbackToken = null; + +/** + * This is only for Android. + * + * Internal function that uses XHR to call into PhoneGap Java code and retrieve + * any JavaScript code that needs to be run. This is used for callbacks from + * Java to JavaScript. + */ +PhoneGap.JSCallback = function() { + + // Exit if shutting down app + if (PhoneGap.shuttingDown) { + return; + } + + // If polling flag was changed, start using polling from now on + if (PhoneGap.UsePolling) { + PhoneGap.JSCallbackPolling(); + return; + } + + var xmlhttp = new XMLHttpRequest(); + + // Callback function when XMLHttpRequest is ready + xmlhttp.onreadystatechange=function(){ + if(xmlhttp.readyState === 4){ + + // Exit if shutting down app + if (PhoneGap.shuttingDown) { + return; + } + + // If callback has JavaScript statement to execute + if (xmlhttp.status === 200) { + + // Need to url decode the response + var msg = decodeURIComponent(xmlhttp.responseText); + setTimeout(function() { + try { + var t = eval(msg); + } + catch (e) { + // If we're getting an error here, seeing the message will help in debugging + console.log("JSCallback: Message from Server: " + msg); + console.log("JSCallback Error: "+e); + } + }, 1); + setTimeout(PhoneGap.JSCallback, 1); + } + + // If callback ping (used to keep XHR request from timing out) + else if (xmlhttp.status === 404) { + setTimeout(PhoneGap.JSCallback, 10); + } + + // If security error + else if (xmlhttp.status === 403) { + console.log("JSCallback Error: Invalid token. Stopping callbacks."); + } + + // If server is stopping + else if (xmlhttp.status === 503) { + console.log("JSCallback Server Closed: Stopping callbacks."); + } + + // If request wasn't GET + else if (xmlhttp.status === 400) { + console.log("JSCallback Error: Bad request. Stopping callbacks."); + } + + // If error, revert to polling + else { + console.log("JSCallback Error: Request failed."); + PhoneGap.UsePolling = true; + PhoneGap.JSCallbackPolling(); + } + } + }; + + if (PhoneGap.JSCallbackPort === null) { + PhoneGap.JSCallbackPort = prompt("getPort", "gap_callbackServer:"); + } + if (PhoneGap.JSCallbackToken === null) { + PhoneGap.JSCallbackToken = prompt("getToken", "gap_callbackServer:"); + } + xmlhttp.open("GET", "http://127.0.0.1:"+PhoneGap.JSCallbackPort+"/"+PhoneGap.JSCallbackToken , true); + xmlhttp.send(); +}; + +/** + * The polling period to use with JSCallbackPolling. + * This can be changed by the application. The default is 50ms. + */ +PhoneGap.JSCallbackPollingPeriod = 50; + +/** + * Flag that can be set by the user to force polling to be used or force XHR to be used. + */ +PhoneGap.UsePolling = false; // T=use polling, F=use XHR + +/** + * This is only for Android. + * + * Internal function that uses polling to call into PhoneGap Java code and retrieve + * any JavaScript code that needs to be run. This is used for callbacks from + * Java to JavaScript. + */ +PhoneGap.JSCallbackPolling = function() { + + // Exit if shutting down app + if (PhoneGap.shuttingDown) { + return; + } + + // If polling flag was changed, stop using polling from now on + if (!PhoneGap.UsePolling) { + PhoneGap.JSCallback(); + return; + } + + var msg = prompt("", "gap_poll:"); + if (msg) { + setTimeout(function() { + try { + var t = eval(""+msg); + } + catch (e) { + console.log("JSCallbackPolling: Message from Server: " + msg); + console.log("JSCallbackPolling Error: "+e); + } + }, 1); + setTimeout(PhoneGap.JSCallbackPolling, 1); + } + else { + setTimeout(PhoneGap.JSCallbackPolling, PhoneGap.JSCallbackPollingPeriod); + } +}; + +/** + * Create a UUID + * + * @return {String} + */ +PhoneGap.createUUID = function() { + return PhoneGap.UUIDcreatePart(4) + '-' + + PhoneGap.UUIDcreatePart(2) + '-' + + PhoneGap.UUIDcreatePart(2) + '-' + + PhoneGap.UUIDcreatePart(2) + '-' + + PhoneGap.UUIDcreatePart(6); +}; + +PhoneGap.UUIDcreatePart = function(length) { + var uuidpart = ""; + var i, uuidchar; + for (i=0; i frequency + 10 sec + PhoneGap.exec( + function(timeout) { + if (timeout < (frequency + 10000)) { + PhoneGap.exec(null, null, "Accelerometer", "setTimeout", [frequency + 10000]); + } + }, + function(e) { }, "Accelerometer", "getTimeout", []); + + // Start watch timer + var id = PhoneGap.createUUID(); + navigator.accelerometer.timers[id] = setInterval(function() { + PhoneGap.exec(successCallback, errorCallback, "Accelerometer", "getAcceleration", []); + }, (frequency ? frequency : 1)); + + return id; +}; + +/** + * Clears the specified accelerometer watch. + * + * @param {String} id The id of the watch returned from #watchAcceleration. + */ +Accelerometer.prototype.clearWatch = function(id) { + + // Stop javascript timer & remove from timer list + if (id && navigator.accelerometer.timers[id] !== undefined) { + clearInterval(navigator.accelerometer.timers[id]); + delete navigator.accelerometer.timers[id]; + } +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.accelerometer === "undefined") { + navigator.accelerometer = new Accelerometer(); + } +}); +} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +if (!PhoneGap.hasResource("app")) { +PhoneGap.addResource("app"); +(function() { + +/** + * Constructor + * @constructor + */ +var App = function() {}; + +/** + * Clear the resource cache. + */ +App.prototype.clearCache = function() { + PhoneGap.exec(null, null, "App", "clearCache", []); +}; + +/** + * Load the url into the webview or into new browser instance. + * + * @param url The URL to load + * @param props Properties that can be passed in to the activity: + * wait: int => wait msec before loading URL + * loadingDialog: "Title,Message" => display a native loading dialog + * loadUrlTimeoutValue: int => time in msec to wait before triggering a timeout error + * clearHistory: boolean => clear webview history (default=false) + * openExternal: boolean => open in a new browser (default=false) + * + * Example: + * navigator.app.loadUrl("http://server/myapp/index.html", {wait:2000, loadingDialog:"Wait,Loading App", loadUrlTimeoutValue: 60000}); + */ +App.prototype.loadUrl = function(url, props) { + PhoneGap.exec(null, null, "App", "loadUrl", [url, props]); +}; + +/** + * Cancel loadUrl that is waiting to be loaded. + */ +App.prototype.cancelLoadUrl = function() { + PhoneGap.exec(null, null, "App", "cancelLoadUrl", []); +}; + +/** + * Clear web history in this web view. + * Instead of BACK button loading the previous web page, it will exit the app. + */ +App.prototype.clearHistory = function() { + PhoneGap.exec(null, null, "App", "clearHistory", []); +}; + +/** + * Go to previous page displayed. + * This is the same as pressing the backbutton on Android device. + */ +App.prototype.backHistory = function() { + PhoneGap.exec(null, null, "App", "backHistory", []); +}; + +/** + * Override the default behavior of the Android back button. + * If overridden, when the back button is pressed, the "backKeyDown" JavaScript event will be fired. + * + * Note: The user should not have to call this method. Instead, when the user + * registers for the "backbutton" event, this is automatically done. + * + * @param override T=override, F=cancel override + */ +App.prototype.overrideBackbutton = function(override) { + PhoneGap.exec(null, null, "App", "overrideBackbutton", [override]); +}; + +/** + * Exit and terminate the application. + */ +App.prototype.exitApp = function() { + return PhoneGap.exec(null, null, "App", "exitApp", []); +}; + +PhoneGap.addConstructor(function() { + navigator.app = new App(); +}); +}()); +} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +if (!PhoneGap.hasResource("battery")) { +PhoneGap.addResource("battery"); + +/** + * This class contains information about the current battery status. + * @constructor + */ +var Battery = function() { + this._level = null; + this._isPlugged = null; + this._batteryListener = []; + this._lowListener = []; + this._criticalListener = []; +}; + +/** + * Registers as an event producer for battery events. + * + * @param {Object} eventType + * @param {Object} handler + * @param {Object} add + */ +Battery.prototype.eventHandler = function(eventType, handler, add) { + var me = navigator.battery; + if (add) { + // If there are no current registered event listeners start the battery listener on native side. + if (me._batteryListener.length === 0 && me._lowListener.length === 0 && me._criticalListener.length === 0) { + PhoneGap.exec(me._status, me._error, "Battery", "start", []); + } + + // Register the event listener in the proper array + if (eventType === "batterystatus") { + if (me._batteryListener.indexOf(handler) === -1) { + me._batteryListener.push(handler); + } + } else if (eventType === "batterylow") { + if (me._lowListener.indexOf(handler) === -1) { + me._lowListener.push(handler); + } + } else if (eventType === "batterycritical") { + if (me._criticalListener.indexOf(handler) === -1) { + me._criticalListener.push(handler); + } + } + } else { + var pos = -1; + // Remove the event listener from the proper array + if (eventType === "batterystatus") { + pos = me._batteryListener.indexOf(handler); + if (pos > -1) { + me._batteryListener.splice(pos, 1); + } + } else if (eventType === "batterylow") { + pos = me._lowListener.indexOf(handler); + if (pos > -1) { + me._lowListener.splice(pos, 1); + } + } else if (eventType === "batterycritical") { + pos = me._criticalListener.indexOf(handler); + if (pos > -1) { + me._criticalListener.splice(pos, 1); + } + } + + // If there are no more registered event listeners stop the battery listener on native side. + if (me._batteryListener.length === 0 && me._lowListener.length === 0 && me._criticalListener.length === 0) { + PhoneGap.exec(null, null, "Battery", "stop", []); + } + } +}; + +/** + * Callback for battery status + * + * @param {Object} info keys: level, isPlugged + */ +Battery.prototype._status = function(info) { + if (info) { + var me = this; + var level = info.level; + if (me._level !== level || me._isPlugged !== info.isPlugged) { + // Fire batterystatus event + PhoneGap.fireWindowEvent("batterystatus", info); + + // Fire low battery event + if (level === 20 || level === 5) { + if (level === 20) { + PhoneGap.fireWindowEvent("batterylow", info); + } + else { + PhoneGap.fireWindowEvent("batterycritical", info); + } + } + } + me._level = level; + me._isPlugged = info.isPlugged; + } +}; + +/** + * Error callback for battery start + */ +Battery.prototype._error = function(e) { + console.log("Error initializing Battery: " + e); +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.battery === "undefined") { + navigator.battery = new Battery(); + PhoneGap.addWindowEventHandler("batterystatus", navigator.battery.eventHandler); + PhoneGap.addWindowEventHandler("batterylow", navigator.battery.eventHandler); + PhoneGap.addWindowEventHandler("batterycritical", navigator.battery.eventHandler); + } +}); +} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +if (!PhoneGap.hasResource("camera")) { +PhoneGap.addResource("camera"); + +/** + * This class provides access to the device camera. + * + * @constructor + */ +var Camera = function() { + this.successCallback = null; + this.errorCallback = null; + this.options = null; +}; + +/** + * Format of image that returned from getPicture. + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) + */ +Camera.DestinationType = { + DATA_URL: 0, // Return base64 encoded string + FILE_URI: 1 // Return file uri (content://media/external/images/media/2 for Android) +}; +Camera.prototype.DestinationType = Camera.DestinationType; + +/** + * Encoding of image returned from getPicture. + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * sourceType: Camera.PictureSourceType.CAMERA, + * encodingType: Camera.EncodingType.PNG}) +*/ +Camera.EncodingType = { + JPEG: 0, // Return JPEG encoded image + PNG: 1 // Return PNG encoded image +}; +Camera.prototype.EncodingType = Camera.EncodingType; + +/** + * Type of pictures to select from. Only applicable when + * PictureSourceType is PHOTOLIBRARY or SAVEDPHOTOALBUM + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * sourceType: Camera.PictureSourceType.PHOTOLIBRARY, + * mediaType: Camera.MediaType.PICTURE}) + */ +Camera.MediaType = { + PICTURE: 0, // allow selection of still pictures only. DEFAULT. Will return format specified via DestinationType + VIDEO: 1, // allow selection of video only, ONLY RETURNS URL + ALLMEDIA : 2 // allow selection from all media types +}; +Camera.prototype.MediaType = Camera.MediaType; + + +/** + * Source to getPicture from. + * + * Example: navigator.camera.getPicture(success, fail, + * { quality: 80, + * destinationType: Camera.DestinationType.DATA_URL, + * sourceType: Camera.PictureSourceType.PHOTOLIBRARY}) + */ +Camera.PictureSourceType = { + PHOTOLIBRARY : 0, // Choose image from picture library (same as SAVEDPHOTOALBUM for Android) + CAMERA : 1, // Take picture from camera + SAVEDPHOTOALBUM : 2 // Choose image from picture library (same as PHOTOLIBRARY for Android) +}; +Camera.prototype.PictureSourceType = Camera.PictureSourceType; + +/** + * Gets a picture from source defined by "options.sourceType", and returns the + * image as defined by the "options.destinationType" option. + + * The defaults are sourceType=CAMERA and destinationType=DATA_URL. + * + * @param {Function} successCallback + * @param {Function} errorCallback + * @param {Object} options + */ +Camera.prototype.getPicture = function(successCallback, errorCallback, options) { + + // successCallback required + if (typeof successCallback !== "function") { + console.log("Camera Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback !== "function")) { + console.log("Camera Error: errorCallback is not a function"); + return; + } + + if (options === null || typeof options === "undefined") { + options = {}; + } + if (options.quality === null || typeof options.quality === "undefined") { + options.quality = 80; + } + if (options.maxResolution === null || typeof options.maxResolution === "undefined") { + options.maxResolution = 0; + } + if (options.destinationType === null || typeof options.destinationType === "undefined") { + options.destinationType = Camera.DestinationType.FILE_URI; + } + if (options.sourceType === null || typeof options.sourceType === "undefined") { + options.sourceType = Camera.PictureSourceType.CAMERA; + } + if (options.encodingType === null || typeof options.encodingType === "undefined") { + options.encodingType = Camera.EncodingType.JPEG; + } + if (options.mediaType === null || typeof options.mediaType === "undefined") { + options.mediaType = Camera.MediaType.PICTURE; + } + if (options.targetWidth === null || typeof options.targetWidth === "undefined") { + options.targetWidth = -1; + } + else if (typeof options.targetWidth === "string") { + var width = new Number(options.targetWidth); + if (isNaN(width) === false) { + options.targetWidth = width.valueOf(); + } + } + if (options.targetHeight === null || typeof options.targetHeight === "undefined") { + options.targetHeight = -1; + } + else if (typeof options.targetHeight === "string") { + var height = new Number(options.targetHeight); + if (isNaN(height) === false) { + options.targetHeight = height.valueOf(); + } + } + + PhoneGap.exec(successCallback, errorCallback, "Camera", "takePicture", [options]); +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.camera === "undefined") { + navigator.camera = new Camera(); + } +}); +} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +if (!PhoneGap.hasResource("capture")) { +PhoneGap.addResource("capture"); + +/** + * Represents a single file. + * + * name {DOMString} name of the file, without path information + * fullPath {DOMString} the full path of the file, including the name + * type {DOMString} mime type + * lastModifiedDate {Date} last modified date + * size {Number} size of the file in bytes + */ +var MediaFile = function(name, fullPath, type, lastModifiedDate, size){ + this.name = name || null; + this.fullPath = fullPath || null; + this.type = type || null; + this.lastModifiedDate = lastModifiedDate || null; + this.size = size || 0; +}; + +/** + * Launch device camera application for recording video(s). + * + * @param {Function} successCB + * @param {Function} errorCB + */ +MediaFile.prototype.getFormatData = function(successCallback, errorCallback){ + PhoneGap.exec(successCallback, errorCallback, "Capture", "getFormatData", [this.fullPath, this.type]); +}; + +/** + * MediaFileData encapsulates format information of a media file. + * + * @param {DOMString} codecs + * @param {long} bitrate + * @param {long} height + * @param {long} width + * @param {float} duration + */ +var MediaFileData = function(codecs, bitrate, height, width, duration){ + this.codecs = codecs || null; + this.bitrate = bitrate || 0; + this.height = height || 0; + this.width = width || 0; + this.duration = duration || 0; +}; + +/** + * The CaptureError interface encapsulates all errors in the Capture API. + */ +var CaptureError = function(){ + this.code = null; +}; + +// Capture error codes +CaptureError.CAPTURE_INTERNAL_ERR = 0; +CaptureError.CAPTURE_APPLICATION_BUSY = 1; +CaptureError.CAPTURE_INVALID_ARGUMENT = 2; +CaptureError.CAPTURE_NO_MEDIA_FILES = 3; +CaptureError.CAPTURE_NOT_SUPPORTED = 20; + +/** + * The Capture interface exposes an interface to the camera and microphone of the hosting device. + */ +var Capture = function(){ + this.supportedAudioModes = []; + this.supportedImageModes = []; + this.supportedVideoModes = []; +}; + +/** + * Launch audio recorder application for recording audio clip(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureAudioOptions} options + */ +Capture.prototype.captureAudio = function(successCallback, errorCallback, options){ + PhoneGap.exec(successCallback, errorCallback, "Capture", "captureAudio", [options]); +}; + +/** + * Launch camera application for taking image(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureImageOptions} options + */ +Capture.prototype.captureImage = function(successCallback, errorCallback, options){ + PhoneGap.exec(successCallback, errorCallback, "Capture", "captureImage", [options]); +}; + +/** + * Launch camera application for taking image(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureImageOptions} options + */ +Capture.prototype._castMediaFile = function(pluginResult){ + var mediaFiles = []; + var i; + for (i = 0; i < pluginResult.message.length; i++) { + var mediaFile = new MediaFile(); + mediaFile.name = pluginResult.message[i].name; + mediaFile.fullPath = pluginResult.message[i].fullPath; + mediaFile.type = pluginResult.message[i].type; + mediaFile.lastModifiedDate = pluginResult.message[i].lastModifiedDate; + mediaFile.size = pluginResult.message[i].size; + mediaFiles.push(mediaFile); + } + pluginResult.message = mediaFiles; + return pluginResult; +}; + +/** + * Launch device camera application for recording video(s). + * + * @param {Function} successCB + * @param {Function} errorCB + * @param {CaptureVideoOptions} options + */ +Capture.prototype.captureVideo = function(successCallback, errorCallback, options){ + PhoneGap.exec(successCallback, errorCallback, "Capture", "captureVideo", [options]); +}; + +/** + * Encapsulates a set of parameters that the capture device supports. + */ +var ConfigurationData = function(){ + // The ASCII-encoded string in lower case representing the media type. + this.type = null; + // The height attribute represents height of the image or video in pixels. + // In the case of a sound clip this attribute has value 0. + this.height = 0; + // The width attribute represents width of the image or video in pixels. + // In the case of a sound clip this attribute has value 0 + this.width = 0; +}; + +/** + * Encapsulates all image capture operation configuration options. + */ +var CaptureImageOptions = function(){ + // Upper limit of images user can take. Value must be equal or greater than 1. + this.limit = 1; + // The selected image mode. Must match with one of the elements in supportedImageModes array. + this.mode = null; +}; + +/** + * Encapsulates all video capture operation configuration options. + */ +var CaptureVideoOptions = function(){ + // Upper limit of videos user can record. Value must be equal or greater than 1. + this.limit = 1; + // Maximum duration of a single video clip in seconds. + this.duration = 0; + // The selected video mode. Must match with one of the elements in supportedVideoModes array. + this.mode = null; +}; + +/** + * Encapsulates all audio capture operation configuration options. + */ +var CaptureAudioOptions = function(){ + // Upper limit of sound clips user can record. Value must be equal or greater than 1. + this.limit = 1; + // Maximum duration of a single sound clip in seconds. + this.duration = 0; + // The selected audio mode. Must match with one of the elements in supportedAudioModes array. + this.mode = null; +}; + +PhoneGap.addConstructor(function(){ + if (typeof navigator.device.capture === "undefined") { + navigator.device.capture = window.device.capture = new Capture(); + } +}); +} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +if (!PhoneGap.hasResource("compass")) { +PhoneGap.addResource("compass"); + +var CompassError = function(){ + this.code = null; +}; + +// Capture error codes +CompassError.COMPASS_INTERNAL_ERR = 0; +CompassError.COMPASS_NOT_SUPPORTED = 20; + +var CompassHeading = function() { + this.magneticHeading = null; + this.trueHeading = null; + this.headingAccuracy = null; + this.timestamp = null; +}; + +/** + * This class provides access to device Compass data. + * @constructor + */ +var Compass = function() { + /** + * The last known Compass position. + */ + this.lastHeading = null; + + /** + * List of compass watch timers + */ + this.timers = {}; +}; + +Compass.ERROR_MSG = ["Not running", "Starting", "", "Failed to start"]; + +/** + * Asynchronously aquires the current heading. + * + * @param {Function} successCallback The function to call when the heading data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) + * @param {PositionOptions} options The options for getting the heading data such as timeout. (OPTIONAL) + */ +Compass.prototype.getCurrentHeading = function(successCallback, errorCallback, options) { + + // successCallback required + if (typeof successCallback !== "function") { + console.log("Compass Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback !== "function")) { + console.log("Compass Error: errorCallback is not a function"); + return; + } + + // Get heading + PhoneGap.exec(successCallback, errorCallback, "Compass", "getHeading", []); +}; + +/** + * Asynchronously aquires the heading repeatedly at a given interval. + * + * @param {Function} successCallback The function to call each time the heading data is available + * @param {Function} errorCallback The function to call when there is an error getting the heading data. (OPTIONAL) + * @param {HeadingOptions} options The options for getting the heading data such as timeout and the frequency of the watch. (OPTIONAL) + * @return String The watch id that must be passed to #clearWatch to stop watching. + */ +Compass.prototype.watchHeading= function(successCallback, errorCallback, options) { + + // Default interval (100 msec) + var frequency = (options !== undefined) ? options.frequency : 100; + + // successCallback required + if (typeof successCallback !== "function") { + console.log("Compass Error: successCallback is not a function"); + return; + } + + // errorCallback optional + if (errorCallback && (typeof errorCallback !== "function")) { + console.log("Compass Error: errorCallback is not a function"); + return; + } + + // Make sure compass timeout > frequency + 10 sec + PhoneGap.exec( + function(timeout) { + if (timeout < (frequency + 10000)) { + PhoneGap.exec(null, null, "Compass", "setTimeout", [frequency + 10000]); + } + }, + function(e) { }, "Compass", "getTimeout", []); + + // Start watch timer to get headings + var id = PhoneGap.createUUID(); + navigator.compass.timers[id] = setInterval( + function() { + PhoneGap.exec(successCallback, errorCallback, "Compass", "getHeading", []); + }, (frequency ? frequency : 1)); + + return id; +}; + + +/** + * Clears the specified heading watch. + * + * @param {String} id The ID of the watch returned from #watchHeading. + */ +Compass.prototype.clearWatch = function(id) { + + // Stop javascript timer & remove from timer list + if (id && navigator.compass.timers[id]) { + clearInterval(navigator.compass.timers[id]); + delete navigator.compass.timers[id]; + } +}; + +Compass.prototype._castDate = function(pluginResult) { + if (pluginResult.message.timestamp) { + var timestamp = new Date(pluginResult.message.timestamp); + pluginResult.message.timestamp = timestamp; + } + return pluginResult; +}; + +PhoneGap.addConstructor(function() { + if (typeof navigator.compass === "undefined") { + navigator.compass = new Compass(); + } +}); +} +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +if (!PhoneGap.hasResource("contact")) { +PhoneGap.addResource("contact"); + +/** +* Contains information about a single contact. +* @constructor +* @param {DOMString} id unique identifier +* @param {DOMString} displayName +* @param {ContactName} name +* @param {DOMString} nickname +* @param {Array.} phoneNumbers array of phone numbers +* @param {Array.} emails array of email addresses +* @param {Array.} addresses array of addresses +* @param {Array.} ims instant messaging user ids +* @param {Array.} organizations +* @param {DOMString} birthday contact's birthday +* @param {DOMString} note user notes about contact +* @param {Array.} photos +* @param {Array.} categories +* @param {Array.} urls contact's web sites +*/ +var Contact = function (id, displayName, name, nickname, phoneNumbers, emails, addresses, + ims, organizations, birthday, note, photos, categories, urls) { + this.id = id || null; + this.rawId = null; + this.displayName = displayName || null; + this.name = name || null; // ContactName + this.nickname = nickname || null; + this.phoneNumbers = phoneNumbers || null; // ContactField[] + this.emails = emails || null; // ContactField[] + this.addresses = addresses || null; // ContactAddress[] + this.ims = ims || null; // ContactField[] + this.organizations = organizations || null; // ContactOrganization[] + this.birthday = birthday || null; + this.note = note || null; + this.photos = photos || null; // ContactField[] + this.categories = categories || null; // ContactField[] + this.urls = urls || null; // ContactField[] +}; + +/** + * ContactError. + * An error code assigned by an implementation when an error has occurreds + * @constructor + */ +var ContactError = function() { + this.code=null; +}; + +/** + * Error codes + */ +ContactError.UNKNOWN_ERROR = 0; +ContactError.INVALID_ARGUMENT_ERROR = 1; +ContactError.TIMEOUT_ERROR = 2; +ContactError.PENDING_OPERATION_ERROR = 3; +ContactError.IO_ERROR = 4; +ContactError.NOT_SUPPORTED_ERROR = 5; +ContactError.PERMISSION_DENIED_ERROR = 20; + +/** +* Removes contact from device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.remove = function(successCB, errorCB) { + if (this.id === null) { + var errorObj = new ContactError(); + errorObj.code = ContactError.UNKNOWN_ERROR; + errorCB(errorObj); + } + else { + PhoneGap.exec(successCB, errorCB, "Contacts", "remove", [this.id]); + } +}; + +/** +* Creates a deep copy of this Contact. +* With the contact ID set to null. +* @return copy of this Contact +*/ +Contact.prototype.clone = function() { + var clonedContact = PhoneGap.clone(this); + var i; + clonedContact.id = null; + clonedContact.rawId = null; + // Loop through and clear out any id's in phones, emails, etc. + if (clonedContact.phoneNumbers) { + for (i = 0; i < clonedContact.phoneNumbers.length; i++) { + clonedContact.phoneNumbers[i].id = null; + } + } + if (clonedContact.emails) { + for (i = 0; i < clonedContact.emails.length; i++) { + clonedContact.emails[i].id = null; + } + } + if (clonedContact.addresses) { + for (i = 0; i < clonedContact.addresses.length; i++) { + clonedContact.addresses[i].id = null; + } + } + if (clonedContact.ims) { + for (i = 0; i < clonedContact.ims.length; i++) { + clonedContact.ims[i].id = null; + } + } + if (clonedContact.organizations) { + for (i = 0; i < clonedContact.organizations.length; i++) { + clonedContact.organizations[i].id = null; + } + } + if (clonedContact.tags) { + for (i = 0; i < clonedContact.tags.length; i++) { + clonedContact.tags[i].id = null; + } + } + if (clonedContact.photos) { + for (i = 0; i < clonedContact.photos.length; i++) { + clonedContact.photos[i].id = null; + } + } + if (clonedContact.urls) { + for (i = 0; i < clonedContact.urls.length; i++) { + clonedContact.urls[i].id = null; + } + } + return clonedContact; +}; + +/** +* Persists contact to device storage. +* @param successCB success callback +* @param errorCB error callback +*/ +Contact.prototype.save = function(successCB, errorCB) { + PhoneGap.exec(successCB, errorCB, "Contacts", "save", [this]); +}; + +/** +* Contact name. +* @constructor +* @param formatted +* @param familyName +* @param givenName +* @param middle +* @param prefix +* @param suffix +*/ +var ContactName = function(formatted, familyName, givenName, middle, prefix, suffix) { + this.formatted = formatted || null; + this.familyName = familyName || null; + this.givenName = givenName || null; + this.middleName = middle || null; + this.honorificPrefix = prefix || null; + this.honorificSuffix = suffix || null; +}; + +/** +* Generic contact field. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code +* @param type +* @param value +* @param pref +*/ +var ContactField = function(type, value, pref) { + this.id = null; + this.type = type || null; + this.value = value || null; + this.pref = pref || null; +}; + +/** +* Contact address. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code +* @param formatted +* @param streetAddress +* @param locality +* @param region +* @param postalCode +* @param country +*/ +var ContactAddress = function(pref, type, formatted, streetAddress, locality, region, postalCode, country) { + this.id = null; + this.pref = pref || null; + this.type = type || null; + this.formatted = formatted || null; + this.streetAddress = streetAddress || null; + this.locality = locality || null; + this.region = region || null; + this.postalCode = postalCode || null; + this.country = country || null; +}; + +/** +* Contact organization. +* @constructor +* @param {DOMString} id unique identifier, should only be set by native code +* @param name +* @param dept +* @param title +* @param startDate +* @param endDate +* @param location +* @param desc +*/ +var ContactOrganization = function(pref, type, name, dept, title) { + this.id = null; + this.pref = pref || null; + this.type = type || null; + this.name = name || null; + this.department = dept || null; + this.title = title || null; +}; + +/** +* Represents a group of Contacts. +* @constructor +*/ +var Contacts = function() { + this.inProgress = false; + this.records = []; +}; +/** +* Returns an array of Contacts matching the search criteria. +* @param fields that should be searched +* @param successCB success callback +* @param errorCB error callback +* @param {ContactFindOptions} options that can be applied to contact searching +* @return array of Contacts matching search criteria +*/ +Contacts.prototype.find = function(fields, successCB, errorCB, options) { + if (successCB === null) { + throw new TypeError("You must specify a success callback for the find command."); + } + if (fields === null || fields === "undefined" || fields.length === "undefined" || fields.length <= 0) { + if (typeof errorCB === "function") { + errorCB({"code": ContactError.INVALID_ARGUMENT_ERROR}); + } + } else { + PhoneGap.exec(successCB, errorCB, "Contacts", "search", [fields, options]); + } +}; + +/** +* This function creates a new contact, but it does not persist the contact +* to device storage. To persist the contact to device storage, invoke +* contact.save(). +* @param properties an object who's properties will be examined to create a new Contact +* @returns new Contact object +*/ +Contacts.prototype.create = function(properties) { + var i; + var contact = new Contact(); + for (i in properties) { + if (contact[i] !== 'undefined') { + contact[i] = properties[i]; + } + } + return contact; +}; + +/** +* This function returns and array of contacts. It is required as we need to convert raw +* JSON objects into concrete Contact objects. Currently this method is called after +* navigator.contacts.find but before the find methods success call back. +* +* @param jsonArray an array of JSON Objects that need to be converted to Contact objects. +* @returns an array of Contact objects +*/ +Contacts.prototype.cast = function(pluginResult) { + var contacts = []; + var i; + for (i=0; i][;base64], + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsDataURL = function(file) { + this.fileName = ""; + if (typeof file.fullPath === "undefined") { + this.fileName = file; + } else { + this.fileName = file.fullPath; + } + + // LOADING state + this.readyState = FileReader.LOADING; + + // If loadstart callback + if (typeof this.onloadstart === "function") { + this.onloadstart({"type":"loadstart", "target":this}); + } + + var me = this; + + // Read file + PhoneGap.exec( + // Success callback + function(r) { + var evt; + + // If DONE (cancelled), then don't do anything + if (me.readyState === FileReader.DONE) { + return; + } + + // Save result + me.result = r; + + // If onload callback + if (typeof me.onload === "function") { + me.onload({"type":"load", "target":me}); + } + + // DONE state + me.readyState = FileReader.DONE; + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend({"type":"loadend", "target":me}); + } + }, + // Error callback + function(e) { + var evt; + // If DONE (cancelled), then don't do anything + if (me.readyState === FileReader.DONE) { + return; + } + + // Save error + me.error = e; + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror({"type":"error", "target":me}); + } + + // DONE state + me.readyState = FileReader.DONE; + + // If onloadend callback + if (typeof me.onloadend === "function") { + me.onloadend({"type":"loadend", "target":me}); + } + }, "File", "readAsDataURL", [this.fileName]); +}; + +/** + * Read file and return data as a binary data. + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsBinaryString = function(file) { + // TODO - Can't return binary data to browser. + this.fileName = file; +}; + +/** + * Read file and return data as a binary data. + * + * @param file {File} File object containing file properties + */ +FileReader.prototype.readAsArrayBuffer = function(file) { + // TODO - Can't return binary data to browser. + this.fileName = file; +}; + +//----------------------------------------------------------------------------- +// File Writer +//----------------------------------------------------------------------------- + +/** + * This class writes to the mobile device file system. + * + * For Android: + * The root directory is the root of the file system. + * To write to the SD card, the file name is "sdcard/my_file.txt" + * + * @constructor + * @param file {File} File object containing file properties + * @param append if true write to the end of the file, otherwise overwrite the file + */ +var FileWriter = function(file) { + this.fileName = ""; + this.length = 0; + if (file) { + this.fileName = file.fullPath || file; + this.length = file.size || 0; + } + // default is to write at the beginning of the file + this.position = 0; + + this.readyState = 0; // EMPTY + + this.result = null; + + // Error + this.error = null; + + // Event handlers + this.onwritestart = null; // When writing starts + this.onprogress = null; // While writing the file, and reporting partial file data + this.onwrite = null; // When the write has successfully completed. + this.onwriteend = null; // When the request has completed (either in success or failure). + this.onabort = null; // When the write has been aborted. For instance, by invoking the abort() method. + this.onerror = null; // When the write has failed (see errors). +}; + +// States +FileWriter.INIT = 0; +FileWriter.WRITING = 1; +FileWriter.DONE = 2; + +/** + * Abort writing file. + */ +FileWriter.prototype.abort = function() { + // check for invalid state + if (this.readyState === FileWriter.DONE || this.readyState === FileWriter.INIT) { + throw FileError.INVALID_STATE_ERR; + } + + // set error + var error = new FileError(), evt; + error.code = error.ABORT_ERR; + this.error = error; + + // If error callback + if (typeof this.onerror === "function") { + this.onerror({"type":"error", "target":this}); + } + // If abort callback + if (typeof this.onabort === "function") { + this.onabort({"type":"abort", "target":this}); + } + + this.readyState = FileWriter.DONE; + + // If write end callback + if (typeof this.onwriteend === "function") { + this.onwriteend({"type":"writeend", "target":this}); + } +}; + +/** + * Writes data to the file + * + * @param text to be written + */ +FileWriter.prototype.write = function(text) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw FileError.INVALID_STATE_ERR; + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart === "function") { + me.onwritestart({"type":"writestart", "target":me}); + } + + // Write file + PhoneGap.exec( + // Success callback + function(r) { + var evt; + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // position always increases by bytes written because file would be extended + me.position += r; + // The length of the file is now where we are done writing. + me.length = me.position; + + // If onwrite callback + if (typeof me.onwrite === "function") { + me.onwrite({"type":"write", "target":me}); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend({"type":"writeend", "target":me}); + } + }, + // Error callback + function(e) { + var evt; + + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // Save error + me.error = e; + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror({"type":"error", "target":me}); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend({"type":"writeend", "target":me}); + } + }, "File", "write", [this.fileName, text, this.position]); +}; + +/** + * Moves the file pointer to the location specified. + * + * If the offset is a negative number the position of the file + * pointer is rewound. If the offset is greater than the file + * size the position is set to the end of the file. + * + * @param offset is the location to move the file pointer to. + */ +FileWriter.prototype.seek = function(offset) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw FileError.INVALID_STATE_ERR; + } + + if (!offset) { + return; + } + + // See back from end of file. + if (offset < 0) { + this.position = Math.max(offset + this.length, 0); + } + // Offset is bigger then file size so set position + // to the end of the file. + else if (offset > this.length) { + this.position = this.length; + } + // Offset is between 0 and file size so set the position + // to start writing. + else { + this.position = offset; + } +}; + +/** + * Truncates the file to the size specified. + * + * @param size to chop the file at. + */ +FileWriter.prototype.truncate = function(size) { + // Throw an exception if we are already writing a file + if (this.readyState === FileWriter.WRITING) { + throw FileError.INVALID_STATE_ERR; + } + + // WRITING state + this.readyState = FileWriter.WRITING; + + var me = this; + + // If onwritestart callback + if (typeof me.onwritestart === "function") { + me.onwritestart({"type":"writestart", "target":this}); + } + + // Write file + PhoneGap.exec( + // Success callback + function(r) { + var evt; + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // Update the length of the file + me.length = r; + me.position = Math.min(me.position, r); + + // If onwrite callback + if (typeof me.onwrite === "function") { + me.onwrite({"type":"write", "target":me}); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend({"type":"writeend", "target":me}); + } + }, + // Error callback + function(e) { + var evt; + // If DONE (cancelled), then don't do anything + if (me.readyState === FileWriter.DONE) { + return; + } + + // Save error + me.error = e; + + // If onerror callback + if (typeof me.onerror === "function") { + me.onerror({"type":"error", "target":me}); + } + + // DONE state + me.readyState = FileWriter.DONE; + + // If onwriteend callback + if (typeof me.onwriteend === "function") { + me.onwriteend({"type":"writeend", "target":me}); + } + }, "File", "truncate", [this.fileName, size]); +}; + +/** + * Information about the state of the file or directory + * + * @constructor + * {Date} modificationTime (readonly) + */ +var Metadata = function() { + this.modificationTime=null; +}; + +/** + * Supplies arguments to methods that lookup or create files and directories + * + * @constructor + * @param {boolean} create file or directory if it doesn't exist + * @param {boolean} exclusive if true the command will fail if the file or directory exists + */ +var Flags = function(create, exclusive) { + this.create = create || false; + this.exclusive = exclusive || false; +}; + +/** + * An interface representing a file system + * + * @constructor + * {DOMString} name the unique name of the file system (readonly) + * {DirectoryEntry} root directory of the file system (readonly) + */ +var FileSystem = function() { + this.name = null; + this.root = null; +}; + +/** + * An interface that lists the files and directories in a directory. + * @constructor + */ +var DirectoryReader = function(fullPath){ + this.fullPath = fullPath || null; +}; + +/** + * Returns a list of entries from a directory. + * + * @param {Function} successCallback is called with a list of entries + * @param {Function} errorCallback is called with a FileError + */ +DirectoryReader.prototype.readEntries = function(successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "readEntries", [this.fullPath]); +}; + +/** + * An interface representing a directory on the file system. + * + * @constructor + * {boolean} isFile always false (readonly) + * {boolean} isDirectory always true (readonly) + * {DOMString} name of the directory, excluding the path leading to it (readonly) + * {DOMString} fullPath the absolute full path to the directory (readonly) + * {FileSystem} filesystem on which the directory resides (readonly) + */ +var DirectoryEntry = function() { + this.isFile = false; + this.isDirectory = true; + this.name = null; + this.fullPath = null; + this.filesystem = null; +}; + +/** + * Copies a directory to a new location + * + * @param {DirectoryEntry} parent the directory to which to copy the entry + * @param {DOMString} newName the new name of the entry, defaults to the current name + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "copyTo", [this.fullPath, parent, newName]); +}; + +/** + * Looks up the metadata of the entry + * + * @param {Function} successCallback is called with a Metadata object + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getMetadata = function(successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "getMetadata", [this.fullPath]); +}; + +/** + * Gets the parent of the entry + * + * @param {Function} successCallback is called with a parent entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getParent = function(successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "getParent", [this.fullPath]); +}; + +/** + * Moves a directory to a new location + * + * @param {DirectoryEntry} parent the directory to which to move the entry + * @param {DOMString} newName the new name of the entry, defaults to the current name + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "moveTo", [this.fullPath, parent, newName]); +}; + +/** + * Removes the entry + * + * @param {Function} successCallback is called with no parameters + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.remove = function(successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "remove", [this.fullPath]); +}; + +/** + * Returns a URI that can be used to identify this entry. + * + * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI. + * @return uri + */ +DirectoryEntry.prototype.toURI = function(mimeType) { + return "file://" + this.fullPath; +}; + +/** + * Creates a new DirectoryReader to read entries from this directory + */ +DirectoryEntry.prototype.createReader = function(successCallback, errorCallback) { + return new DirectoryReader(this.fullPath); +}; + +/** + * Creates or looks up a directory + * + * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a directory + * @param {Flags} options to create or excluively create the directory + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getDirectory = function(path, options, successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "getDirectory", [this.fullPath, path, options]); +}; + +/** + * Creates or looks up a file + * + * @param {DOMString} path either a relative or absolute path from this directory in which to look up or create a file + * @param {Flags} options to create or excluively create the file + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.getFile = function(path, options, successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "getFile", [this.fullPath, path, options]); +}; + +/** + * Deletes a directory and all of it's contents + * + * @param {Function} successCallback is called with no parameters + * @param {Function} errorCallback is called with a FileError + */ +DirectoryEntry.prototype.removeRecursively = function(successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "removeRecursively", [this.fullPath]); +}; + +/** + * An interface representing a directory on the file system. + * + * @constructor + * {boolean} isFile always true (readonly) + * {boolean} isDirectory always false (readonly) + * {DOMString} name of the file, excluding the path leading to it (readonly) + * {DOMString} fullPath the absolute full path to the file (readonly) + * {FileSystem} filesystem on which the directory resides (readonly) + */ +var FileEntry = function() { + this.isFile = true; + this.isDirectory = false; + this.name = null; + this.fullPath = null; + this.filesystem = null; +}; + +/** + * Copies a file to a new location + * + * @param {DirectoryEntry} parent the directory to which to copy the entry + * @param {DOMString} newName the new name of the entry, defaults to the current name + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.copyTo = function(parent, newName, successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "copyTo", [this.fullPath, parent, newName]); +}; + +/** + * Looks up the metadata of the entry + * + * @param {Function} successCallback is called with a Metadata object + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.getMetadata = function(successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "getMetadata", [this.fullPath]); +}; + +/** + * Gets the parent of the entry + * + * @param {Function} successCallback is called with a parent entry + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.getParent = function(successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "getParent", [this.fullPath]); +}; + +/** + * Moves a directory to a new location + * + * @param {DirectoryEntry} parent the directory to which to move the entry + * @param {DOMString} newName the new name of the entry, defaults to the current name + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.moveTo = function(parent, newName, successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "moveTo", [this.fullPath, parent, newName]); +}; + +/** + * Removes the entry + * + * @param {Function} successCallback is called with no parameters + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.remove = function(successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "remove", [this.fullPath]); +}; + +/** + * Returns a URI that can be used to identify this entry. + * + * @param {DOMString} mimeType for a FileEntry, the mime type to be used to interpret the file, when loaded through this URI. + * @return uri + */ +FileEntry.prototype.toURI = function(mimeType) { + return "file://" + this.fullPath; +}; + +/** + * Creates a new FileWriter associated with the file that this FileEntry represents. + * + * @param {Function} successCallback is called with the new FileWriter + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.createWriter = function(successCallback, errorCallback) { + this.file(function(filePointer) { + var writer = new FileWriter(filePointer); + + if (writer.fileName === null || writer.fileName === "") { + if (typeof errorCallback === "function") { + errorCallback({ + "code": FileError.INVALID_STATE_ERR + }); + } + } + + if (typeof successCallback === "function") { + successCallback(writer); + } + }, errorCallback); +}; + +/** + * Returns a File that represents the current state of the file that this FileEntry represents. + * + * @param {Function} successCallback is called with the new File object + * @param {Function} errorCallback is called with a FileError + */ +FileEntry.prototype.file = function(successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "getFileMetadata", [this.fullPath]); +}; + +/** @constructor */ +var LocalFileSystem = function() { +}; + +// File error codes +LocalFileSystem.TEMPORARY = 0; +LocalFileSystem.PERSISTENT = 1; +LocalFileSystem.RESOURCE = 2; +LocalFileSystem.APPLICATION = 3; + +/** + * Requests a filesystem in which to store application data. + * + * @param {int} type of file system being requested + * @param {Function} successCallback is called with the new FileSystem + * @param {Function} errorCallback is called with a FileError + */ +LocalFileSystem.prototype.requestFileSystem = function(type, size, successCallback, errorCallback) { + if (type < 0 || type > 3) { + if (typeof errorCallback === "function") { + errorCallback({ + "code": FileError.SYNTAX_ERR + }); + } + } + else { + PhoneGap.exec(successCallback, errorCallback, "File", "requestFileSystem", [type, size]); + } +}; + +/** + * + * @param {DOMString} uri referring to a local file in a filesystem + * @param {Function} successCallback is called with the new entry + * @param {Function} errorCallback is called with a FileError + */ +LocalFileSystem.prototype.resolveLocalFileSystemURI = function(uri, successCallback, errorCallback) { + PhoneGap.exec(successCallback, errorCallback, "File", "resolveLocalFileSystemURI", [uri]); +}; + +/** +* This function returns and array of contacts. It is required as we need to convert raw +* JSON objects into concrete Contact objects. Currently this method is called after +* navigator.service.contacts.find but before the find methods success call back. +* +* @param a JSON Objects that need to be converted to DirectoryEntry or FileEntry objects. +* @returns an entry +*/ +LocalFileSystem.prototype._castFS = function(pluginResult) { + var entry = null; + entry = new DirectoryEntry(); + entry.isDirectory = pluginResult.message.root.isDirectory; + entry.isFile = pluginResult.message.root.isFile; + entry.name = pluginResult.message.root.name; + entry.fullPath = pluginResult.message.root.fullPath; + pluginResult.message.root = entry; + return pluginResult; +}; + +LocalFileSystem.prototype._castEntry = function(pluginResult) { + var entry = null; + if (pluginResult.message.isDirectory) { + entry = new DirectoryEntry(); + } + else if (pluginResult.message.isFile) { + entry = new FileEntry(); + } + entry.isDirectory = pluginResult.message.isDirectory; + entry.isFile = pluginResult.message.isFile; + entry.name = pluginResult.message.name; + entry.fullPath = pluginResult.message.fullPath; + pluginResult.message = entry; + return pluginResult; +}; + +LocalFileSystem.prototype._castEntries = function(pluginResult) { + var entries = pluginResult.message; + var retVal = []; + for (var i=0; i + * + * @param name The plugin name + * @param obj The plugin object + */ +PhoneGap.addPlugin = function(name, obj) { + if (!window.plugins[name]) { + window.plugins[name] = obj; + } + else { + console.log("Error: Plugin "+name+" already exists."); + } +}; + +/** + * onDOMContentLoaded channel is fired when the DOM content + * of the page has been parsed. + */ +PhoneGap.onDOMContentLoaded = new PhoneGap.Channel('onDOMContentLoaded'); + +/** + * onNativeReady channel is fired when the PhoneGap native code + * has been initialized. + */ +PhoneGap.onNativeReady = new PhoneGap.Channel('onNativeReady'); + +/** + * onPhoneGapInit channel is fired when the web page is fully loaded and + * PhoneGap native code has been initialized. + */ +PhoneGap.onPhoneGapInit = new PhoneGap.Channel('onPhoneGapInit'); + +/** + * onPhoneGapReady channel is fired when the JS PhoneGap objects have been created. + */ +PhoneGap.onPhoneGapReady = new PhoneGap.Channel('onPhoneGapReady'); + +/** + * onPhoneGapInfoReady channel is fired when the PhoneGap device properties + * has been set. + */ +PhoneGap.onPhoneGapInfoReady = new PhoneGap.Channel('onPhoneGapInfoReady'); + +/** + * onPhoneGapConnectionReady channel is fired when the PhoneGap connection properties + * has been set. + */ +PhoneGap.onPhoneGapConnectionReady = new PhoneGap.Channel('onPhoneGapConnectionReady'); + +/** + * onDestroy channel is fired when the PhoneGap native code + * is destroyed. It is used internally. + * Window.onunload should be used by the user. + */ +PhoneGap.onDestroy = new PhoneGap.Channel('onDestroy'); +PhoneGap.onDestroy.subscribeOnce(function() { + PhoneGap.shuttingDown = true; +}); +PhoneGap.shuttingDown = false; + +// _nativeReady is global variable that the native side can set +// to signify that the native code is ready. It is a global since +// it may be called before any PhoneGap JS is ready. +if (typeof _nativeReady !== 'undefined') { PhoneGap.onNativeReady.fire(); } + +/** + * onDeviceReady is fired only after all PhoneGap objects are created and + * the device properties are set. + */ +PhoneGap.onDeviceReady = new PhoneGap.Channel('onDeviceReady'); + + +// Array of channels that must fire before "deviceready" is fired +PhoneGap.deviceReadyChannelsArray = [ PhoneGap.onPhoneGapReady, PhoneGap.onPhoneGapInfoReady, PhoneGap.onPhoneGapConnectionReady]; + +// Hashtable of user defined channels that must also fire before "deviceready" is fired +PhoneGap.deviceReadyChannelsMap = {}; + +/** + * Indicate that a feature needs to be initialized before it is ready to be used. + * This holds up PhoneGap's "deviceready" event until the feature has been initialized + * and PhoneGap.initComplete(feature) is called. + * + * @param feature {String} The unique feature name + */ +PhoneGap.waitForInitialization = function(feature) { + if (feature) { + var channel = new PhoneGap.Channel(feature); + PhoneGap.deviceReadyChannelsMap[feature] = channel; + PhoneGap.deviceReadyChannelsArray.push(channel); + } +}; + +/** + * Indicate that initialization code has completed and the feature is ready to be used. + * + * @param feature {String} The unique feature name + */ +PhoneGap.initializationComplete = function(feature) { + var channel = PhoneGap.deviceReadyChannelsMap[feature]; + if (channel) { + channel.fire(); + } +}; + +/** + * Create all PhoneGap objects once page has fully loaded and native side is ready. + */ +PhoneGap.Channel.join(function() { + + // Start listening for XHR callbacks + setTimeout(function() { + if (PhoneGap.UsePolling) { + PhoneGap.JSCallbackPolling(); + } + else { + var polling = prompt("usePolling", "gap_callbackServer:"); + PhoneGap.UsePolling = polling; + if (polling == "true") { + PhoneGap.UsePolling = true; + PhoneGap.JSCallbackPolling(); + } + else { + PhoneGap.UsePolling = false; + PhoneGap.JSCallback(); + } + } + }, 1); + + // Run PhoneGap constructors + PhoneGap.onPhoneGapInit.fire(); + + // Fire event to notify that all objects are created + PhoneGap.onPhoneGapReady.fire(); + + // Fire onDeviceReady event once all constructors have run and PhoneGap info has been + // received from native side, and any user defined initialization channels. + PhoneGap.Channel.join(function() { + // Let native code know we are inited on JS side + prompt("", "gap_init:"); + + PhoneGap.onDeviceReady.fire(); + }, PhoneGap.deviceReadyChannelsArray); + +}, [ PhoneGap.onDOMContentLoaded, PhoneGap.onNativeReady ]); + +// Listen for DOMContentLoaded and notify our channel subscribers +document.addEventListener('DOMContentLoaded', function() { + PhoneGap.onDOMContentLoaded.fire(); +}, false); + +// Intercept calls to document.addEventListener and watch for deviceready +PhoneGap.m_document_addEventListener = document.addEventListener; + +// Intercept calls to window.addEventListener +PhoneGap.m_window_addEventListener = window.addEventListener; + +/** + * Add a custom window event handler. + * + * @param {String} event The event name that callback handles + * @param {Function} callback The event handler + */ +PhoneGap.addWindowEventHandler = function(event, callback) { + PhoneGap.windowEventHandler[event] = callback; +}; + +/** + * Add a custom document event handler. + * + * @param {String} event The event name that callback handles + * @param {Function} callback The event handler + */ +PhoneGap.addDocumentEventHandler = function(event, callback) { + PhoneGap.documentEventHandler[event] = callback; +}; + +/** + * Intercept adding document event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +document.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + if (e === 'deviceready') { + PhoneGap.onDeviceReady.subscribeOnce(handler); + } + else { + // If subscribing to Android backbutton + if (e === 'backbutton') { + PhoneGap.exec(null, null, "App", "overrideBackbutton", [true]); + } + + // If subscribing to an event that is handled by a plugin + else if (typeof PhoneGap.documentEventHandler[e] !== "undefined") { + if (PhoneGap.documentEventHandler[e](e, handler, true)) { + return; // Stop default behavior + } + } + + PhoneGap.m_document_addEventListener.call(document, evt, handler, capture); + } +}; + +/** + * Intercept adding window event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +window.addEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If subscribing to an event that is handled by a plugin + if (typeof PhoneGap.windowEventHandler[e] !== "undefined") { + if (PhoneGap.windowEventHandler[e](e, handler, true)) { + return; // Stop default behavior + } + } + + PhoneGap.m_window_addEventListener.call(window, evt, handler, capture); +}; + +// Intercept calls to document.removeEventListener and watch for events that +// are generated by PhoneGap native code +PhoneGap.m_document_removeEventListener = document.removeEventListener; + +// Intercept calls to window.removeEventListener +PhoneGap.m_window_removeEventListener = window.removeEventListener; + +/** + * Intercept removing document event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +document.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If unsubscribing to Android backbutton + if (e === 'backbutton') { + PhoneGap.exec(null, null, "App", "overrideBackbutton", [false]); + } + + // If unsubcribing from an event that is handled by a plugin + if (typeof PhoneGap.documentEventHandler[e] !== "undefined") { + if (PhoneGap.documentEventHandler[e](e, handler, false)) { + return; // Stop default behavior + } + } + + PhoneGap.m_document_removeEventListener.call(document, evt, handler, capture); +}; + +/** + * Intercept removing window event listeners and handle our own + * + * @param {Object} evt + * @param {Function} handler + * @param capture + */ +window.removeEventListener = function(evt, handler, capture) { + var e = evt.toLowerCase(); + + // If unsubcribing from an event that is handled by a plugin + if (typeof PhoneGap.windowEventHandler[e] !== "undefined") { + if (PhoneGap.windowEventHandler[e](e, handler, false)) { + return; // Stop default behavior + } + } + + PhoneGap.m_window_removeEventListener.call(window, evt, handler, capture); +}; + +/** + * Method to fire document event + * + * @param {String} type The event type to fire + * @param {Object} data Data to send with event + */ +PhoneGap.fireDocumentEvent = function(type, data) { + var e = document.createEvent('Events'); + e.initEvent(type); + if (data) { + for (var i in data) { + e[i] = data[i]; + } + } + document.dispatchEvent(e); +}; + +/** + * Method to fire window event + * + * @param {String} type The event type to fire + * @param {Object} data Data to send with event + */ +PhoneGap.fireWindowEvent = function(type, data) { + var e = document.createEvent('Events'); + e.initEvent(type); + if (data) { + for (var i in data) { + e[i] = data[i]; + } + } + window.dispatchEvent(e); +}; + +/** + * Does a deep clone of the object. + * + * @param obj + * @return {Object} + */ +PhoneGap.clone = function(obj) { + var i, retVal; + if(!obj) { + return obj; + } + + if(obj instanceof Array){ + retVal = []; + for(i = 0; i < obj.length; ++i){ + retVal.push(PhoneGap.clone(obj[i])); + } + return retVal; + } + + if (typeof obj === "function") { + return obj; + } + + if(!(obj instanceof Object)){ + return obj; + } + + if (obj instanceof Date) { + return obj; + } + + retVal = {}; + for(i in obj){ + if(!(i in retVal) || retVal[i] !== obj[i]) { + retVal[i] = PhoneGap.clone(obj[i]); + } + } + return retVal; +}; + +PhoneGap.callbackId = 0; +PhoneGap.callbacks = {}; +PhoneGap.callbackStatus = { + NO_RESULT: 0, + OK: 1, + CLASS_NOT_FOUND_EXCEPTION: 2, + ILLEGAL_ACCESS_EXCEPTION: 3, + INSTANTIATION_EXCEPTION: 4, + MALFORMED_URL_EXCEPTION: 5, + IO_EXCEPTION: 6, + INVALID_ACTION: 7, + JSON_EXCEPTION: 8, + ERROR: 9 + }; + + +/** + * Execute a PhoneGap command. It is up to the native side whether this action is synch or async. + * The native side can return: + * Synchronous: PluginResult object as a JSON string + * Asynchrounous: Empty string "" + * If async, the native side will PhoneGap.callbackSuccess or PhoneGap.callbackError, + * depending upon the result of the action. + * + * @param {Function} success The success callback + * @param {Function} fail The fail callback + * @param {String} service The name of the service to use + * @param {String} action Action to be run in PhoneGap + * @param {Array.} [args] Zero or more arguments to pass to the method + */ +PhoneGap.exec = function(success, fail, service, action, args) { + try { + var callbackId = service + PhoneGap.callbackId++; + if (success || fail) { + PhoneGap.callbacks[callbackId] = {success:success, fail:fail}; + } + + var r = prompt(JSON.stringify(args), "gap:"+JSON.stringify([service, action, callbackId, true])); + + // If a result was returned + if (r.length > 0) { + eval("var v="+r+";"); + + // If status is OK, then return value back to caller + if (v.status === PhoneGap.callbackStatus.OK) { + + // If there is a success callback, then call it now with + // returned value + if (success) { + try { + success(v.message); + } catch (e) { + console.log("Error in success callback: " + callbackId + " = " + e); + } + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } + return v.message; + } + + // If no result + else if (v.status === PhoneGap.callbackStatus.NO_RESULT) { + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } + + // If error, then display error + else { + console.log("Error: Status="+v.status+" Message="+v.message); + + // If there is a fail callback, then call it now with returned value + if (fail) { + try { + fail(v.message); + } + catch (e1) { + console.log("Error in error callback: "+callbackId+" = "+e1); + } + + // Clear callback if not expecting any more results + if (!v.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } + return null; + } + } + } catch (e2) { + console.log("Error: "+e2); + } +}; + +/** + * Called by native code when returning successful result from an action. + * + * @param callbackId + * @param args + */ +PhoneGap.callbackSuccess = function(callbackId, args) { + if (PhoneGap.callbacks[callbackId]) { + + // If result is to be sent to callback + if (args.status === PhoneGap.callbackStatus.OK) { + try { + if (PhoneGap.callbacks[callbackId].success) { + PhoneGap.callbacks[callbackId].success(args.message); + } + } + catch (e) { + console.log("Error in success callback: "+callbackId+" = "+e); + } + } + + // Clear callback if not expecting any more results + if (!args.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } +}; + +/** + * Called by native code when returning error result from an action. + * + * @param callbackId + * @param args + */ +PhoneGap.callbackError = function(callbackId, args) { + if (PhoneGap.callbacks[callbackId]) { + try { + if (PhoneGap.callbacks[callbackId].fail) { + PhoneGap.callbacks[callbackId].fail(args.message); + } + } + catch (e) { + console.log("Error in error callback: "+callbackId+" = "+e); + } + + // Clear callback if not expecting any more results + if (!args.keepCallback) { + delete PhoneGap.callbacks[callbackId]; + } + } +}; + +PhoneGap.JSCallbackPort = null; +PhoneGap.JSCallbackToken = null; + +/** + * This is only for Android. + * + * Internal function that uses XHR to call into PhoneGap Java code and retrieve + * any JavaScript code that needs to be run. This is used for callbacks from + * Java to JavaScript. + */ +PhoneGap.JSCallback = function() { + + // Exit if shutting down app + if (PhoneGap.shuttingDown) { + return; + } + + // If polling flag was changed, start using polling from now on + if (PhoneGap.UsePolling) { + PhoneGap.JSCallbackPolling(); + return; + } + + var xmlhttp = new XMLHttpRequest(); + + // Callback function when XMLHttpRequest is ready + xmlhttp.onreadystatechange=function(){ + if(xmlhttp.readyState === 4){ + + // Exit if shutting down app + if (PhoneGap.shuttingDown) { + return; + } + + // If callback has JavaScript statement to execute + if (xmlhttp.status === 200) { + + // Need to url decode the response + var msg = decodeURIComponent(xmlhttp.responseText); + setTimeout(function() { + try { + var t = eval(msg); + } + catch (e) { + // If we're getting an error here, seeing the message will help in debugging + console.log("JSCallback: Message from Server: " + msg); + console.log("JSCallback Error: "+e); + } + }, 1); + setTimeout(PhoneGap.JSCallback, 1); + } + + // If callback ping (used to keep XHR request from timing out) + else if (xmlhttp.status === 404) { + setTimeout(PhoneGap.JSCallback, 10); + } + + // If security error + else if (xmlhttp.status === 403) { + console.log("JSCallback Error: Invalid token. Stopping callbacks."); + } + + // If server is stopping + else if (xmlhttp.status === 503) { + console.log("JSCallback Server Closed: Stopping callbacks."); + } + + // If request wasn't GET + else if (xmlhttp.status === 400) { + console.log("JSCallback Error: Bad request. Stopping callbacks."); + } + + // If error, revert to polling + else { + console.log("JSCallback Error: Request failed."); + PhoneGap.UsePolling = true; + PhoneGap.JSCallbackPolling(); + } + } + }; + + if (PhoneGap.JSCallbackPort === null) { + PhoneGap.JSCallbackPort = prompt("getPort", "gap_callbackServer:"); + } + if (PhoneGap.JSCallbackToken === null) { + PhoneGap.JSCallbackToken = prompt("getToken", "gap_callbackServer:"); + } + xmlhttp.open("GET", "http://127.0.0.1:"+PhoneGap.JSCallbackPort+"/"+PhoneGap.JSCallbackToken , true); + xmlhttp.send(); +}; + +/** + * The polling period to use with JSCallbackPolling. + * This can be changed by the application. The default is 50ms. + */ +PhoneGap.JSCallbackPollingPeriod = 50; + +/** + * Flag that can be set by the user to force polling to be used or force XHR to be used. + */ +PhoneGap.UsePolling = false; // T=use polling, F=use XHR + +/** + * This is only for Android. + * + * Internal function that uses polling to call into PhoneGap Java code and retrieve + * any JavaScript code that needs to be run. This is used for callbacks from + * Java to JavaScript. + */ +PhoneGap.JSCallbackPolling = function() { + + // Exit if shutting down app + if (PhoneGap.shuttingDown) { + return; + } + + // If polling flag was changed, stop using polling from now on + if (!PhoneGap.UsePolling) { + PhoneGap.JSCallback(); + return; + } + + var msg = prompt("", "gap_poll:"); + if (msg) { + setTimeout(function() { + try { + var t = eval(""+msg); + } + catch (e) { + console.log("JSCallbackPolling: Message from Server: " + msg); + console.log("JSCallbackPolling Error: "+e); + } + }, 1); + setTimeout(PhoneGap.JSCallbackPolling, 1); + } + else { + setTimeout(PhoneGap.JSCallbackPolling, PhoneGap.JSCallbackPollingPeriod); + } +}; + +/** + * Create a UUID + * + * @return {String} + */ +PhoneGap.createUUID = function() { + return PhoneGap.UUIDcreatePart(4) + '-' + + PhoneGap.UUIDcreatePart(2) + '-' + + PhoneGap.UUIDcreatePart(2) + '-' + + PhoneGap.UUIDcreatePart(2) + '-' + + PhoneGap.UUIDcreatePart(6); +}; + +PhoneGap.UUIDcreatePart = function(length) { + var uuidpart = ""; + var i, uuidchar; + for (i=0; i'); +document.write(''); + +function backHome() { + if (device.platform.toLowerCase() == 'android') { + navigator.app.backHistory(); + } + else { + document.location = "../index.html"; + } +} diff --git a/test/assets/www/sql/index.html b/test/assets/www/sql/index.html new file mode 100755 index 00000000..c9e1a539 --- /dev/null +++ b/test/assets/www/sql/index.html @@ -0,0 +1,132 @@ + + + + + + PhoneGap + + + + + + + + + +

    HTML5 Database

    +
    + Results:
    + +
    +

    Action

    + Create, Add, Read Database + Read Database +

     

    Back + + diff --git a/test/assets/www/storage/index.html b/test/assets/www/storage/index.html new file mode 100755 index 00000000..ab0ccaa9 --- /dev/null +++ b/test/assets/www/storage/index.html @@ -0,0 +1,50 @@ + + + + + + PhoneGap + + + + + + + + + +

    Local Storage

    +
    + You have run this app an untold number of time(s). +
    + + + +

     

    Back + + diff --git a/test/assets/www/whitelist/index.html b/test/assets/www/whitelist/index.html new file mode 100644 index 00000000..ac60e013 --- /dev/null +++ b/test/assets/www/whitelist/index.html @@ -0,0 +1,35 @@ + + + + + + PhoneGap + + + + + + +

    PhoneGap Tests

    +
    +

    Platform:  

    +

    Version:  

    +

    UUID:  

    +

    Name:  

    +

    Width:  , Height:   + , Color Depth:

    +
    + Automatic Test + Accelerometer + Audio Play/Record + Camera + Compass + Contacts + Events + Location + Misc Content + Notification + Web SQL + Local Storage + + diff --git a/test/libs/android.jar b/test/libs/android.jar new file mode 100644 index 00000000..8854842b Binary files /dev/null and b/test/libs/android.jar differ diff --git a/test/libs/android_library.jar b/test/libs/android_library.jar new file mode 100644 index 00000000..53029eec Binary files /dev/null and b/test/libs/android_library.jar differ diff --git a/test/libs/android_webdriver_library.jar b/test/libs/android_webdriver_library.jar new file mode 100644 index 00000000..d38a6305 Binary files /dev/null and b/test/libs/android_webdriver_library.jar differ diff --git a/test/libs/base.jar b/test/libs/base.jar new file mode 100644 index 00000000..bc2d933d Binary files /dev/null and b/test/libs/base.jar differ diff --git a/test/libs/commons-codec-1.3.jar b/test/libs/commons-codec-1.3.jar new file mode 100644 index 00000000..957b6752 Binary files /dev/null and b/test/libs/commons-codec-1.3.jar differ diff --git a/test/libs/cordova-1.4.1.jar b/test/libs/cordova-1.4.1.jar new file mode 100644 index 00000000..927470c8 Binary files /dev/null and b/test/libs/cordova-1.4.1.jar differ diff --git a/test/libs/guava-10.0.1.jar b/test/libs/guava-10.0.1.jar new file mode 100644 index 00000000..d107c0f3 Binary files /dev/null and b/test/libs/guava-10.0.1.jar differ diff --git a/test/libs/junit-4.10.jar b/test/libs/junit-4.10.jar new file mode 100644 index 00000000..bf5c0b9c Binary files /dev/null and b/test/libs/junit-4.10.jar differ diff --git a/test/libs/logging.jar b/test/libs/logging.jar new file mode 100644 index 00000000..e708029e Binary files /dev/null and b/test/libs/logging.jar differ diff --git a/test/proguard.cfg b/test/proguard.cfg new file mode 100644 index 00000000..b1cdf17b --- /dev/null +++ b/test/proguard.cfg @@ -0,0 +1,40 @@ +-optimizationpasses 5 +-dontusemixedcaseclassnames +-dontskipnonpubliclibraryclasses +-dontpreverify +-verbose +-optimizations !code/simplification/arithmetic,!field/*,!class/merging/* + +-keep public class * extends android.app.Activity +-keep public class * extends android.app.Application +-keep public class * extends android.app.Service +-keep public class * extends android.content.BroadcastReceiver +-keep public class * extends android.content.ContentProvider +-keep public class * extends android.app.backup.BackupAgentHelper +-keep public class * extends android.preference.Preference +-keep public class com.android.vending.licensing.ILicensingService + +-keepclasseswithmembernames class * { + native ; +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet); +} + +-keepclasseswithmembers class * { + public (android.content.Context, android.util.AttributeSet, int); +} + +-keepclassmembers class * extends android.app.Activity { + public void *(android.view.View); +} + +-keepclassmembers enum * { + public static **[] values(); + public static ** valueOf(java.lang.String); +} + +-keep class * implements android.os.Parcelable { + public static final android.os.Parcelable$Creator *; +} diff --git a/test/project.properties b/test/project.properties new file mode 100644 index 00000000..730e911f --- /dev/null +++ b/test/project.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-14 diff --git a/test/res/drawable-hdpi/ic_launcher.png b/test/res/drawable-hdpi/ic_launcher.png new file mode 100644 index 00000000..8074c4c5 Binary files /dev/null and b/test/res/drawable-hdpi/ic_launcher.png differ diff --git a/test/res/drawable-ldpi/ic_launcher.png b/test/res/drawable-ldpi/ic_launcher.png new file mode 100644 index 00000000..1095584e Binary files /dev/null and b/test/res/drawable-ldpi/ic_launcher.png differ diff --git a/test/res/drawable-mdpi/ic_launcher.png b/test/res/drawable-mdpi/ic_launcher.png new file mode 100644 index 00000000..a07c69fa Binary files /dev/null and b/test/res/drawable-mdpi/ic_launcher.png differ diff --git a/test/res/layout/main.xml b/test/res/layout/main.xml new file mode 100644 index 00000000..2b020ba6 --- /dev/null +++ b/test/res/layout/main.xml @@ -0,0 +1,13 @@ + + + + + + + \ No newline at end of file diff --git a/test/res/values/strings.xml b/test/res/values/strings.xml new file mode 100644 index 00000000..27c3286f --- /dev/null +++ b/test/res/values/strings.xml @@ -0,0 +1,7 @@ + + + + Hello World! + CordovaTestTest + + \ No newline at end of file diff --git a/test/res/xml/phonegap.xml b/test/res/xml/phonegap.xml new file mode 100644 index 00000000..97f31ea1 --- /dev/null +++ b/test/res/xml/phonegap.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/test/res/xml/plugins.xml b/test/res/xml/plugins.xml new file mode 100644 index 00000000..4d84f599 --- /dev/null +++ b/test/res/xml/plugins.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/test/src/org/apache/cordova/test/CordovaActivityTest.java b/test/src/org/apache/cordova/test/CordovaActivityTest.java new file mode 100644 index 00000000..96e4b004 --- /dev/null +++ b/test/src/org/apache/cordova/test/CordovaActivityTest.java @@ -0,0 +1,55 @@ +package org.apache.cordova.test; + +import org.apache.cordova.CordovaWebView; +import com.phonegap.api.PluginManager; + +import android.test.ActivityInstrumentationTestCase2; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +public class CordovaActivityTest extends ActivityInstrumentationTestCase2 { + + private PhoneGapActivity testActivity; + private FrameLayout containerView; + private LinearLayout innerContainer; + private CordovaWebView testView; + + public CordovaActivityTest() + { + super("com.phonegap.test.activities",PhoneGapActivity.class); + } + + protected void setUp() throws Exception { + super.setUp(); + testActivity = this.getActivity(); + containerView = (FrameLayout) testActivity.findViewById(android.R.id.content); + innerContainer = (LinearLayout) containerView.getChildAt(0); + testView = (CordovaWebView) innerContainer.getChildAt(0); + + } + + public void testPreconditions(){ + assertNotNull(innerContainer); + assertNotNull(testView); + } + + + public void testForCordovaView() { + String className = testView.getClass().getSimpleName(); + assertTrue(className.equals("CordovaWebView")); + } + + public void testForLinearLayout() { + String className = innerContainer.getClass().getSimpleName(); + assertTrue(className.equals("LinearLayoutSoftKeyboardDetect")); + } + + public void testForPluginManager() { + CordovaWebView v = (CordovaWebView) testView; + PluginManager p = v.getPluginManager(); + assertNotNull(p); + String className = p.getClass().getSimpleName(); + assertTrue(className.equals("PluginManager")); + } + +} diff --git a/test/src/org/apache/cordova/test/CordovaDriverAction.java b/test/src/org/apache/cordova/test/CordovaDriverAction.java new file mode 100644 index 00000000..bfb8c0d4 --- /dev/null +++ b/test/src/org/apache/cordova/test/CordovaDriverAction.java @@ -0,0 +1,13 @@ +package org.apache.cordova.test; + +import android.app.Activity; +import android.os.Bundle; + + +public class CordovaDriverAction extends Activity { + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } +} diff --git a/test/src/org/apache/cordova/test/CordovaSplashTest.java b/test/src/org/apache/cordova/test/CordovaSplashTest.java new file mode 100644 index 00000000..307f9660 --- /dev/null +++ b/test/src/org/apache/cordova/test/CordovaSplashTest.java @@ -0,0 +1,54 @@ +package org.apache.cordova.test; + +import org.apache.cordova.CordovaWebView; +import com.phonegap.api.PluginManager; + +import android.test.ActivityInstrumentationTestCase2; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +public class CordovaSplashTest extends ActivityInstrumentationTestCase2 { + + private PhoneGapSplash testActivity; + private FrameLayout containerView; + private LinearLayout innerContainer; + private CordovaWebView testView; + + public CordovaSplashTest() + { + super("com.phonegap.test.activities",PhoneGapSplash.class); + } + + protected void setUp() throws Exception{ + super.setUp(); + testActivity = this.getActivity(); + containerView = (FrameLayout) testActivity.findViewById(android.R.id.content); + innerContainer = (LinearLayout) containerView.getChildAt(0); + testView = (CordovaWebView) innerContainer.getChildAt(0); + + } + + public void testPreconditions(){ + assertNotNull(innerContainer); + assertNotNull(testView); + } + + + public void testForCordovaView() { + String className = testView.getClass().getSimpleName(); + assertTrue(className.equals("CordovaWebView")); + } + + public void testForPluginManager() { + CordovaWebView v = (CordovaWebView) testView; + PluginManager p = v.getPluginManager(); + assertNotNull(p); + String className = p.getClass().getSimpleName(); + assertTrue(className.equals("PluginManager")); + } + + public void testBackButton() { + CordovaWebView v = (CordovaWebView) testView; + assertFalse(v.checkBackKey()); + } +} diff --git a/test/src/org/apache/cordova/test/CordovaTest.java b/test/src/org/apache/cordova/test/CordovaTest.java new file mode 100644 index 00000000..8d290f46 --- /dev/null +++ b/test/src/org/apache/cordova/test/CordovaTest.java @@ -0,0 +1,90 @@ +package org.apache.cordova.test; + +import org.apache.cordova.CordovaWebView; +import com.phonegap.api.PluginManager; + +import android.test.ActivityInstrumentationTestCase2; +import android.view.View; + +public class CordovaTest extends + ActivityInstrumentationTestCase2 { + + private static final long TIMEOUT = 1000; + private PhoneGapViewTestActivity testActivity; + private View testView; + private String rString; + + public CordovaTest() { + super("com.phonegap.test.activities", PhoneGapViewTestActivity.class); + } + + protected void setUp() throws Exception { + super.setUp(); + testActivity = this.getActivity(); + testView = testActivity.findViewById(R.id.phoneGapView); + } + + public void testPreconditions() { + assertNotNull(testView); + } + + public void testForCordovaView() { + String className = testView.getClass().getSimpleName(); + assertTrue(className.equals("CordovaWebView")); + } + + public void testForPluginManager() { + CordovaWebView v = (CordovaWebView) testView; + PluginManager p = v.getPluginManager(); + assertNotNull(p); + String className = p.getClass().getSimpleName(); + assertTrue(className.equals("PluginManager")); + } + + public void testBackButton() { + CordovaWebView v = (CordovaWebView) testView; + assertFalse(v.checkBackKey()); + } + + public void testLoadUrl() { + CordovaWebView v = (CordovaWebView) testView; + v.loadUrlIntoView("file:///android_asset/www/index.html"); + sleep(); + String url = v.getUrl(); + boolean result = url.equals("file:///android_asset/www/index.html"); + assertTrue(result); + int visible = v.getVisibility(); + assertTrue(visible == View.VISIBLE); + } + + public void testBackHistoryFalse() { + CordovaWebView v = (CordovaWebView) testView; + // Move back in the history + boolean test = v.backHistory(); + assertFalse(test); + } + + // Make sure that we can go back + public void testBackHistoryTrue() { + this.testLoadUrl(); + CordovaWebView v = (CordovaWebView) testView; + v.loadUrlIntoView("file:///android_asset/www/compass/index.html"); + sleep(); + String url = v.getUrl(); + assertTrue(url.equals("file:///android_asset/www/compass/index.html")); + // Move back in the history + boolean test = v.backHistory(); + assertTrue(test); + sleep(); + url = v.getUrl(); + assertTrue(url.equals("file:///android_asset/www/index.html")); + } + + private void sleep() { + try { + Thread.sleep(TIMEOUT); + } catch (InterruptedException e) { + fail("Unexpected Timeout"); + } + } +} diff --git a/test/src/org/apache/cordova/test/CordovaViewFactory.java b/test/src/org/apache/cordova/test/CordovaViewFactory.java new file mode 100644 index 00000000..4e2e6a5c --- /dev/null +++ b/test/src/org/apache/cordova/test/CordovaViewFactory.java @@ -0,0 +1,17 @@ +package org.apache.cordova.test; + +import org.openqa.selenium.android.library.WebViewFactory; + +import org.apache.cordova.CordovaWebView; + +import android.app.Activity; +import android.webkit.WebView; + +public class CordovaViewFactory implements WebViewFactory { + + public WebView createNewView(Activity arg0) { + // TODO Auto-generated method stub + return new CordovaWebView(arg0); + } + +} diff --git a/test/src/org/apache/cordova/test/GapClientTest.java b/test/src/org/apache/cordova/test/GapClientTest.java new file mode 100644 index 00000000..d826ae71 --- /dev/null +++ b/test/src/org/apache/cordova/test/GapClientTest.java @@ -0,0 +1,71 @@ +package org.apache.cordova.test; + +import org.apache.cordova.CordovaWebView; +import org.apache.cordova.CordovaChromeClient; +import org.apache.cordova.api.PluginManager; + +import android.content.Context; +import android.content.res.AssetManager; +import android.content.res.Resources; +import android.test.ActivityInstrumentationTestCase2; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +public class GapClientTest extends ActivityInstrumentationTestCase2 { + + private PhoneGapViewTestActivity testActivity; + private FrameLayout containerView; + private LinearLayout innerContainer; + private View testView; + private String rString; + private CordovaChromeClient appCode; + + public GapClientTest() { + super("com.phonegap.test.activities",PhoneGapViewTestActivity.class); + } + + protected void setUp() throws Exception{ + super.setUp(); + testActivity = this.getActivity(); + containerView = (FrameLayout) testActivity.findViewById(android.R.id.content); + innerContainer = (LinearLayout) containerView.getChildAt(0); + testView = innerContainer.getChildAt(0); + appCode = ((CordovaWebView) testView).getGapClient(); + + } + + public void testPreconditions(){ + assertNotNull(innerContainer); + assertNotNull(testView); + } + + public void testForCordovaView() { + String className = testView.getClass().getSimpleName(); + assertTrue(className.equals("CordovaWebView")); + } + + public void testGetResources() { + Resources ls = testActivity.getResources(); + Resources rs = appCode.getResources(); + assertTrue(ls.equals(rs)); + } + + public void testGetPackageName() { + String ls = testActivity.getPackageName(); + String rs = appCode.getPackageName(); + assertTrue(ls.equals(rs)); + } + + public void testGetAssets() { + AssetManager ls = testActivity.getAssets(); + AssetManager rs = testActivity.getAssets(); + assertTrue(ls.equals(rs)); + } + + public void testGetBaseContext() { + Context ls = testActivity.getBaseContext(); + Context rs = testActivity.getBaseContext(); + assertTrue(ls.equals(rs)); + } +} diff --git a/test/src/org/apache/cordova/test/JailActivity.java b/test/src/org/apache/cordova/test/JailActivity.java new file mode 100644 index 00000000..77a8527d --- /dev/null +++ b/test/src/org/apache/cordova/test/JailActivity.java @@ -0,0 +1,19 @@ +package org.apache.cordova.test; + +import org.apache.cordova.DroidGap; + +import android.app.Activity; +import android.os.Bundle; + +public class JailActivity extends DroidGap { + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if(!super.areAssetsInJail()) + { + super.moveAssetsToJail(); + } + super.loadJailedFile("www/index.html"); + } +} diff --git a/test/src/org/apache/cordova/test/JailTest.java b/test/src/org/apache/cordova/test/JailTest.java new file mode 100644 index 00000000..027c8f65 --- /dev/null +++ b/test/src/org/apache/cordova/test/JailTest.java @@ -0,0 +1,62 @@ +package org.apache.cordova.test; + +import org.apache.cordova.CordovaWebView; +import org.apache.cordova.api.PluginManager; + +import android.test.ActivityInstrumentationTestCase2; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +public class JailTest extends ActivityInstrumentationTestCase2 { + + private JailActivity testActivity; + private FrameLayout containerView; + private LinearLayout innerContainer; + private CordovaWebView testView; + private static final long TIMEOUT = 2000; + + public JailTest() + { + super("com.phonegap.test.activities",JailActivity.class); + } + + protected void setUp() throws Exception { + super.setUp(); + testActivity = this.getActivity(); + containerView = (FrameLayout) testActivity.findViewById(android.R.id.content); + innerContainer = (LinearLayout) containerView.getChildAt(0); + testView = (CordovaWebView) innerContainer.getChildAt(0); + + } + + public void testPreconditions(){ + assertNotNull(innerContainer); + assertNotNull(testView); + } + + + public void testForCordovaView() { + String className = testView.getClass().getSimpleName(); + assertTrue(className.equals("CordovaWebView")); + } + + public void testForJailedItems() { + sleep(); + String url = testView.getUrl(); + assertTrue(url.contains("file:///data/data/")); + } + + public void testForJailCheck() { + sleep(); + assertTrue(testActivity.areAssetsInJail()); + } + + private void sleep() { + try { + Thread.sleep(TIMEOUT); + } catch (InterruptedException e) { + fail("Unexpected Timeout"); + } + } + +} diff --git a/test/src/org/apache/cordova/test/PhoneGapActivity.java b/test/src/org/apache/cordova/test/PhoneGapActivity.java new file mode 100644 index 00000000..d15bdb1e --- /dev/null +++ b/test/src/org/apache/cordova/test/PhoneGapActivity.java @@ -0,0 +1,15 @@ +package org.apache.cordova.test; + +import org.apache.cordova.DroidGap; + +import android.app.Activity; +import android.os.Bundle; + +public class PhoneGapActivity extends DroidGap { + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + super.loadUrl("file:///android_asset/index.html"); + } +} diff --git a/test/src/org/apache/cordova/test/PhoneGapSplash.java b/test/src/org/apache/cordova/test/PhoneGapSplash.java new file mode 100644 index 00000000..59417e3e --- /dev/null +++ b/test/src/org/apache/cordova/test/PhoneGapSplash.java @@ -0,0 +1,26 @@ +package org.apache.cordova.test; + +import org.apache.cordova.CordovaWebView; + +import android.app.Activity; +import android.os.Bundle; + +public class PhoneGapSplash extends Activity { + CordovaWebView phoneGap; + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + phoneGap = (CordovaWebView) findViewById(R.id.phoneGapView); + phoneGap.init(); + phoneGap.loadUrl("file:///android_asset/index.html", 5000); + } + + public void onDestroy() + { + super.onDestroy(); + phoneGap.onDestroy(); + } +} diff --git a/test/src/org/apache/cordova/test/PhoneGapViewTestActivity.java b/test/src/org/apache/cordova/test/PhoneGapViewTestActivity.java new file mode 100644 index 00000000..10d4b369 --- /dev/null +++ b/test/src/org/apache/cordova/test/PhoneGapViewTestActivity.java @@ -0,0 +1,29 @@ +package org.apache.cordova.test; + +import org.apache.cordova.CordovaWebView; + +import android.app.Activity; +import android.os.Bundle; + +public class PhoneGapViewTestActivity extends Activity { + + CordovaWebView phoneGap; + /** Called when the activity is first created. */ + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + phoneGap = (CordovaWebView) findViewById(R.id.phoneGapView); + phoneGap.init(); + + phoneGap.loadUrl("file:///android_asset/index.html"); + + } + + public void onDestroy() + { + super.onDestroy(); + phoneGap.onDestroy(); + } +} \ No newline at end of file diff --git a/test/src/org/apache/cordova/test/PluginManagerTest.java b/test/src/org/apache/cordova/test/PluginManagerTest.java new file mode 100644 index 00000000..3c36f274 --- /dev/null +++ b/test/src/org/apache/cordova/test/PluginManagerTest.java @@ -0,0 +1,48 @@ +package org.apache.cordova.test; + +import org.apache.cordova.CordovaWebView; +import com.phonegap.api.PluginManager; + +import android.test.ActivityInstrumentationTestCase2; +import android.view.View; +import android.widget.FrameLayout; +import android.widget.LinearLayout; + +public class PluginManagerTest extends ActivityInstrumentationTestCase2 { + + private PhoneGapViewTestActivity testActivity; + private FrameLayout containerView; + private LinearLayout innerContainer; + private View testView; + private String rString; + private PluginManager pMan; + + public PluginManagerTest() { + super("com.phonegap.test.activities",PhoneGapViewTestActivity.class); + } + + protected void setUp() throws Exception{ + super.setUp(); + testActivity = this.getActivity(); + containerView = (FrameLayout) testActivity.findViewById(android.R.id.content); + innerContainer = (LinearLayout) containerView.getChildAt(0); + testView = innerContainer.getChildAt(0); + + } + + public void testPreconditions(){ + assertNotNull(innerContainer); + assertNotNull(testView); + } + + + public void testForPluginManager() { + CordovaWebView v = (CordovaWebView) testView; + pMan = v.getPluginManager(); + assertNotNull(pMan); + String className = pMan.getClass().getSimpleName(); + assertTrue(className.equals("PluginManager")); + } + + +} diff --git a/test/src/org/apache/cordova/test/WebDriverTest.java b/test/src/org/apache/cordova/test/WebDriverTest.java new file mode 100644 index 00000000..68cf285f --- /dev/null +++ b/test/src/org/apache/cordova/test/WebDriverTest.java @@ -0,0 +1,64 @@ +package org.apache.cordova.test; + +import org.apache.cordova.CordovaWebViewClient; +import org.apache.cordova.CordovaWebView; +import org.apache.cordova.CordovaChromeClient; + +import org.apache.cordova.test.CordovaViewFactory; +import org.openqa.selenium.By; +import org.openqa.selenium.WebElement; +import org.openqa.selenium.android.library.AndroidWebDriver; + +import android.test.ActivityInstrumentationTestCase2; + +public class WebDriverTest extends ActivityInstrumentationTestCase2 { + + private static final long TIMEOUT = 5000; + private CordovaDriverAction testActivity; + private CordovaWebView testView; + private CordovaViewFactory viewFactory; + private CordovaChromeClient appCode; + private CordovaWebViewClient viewHandler; + private AndroidWebDriver testDriver; + + public WebDriverTest() { + super("com.phonegap.test.activities",CordovaDriverAction.class); + } + + protected void setUp() throws Exception{ + super.setUp(); + testActivity = this.getActivity(); + viewFactory = new CordovaViewFactory(); + appCode = new CordovaChromeClient(testActivity); + viewHandler = new CordovaWebViewClient(testActivity); + testDriver = new AndroidWebDriver(testActivity, viewFactory, viewHandler, appCode); + testView = (CordovaWebView) testDriver.getWebView(); + viewHandler.setCordovaView(testView); + appCode.testInit(testView); + } + + public void testPreconditions(){ + assertNotNull(testView); + } + + public void testWebLoad() { + testDriver.get("file:///android_asset/www/index.html"); + sleep(); + String url = testView.getUrl(); + //Check the sanity! + boolean result = url.equals("file:///android_asset/www/index.html"); + assertTrue(result); + WebElement platformSpan = testDriver.findElement(By.id("platform")); + String text = platformSpan.getText(); + assertTrue(text.equals("Android")); + } + + + private void sleep() { + try { + Thread.sleep(TIMEOUT); + } catch (InterruptedException e) { + fail("Unexpected Timeout"); + } + } +}