/* * * 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. * */ /* eslint-env jasmine */ /* global WinJS, device */ exports.defineAutoTests = function () { var fail = function (done, context, message) { // prevents done() to be called several times if (context) { if (context.done) return; context.done = true; } if (message) { expect(false).toBe(true, message); } else { expect(false).toBe(true); } // watchPosition could call its callback sync (before returning the value) // so we invoke done async to make sure we know watcher id to .clear in afterEach setTimeout(function () { done(); }); }; var succeed = function (done, context) { // prevents done() to be called several times if (context) { if (context.done) return; context.done = true; } expect(true).toBe(true); // watchPosition could call its callback sync (before returning the value) // so we invoke done async to make sure we know watcher id to .clear in afterEach setTimeout(function () { done(); }); }; // On Windows, some tests prompt user for permission to use geolocation and interrupt autotests run var isWindowsStore = (cordova.platformId === 'windows8') || (cordova.platformId === 'windows' && !WinJS.Utilities.isPhone); // eslint-disable-line no-undef var majorDeviceVersion = null; var versionRegex = /(\d)\..+/.exec(device.version); if (versionRegex !== null) { majorDeviceVersion = Number(versionRegex[1]); } // Starting from Android 6.0 there are confirmation dialog which prevents us from running auto tests in silent mode (user interaction needed) // Also, Android emulator doesn't provide geo fix without manual interactions or mocks var skipAndroid = cordova.platformId === 'android' && (device.isVirtual || majorDeviceVersion >= 6); // eslint-disable-line no-undef var isIOSSim = false; // if iOS simulator does not have a location set, it will fail. describe('Geolocation (navigator.geolocation)', function () { it('geolocation.spec.1 should exist', function () { expect(navigator.geolocation).toBeDefined(); }); it('geolocation.spec.2 should contain a getCurrentPosition function', function () { expect(typeof navigator.geolocation.getCurrentPosition).toBeDefined(); expect(typeof navigator.geolocation.getCurrentPosition === 'function').toBe(true); }); it('geolocation.spec.3 should contain a watchPosition function', function () { expect(typeof navigator.geolocation.watchPosition).toBeDefined(); expect(typeof navigator.geolocation.watchPosition === 'function').toBe(true); }); it('geolocation.spec.4 should contain a clearWatch function', function () { expect(typeof navigator.geolocation.clearWatch).toBeDefined(); expect(typeof navigator.geolocation.clearWatch === 'function').toBe(true); }); }); describe('getCurrentPosition method', function () { describe('error callback', function () { it('geolocation.spec.5 should be called if we set timeout to 0 and maximumAge to a very small number', function (done) { if (isWindowsStore || skipAndroid) { pending(); } navigator.geolocation.getCurrentPosition( fail.bind(null, done), succeed.bind(null, done), { maximumAge: 0, timeout: 0 }); }); it('geolocation.spec.9 on failure should return PositionError object with error code constants', function (done) { if (isWindowsStore || skipAndroid) { pending(); } navigator.geolocation.getCurrentPosition( fail.bind(this, done), function (gpsError) { // W3C specs: http://dev.w3.org/geo/api/spec-source.html#position_error_interface expect(gpsError.PERMISSION_DENIED).toBe(1); expect(gpsError.POSITION_UNAVAILABLE).toBe(2); expect(gpsError.TIMEOUT).toBe(3); done(); }, { maximumAge: 0, timeout: 0 }); }); }); describe('success callback', function () { it('geolocation.spec.6 should be called with a Position object', function (done) { if (isWindowsStore || skipAndroid) { pending(); } navigator.geolocation.getCurrentPosition(function (p) { expect(p.coords).toBeDefined(); expect(p.timestamp).toBeDefined(); done(); }, function (err) { if (err.message && err.message.indexOf('kCLErrorDomain') > -1) { console.log('Error: Location not set in simulator, tests will fail.'); expect(true).toBe(true); isIOSSim = true; done(); } else { fail(done); } }, { maximumAge: (5 * 60 * 1000) // 5 minutes maximum age of cached position }); }, 25000); // first geolocation call can take several seconds on some devices }); }); describe('watchPosition method', function () { beforeEach(function (done) { // This timeout is set to lessen the load on platform's geolocation services // which were causing occasional test failures setTimeout(function () { done(); }, 100); }); describe('error callback', function () { var errorWatch = null; afterEach(function () { navigator.geolocation.clearWatch(errorWatch); }); it('geolocation.spec.7 should be called if we set timeout to 0 and maximumAge to a very small number', function (done) { if (isWindowsStore || skipAndroid) { pending(); } var context = this; errorWatch = navigator.geolocation.watchPosition( fail.bind(null, done, context, 'Unexpected win'), succeed.bind(null, done, context), { maximumAge: 0, timeout: 0 }); }); it('geolocation.spec.10 on failure should return PositionError object with error code constants', function (done) { if (isWindowsStore || skipAndroid) { pending(); } var context = this; errorWatch = navigator.geolocation.watchPosition( fail.bind(this, done, context, 'Unexpected win'), function (gpsError) { if (context.done) return; context.done = true; // W3C specs: http://dev.w3.org/geo/api/spec-source.html#position_error_interface expect(gpsError.PERMISSION_DENIED).toBe(1); expect(gpsError.POSITION_UNAVAILABLE).toBe(2); expect(gpsError.TIMEOUT).toBe(3); done(); }, { maximumAge: 0, timeout: 0 }); }); }); describe('success callback', function () { var successWatch = null; afterEach(function () { navigator.geolocation.clearWatch(successWatch); }); it('geolocation.spec.8 should be called with a Position object', function (done) { if (isWindowsStore || skipAndroid || isIOSSim) { pending(); } var context = this; successWatch = navigator.geolocation.watchPosition( function (p) { // prevents done() to be called several times if (context.done) return; context.done = true; expect(p.coords).toBeDefined(); expect(p.timestamp).toBeDefined(); // callback could be called sync so we invoke done async to make sure we know watcher id to .clear in afterEach setTimeout(function () { done(); }); }, fail.bind(null, done, context, 'Unexpected fail callback'), { maximumAge: (5 * 60 * 1000) // 5 minutes maximum age of cached position }); expect(successWatch).toBeDefined(); }); }); }); }; /******************************************************************************/ /******************************************************************************/ /******************************************************************************/ exports.defineManualTests = function (contentEl, createActionButton) { var watchLocationId = null; /** * Set location status */ function setLocationStatus (status) { document.getElementById('location_status').innerHTML = status; } function setLocationDetails (p) { var date = (new Date(p.timestamp)); document.getElementById('latitude').innerHTML = p.coords.latitude; document.getElementById('longitude').innerHTML = p.coords.longitude; document.getElementById('altitude').innerHTML = p.coords.altitude; document.getElementById('accuracy').innerHTML = p.coords.accuracy; document.getElementById('heading').innerHTML = p.coords.heading; document.getElementById('speed').innerHTML = p.coords.speed; document.getElementById('altitude_accuracy').innerHTML = p.coords.altitudeAccuracy; document.getElementById('timestamp').innerHTML = date.toDateString() + ' ' + date.toTimeString(); } /** * Stop watching the location */ function stopLocation () { var geo = navigator.geolocation; if (!geo) { alert('navigator.geolocation object is missing.'); // eslint-disable-line no-undef return; } setLocationStatus('Stopped'); if (watchLocationId) { geo.clearWatch(watchLocationId); watchLocationId = null; } } /** * Start watching location */ var watchLocation = function () { var geo = navigator.geolocation; if (!geo) { alert('navigator.geolocation object is missing.'); // eslint-disable-line no-undef return; } // Success callback var success = function (p) { setLocationDetails(p); }; // Fail callback var fail = function (e) { console.log('watchLocation fail callback with error code ' + e); stopLocation(geo); }; // Get location watchLocationId = geo.watchPosition(success, fail, { enableHighAccuracy: true }); setLocationStatus('Running'); }; /** * Get current location */ var getLocation = function (opts) { var geo = navigator.geolocation; if (!geo) { alert('navigator.geolocation object is missing.'); // eslint-disable-line no-undef return; } // Stop location if running stopLocation(geo); // Success callback var success = function (p) { setLocationDetails(p); setLocationStatus('Done'); }; // Fail callback var fail = function (e) { console.log('getLocation fail callback with error code ' + e.code); setLocationStatus('Error: ' + e.code); }; setLocationStatus('Retrieving location...'); // Get location geo.getCurrentPosition(success, fail, opts || { enableHighAccuracy: true }); //, {timeout: 10000}); }; /******************************************************************************/ var location_div = '
' + 'Status: Stopped' + ''; var latitude = '' + '' + '' + '' + ''; var longitude = '' + '' + '' + '' + ''; var altitude = '' + '' + '' + '' + ''; var accuracy = '' + '' + '' + '' + ''; var heading = '' + '' + '' + '' + ''; var speed = '' + '' + '' + '' + ''; var altitude_accuracy = '' + '' + '' + '' + ''; var time = '' + '' + '' + '' + '' + '
Latitude: (decimal degrees) geographic coordinate [#ref]
Longitude: (decimal degrees) geographic coordinate [#ref]
Altitude: null if not supported;
' + '(meters) height above the [WGS84] ellipsoid. [#ref]
Accuracy: (meters; non-negative; 95% confidence level) the accuracy level of the latitude and longitude coordinates. [#ref]
Heading: null if not supported;
' + 'NaN if speed == 0;
' + '(degrees; 0° ≤ heading < 360°) direction of travel of the hosting device- counting clockwise relative to the true north. [#ref]
Speed: null if not supported;
' + '(meters per second; non-negative) magnitude of the horizontal component of the hosting device current velocity. [#ref]
Altitude Accuracy: null if not supported;
(meters; non-negative; 95% confidence level) the accuracy level of the altitude. [#ref]
Time: (DOMTimeStamp) when the position was acquired [#ref]
' + '
'; var actions = '
' + 'Expected result: Will update all applicable values in status box for current location. Status will read Retrieving Location (may not see this if location is retrieved immediately) then Done.' + '

' + 'Expected result: Will update all applicable values in status box for current location and update as location changes. Status will read Running.' + '

' + 'Expected result: Will stop watching the location so values will not be updated. Status will read Stopped.' + '

' + 'Expected result: Will update location values with a cached position that is up to 30 seconds old. Verify with time value. Status will read Done.'; var values_info = '

Details about each value are listed below in the status box

'; var note = '

Allow use of current location, if prompted

'; contentEl.innerHTML = values_info + location_div + latitude + longitude + altitude + accuracy + heading + speed + altitude_accuracy + time + note + actions; createActionButton('Get Location', function () { getLocation(); }, 'cordova-getLocation'); createActionButton('Start Watching Location', function () { watchLocation(); }, 'cordova-watchLocation'); createActionButton('Stop Watching Location', function () { stopLocation(); }, 'cordova-stopLocation'); createActionButton('Get Location Up to 30 Sec Old', function () { getLocation({ maximumAge: 30000 }); }, 'cordova-getOld'); };