diff --git a/libs/permissions/permissionLogic/src/lib/lifecycle/lifecycle.stubs.ts b/libs/permissions/permissionLogic/src/lib/lifecycle/lifecycle.stubs.ts index f6d7999..1cae552 100644 --- a/libs/permissions/permissionLogic/src/lib/lifecycle/lifecycle.stubs.ts +++ b/libs/permissions/permissionLogic/src/lib/lifecycle/lifecycle.stubs.ts @@ -15,6 +15,7 @@ export const stubApplicationLifecycleReportingActorLogic = */ const unsubscribeApplicationStateListeners = stubSubscribeToApplicationStateChanges((event) => { + console.log({ event }); switch (event) { case 'applicationForegrounded': sendBack({ type: 'applicationForegrounded' }); diff --git a/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts b/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts index 0b142d4..2fddb87 100644 --- a/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts +++ b/libs/permissions/permissionLogic/src/lib/permission-logic.spec.ts @@ -32,8 +32,11 @@ describe('Counting Machine That Needs Permission At 3', () => { const applicationActor = createActor(applicationMachine, { systemId: ActorSystemIds.application, }); + applicationActor.start(); - // const permissionMonitorActor = applicationActor.system.get(ActorSystemIds.permissionMonitoring) + const permissionMonitorActor = applicationActor.system.get( + ActorSystemIds.permissionMonitoring + ); // const permissionMonitorActor = applicationActor // .getSnapshot() @@ -41,11 +44,11 @@ describe('Counting Machine That Needs Permission At 3', () => { // ActorSystemIds.permissionMonitoring // ]; - const permissionMonitorActor = applicationActor.system - .get(ActorSystemIds.systemManagement) - .getSnapshot().children[ActorSystemIds.permissionMonitoring]; + // const permissionMonitorActor = applicationActor.system + // .get(ActorSystemIds.systemManagement) + // .getSnapshot().children[ActorSystemIds.permissionMonitoring]; + expect(permissionMonitorActor).toBeDefined(); - console.log({ permissionMonitorActor }); // const permissionMonitorActor = createActor( // permissionMonitoringMachine, // // permissionMonitoringMachine.provide({ @@ -59,37 +62,38 @@ describe('Counting Machine That Needs Permission At 3', () => { // ).start(); const state = permissionMonitorActor.getSnapshot(); - expect( - state.context.permissionSubscribers[Permissions.bluetooth].length - ).toEqual(1); - - const featureMachineActor = - permissionMonitorActor.getSnapshot().children.featuresMachineId; - expect(featureMachineActor?.getSnapshot().value).toStrictEqual({ - counting: 'enabled', - handlingPermissions: {}, - }); - - const countingActor = createActor( - countingMachineThatNeedsPermissionAt3, - {} - ).start(); + console.log({ v: state.context }); + // expect( + // state.context.permissionSubscribers[Permissions.bluetooth].length + // ).toEqual(1); + + // const featureMachineActor = + // permissionMonitorActor.getSnapshot().children.featuresMachineId; + // expect(featureMachineActor?.getSnapshot().value).toStrictEqual({ + // counting: 'enabled', + // handlingPermissions: {}, + // }); - countingActor.send({ type: 'count.inc' }); - countingActor.send({ type: 'count.inc' }); - countingActor.send({ type: 'count.inc' }); - expect(countingActor.getSnapshot().context.count).toBe(3); - expect(countingActor.getSnapshot().value).toStrictEqual({ - counting: 'disabled', - handlingPermissions: {}, - }); + // const countingActor = createActor( + // countingMachineThatNeedsPermissionAt3, + // {} + // ).start(); - countingActor.send({ type: 'count.inc' }); - expect(countingActor.getSnapshot().context.count).toBe(3); - expect(countingActor.getSnapshot().value).toStrictEqual({ - counting: 'disabled', - handlingPermissions: {}, - }); + // countingActor.send({ type: 'count.inc' }); + // countingActor.send({ type: 'count.inc' }); + // countingActor.send({ type: 'count.inc' }); + // expect(countingActor.getSnapshot().context.count).toBe(3); + // expect(countingActor.getSnapshot().value).toStrictEqual({ + // counting: 'disabled', + // handlingPermissions: {}, + // }); + // + // countingActor.send({ type: 'count.inc' }); + // expect(countingActor.getSnapshot().context.count).toBe(3); + // expect(countingActor.getSnapshot().value).toStrictEqual({ + // counting: 'disabled', + // handlingPermissions: {}, + // }); // Configure the permission actor to grant permission // const permissionCheckerActor = @@ -115,445 +119,443 @@ describe('Counting Machine That Needs Permission At 3', () => { // }); }); // prettyMuchForever - it('should start in idle state', async () => { - const countingActor = createActor( - countingMachineThatNeedsPermissionAt3 - ).start(); - expect(countingActor.getSnapshot().value).toStrictEqual({ - counting: 'enabled', - handlingPermissions: {}, - }); - }); - - it('should increment count', async () => { - const countingActor = createActor( - countingMachineThatNeedsPermissionAt3 - ).start(); - countingActor.send({ type: 'count.inc' }); - expect(countingActor.getSnapshot().context.count).toBe(1); - }); -}); - -describe('Permission Requester and Checker Machine', () => { - describe('Checking Permissions', () => { - it('should check permission when triggered', async () => { - const bluetoothPermissionActor = createActor( - permissionCheckerAndRequesterMachine, - { input: { parent: undefined } } - ).start(); - - bluetoothPermissionActor.send({ type: 'triggerPermissionCheck' }); - - await waitFor( - bluetoothPermissionActor, - (state) => state.value === 'idle' - ); - - expect(bluetoothPermissionActor.getSnapshot().value).toBe('idle'); - expect(bluetoothPermissionActor.getSnapshot().context.statuses).toEqual({ - [Permissions.bluetooth]: PermissionStatuses.denied, - [Permissions.microphone]: PermissionStatuses.denied, - }); - }); - - it('should report permission to parent after a check', async () => { - let result: any; - const spy = ( - something: /* TODO: change type to whatever an event is in xstate*/ any - ) => { - result = something; - }; - - const parentMachine = setup({ - types: {} as { - events: PermissionMonitoringMachineEvents; - children: {}; - }, - - actors: { - permissionCheckerAndRequesterMachine, - }, - }).createMachine({ - on: { - allPermissionsChecked: { - actions: spy, - }, - triggerPermissionCheck: { - actions: [ - sendTo(ActorSystemIds.permissionCheckerAndRequester, { - type: 'triggerPermissionCheck', - }), - ], - }, - }, - invoke: { - id: ActorSystemIds.permissionCheckerAndRequester, - systemId: ActorSystemIds.permissionCheckerAndRequester, - src: 'permissionCheckerAndRequesterMachine', - input: ({ self }) => ({ parent: self }), - }, - }); - - const actorRef = createActor(parentMachine).start(); - actorRef.send({ type: 'triggerPermissionCheck' }); - - await waitFor( - actorRef, - (state) => - state.children.permissionCheckerAndRequesterMachineId!.getSnapshot() - .value === 'idle' - ); - - expect(result).not.toBeNull(); - expect(result.event).toStrictEqual({ - type: 'allPermissionsChecked', - statuses: { - [Permissions.bluetooth]: PermissionStatuses.denied, - [Permissions.microphone]: PermissionStatuses.denied, - }, - }); - }); - }); - - describe('Requesting Permissions', () => { - it('should request permission when triggered', async () => { - const permissionActor = createActor( - permissionCheckerAndRequesterMachine, - { input: { parent: undefined } } - ).start(); - const permission: Permission = Permissions.bluetooth; - - expect(permissionActor.getSnapshot().context.statuses[permission]).toBe( - PermissionStatuses.unasked - ); - - permissionActor.send({ - type: 'triggerPermissionRequest', - permission, - }); - - await waitFor(permissionActor, (state) => state.value === 'idle'); - - expect(permissionActor.getSnapshot().value).toBe('idle'); - expect(permissionActor.getSnapshot().context.statuses[permission]).toBe( - PermissionStatuses.granted - ); - }); - - it('should report permission to parent after a request', async () => { - let result: any; - const spy = ( - something: /* TODO: change type to whatever an event is in xstate*/ any - ) => { - result = something; - }; - - const parentMachine = setup({ - types: {} as { - events: PermissionMonitoringMachineEvents; - children: { - [ActorSystemIds.permissionCheckerAndRequester]: 'permissionCheckerAndRequesterMachine'; - }; - }, - actors: { - permissionCheckerAndRequesterMachine, - }, - }).createMachine({ - on: { - permissionRequestCompleted: { - actions: spy, - }, - triggerPermissionRequest: { - actions: [ - sendTo(ActorSystemIds.permissionCheckerAndRequester, { - type: 'triggerPermissionRequest', - permission: Permissions.bluetooth, - }), - ], - }, - }, - invoke: { - id: ActorSystemIds.permissionCheckerAndRequester, - src: 'permissionCheckerAndRequesterMachine', - input: ({ self }) => ({ parent: self }), - }, - }); - - const actorRef = createActor(parentMachine).start(); - actorRef.send({ - type: 'triggerPermissionRequest', - permission: Permissions.bluetooth, - }); - - await waitFor( - actorRef, - (state) => - state.children[ - ActorSystemIds.permissionCheckerAndRequester - ]!.getSnapshot().value === 'idle' - ); - - expect(result).not.toBeNull(); - expect(result.event).toStrictEqual({ - type: 'permissionRequestCompleted', - status: PermissionStatuses.granted, - permission: Permissions.bluetooth, - }); - }); - }); -}); - -export type PermissionSubscribers = Array; -export type PermissionSubscriberMap = Record; - -/** - * A map of that looks like this to start: - * { - * bluetooth: [], - * microphone: [], - * } - */ - -describe('Permission Monitoring Machine', () => { - describe('Subscriptions', () => { - it('should initialize with no subscriptions', () => { - const actor = createActor(permissionMonitoringMachine, { - parent: undefined, - }).start(); - const state = actor.getSnapshot(); - expect(state.context.permissionSubscribers).toEqual( - EmptyPermissionSubscriberMap - ); - }); - - describe('Single Subscriber', () => { - it('should allow subscriptions from a subscriber to a single permission', () => { - const actor = createActor( - permissionMonitoringMachine.provide({ - actors: { - features: someFeatureMachine, - }, - }), - { - parent: undefined, - systemId: ActorSystemIds.permissionMonitoring, - } - ).start(); - - const state = actor.getSnapshot(); - expect( - state.context.permissionSubscribers[Permissions.bluetooth].length - ).toEqual(1); - - const id = - state.context.permissionSubscribers[Permissions.bluetooth][0].id; - expect(id).toBe('permissionHandler'); - }); - - it('should notify subscribers of changes to permissions', async () => { - const permissionMonitorActor = createActor( - permissionMonitoringMachine.provide({ - actors: { - features: someFeatureMachine, - }, - }), - { - systemId: ActorSystemIds.permissionMonitoring, - inspect: createSkyInspector( - // @ts-expect-error - { inspectorType: 'node', WebSocket: WebSocket, autoStart: true } - ).inspect, - } - ).start(); - - const state = permissionMonitorActor.getSnapshot(); - expect( - state.context.permissionSubscribers[Permissions.bluetooth].length - ).toEqual(1); - - const featureMachineActor = - permissionMonitorActor.getSnapshot().children.featuresMachineId; - expect(featureMachineActor?.getSnapshot().value).toStrictEqual({ - foo: 'waitingForPermission', - handlingPermissions: {}, - }); - - expect(permissionMonitorActor.getSnapshot().value).toStrictEqual({ - applicationLifecycle: 'applicationIsInForeground', - permissions: {}, - }); - expect( - permissionMonitorActor.getSnapshot().context.permissionsStatuses - ).toStrictEqual({ - bluetooth: 'unasked', - microphone: 'unasked', - }); - - const permissionCheckerActor = - permissionMonitorActor.getSnapshot().children[ - ActorSystemIds.permissionCheckerAndRequester - ]!; - - expect(permissionCheckerActor?.getSnapshot().value).toBe( - 'checkingPermissions' - ); - - await waitFor(permissionCheckerActor, (state) => { - return state.value === 'idle'; - }); - expect( - permissionMonitorActor.getSnapshot().context.permissionsStatuses[ - Permissions.bluetooth - ] - ).toBe('denied'); - - expect(permissionCheckerActor?.getSnapshot().value).toBe('idle'); - expect(featureMachineActor?.getSnapshot().value).toStrictEqual({ - foo: 'bluetoothDenied', - handlingPermissions: {}, - }); - - // await waitFor(featureMachineActor, (state) => { - // return state.value === 'bluetoothDenied'; - // }); - - featureMachineActor?.send({ - type: 'user.didTapBluetoothRequestPermission', - permission: Permissions.bluetooth, - }); - - expect(permissionCheckerActor?.getSnapshot().value).toBe( - 'requestingPermission' - ); - - await waitFor(permissionCheckerActor, (state) => { - return state.value === 'idle'; - }); - expect(featureMachineActor?.getSnapshot().value).toStrictEqual({ - foo: 'bluetoothGranted', - handlingPermissions: {}, - }); - // await new Promise((resolve) => setTimeout(resolve, forever)); - }); - - describe('Edge Cases', () => { - it('should not add a subscriber if the subscriber is already subscribed', () => { - /*FIXME: I don't like having to create another test actor for this - how do I access the actor - or trigger the subscription request again - or configure different starting context via input - */ - const dummyFeatureMachineThatSubscribesTwice = setup({ - actions: { - sendSubscriptionRequestForStatusUpdates: sendTo( - ({ system }) => { - const actorRef: AnyActorRef = system.get( - ActorSystemIds.permissionMonitoring - ); - return actorRef; - }, - ({ self }) => ({ - type: 'subscribeToPermissionStatuses', - permissions: [Permissions.bluetooth], - self, - }) - ), - // satisfies /*TODO type these events to the receiving machine event type*/ AnyEventObject); - }, - }).createMachine({ - id: 'dummyFeatureId', - entry: [ - 'sendSubscriptionRequestForStatusUpdates', - /*Second subscription should have no effect*/ 'sendSubscriptionRequestForStatusUpdates', - log('subscribe to status updates'), - ], - }); - - const actor = createActor( - permissionMonitoringMachine.provide({ - actors: { - features: dummyFeatureMachineThatSubscribesTwice, - }, - }), - { - parent: undefined, - systemId: ActorSystemIds.permissionMonitoring, - } - ).start(); - - expect( - actor.getSnapshot().context.permissionSubscribers[ - Permissions.bluetooth - ].length - ).toEqual(1); - }); - }); - }); - }); - - it('handle the happy path of being invoked, checking permission initially and then handle a permission request', async () => { - const permission: Permission = Permissions.microphone; - - const actorRef = createActor(permissionMonitoringMachine, { - inspect: { - next: (event: InspectionEvent) => {}, - error: (error) => { - console.log(error); - }, - complete: () => { - console.log('complete'); - }, - }, - }).start(); - - expect(actorRef.getSnapshot().context.permissionsStatuses).toStrictEqual({ - [Permissions.bluetooth]: PermissionStatuses.unasked, - [permission]: PermissionStatuses.unasked, - }); - - expect(actorRef.getSnapshot().value).toStrictEqual({ - applicationLifecycle: 'applicationIsInForeground', - permissions: {}, - }); - - await waitFor(actorRef, (state) => { - return ( - state.children.permissionCheckerAndRequesterMachineId!.getSnapshot() - .value === 'idle' - ); - }); - - expect(actorRef.getSnapshot().context.permissionsStatuses).toStrictEqual({ - [Permissions.bluetooth]: PermissionStatuses.denied, - [permission]: PermissionStatuses.denied, - }); - - actorRef.send({ - type: 'triggerPermissionRequest', - permission: permission, - }); - - expect( - actorRef - .getSnapshot() - .children.permissionCheckerAndRequesterMachineId!.getSnapshot().value - ).toBe('requestingPermission'); - - await waitFor(actorRef, (state) => { - return ( - state.children.permissionCheckerAndRequesterMachineId!.getSnapshot() - .value === 'idle' - ); - }); - - expect(actorRef.getSnapshot().context.permissionsStatuses).toStrictEqual({ - [Permissions.bluetooth]: PermissionStatuses.denied, - [permission]: PermissionStatuses.granted, - }); - }); - - it('should immediately report back to parent if permission is already granted', async () => {}); - describe('Blocked Permission', () => { - it('should immediately report back to parent if permission is blocked', async () => {}); - }); + // it('should start in idle state', async () => { + // const countingActor = createActor( + // countingMachineThatNeedsPermissionAt3 + // ).start(); + // expect(countingActor.getSnapshot().value).toStrictEqual({ + // counting: 'enabled', + // handlingPermissions: {}, + // }); + // }); + // + // it('should increment count', async () => { + // const countingActor = createActor( + // countingMachineThatNeedsPermissionAt3 + // ).start(); + // countingActor.send({ type: 'count.inc' }); + // expect(countingActor.getSnapshot().context.count).toBe(1); + // }); + // }); + // + // describe('Permission Requester and Checker Machine', () => { + // describe('Checking Permissions', () => { + // it('should check permission when triggered', async () => { + // const bluetoothPermissionActor = createActor( + // permissionCheckerAndRequesterMachine, + // { input: { parent: undefined } } + // ).start(); + // + // bluetoothPermissionActor.send({ type: 'triggerPermissionCheck' }); + // + // await waitFor( + // bluetoothPermissionActor, + // (state) => state.value === 'idle' + // ); + // + // expect(bluetoothPermissionActor.getSnapshot().value).toBe('idle'); + // expect(bluetoothPermissionActor.getSnapshot().context.statuses).toEqual({ + // [Permissions.bluetooth]: PermissionStatuses.denied, + // [Permissions.microphone]: PermissionStatuses.denied, + // }); + // }); + // + // it('should report permission to parent after a check', async () => { + // let result: any; + // const spy = ( + // something: /* TODO: change type to whatever an event is in xstate*/ any + // ) => { + // result = something; + // }; + // + // const parentMachine = setup({ + // types: {} as { + // events: PermissionMonitoringMachineEvents; + // children: {}; + // }, + // + // actors: { + // permissionCheckerAndRequesterMachine, + // }, + // }).createMachine({ + // on: { + // allPermissionsChecked: { + // actions: spy, + // }, + // triggerPermissionCheck: { + // actions: [ + // sendTo(ActorSystemIds.permissionCheckerAndRequester, { + // type: 'triggerPermissionCheck', + // }), + // ], + // }, + // }, + // invoke: { + // id: ActorSystemIds.permissionCheckerAndRequester, + // systemId: ActorSystemIds.permissionCheckerAndRequester, + // src: 'permissionCheckerAndRequesterMachine', + // input: ({ self }) => ({ parent: self }), + // }, + // }); + // + // const actorRef = createActor(parentMachine).start(); + // actorRef.send({ type: 'triggerPermissionCheck' }); + // + // await waitFor( + // actorRef, + // (state) => + // state.children.permissionCheckerAndRequesterMachineId!.getSnapshot() + // .value === 'idle' + // ); + // + // expect(result).not.toBeNull(); + // expect(result.event).toStrictEqual({ + // type: 'allPermissionsChecked', + // statuses: { + // [Permissions.bluetooth]: PermissionStatuses.denied, + // [Permissions.microphone]: PermissionStatuses.denied, + // }, + // }); + // }); + // }); + // + // describe('Requesting Permissions', () => { + // it('should request permission when triggered', async () => { + // const permissionActor = createActor( + // permissionCheckerAndRequesterMachine, + // { input: { parent: undefined } } + // ).start(); + // const permission: Permission = Permissions.bluetooth; + // + // expect(permissionActor.getSnapshot().context.statuses[permission]).toBe( + // PermissionStatuses.unasked + // ); + // + // permissionActor.send({ + // type: 'triggerPermissionRequest', + // permission, + // }); + // + // await waitFor(permissionActor, (state) => state.value === 'idle'); + // + // expect(permissionActor.getSnapshot().value).toBe('idle'); + // expect(permissionActor.getSnapshot().context.statuses[permission]).toBe( + // PermissionStatuses.granted + // ); + // }); + // + // it('should report permission to parent after a request', async () => { + // let result: any; + // const spy = ( + // something: /* TODO: change type to whatever an event is in xstate*/ any + // ) => { + // result = something; + // }; + // + // const parentMachine = setup({ + // types: {} as { + // events: PermissionMonitoringMachineEvents; + // children: { + // [ActorSystemIds.permissionCheckerAndRequester]: 'permissionCheckerAndRequesterMachine'; + // }; + // }, + // actors: { + // permissionCheckerAndRequesterMachine, + // }, + // }).createMachine({ + // on: { + // permissionRequestCompleted: { + // actions: spy, + // }, + // triggerPermissionRequest: { + // actions: [ + // sendTo(ActorSystemIds.permissionCheckerAndRequester, { + // type: 'triggerPermissionRequest', + // permission: Permissions.bluetooth, + // }), + // ], + // }, + // }, + // invoke: { + // id: ActorSystemIds.permissionCheckerAndRequester, + // src: 'permissionCheckerAndRequesterMachine', + // input: ({ self }) => ({ parent: self }), + // }, + // }); + // + // const actorRef = createActor(parentMachine).start(); + // actorRef.send({ + // type: 'triggerPermissionRequest', + // permission: Permissions.bluetooth, + // }); + // + // await waitFor( + // actorRef, + // (state) => + // state.children[ + // ActorSystemIds.permissionCheckerAndRequester + // ]!.getSnapshot().value === 'idle' + // ); + // + // expect(result).not.toBeNull(); + // expect(result.event).toStrictEqual({ + // type: 'permissionRequestCompleted', + // status: PermissionStatuses.granted, + // permission: Permissions.bluetooth, + // }); + // }); + // }); + // }); + // + // + // /** + // * A map of that looks like this to start: + // * { + // * bluetooth: [], + // * microphone: [], + // * } + // */ + // + // describe('Permission Monitoring Machine', () => { + // describe('Subscriptions', () => { + // it('should initialize with no subscriptions', () => { + // const actor = createActor(permissionMonitoringMachine, { + // parent: undefined, + // }).start(); + // const state = actor.getSnapshot(); + // expect(state.context.permissionSubscribers).toEqual( + // EmptyPermissionSubscriberMap + // ); + // }); + // + // describe('Single Subscriber', () => { + // it('should allow subscriptions from a subscriber to a single permission', () => { + // const actor = createActor( + // permissionMonitoringMachine.provide({ + // actors: { + // features: someFeatureMachine, + // }, + // }), + // { + // parent: undefined, + // systemId: ActorSystemIds.permissionMonitoring, + // } + // ).start(); + // + // const state = actor.getSnapshot(); + // expect( + // state.context.permissionSubscribers[Permissions.bluetooth].length + // ).toEqual(1); + // + // const id = + // state.context.permissionSubscribers[Permissions.bluetooth][0].id; + // expect(id).toBe('permissionHandler'); + // }); + // + // it('should notify subscribers of changes to permissions', async () => { + // const permissionMonitorActor = createActor( + // permissionMonitoringMachine.provide({ + // actors: { + // features: someFeatureMachine, + // }, + // }), + // { + // systemId: ActorSystemIds.permissionMonitoring, + // inspect: createSkyInspector( + // // @ts-expect-error + // { inspectorType: 'node', WebSocket: WebSocket, autoStart: true } + // ).inspect, + // } + // ).start(); + // + // const state = permissionMonitorActor.getSnapshot(); + // expect( + // state.context.permissionSubscribers[Permissions.bluetooth].length + // ).toEqual(1); + // + // const featureMachineActor = + // permissionMonitorActor.getSnapshot().children.featuresMachineId; + // expect(featureMachineActor?.getSnapshot().value).toStrictEqual({ + // foo: 'waitingForPermission', + // handlingPermissions: {}, + // }); + // + // expect(permissionMonitorActor.getSnapshot().value).toStrictEqual({ + // applicationLifecycle: 'applicationIsInForeground', + // permissions: {}, + // }); + // expect( + // permissionMonitorActor.getSnapshot().context.permissionsStatuses + // ).toStrictEqual({ + // bluetooth: 'unasked', + // microphone: 'unasked', + // }); + // + // const permissionCheckerActor = + // permissionMonitorActor.getSnapshot().children[ + // ActorSystemIds.permissionCheckerAndRequester + // ]!; + // + // expect(permissionCheckerActor?.getSnapshot().value).toBe( + // 'checkingPermissions' + // ); + // + // await waitFor(permissionCheckerActor, (state) => { + // return state.value === 'idle'; + // }); + // expect( + // permissionMonitorActor.getSnapshot().context.permissionsStatuses[ + // Permissions.bluetooth + // ] + // ).toBe('denied'); + // + // expect(permissionCheckerActor?.getSnapshot().value).toBe('idle'); + // expect(featureMachineActor?.getSnapshot().value).toStrictEqual({ + // foo: 'bluetoothDenied', + // handlingPermissions: {}, + // }); + // + // // await waitFor(featureMachineActor, (state) => { + // // return state.value === 'bluetoothDenied'; + // // }); + // + // featureMachineActor?.send({ + // type: 'user.didTapBluetoothRequestPermission', + // permission: Permissions.bluetooth, + // }); + // + // expect(permissionCheckerActor?.getSnapshot().value).toBe( + // 'requestingPermission' + // ); + // + // await waitFor(permissionCheckerActor, (state) => { + // return state.value === 'idle'; + // }); + // expect(featureMachineActor?.getSnapshot().value).toStrictEqual({ + // foo: 'bluetoothGranted', + // handlingPermissions: {}, + // }); + // // await new Promise((resolve) => setTimeout(resolve, forever)); + // }); + // + // describe('Edge Cases', () => { + // it('should not add a subscriber if the subscriber is already subscribed', () => { + // /*FIXME: I don't like having to create another test actor for this + // how do I access the actor + // or trigger the subscription request again + // or configure different starting context via input + // */ + // const dummyFeatureMachineThatSubscribesTwice = setup({ + // actions: { + // sendSubscriptionRequestForStatusUpdates: sendTo( + // ({ system }) => { + // const actorRef: AnyActorRef = system.get( + // ActorSystemIds.permissionMonitoring + // ); + // return actorRef; + // }, + // ({ self }) => ({ + // type: 'subscribeToPermissionStatuses', + // permissions: [Permissions.bluetooth], + // self, + // }) + // ), + // // satisfies /*TODO type these events to the receiving machine event type*/ AnyEventObject); + // }, + // }).createMachine({ + // id: 'dummyFeatureId', + // entry: [ + // 'sendSubscriptionRequestForStatusUpdates', + // /*Second subscription should have no effect*/ 'sendSubscriptionRequestForStatusUpdates', + // log('subscribe to status updates'), + // ], + // }); + // + // const actor = createActor( + // permissionMonitoringMachine.provide({ + // actors: { + // features: dummyFeatureMachineThatSubscribesTwice, + // }, + // }), + // { + // parent: undefined, + // systemId: ActorSystemIds.permissionMonitoring, + // } + // ).start(); + // + // expect( + // actor.getSnapshot().context.permissionSubscribers[ + // Permissions.bluetooth + // ].length + // ).toEqual(1); + // }); + // }); + // }); + // }); + // + // it('handle the happy path of being invoked, checking permission initially and then handle a permission request', async () => { + // const permission: Permission = Permissions.microphone; + // + // const actorRef = createActor(permissionMonitoringMachine, { + // inspect: { + // next: (event: InspectionEvent) => {}, + // error: (error) => { + // console.log(error); + // }, + // complete: () => { + // console.log('complete'); + // }, + // }, + // }).start(); + // + // expect(actorRef.getSnapshot().context.permissionsStatuses).toStrictEqual({ + // [Permissions.bluetooth]: PermissionStatuses.unasked, + // [permission]: PermissionStatuses.unasked, + // }); + // + // expect(actorRef.getSnapshot().value).toStrictEqual({ + // applicationLifecycle: 'applicationIsInForeground', + // permissions: {}, + // }); + // + // await waitFor(actorRef, (state) => { + // return ( + // state.children.permissionCheckerAndRequesterMachineId!.getSnapshot() + // .value === 'idle' + // ); + // }); + // + // expect(actorRef.getSnapshot().context.permissionsStatuses).toStrictEqual({ + // [Permissions.bluetooth]: PermissionStatuses.denied, + // [permission]: PermissionStatuses.denied, + // }); + // + // actorRef.send({ + // type: 'triggerPermissionRequest', + // permission: permission, + // }); + // + // expect( + // actorRef + // .getSnapshot() + // .children.permissionCheckerAndRequesterMachineId!.getSnapshot().value + // ).toBe('requestingPermission'); + // + // await waitFor(actorRef, (state) => { + // return ( + // state.children.permissionCheckerAndRequesterMachineId!.getSnapshot() + // .value === 'idle' + // ); + // }); + // + // expect(actorRef.getSnapshot().context.permissionsStatuses).toStrictEqual({ + // [Permissions.bluetooth]: PermissionStatuses.denied, + // [permission]: PermissionStatuses.granted, + // }); + // }); + // + // it('should immediately report back to parent if permission is already granted', async () => {}); + // describe('Blocked Permission', () => { + // it('should immediately report back to parent if permission is blocked', async () => {}); + // }); }); diff --git a/libs/permissions/permissionLogic/src/lib/permission.types.ts b/libs/permissions/permissionLogic/src/lib/permission.types.ts index 92160f7..1d70c4f 100644 --- a/libs/permissions/permissionLogic/src/lib/permission.types.ts +++ b/libs/permissions/permissionLogic/src/lib/permission.types.ts @@ -58,3 +58,6 @@ export type PermissionMachineEvents = type: 'triggerPermissionRequest'; permission: Permission; }; + +export type PermissionSubscribers = Array; +export type PermissionSubscriberMap = Record; diff --git a/libs/permissions/permissionLogic/src/lib/permissionMonitor.machine.ts b/libs/permissions/permissionLogic/src/lib/permissionMonitor.machine.ts index a21d0b9..5564ef8 100644 --- a/libs/permissions/permissionLogic/src/lib/permissionMonitor.machine.ts +++ b/libs/permissions/permissionLogic/src/lib/permissionMonitor.machine.ts @@ -10,13 +10,13 @@ import { } from 'xstate'; import { ActorSystemIds } from './application/actorIds'; import { stubApplicationLifecycleReportingActorLogic } from './lifecycle/lifecycle.stubs'; -import { PermissionSubscriberMap } from './permission-logic.spec'; import { InitialPermissionStatusMap } from './permission.fixtures'; import { Permission, PermissionMonitoringMachineEvents, PermissionStatusMapType, Permissions, + PermissionSubscriberMap, } from './permission.types'; import { permissionCheckerAndRequesterMachine } from './permissionCheckAndRequestMachine'; import { countingMachineThatNeedsPermissionAt3 } from './features/counting/counting.machine'; @@ -41,14 +41,13 @@ export const permissionMonitoringMachine = setup({ children: { [ActorSystemIds.permissionCheckerAndRequester]: 'permissionCheckerAndRequesterMachine'; [ActorSystemIds.lifecycleReporting]: 'applicationLifecycleReportingMachine'; - [ActorSystemIds.features]: 'features'; + // [ActorSystemIds.features]: 'features'; }; }, actors: { applicationLifecycleReportingMachine: stubApplicationLifecycleReportingActorLogic, permissionCheckerAndRequesterMachine, - features: setup({}).createMachine({}), }, actions: { @@ -105,16 +104,6 @@ export const permissionMonitoringMachine = setup({ id: ActorSystemIds.permissionMonitoring, type: 'parallel', - // TODO: this should live at the top level of the application, not here - // https://jabraenhance.atlassian.net/browse/JES-3817 - invoke: [ - { - src: 'features', - systemId: ActorSystemIds.features, - id: ActorSystemIds.features, - }, - ], - context: { permissionsStatuses: InitialPermissionStatusMap, permissionSubscribers: EmptyPermissionSubscriberMap, diff --git a/libs/permissions/permissionLogic/src/lib/systemManagement/systemManagement.machine.ts b/libs/permissions/permissionLogic/src/lib/systemManagement/systemManagement.machine.ts index 6850c99..b9ce2fd 100644 --- a/libs/permissions/permissionLogic/src/lib/systemManagement/systemManagement.machine.ts +++ b/libs/permissions/permissionLogic/src/lib/systemManagement/systemManagement.machine.ts @@ -1,15 +1,18 @@ -import { setup } from 'xstate'; +import { createMachine, setup } from 'xstate'; import { ActorSystemIds } from '../application/actorIds'; import { permissionMonitoringMachine } from '../permissionMonitor.machine'; +import { types } from 'node:util'; export const systemManagementMachine = setup({ types: {} as { children: { [ActorSystemIds.permissionMonitoring]: 'permissionMonitoringMachine'; + // eventually get the lifecycle monitoring in here... }; }, actors: { permissionMonitoringMachine: permissionMonitoringMachine, + // eventually get the lifecycle monitoring in here... }, }).createMachine({ invoke: [ @@ -18,5 +21,7 @@ export const systemManagementMachine = setup({ systemId: ActorSystemIds.permissionMonitoring, src: 'permissionMonitoringMachine', }, + // eventually get the lifecycle monitoring in here... ], + entry: () => console.log('systemManagement started'), });