diff --git a/src/geolocation.ts b/src/geolocation.ts index dbb5173..2a4e91a 100644 --- a/src/geolocation.ts +++ b/src/geolocation.ts @@ -1,14 +1,14 @@ -import { errorMessage } from "./error.js"; +import { GRANTED } from "./constants/permission-state.js"; import { + GeolocationPositionError, createPermissionDeniedError, createPositionUnavailableError, createTimeoutError, - isGeolocationPositionError, } from "./geolocation-position-error.js"; +import { createPosition } from "./geolocation-position.js"; import { LocationServices } from "./location-services.js"; import { StdGeolocation, - StdGeolocationPosition, StdPositionCallback, StdPositionErrorCallback, StdPositionOptions, @@ -34,28 +34,48 @@ export class Geolocation { canConstruct = false; this.#locationServices = locationServices; + this.#watchIds = []; } + /** + * ยง 6.2 getCurrentPosition() method + */ getCurrentPosition( successCallback: StdPositionCallback, errorCallback?: StdPositionErrorCallback | null, options?: StdPositionOptions | null, ): void { - this.#requestPosition(options ?? {}) - .then((position) => successCallback(position)) - .catch((error) => { - if (!errorCallback) return; - - if (isGeolocationPositionError(error)) { - errorCallback(error); - } else { - errorCallback( - createPositionUnavailableError( - `Location services error: ${errorMessage(error)}`, - ), - ); - } - }); + const { + enableHighAccuracy = false, + maximumAge = 0, + timeout = Infinity, + } = options ?? {}; + const normalizedOptions: Required = { + enableHighAccuracy, + maximumAge, + timeout, + }; + + /* + * 1. If the current settings object's relevant global object's associated + * Document is not fully active: + * 1. Call back with error errorCallback and POSITION_UNAVAILABLE. + * 2. Terminate this algorithm. + */ + // step 1 is ignored since there is no "document" + + /* + * 2. In parallel, request a position passing successCallback, + * errorCallback, and options. + */ + this.#requestPosition( + successCallback, + errorCallback ?? undefined, + normalizedOptions, + ).catch( + /* istanbul ignore next */ + () => {}, + ); } /* istanbul ignore next */ @@ -76,12 +96,15 @@ export class Geolocation { * optional watchId: */ async #requestPosition( - options: StdPositionOptions, - ): Promise { + successCallback: StdPositionCallback, + errorCallback: StdPositionErrorCallback | undefined, + options: Required, + watchId?: number, + ): Promise { /* * 1. Let watchIDs be this's [[watchIDs]]. */ - // TODO: implement watchPosition() + const watchIds = this.#watchIds; /* * 2. Let document be the current settings object's relevant global object's @@ -102,22 +125,43 @@ export class Geolocation { * "geolocation". * 6. Set permission to request permission to use descriptor. * 7. If permission is "denied", then: - * 1. If watchId was passed, remove watchId from watchIDs. - * 2. Call back with error passing errorCallback and PERMISSION_DENIED. - * 3. Terminate this algorithm. */ if (!(await this.#locationServices.requestPermission())) { - throw createPermissionDeniedError(""); + /* + * 7. (cont.) + * 1. If watchId was passed, remove watchId from watchIDs. + */ + if (typeof watchId === "number") { + const watchIdIndex = watchIds.indexOf(watchId); + if (watchIdIndex !== -1) watchIds.splice(watchIdIndex, 1); + } + + /* + * 7. (cont.) + * 2. Call back with error passing errorCallback and PERMISSION_DENIED. + * 3. Terminate this algorithm. + */ + errorCallback?.(createPermissionDeniedError("")); + return; } /* * 8. Wait to [acquire a position] passing _successCallback_, * _errorCallback_, _options_, and _watchId_. */ - return this.#acquirePosition(options); + await this.#acquirePosition( + successCallback, + errorCallback, + options, + watchId, + ); /* * 9. If watchId was not passed, terminate this algorithm. + */ + if (typeof watchId !== "number") return; + + /* * 10. While watchIDs contains watchId: * 1. Wait for a significant change of geographic position. What * constitutes a significant change of geographic position is left to @@ -139,92 +183,225 @@ export class Geolocation { * PositionErrorCallback? errorCallback, PositionOptions options, and an * optional watchId. */ - async #acquirePosition({ - timeout = Infinity, - }: StdPositionOptions): Promise { + async #acquirePosition( + successCallback: StdPositionCallback, + errorCallback: StdPositionErrorCallback | undefined, + options: Required, + watchId?: number, + ): Promise { /* * 1. If watchId was passed and this's [[watchIDs]] does not contain * watchId, terminate this algorithm. + */ + if (typeof watchId === "number" && !this.#watchIds.includes(watchId)) { + return; + } + + /* * 2. Let acquisitionTime be a new EpochTimeStamp that represents now. */ - // TODO: implement watchPosition() + const acquisitionTime = Date.now(); + + /* + * 3. Let timeoutTime be the sum of acquisitionTime and options.timeout. + */ + const timeoutTime = acquisitionTime + options.timeout; + + /* + * 4. Let cachedPosition be this's [[cachedPosition]]. + */ // TODO: implement maximumAge option - if (!Number.isFinite(timeout)) return this.#locationServices.getPosition(); + /* + * 5. Create an implementation-specific timeout task that elapses at + * timeoutTime, during which it tries to acquire the device's position by + * running the following steps: + */ + try { + await new Promise((resolve, reject) => { + const timeoutDelay = timeoutTime - acquisitionTime; + let timeoutId: ReturnType | undefined; - return new Promise((resolve, reject) => { - /* - * 3. Let timeoutTime be the sum of acquisitionTime and options.timeout. - */ - const timeoutId = setTimeout(() => { - reject(createTimeoutError("")); - }, timeout); + if (Number.isFinite(timeoutDelay)) { + timeoutId = setTimeout(() => { + reject(GeolocationPositionError.TIMEOUT); + }, timeoutDelay); + } - /* - * 4. Let cachedPosition be this's [[cachedPosition]]. - */ - // TODO: implement maximumAge option + /* + * 5. (cont.) + * 1. Let permission be get the current permission state of + * "geolocation". + */ + const permission = this.#locationServices.getPermissionState(); - /* - * 5. Create an implementation-specific timeout task that elapses at - * timeoutTime, during which it tries to acquire the device's position - * by running the following steps: - * 1. Let permission be get the current permission state of - * "geolocation". - * 2. If permission is "denied": - * 1. Stop timeout. - * 2. Do the user or system denied permission failure case step. - */ - // TODO: this might be relevant for watchPosition() + /* + * 5. (cont.) + * 2. If permission is "denied": + */ + if (permission !== GRANTED) { + /* + * 5. (cont.) + * 2. (cont.) + * 1. Stop timeout. + */ + clearTimeout(timeoutId); - /* - * 5. (cont.) - * 3. If permission is "granted": - * 1. Let position be null. - * 2. If cachedPosition is not null, and options.maximumAge is - * greater than 0: - * 1. Let cacheTime be acquisitionTime minus the value of the - * options.maximumAge member. - * 2. If cachedPosition's timestamp's value is greater than - * cacheTime, and cachedPosition.[[isHighAccuracy]] equals - * options.enableHighAccuracy, set position to cachedPosition. - */ - // TODO: implement maximumAge option + /* + * 5. (cont.) + * 2. (cont.) + * 2. Do the user or system denied permission failure case step. + */ + reject(GeolocationPositionError.PERMISSION_DENIED); + } + + /* + * 5. (cont.) + * 3. If permission is "granted": + */ + if (permission === GRANTED) { + /* + * 5. (cont.) + * 3. (cont.) + * 1. Let position be null. + */ + let position = null; + + /* + * 5. (cont.) + * 3. (cont.) + * 2. If cachedPosition is not null, and options.maximumAge is + * greater than 0: + * 1. Let cacheTime be acquisitionTime minus the value of the + * options.maximumAge member. + * 2. If cachedPosition's timestamp's value is greater than + * cacheTime, and cachedPosition.[[isHighAccuracy]] equals + * options.enableHighAccuracy, set position to + * cachedPosition. + */ + // TODO: implement maximumAge option + + /* + * 5. (cont.) + * 3. (cont.) + * 3. Otherwise, if position is not cachedPosition, try to + * acquire position data from the underlying system, + * optionally taking into consideration the value of + * options.enableHighAccuracy during acquisition. + */ + this.#locationServices + .acquireCoordinates(options.enableHighAccuracy) + .then((coords) => { + /* + * 5. (cont.) + * 3. (cont.) + * 4. If the timeout elapses during acquisition, or acquiring + * the device's position results in failure: + */ + if (!coords) { + /* + * 5. (cont.) + * 3. (cont.) + * 4. (cont.) + * 1. Stop the timeout. + */ + clearTimeout(timeoutId); + + /* + * 5. (cont.) + * 3. (cont.) + * 4. (cont.) + * 2. Go to dealing with failures. + * 3. Terminate this algorithm. + */ + reject(GeolocationPositionError.POSITION_UNAVAILABLE); + return; + } + + /* + * 5. (cont.) + * 3. (cont.) + * 5. If acquiring the position data from the system + * succeeds: + */ + if (coords) { + /* + * 5. (cont.) + * 3. (cont.) + * 5. (cont.) + * 1. Set position be a new GeolocationPosition passing + * acquisitionTime and options.enableHighAccuracy. + */ + // TODO: implement isHighAccuracy slot + position = createPosition({ + coords, + timestamp: acquisitionTime, + }); + /* + * 5. (cont.) + * 3. (cont.) + * 5. (cont.) + * 2. Set this's [[cachedPosition]] to position. + */ + // TODO: implement maximumAge option + + /* + * 5. (cont.) + * 3. (cont.) + * 6. Stop the timeout. + */ + clearTimeout(timeoutId); + + /* + * 5. (cont.) + * 3. (cont.) + * 7. Queue a task on the geolocation task source with a + * step that invokes successCallback with position. + */ + successCallback(position); + } + + resolve(); + return; + }) + .catch((error) => { + clearTimeout(timeoutId); + reject(error); + }); + } + }); + } catch (condition) { /* - * 5. (cont.) - * 3. (cont.) - * 3. Otherwise, if position is not cachedPosition, try to acquire - * position data from the underlying system, optionally taking - * into consideration the value of options.enableHighAccuracy - * during acquisition. - * 4. If the timeout elapses during acquisition, or acquiring the - * device's position results in failure: - * 1. Stop the timeout. - * 2. Go to dealing with failures. - * 3. Terminate this algorithm. - * 1. If acquiring the position data from the system succeeds: - * 1. Set position be a new GeolocationPosition passing - * acquisitionTime and options.enableHighAccuracy. - * 2. Set this's [[cachedPosition]] to position. - * 6. Stop the timeout. - * 7. Queue a task on the geolocation task source with a step that - * invokes successCallback with position. + * Dealing with failures: + * + * If acquiring a position fails, do one of the following based on the + * condition that matches the failure: */ - this.#locationServices - .getPosition() - .then(resolve, reject) - .finally(() => { - clearTimeout(timeoutId); - }) - .catch( - /* istanbul ignore next */ - () => {}, - ); - }); + if (condition === GeolocationPositionError.PERMISSION_DENIED) { + /* + * - User or system denied permission: + * - Call back with error passing errorCallback and PERMISSION_DENIED. + */ + errorCallback?.(createPermissionDeniedError("")); + } else if (condition === GeolocationPositionError.TIMEOUT) { + /* + * - Timeout elapsed: + * - Call back with error with errorCallback and TIMEOUT. + */ + errorCallback?.(createTimeoutError("")); + } else { + /* + * Data acquisition error or any other reason: + * - Call back with error passing errorCallback and POSITION_UNAVAILABLE. + */ + errorCallback?.(createPositionUnavailableError("")); + } + } } #locationServices: LocationServices; + #watchIds: number[]; } Geolocation satisfies new (...args: never[]) => StdGeolocation; diff --git a/src/location-services.ts b/src/location-services.ts index b1dc82c..ef581df 100644 --- a/src/location-services.ts +++ b/src/location-services.ts @@ -1,10 +1,9 @@ import { sleep } from "./async.js"; import { GRANTED, PROMPT } from "./constants/permission-state.js"; -import { createPositionUnavailableError } from "./geolocation-position-error.js"; -import { createPosition } from "./geolocation-position.js"; +import { createCoordinates } from "./geolocation-coordinates.js"; import { HandlePermissionRequest } from "./handle-permission-request.js"; import { - StdGeolocationPosition, + StdGeolocationCoordinates, StdPermissionState, StdPermissionStatus, } from "./types/std.js"; @@ -12,7 +11,9 @@ import { export interface LocationServices { getPermissionState(): StdPermissionState; requestPermission(): Promise; - getPosition(): Promise; + acquireCoordinates( + enableHighAccuracy: boolean, + ): Promise; } export interface MutableLocationServices extends LocationServices { @@ -20,13 +21,15 @@ export interface MutableLocationServices extends LocationServices { removePermissionRequestHandler(handler: HandlePermissionRequest): void; setPermissionState(state: StdPermissionState): void; watchPermission(status: StdPermissionStatus): () => void; - setPosition(position: StdGeolocationPosition | undefined): void; + setCoordinates(coords: StdGeolocationCoordinates | undefined): void; } -export function createLocationServices(): MutableLocationServices { +export function createLocationServices({ + acquireDelay = 0, +}: { acquireDelay?: number } = {}): MutableLocationServices { const permissionRequestHandlers: HandlePermissionRequest[] = []; let permissionState: StdPermissionState = PROMPT; - let position: StdGeolocationPosition | undefined; + let coords: StdGeolocationCoordinates | undefined; return { addPermissionRequestHandler(handler) { @@ -68,16 +71,14 @@ export function createLocationServices(): MutableLocationServices { }; }, - async getPosition() { - // systems should not rely on the position being available immediately - await sleep(0); + async acquireCoordinates() { + await sleep(acquireDelay); - if (position) return position; - throw createPositionUnavailableError(""); + return coords; }, - setPosition(nextPosition) { - position = nextPosition && createPosition(nextPosition); + setCoordinates(nextCoords) { + coords = nextCoords && createCoordinates(nextCoords); }, }; } diff --git a/src/user.ts b/src/user.ts index e6954b4..f73361c 100644 --- a/src/user.ts +++ b/src/user.ts @@ -40,7 +40,7 @@ export function createUser({ }, jumpToCoordinates(coords: StdGeolocationCoordinates) { - locationServices.setPosition({ coords, timestamp: Date.now() }); + locationServices.setCoordinates(coords); }, }; } diff --git a/test/jest/geolocation/get-current-position.spec.ts b/test/jest/geolocation/get-current-position.spec.ts index dd711d5..9e6a43b 100644 --- a/test/jest/geolocation/get-current-position.spec.ts +++ b/test/jest/geolocation/get-current-position.spec.ts @@ -11,41 +11,34 @@ import { MutableLocationServices, createGeolocation, createLocationServices, - createPositionUnavailableError, } from "../../../src/index.js"; import { StdGeolocation, + StdGeolocationCoordinates, StdGeolocationPosition, - StdGeolocationPositionError, StdPermissionState, StdPositionCallback, StdPositionErrorCallback, StdPositionOptions, } from "../../../src/types/std.js"; -const positionA: StdGeolocationPosition = { - coords: { - latitude: 40.71703581534977, - longitude: -74.03457283319447, - accuracy: 25.019, - altitude: 22.27227783203125, - altitudeAccuracy: 9.838127136230469, - heading: null, - speed: null, - }, - timestamp: 1687923355537, +const coordsA: StdGeolocationCoordinates = { + latitude: 40.71703581534977, + longitude: -74.03457283319447, + accuracy: 25.019, + altitude: 22.27227783203125, + altitudeAccuracy: 9.838127136230469, + heading: null, + speed: null, }; -const positionB: StdGeolocationPosition = { - coords: { - latitude: 12, - longitude: 34, - accuracy: 56, - altitude: 78, - altitudeAccuracy: 9, - heading: null, - speed: null, - }, - timestamp: 1690606392152, +const coordsB: StdGeolocationCoordinates = { + latitude: 12, + longitude: 34, + accuracy: 56, + altitude: 78, + altitudeAccuracy: 9, + heading: null, + speed: null, }; describe("Geolocation.getCurrentPosition()", () => { @@ -76,9 +69,9 @@ describe("Geolocation.getCurrentPosition()", () => { locationServices.setPermissionState(PROMPT); }); - describe("when there is a position", () => { + describe("when coords can be acquired", () => { beforeEach(() => { - locationServices.setPosition(positionA); + locationServices.setCoordinates(coordsA); }); describe("when the handler resets the permission immediately", () => { @@ -292,7 +285,10 @@ describe("Geolocation.getCurrentPosition()", () => { }); it("calls the success callback with the position", () => { - expect(successCallback).toHaveBeenCalledWith(positionA); + expect(successCallback).toHaveBeenCalledWith({ + coords: coordsA, + timestamp: expect.any(Number) as number, + }); }); it("does not call the error callback", () => { @@ -325,7 +321,10 @@ describe("Geolocation.getCurrentPosition()", () => { }); it("does not include the time spent waiting for permission in the timeout", () => { - expect(successCallback).toHaveBeenCalledWith(positionA); + expect(successCallback).toHaveBeenCalledWith({ + coords: coordsA, + timestamp: expect.any(Number) as number, + }); }); }); }); @@ -400,7 +399,10 @@ describe("Geolocation.getCurrentPosition()", () => { }); it("uses the response from the original handler", () => { - expect(successCallback).toHaveBeenCalledWith(positionA); + expect(successCallback).toHaveBeenCalledWith({ + coords: coordsA, + timestamp: expect.any(Number) as number, + }); }); }); }); @@ -425,7 +427,10 @@ describe("Geolocation.getCurrentPosition()", () => { }); it("uses the response from the newly-added handler", () => { - expect(successCallback).toHaveBeenCalledWith(positionA); + expect(successCallback).toHaveBeenCalledWith({ + coords: coordsA, + timestamp: expect.any(Number) as number, + }); }); }); }); @@ -454,7 +459,10 @@ describe("Geolocation.getCurrentPosition()", () => { }); it("uses the response from the original handler", () => { - expect(successCallback).toHaveBeenCalledWith(positionA); + expect(successCallback).toHaveBeenCalledWith({ + coords: coordsA, + timestamp: expect.any(Number) as number, + }); }); }); }); @@ -483,16 +491,19 @@ describe("Geolocation.getCurrentPosition()", () => { }); it("uses the response from the newly-added handler", () => { - expect(successCallback).toHaveBeenCalledWith(positionA); + expect(successCallback).toHaveBeenCalledWith({ + coords: coordsA, + timestamp: expect.any(Number) as number, + }); }); }); }); }); }); - describe("when there is no position", () => { + describe("when coords cannot be acquired", () => { beforeEach(() => { - locationServices.setPosition(undefined); + locationServices.setCoordinates(undefined); }); describe("when the handler grants the permission", () => { @@ -570,72 +581,41 @@ describe("Geolocation.getCurrentPosition()", () => { locationServices.setPermissionState(GRANTED); }); - describe("when location services throws an error", () => { - describe("when the error is a GeolocationPositionError", () => { - let error: StdGeolocationPositionError; - - beforeEach(() => { - error = createPositionUnavailableError(""); - jest - .spyOn(locationServices, "getPosition") - .mockRejectedValue(error); - }); - - describe("when reading the position", () => { - beforeEach(async () => { - await getCurrentPosition( - geolocation, - successCallback, - errorCallback, - ); - }); - - it("calls the error callback with the same error", () => { - expect(errorCallback).toHaveBeenCalled(); - expect(errorCallback.mock.calls[0][0]).toBe(error); - }); - }); + describe("when acquiring coords throws an error", () => { + beforeEach(() => { + jest + .spyOn(locationServices, "acquireCoordinates") + .mockRejectedValue(new Error("An error occurred")); }); - describe("when the error is not a GeolocationPositionError", () => { - let error: Error; - - beforeEach(() => { - error = new Error(""); - jest - .spyOn(locationServices, "getPosition") - .mockRejectedValue(error); + describe("when reading the position", () => { + beforeEach(async () => { + await getCurrentPosition( + geolocation, + successCallback, + errorCallback, + ); }); - describe("when reading the position", () => { - beforeEach(async () => { - await getCurrentPosition( - geolocation, - successCallback, - errorCallback, - ); - }); - - it("calls the error callback with a GeolocationPositionError with a code of POSITION_UNAVAILABLE and includes the original message", () => { - expect(errorCallback).toHaveBeenCalled(); - expect(errorCallback.mock.calls[0][0]).toBeDefined(); + it("calls the error callback with a GeolocationPositionError with a code of POSITION_UNAVAILABLE and an empty message", () => { + expect(errorCallback).toHaveBeenCalled(); + expect(errorCallback.mock.calls[0][0]).toBeDefined(); - const error = errorCallback.mock - .calls[0][0] as GeolocationPositionError; + const error = errorCallback.mock + .calls[0][0] as GeolocationPositionError; - expect(error).toBeInstanceOf(GeolocationPositionError); - expect(error.code).toBe( - GeolocationPositionError.POSITION_UNAVAILABLE, - ); - expect(error.message).toBe("Location services error: "); - }); + expect(error).toBeInstanceOf(GeolocationPositionError); + expect(error.code).toBe( + GeolocationPositionError.POSITION_UNAVAILABLE, + ); + expect(error.message).toBe(""); }); }); }); - describe("when there is no position", () => { + describe("when coords cannot be acquired", () => { beforeEach(() => { - locationServices.setPosition(undefined); + locationServices.setCoordinates(undefined); }); describe("when reading the position", () => { @@ -671,9 +651,9 @@ describe("Geolocation.getCurrentPosition()", () => { }); }); - describe("when there is a position", () => { + describe("when coords can be acquired", () => { beforeEach(() => { - locationServices.setPosition(positionA); + locationServices.setCoordinates(coordsA); }); it("cannot be read synchronously", () => { @@ -699,7 +679,10 @@ describe("Geolocation.getCurrentPosition()", () => { }); it("calls the success callback with the position", () => { - expect(successCallback).toHaveBeenCalledWith(positionA); + expect(successCallback).toHaveBeenCalledWith({ + coords: coordsA, + timestamp: expect.any(Number) as number, + }); }); it("does not call the error callback", () => { @@ -707,9 +690,9 @@ describe("Geolocation.getCurrentPosition()", () => { }); }); - describe("when the position changes", () => { + describe("when the coords change", () => { beforeEach(() => { - locationServices.setPosition(positionB); + locationServices.setCoordinates(coordsB); }); describe("when reading the position", () => { @@ -722,7 +705,10 @@ describe("Geolocation.getCurrentPosition()", () => { }); it("calls the success callback with the new position", () => { - expect(successCallback).toHaveBeenCalledWith(positionB); + expect(successCallback).toHaveBeenCalledWith({ + coords: coordsB, + timestamp: expect.any(Number) as number, + }); }); it("does not call the error callback", () => { @@ -734,9 +720,9 @@ describe("Geolocation.getCurrentPosition()", () => { describe("when reading the position with a timeout", () => { describe("when the timeout is not exceeded", () => { - describe("when there is a position", () => { + describe("when coords can be acquired", () => { beforeEach(async () => { - locationServices.setPosition(positionA); + locationServices.setCoordinates(coordsA); await getCurrentPosition( geolocation, @@ -749,13 +735,16 @@ describe("Geolocation.getCurrentPosition()", () => { }); it("calls the success callback with the position", () => { - expect(successCallback).toHaveBeenCalledWith(positionA); + expect(successCallback).toHaveBeenCalledWith({ + coords: coordsA, + timestamp: expect.any(Number) as number, + }); }); }); - describe("when there is no position", () => { + describe("when coords cannot be acquired", () => { beforeEach(async () => { - locationServices.setPosition(undefined); + locationServices.setCoordinates(undefined); await getCurrentPosition( geolocation, @@ -785,7 +774,7 @@ describe("Geolocation.getCurrentPosition()", () => { describe("when the timeout is exceeded", () => { beforeEach(async () => { - locationServices.setPosition(positionA); + locationServices.setCoordinates(coordsA); await getCurrentPosition( geolocation, @@ -812,7 +801,7 @@ describe("Geolocation.getCurrentPosition()", () => { describe("when the timeout is negative", () => { beforeEach(async () => { - locationServices.setPosition(positionA); + locationServices.setCoordinates(coordsA); await getCurrentPosition( geolocation, @@ -907,9 +896,9 @@ describe("Geolocation.getCurrentPosition()", () => { locationServices.setPermissionState(GRANTED); }); - describe("when there is a position", () => { + describe("when coords can be acquired", () => { beforeEach(() => { - locationServices.setPosition(positionA); + locationServices.setCoordinates(coordsA); }); describe("when reading the position", () => { @@ -922,7 +911,10 @@ describe("Geolocation.getCurrentPosition()", () => { }); it("calls the success callback with the position", () => { - expect(successCallback).toHaveBeenCalledWith(positionA); + expect(successCallback).toHaveBeenCalledWith({ + coords: coordsA, + timestamp: expect.any(Number) as number, + }); }); it("does not call the error callback", () => { diff --git a/test/jest/user.spec.ts b/test/jest/user.spec.ts index a28d00a..68d5668 100644 --- a/test/jest/user.spec.ts +++ b/test/jest/user.spec.ts @@ -154,32 +154,15 @@ describe("User", () => { user = createUser({ locationServices }); }); - describe("when permission is granted", () => { + describe("when jumping to coords", () => { beforeEach(() => { - user.grantGeolocationPermission(); + user.jumpToCoordinates(coordinatesA); }); - describe("when jumping to coordinates", () => { - beforeEach(() => { - // use fake timers so that the timestamp can be tested - jest.useFakeTimers({ advanceTimers: true, now: 111 }); - - user.jumpToCoordinates(coordinatesA); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - it("updates the position coordinates", async () => { - expect((await locationServices.getPosition()).coords).toEqual( - coordinatesA, - ); - }); - - it("updates the position timestamp to the current time", async () => { - expect((await locationServices.getPosition()).timestamp).toBe(111); - }); + it("updates the coords", async () => { + expect(await locationServices.acquireCoordinates(true)).toEqual( + coordinatesA, + ); }); }); });