Skip to content
tests.js 17.8 KiB
Newer Older
wangqinghua's avatar
wangqinghua committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446
/*
 *
 * 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 = '<div id="info">' +
            '<b>Status:</b> <span id="location_status">Stopped</span>' +
            '<table width="100%">';
    var latitude = '<tr>' +
            '<td><b>Latitude:</b></td>' +
            '<td id="latitude">&nbsp;</td>' +
            '<td>(decimal degrees) geographic coordinate [<a href="http://dev.w3.org/geo/api/spec-source.html#lat">#ref]</a></td>' +
            '</tr>';
    var longitude = '<tr>' +
            '<td><b>Longitude:</b></td>' +
            '<td id="longitude">&nbsp;</td>' +
            '<td>(decimal degrees) geographic coordinate [<a href="http://dev.w3.org/geo/api/spec-source.html#lat">#ref]</a></td>' +
            '</tr>';
    var altitude = '<tr>' +
            '<td><b>Altitude:</b></td>' +
            '<td id="altitude">&nbsp;</td>' +
            '<td>null if not supported;<br>' +
            '(meters) height above the [<a href="http://dev.w3.org/geo/api/spec-source.html#ref-wgs">WGS84</a>] ellipsoid. [<a href="http://dev.w3.org/geo/api/spec-source.html#altitude">#ref]</a></td>' +
            '</tr>';
    var accuracy = '<tr>' +
            '<td><b>Accuracy:</b></td>' +
            '<td id="accuracy">&nbsp;</td>' +
            '<td>(meters; non-negative; 95% confidence level) the accuracy level of the latitude and longitude coordinates. [<a href="http://dev.w3.org/geo/api/spec-source.html#accuracy">#ref]</a></td>' +
            '</tr>';
    var heading = '<tr>' +
            '<td><b>Heading:</b></td>' +
            '<td id="heading">&nbsp;</td>' +
            '<td>null if not supported;<br>' +
            'NaN if speed == 0;<br>' +
            '(degrees; 0° ≤ heading < 360°) direction of travel of the hosting device- counting clockwise relative to the true north. [<a href="http://dev.w3.org/geo/api/spec-source.html#heading">#ref]</a></td>' +
            '</tr>';
    var speed = '<tr>' +
            '<td><b>Speed:</b></td>' +
            '<td id="speed">&nbsp;</td>' +
            '<td>null if not supported;<br>' +
            '(meters per second; non-negative) magnitude of the horizontal component of the hosting device current velocity. [<a href="http://dev.w3.org/geo/api/spec-source.html#speed">#ref]</a></td>' +
            '</tr>';
    var altitude_accuracy = '<tr>' +
            '<td><b>Altitude Accuracy:</b></td>' +
            '<td id="altitude_accuracy">&nbsp;</td>' +
            '<td>null if not supported;<br>(meters; non-negative; 95% confidence level) the accuracy level of the altitude. [<a href="http://dev.w3.org/geo/api/spec-source.html#altitude-accuracy">#ref]</a></td>' +
            '</tr>';
    var time = '<tr>' +
            '<td><b>Time:</b></td>' +
            '<td id="timestamp">&nbsp;</td>' +
            '<td>(DOMTimeStamp) when the position was acquired [<a href="http://dev.w3.org/geo/api/spec-source.html#timestamp">#ref]</a></td>' +
            '</tr>' +
            '</table>' +
            '</div>';
    var actions =
            '<div id="cordova-getLocation"></div>' +
            '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.' +
            '<p/> <div id="cordova-watchLocation"></div>' +
            'Expected result: Will update all applicable values in status box for current location and update as location changes. Status will read Running.' +
            '<p/> <div id="cordova-stopLocation"></div>' +
            'Expected result: Will stop watching the location so values will not be updated. Status will read Stopped.' +
            '<p/> <div id="cordova-getOld"></div>' +
            '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 =
            '<h3>Details about each value are listed below in the status box</h3>';
    var note =
            '<h3>Allow use of current location, if prompted</h3>';

    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');
};