diff --git a/CHANGELOG.md b/CHANGELOG.md index 2444132..f88abab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,16 @@ Versioning]. ## Unreleased +### Changed + +- **\[BREAKING]** This release includes an update to the version of + `fake-permissions` used for permissions handling, which includes many breaking + changes. See the [`fake-permissions` releases] for details. +- **\[BREAKING]** The `createGeolocation()` function no longer takes a `user` + option. Access requests are now handled by the `permissionStore` object. + +[`fake-permissions` releases]: https://github.com/ezzatron/fake-permissions/releases + ## [v0.12.0] - 2024-08-08 [v0.12.0]: https://github.com/ezzatron/fake-geolocation/releases/tag/v0.12.0 @@ -76,7 +86,7 @@ const coordsB = createCoordinates({ latitude: 3, longitude: 4 }); // Jump to some coords and grant permission user.jumpToCoordinates(coordsA); -user.grantPermission({ name: "geolocation" }); +user.grantAccess({ name: "geolocation" }); // Start watching the position let position: GeolocationPosition | undefined; @@ -136,14 +146,14 @@ console.log(error?.code === POSITION_UNAVAILABLE); // Wait for a PERMISSION_DENIED error, while running a task await observer.waitForPositionError(PERMISSION_DENIED, async () => { - user.denyPermission({ name: "geolocation" }); + user.blockAccess({ name: "geolocation" }); }); // Outputs "true" console.log(error?.code === PERMISSION_DENIED); // You can also wait for geolocation permission states await observer.waitForPermissionState("granted", async () => { - user.grantPermission({ name: "geolocation" }); + user.grantAccess({ name: "geolocation" }); }); // Outputs "true" console.log(status.state === "granted"); diff --git a/package.json b/package.json index 96f6fb7..431e5ce 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "prepublishOnly": "tsc -p tsconfig.build.json" }, "dependencies": { - "fake-permissions": "^0.10.0" + "fake-permissions": "^0.12.0" }, "devDependencies": { "@types/web": "^0.0.152", diff --git a/src/create-apis.ts b/src/create-apis.ts index eb9afd5..5a42090 100644 --- a/src/create-apis.ts +++ b/src/create-apis.ts @@ -37,6 +37,8 @@ export function createAPIs({ } { const locationServices = createLocationServices({ acquireDelay }); const permissions = createPermissions({ permissionStore }); + const geolocation = createGeolocation({ locationServices, permissionStore }); + const observer = createGeolocationObserver(geolocation, permissions); const user = createUser({ handleAccessRequest, @@ -45,14 +47,6 @@ export function createAPIs({ permissionStore, }); - const geolocation = createGeolocation({ - locationServices, - permissionStore, - user, - }); - - const observer = createGeolocationObserver(geolocation, permissions); - return { geolocation, locationServices, diff --git a/src/geolocation.ts b/src/geolocation.ts index 19e4ca1..ac21675 100644 --- a/src/geolocation.ts +++ b/src/geolocation.ts @@ -7,14 +7,13 @@ import { } from "./geolocation-position-error.js"; import { createPosition, isHighAccuracy } from "./geolocation-position.js"; import { LocationServices, Unsubscribe } from "./location-services.js"; -import type { User } from "./user.js"; type GeolocationParameters = { locationServices: LocationServices; permissionStore: PermissionStore; - user: User; }; +const descriptor: PermissionDescriptor = { name: "geolocation" }; let canConstruct = false; export function createGeolocation( @@ -26,17 +25,12 @@ export function createGeolocation( } export class Geolocation { - constructor({ - locationServices, - permissionStore, - user, - }: GeolocationParameters) { + constructor({ locationServices, permissionStore }: GeolocationParameters) { if (!canConstruct) throw new TypeError("Illegal constructor"); canConstruct = false; this.#locationServices = locationServices; this.#permissionStore = permissionStore; - this.#user = user; this.#cachedPosition = null; this.#watchIds = []; this.#watchUnsubscribers = {}; @@ -191,14 +185,12 @@ export class Geolocation { * 5. Let descriptor be a new PermissionDescriptor whose name is * "geolocation". */ - const descriptor: PermissionDescriptor = { - name: "geolocation", - }; + // descriptor is defined as a constant at the top of the file /* * 6. Set permission to request permission to use descriptor. */ - const isAllowed = await this.#user.requestAccess(descriptor); + const isAllowed = await this.#permissionStore.requestAccess(descriptor); /* * 7. If permission is "denied", then: @@ -275,12 +267,12 @@ export class Geolocation { ); const unsubscribePermission = this.#permissionStore.subscribe( - (descriptor, toState) => { + (descriptor, { hasAccess, hadAccess }) => { if (descriptor.name !== "geolocation") return; + if (hasAccess === hadAccess) return; - if (toState === "granted") { - // Produce a new position immediately when the permission changes to - // "granted". + if (hasAccess) { + // Produce a new position immediately when access is granted. this.#acquirePosition( successCallback, errorCallback, @@ -292,9 +284,9 @@ export class Geolocation { /* v8 ignore stop */ ); } else { - // Produce PERMISSION_DENIED errors immediately when the permission - // changes to something other than "granted". This is not part of the - // spec, but Chrome does it, and it's useful for testing. + // Produce PERMISSION_DENIED errors immediately when access is + // revoked. This is not part of the spec, but Chrome does it, and it's + // useful for testing. this.#invokeErrorCallback( errorCallback, createPermissionDeniedError(""), @@ -372,13 +364,13 @@ export class Geolocation { * 1. Let permission be get the current permission state of * "geolocation". */ - const permission = this.#permissionStore.get({ name: "geolocation" }); + const hasAccess = this.#permissionStore.hasAccess(descriptor); /* * 5. (cont.) * 2. If permission is "denied": */ - if (permission === "denied") { + if (!hasAccess) { /* * 5. (cont.) * 2. (cont.) @@ -482,9 +474,8 @@ export class Geolocation { new Promise((_resolve, reject) => { const unsubscribePermission = this.#permissionStore.subscribe( - (descriptor, toState) => { - if (descriptor.name !== "geolocation") return; - if (toState === "granted") return; + (descriptor, { hasAccess }) => { + if (descriptor.name !== "geolocation" || hasAccess) return; // eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors reject(GeolocationPositionError.PERMISSION_DENIED); @@ -642,7 +633,6 @@ export class Geolocation { #locationServices: LocationServices; #permissionStore: PermissionStore; - #user: User; #cachedPosition: GeolocationPosition | null; #watchIds: number[]; #watchUnsubscribers: Record; diff --git a/test/vitest/create-apis.spec.ts b/test/vitest/create-apis.spec.ts index 53e7d01..782ccd2 100644 --- a/test/vitest/create-apis.spec.ts +++ b/test/vitest/create-apis.spec.ts @@ -39,7 +39,7 @@ describe("createAPIs()", () => { await expect( observer.waitForPermissionState("granted", async () => { - user.grantPermission({ name: "geolocation" }); + user.grantAccess({ name: "geolocation" }); }), ).resolves.toBeUndefined(); expect((await permissions.query({ name: "geolocation" })).state).toBe( diff --git a/test/vitest/create-wrapped-apis.spec.ts b/test/vitest/create-wrapped-apis.spec.ts index b0a3433..938388d 100644 --- a/test/vitest/create-wrapped-apis.spec.ts +++ b/test/vitest/create-wrapped-apis.spec.ts @@ -5,6 +5,7 @@ import { createWrappedAPIs, type GeolocationObserver, } from "fake-geolocation"; +import type { PermissionStore } from "fake-permissions"; import { afterEach, beforeEach, @@ -22,9 +23,11 @@ describe("createWrappedAPIs()", () => { const startTime = 100; let suppliedUser: User; + let suppliedPermissionStore: PermissionStore; let geolocation: Geolocation; let observer: GeolocationObserver; + let permissionStore: PermissionStore; let permissions: Permissions; let user: User; let selectAPIs: (useSuppliedAPIs: boolean) => void; @@ -42,9 +45,10 @@ describe("createWrappedAPIs()", () => { dialog.deny(true); }, }); + suppliedPermissionStore = supplied.permissionStore; suppliedUser = supplied.user; suppliedUser.jumpToCoordinates(coordsA); - suppliedUser.grantPermission({ name: "geolocation" }); + suppliedUser.grantAccess({ name: "geolocation" }); const wrapped = createWrappedAPIs({ geolocation: supplied.geolocation, @@ -55,12 +59,13 @@ describe("createWrappedAPIs()", () => { }); geolocation = wrapped.geolocation; observer = wrapped.observer; + permissionStore = wrapped.permissionStore; permissions = wrapped.permissions; user = wrapped.user; selectAPIs = wrapped.selectAPIs; isUsingSuppliedAPIs = wrapped.isUsingSuppliedAPIs; user.jumpToCoordinates(coordsB); - user.grantPermission({ name: "geolocation" }); + user.grantAccess({ name: "geolocation" }); successCallback = vi.fn(); errorCallback = vi.fn(); @@ -86,7 +91,7 @@ describe("createWrappedAPIs()", () => { }); it("delegates to the fake Permissions API", async () => { - expect(await user.requestAccess({ name: "push" })).toBe(true); + expect(await permissionStore.requestAccess({ name: "push" })).toBe(true); expect((await permissions.query({ name: "push" })).state).toBe("granted"); }); }); @@ -111,7 +116,9 @@ describe("createWrappedAPIs()", () => { }); it("delegates to the supplied Permissions API", async () => { - expect(await suppliedUser.requestAccess({ name: "push" })).toBe(false); + expect( + await suppliedPermissionStore.requestAccess({ name: "push" }), + ).toBe(false); expect((await permissions.query({ name: "push" })).state).toBe("denied"); }); @@ -126,7 +133,7 @@ describe("createWrappedAPIs()", () => { it("observes the supplied Permissions API", async () => { await expect( observer.waitForPermissionState("denied", async () => { - suppliedUser.denyPermission({ name: "geolocation" }); + suppliedUser.blockAccess({ name: "geolocation" }); }), ).resolves.toBeUndefined(); }); @@ -147,7 +154,9 @@ describe("createWrappedAPIs()", () => { }); it("delegates to the fake Permissions API", async () => { - expect(await user.requestAccess({ name: "push" })).toBe(true); + expect(await permissionStore.requestAccess({ name: "push" })).toBe( + true, + ); expect((await permissions.query({ name: "push" })).state).toBe( "granted", ); @@ -175,7 +184,7 @@ describe("createWrappedAPIs()", () => { }); it("delegates to the fake Permissions API", async () => { - expect(await user.requestAccess({ name: "push" })).toBe(true); + expect(await permissionStore.requestAccess({ name: "push" })).toBe(true); expect((await permissions.query({ name: "push" })).state).toBe("granted"); }); @@ -190,7 +199,7 @@ describe("createWrappedAPIs()", () => { it("observes the fake Permissions API", async () => { await expect( observer.waitForPermissionState("denied", async () => { - user.denyPermission({ name: "geolocation" }); + user.blockAccess({ name: "geolocation" }); }), ).resolves.toBeUndefined(); }); @@ -211,7 +220,9 @@ describe("createWrappedAPIs()", () => { }); it("delegates to the supplied Permissions API", async () => { - expect(await suppliedUser.requestAccess({ name: "push" })).toBe(false); + expect( + await suppliedPermissionStore.requestAccess({ name: "push" }), + ).toBe(false); expect((await permissions.query({ name: "push" })).state).toBe( "denied", ); diff --git a/test/vitest/delegated.spec.ts b/test/vitest/delegated.spec.ts index 8ecd53f..5109404 100644 --- a/test/vitest/delegated.spec.ts +++ b/test/vitest/delegated.spec.ts @@ -10,7 +10,11 @@ import { createPosition, createUser, } from "fake-geolocation"; -import { createPermissionStore, createPermissions } from "fake-permissions"; +import { + createPermissionStore, + createPermissions, + type PermissionStore, +} from "fake-permissions"; import { afterEach, beforeEach, @@ -30,6 +34,8 @@ describe("Delegated geolocation", () => { const startTime = 100; let locationServicesA: MutableLocationServices; let locationServicesB: MutableLocationServices; + let permissionStoreA: PermissionStore; + let permissionStoreB: PermissionStore; let permissionsA: Permissions; let permissionsB: Permissions; let userA: User; @@ -52,11 +58,11 @@ describe("Delegated geolocation", () => { locationServicesA = createLocationServices(); locationServicesB = createLocationServices(); - const permissionStoreA = createPermissionStore({ - initialStates: new Map([[{ name: "geolocation" }, "granted"]]), + permissionStoreA = createPermissionStore({ + initialStates: new Map([[{ name: "geolocation" }, "GRANTED"]]), }); - const permissionStoreB = createPermissionStore({ - initialStates: new Map([[{ name: "geolocation" }, "granted"]]), + permissionStoreB = createPermissionStore({ + initialStates: new Map([[{ name: "geolocation" }, "GRANTED"]]), }); permissionsA = createPermissions({ permissionStore: permissionStoreA }); @@ -77,12 +83,10 @@ describe("Delegated geolocation", () => { delegateA = createGeolocation({ locationServices: locationServicesA, permissionStore: permissionStoreA, - user: userA, }); delegateB = createGeolocation({ locationServices: locationServicesB, permissionStore: permissionStoreB, - user: userB, }); ({ geolocation, selectDelegate, isDelegateSelected } = @@ -288,10 +292,10 @@ describe("Delegated geolocation", () => { beforeEach(async () => { await vi.runOnlyPendingTimersAsync(); // ensure that the first position is acquired await sleep(delay); - userB.resetPermission({ name: "geolocation" }); + userB.resetAccess({ name: "geolocation" }); successCallback.mockClear(); errorCallback.mockClear(); - vi.spyOn(userB, "requestAccess"); + vi.spyOn(permissionStoreB, "requestAccess"); selectDelegate(delegateB); }); @@ -304,15 +308,14 @@ describe("Delegated geolocation", () => { }); it("does not cause an access request", () => { - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(userB.requestAccess).not.toBeCalled(); + expect(permissionStoreB.requestAccess).not.toBeCalled(); }); describe("when permission is granted", () => { beforeEach(() => { successCallback.mockClear(); errorCallback.mockClear(); - userB.grantPermission({ name: "geolocation" }); + userB.grantAccess({ name: "geolocation" }); }); it("calls the success callback with a position that matches the selected delegate", async () => { @@ -330,7 +333,7 @@ describe("Delegated geolocation", () => { beforeEach(async () => { successCallback.mockClear(); errorCallback.mockClear(); - userB.denyPermission({ name: "geolocation" }); + userB.blockAccess({ name: "geolocation" }); await vi.runOnlyPendingTimersAsync(); }); @@ -411,7 +414,7 @@ describe("Delegated geolocation", () => { describe("when the first delegate's permission becomes denied", () => { beforeEach(() => { - userA.denyPermission({ name: "geolocation" }); + userA.blockAccess({ name: "geolocation" }); }); describe("when reading the position", () => { @@ -630,7 +633,7 @@ describe("Delegated geolocation", () => { describe("when the selected delegate's permission becomes denied", () => { beforeEach(() => { - userB.denyPermission({ name: "geolocation" }); + userB.blockAccess({ name: "geolocation" }); }); describe("when reading the position", () => { diff --git a/test/vitest/example/changelog/geolocation-observers.spec.ts b/test/vitest/example/changelog/geolocation-observers.spec.ts index c0eba03..014ad0e 100644 --- a/test/vitest/example/changelog/geolocation-observers.spec.ts +++ b/test/vitest/example/changelog/geolocation-observers.spec.ts @@ -24,7 +24,7 @@ describe("Geolocation observers", () => { // Jump to some coords and grant permission user.jumpToCoordinates(coordsA); - user.grantPermission({ name: "geolocation" }); + user.grantAccess({ name: "geolocation" }); // Start watching the position let position: GeolocationPosition | undefined; @@ -87,14 +87,14 @@ describe("Geolocation observers", () => { // Wait for a PERMISSION_DENIED error, while running a task await observer.waitForPositionError(PERMISSION_DENIED, async () => { - user.denyPermission({ name: "geolocation" }); + user.blockAccess({ name: "geolocation" }); }); // Outputs "true" console.log(error?.code === PERMISSION_DENIED); // You can also wait for geolocation permission states await observer.waitForPermissionState("granted", async () => { - user.grantPermission({ name: "geolocation" }); + user.grantAccess({ name: "geolocation" }); }); // Outputs "true" console.log(status.state === "granted"); diff --git a/test/vitest/geolocation-observer.spec.ts b/test/vitest/geolocation-observer.spec.ts index cdd544f..14fa04e 100644 --- a/test/vitest/geolocation-observer.spec.ts +++ b/test/vitest/geolocation-observer.spec.ts @@ -2,6 +2,7 @@ import { createAPIs, createGeolocationObserver, GeolocationPositionError, + PERMISSION_DENIED, type MutableLocationServices, } from "fake-geolocation"; import type { PermissionStore } from "fake-permissions"; @@ -23,7 +24,7 @@ describe("GeolocationObserver", () => { describe("when called with no matchers", () => { it("resolves when any coords are already acquired", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); await runTaskQueue(); @@ -45,7 +46,7 @@ describe("GeolocationObserver", () => { expect(isResolved).toBe(false); locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await expect(promise).resolves.toBeUndefined(); }); @@ -53,7 +54,7 @@ describe("GeolocationObserver", () => { it("doesn't resolve when coords are not acquired", async () => { locationServices.setLowAccuracyCoordinates(coordsA); locationServices.disable(); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); @@ -76,7 +77,7 @@ describe("GeolocationObserver", () => { describe("when called with a single matcher", () => { it("resolves when the coords already match the matcher", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); await runTaskQueue(); @@ -88,7 +89,7 @@ describe("GeolocationObserver", () => { it("resolves when the coords change to match the matcher", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); @@ -109,7 +110,7 @@ describe("GeolocationObserver", () => { it("doesn't resolve when the coords change but don't match the matcher", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); @@ -137,7 +138,7 @@ describe("GeolocationObserver", () => { describe("when called with multiple matchers", () => { it("resolves when the coords already match any of the matchers", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); await runTaskQueue(); @@ -152,7 +153,7 @@ describe("GeolocationObserver", () => { it("resolves when the coords change to match any of the matchers", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); @@ -175,7 +176,7 @@ describe("GeolocationObserver", () => { it("doesn't resolve when the coords change but don't match any of the matchers", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); @@ -205,7 +206,7 @@ describe("GeolocationObserver", () => { describe("when called with a partial matcher", () => { it("resolves when the coords match", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); await runTaskQueue(); @@ -217,7 +218,7 @@ describe("GeolocationObserver", () => { it("doesn't resolve when the coords don't match", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); @@ -247,7 +248,7 @@ describe("GeolocationObserver", () => { describe("when called with an async task function", () => { it("runs the task while waiting", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); await runTaskQueue(); @@ -263,7 +264,7 @@ describe("GeolocationObserver", () => { describe("when called with position options", () => { it("uses the provided options", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); @@ -290,7 +291,7 @@ describe("GeolocationObserver", () => { describe("when an error has been received after a position was acquired", () => { it("doesn't resolve until a new position is acquired", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); await runTaskQueue(); @@ -317,7 +318,7 @@ describe("GeolocationObserver", () => { describe("waitForPositionError()", () => { describe("when called with no codes", () => { it("resolves when any error has already been received", async () => { - permissionStore.set({ name: "geolocation" }, "denied"); + permissionStore.setStatus({ name: "geolocation" }, "BLOCKED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); await runTaskQueue(); @@ -327,7 +328,7 @@ describe("GeolocationObserver", () => { it("resolves when any error is subsequently received", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); await runTaskQueue(); @@ -342,14 +343,14 @@ describe("GeolocationObserver", () => { expect(isResolved).toBe(false); - permissionStore.set({ name: "geolocation" }, "denied"); + permissionStore.setStatus({ name: "geolocation" }, "BLOCKED"); await expect(promise).resolves.toBeUndefined(); }); it("doesn't resolve when no error is received", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); await runTaskQueue(); @@ -364,7 +365,7 @@ describe("GeolocationObserver", () => { expect(isResolved).toBe(false); - permissionStore.set({ name: "geolocation" }, "denied"); + permissionStore.setStatus({ name: "geolocation" }, "BLOCKED"); await expect(promise).resolves.toBeUndefined(); }); @@ -372,7 +373,7 @@ describe("GeolocationObserver", () => { describe("when called with a single code", () => { it("resolves when the most recently received error already matches the code", async () => { - permissionStore.set({ name: "geolocation" }, "denied"); + permissionStore.setStatus({ name: "geolocation" }, "BLOCKED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); await runTaskQueue(); @@ -385,7 +386,7 @@ describe("GeolocationObserver", () => { }); it("resolves when an error is received that matches the code", async () => { - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); @@ -401,14 +402,14 @@ describe("GeolocationObserver", () => { expect(isResolved).toBe(false); - permissionStore.set({ name: "geolocation" }, "denied"); + permissionStore.setStatus({ name: "geolocation" }, "BLOCKED"); await expect(promise).resolves.toBeUndefined(); }); it("doesn't resolve when an error is received that doesn't match the code", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); @@ -424,13 +425,13 @@ describe("GeolocationObserver", () => { expect(isResolved).toBe(false); - permissionStore.set({ name: "geolocation" }, "denied"); + permissionStore.setStatus({ name: "geolocation" }, "BLOCKED"); await runTaskQueue(); expect(isResolved).toBe(false); locationServices.disable(); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await expect(promise).resolves.toBeUndefined(); }); @@ -438,7 +439,7 @@ describe("GeolocationObserver", () => { describe("when called with multiple codes", () => { it("resolves when the most recently received error already matches any of the codes", async () => { - permissionStore.set({ name: "geolocation" }, "denied"); + permissionStore.setStatus({ name: "geolocation" }, "BLOCKED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); await runTaskQueue(); @@ -459,7 +460,7 @@ describe("GeolocationObserver", () => { it("resolves when an error is received that matches any of the codes", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); @@ -478,14 +479,14 @@ describe("GeolocationObserver", () => { expect(isResolved).toBe(false); - permissionStore.set({ name: "geolocation" }, "denied"); + permissionStore.setStatus({ name: "geolocation" }, "BLOCKED"); await expect(promise).resolves.toBeUndefined(); }); it("doesn't resolve when an error is received that doesn't match any of the codes", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); @@ -509,7 +510,7 @@ describe("GeolocationObserver", () => { expect(isResolved).toBe(false); - permissionStore.set({ name: "geolocation" }, "denied"); + permissionStore.setStatus({ name: "geolocation" }, "BLOCKED"); await expect(promise).resolves.toBeUndefined(); }); @@ -518,7 +519,7 @@ describe("GeolocationObserver", () => { describe("when called with an async task function", () => { it("runs the task while waiting", async () => { locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); @@ -526,7 +527,7 @@ describe("GeolocationObserver", () => { observer.waitForPositionError( GeolocationPositionError.PERMISSION_DENIED, async () => { - permissionStore.set({ name: "geolocation" }, "denied"); + permissionStore.setStatus({ name: "geolocation" }, "BLOCKED"); }, ), ).resolves.toBeUndefined(); @@ -539,7 +540,7 @@ describe("GeolocationObserver", () => { acquireDelay: 0, })); locationServices.setLowAccuracyCoordinates(coordsA); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); @@ -558,7 +559,7 @@ describe("GeolocationObserver", () => { describe("when a coords have been acquired after an error was received", () => { it("doesn't resolve until a new error is received", async () => { locationServices.setLowAccuracyCoordinates(undefined); - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); await runTaskQueue(); @@ -580,18 +581,34 @@ describe("GeolocationObserver", () => { await expect(promise).resolves.toBeUndefined(); }); }); + + describe("Regression tests", () => { + it("can wait for a PERMISSION_DENIED error caused by resetting the permission during an active watch", async () => { + locationServices.setLowAccuracyCoordinates(coordsA); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); + await runTaskQueue(); + const observer = createGeolocationObserver(geolocation, permissions); + await runTaskQueue(); + + await expect( + observer.waitForPositionError(PERMISSION_DENIED, async () => { + permissionStore.setStatus({ name: "geolocation" }, "PROMPT"); + }), + ).resolves.toBeUndefined(); + }); + }); }); describe("waitForPermissionState()", () => { it("delegates to the permission observer", async () => { - permissionStore.set({ name: "geolocation" }, "denied"); + permissionStore.setStatus({ name: "geolocation" }, "BLOCKED"); await runTaskQueue(); const observer = createGeolocationObserver(geolocation, permissions); await runTaskQueue(); await expect( observer.waitForPermissionState("granted", async () => { - permissionStore.set({ name: "geolocation" }, "granted"); + permissionStore.setStatus({ name: "geolocation" }, "GRANTED"); }), ).resolves.toBeUndefined(); }); diff --git a/test/vitest/geolocation/get-current-position.spec.ts b/test/vitest/geolocation/get-current-position.spec.ts index deb44e6..405c43a 100644 --- a/test/vitest/geolocation/get-current-position.spec.ts +++ b/test/vitest/geolocation/get-current-position.spec.ts @@ -46,7 +46,7 @@ describe("Geolocation.getCurrentPosition()", () => { describe("when permission has not been requested", () => { beforeEach(() => { - user.resetPermission({ name: "geolocation" }); + user.resetAccess({ name: "geolocation" }); }); describe("when coords can be acquired", () => { @@ -310,7 +310,7 @@ describe("Geolocation.getCurrentPosition()", () => { describe("when permission is denied", () => { beforeEach(() => { - user.denyPermission({ name: "geolocation" }); + user.blockAccess({ name: "geolocation" }); }); describe("when reading the position", () => { @@ -330,7 +330,7 @@ describe("Geolocation.getCurrentPosition()", () => { describe("when permission is granted", () => { beforeEach(() => { - user.grantPermission({ name: "geolocation" }); + user.grantAccess({ name: "geolocation" }); }); describe("when acquiring coords throws an error", () => { diff --git a/test/vitest/geolocation/watch-position.spec.ts b/test/vitest/geolocation/watch-position.spec.ts index e1f9079..c4264c4 100644 --- a/test/vitest/geolocation/watch-position.spec.ts +++ b/test/vitest/geolocation/watch-position.spec.ts @@ -59,7 +59,7 @@ describe("Geolocation.watchPosition()", () => { describe("when permission has not been requested", () => { beforeEach(() => { - user.resetPermission({ name: "geolocation" }); + user.resetAccess({ name: "geolocation" }); }); describe("when coords can be acquired", () => { @@ -340,7 +340,7 @@ describe("Geolocation.watchPosition()", () => { describe("when permission is denied", () => { beforeEach(() => { - user.denyPermission({ name: "geolocation" }); + user.blockAccess({ name: "geolocation" }); }); describe("when watching the position", () => { @@ -364,7 +364,7 @@ describe("Geolocation.watchPosition()", () => { describe("when permission is granted", () => { beforeEach(() => { - user.grantPermission({ name: "geolocation" }); + user.grantAccess({ name: "geolocation" }); }); describe("when acquiring coords throws an error", () => { @@ -499,7 +499,7 @@ describe("Geolocation.watchPosition()", () => { await sleep(delay); successCallback.mockClear(); errorCallback.mockClear(); - user.denyPermission({ name: "geolocation" }); + user.blockAccess({ name: "geolocation" }); }); it("calls the error callback with a GeolocationPositionError with a code of PERMISSION_DENIED and an empty message", async () => { @@ -537,7 +537,7 @@ describe("Geolocation.watchPosition()", () => { beforeEach(() => { successCallback.mockClear(); errorCallback.mockClear(); - user.grantPermission({ name: "geolocation" }); + user.grantAccess({ name: "geolocation" }); }); it("calls the success callback with the position", async () => { @@ -571,6 +571,23 @@ describe("Geolocation.watchPosition()", () => { }); }); }); + + describe("when the permission changes, but access is still not allowed", () => { + beforeEach(() => { + successCallback.mockClear(); + errorCallback.mockClear(); + user.setAccessRequestHandler(async (dialog) => { + dialog.deny(false); + }); + user.resetAccess({ name: "geolocation" }); + }); + + it("does not call the error callback", async () => { + await sleep(20); + + expect(errorCallback).not.toBeCalled(); + }); + }); }); describe("when a permission other than geolocation is revoked", () => { @@ -581,7 +598,7 @@ describe("Geolocation.watchPosition()", () => { await sleep(delay); successCallback.mockClear(); errorCallback.mockClear(); - user.denyPermission({ name: "notifications" }); + user.blockAccess({ name: "notifications" }); }); it("does not call the error callback", async () => { diff --git a/test/vitest/user.spec.ts b/test/vitest/user.spec.ts index a541e83..bf11fa8 100644 --- a/test/vitest/user.spec.ts +++ b/test/vitest/user.spec.ts @@ -16,7 +16,7 @@ describe("User", () => { beforeEach(() => { locationServices = createLocationServices(); permissionStore = createPermissionStore({ - initialStates: new Map([[{ name: "geolocation" }, "prompt"]]), + initialStates: new Map([[{ name: "geolocation" }, "PROMPT"]]), }); user = createUser({ locationServices, permissionStore });