diff --git a/libs/permissions/permissionLogic/src/lib/features/counting/counting.spec.ts b/libs/permissions/permissionLogic/src/lib/features/counting/counting.spec.ts index 14951d6..de44339 100644 --- a/libs/permissions/permissionLogic/src/lib/features/counting/counting.spec.ts +++ b/libs/permissions/permissionLogic/src/lib/features/counting/counting.spec.ts @@ -20,105 +20,99 @@ type JESActorSystem = ActorSystem<{ describe('Counting Machine That Needs Permission At 3', () => { describe('Actor system tests', () => { - it( - 'should increment count to 3, ask for permission, and continue counting to 5 when permission is granted', - async () => { - const applicationActor = createActor(applicationMachine, { - systemId: ActorSystemIds.application, - inspect: createSkyInspector({ - // @ts-expect-error - WebSocket: WebSocket, - inspectorType: 'node', - autoStart: true, - }).inspect, - }); - applicationActor.start(); - const actorSystem: JESActorSystem = applicationActor.system; - - const permissionMonitorActor = actorSystem.get( - ActorSystemIds.permissionMonitoring - )!; - - const countingPermissionReporter = applicationActor.system.get( - 'permissionReportingCounting' + it('should increment count to 3, ask for permission, and continue counting to 5 when permission is granted', async () => { + const applicationActor = createActor(applicationMachine, { + systemId: ActorSystemIds.application, + inspect: createSkyInspector({ + // @ts-expect-error + WebSocket: WebSocket, + inspectorType: 'node', + autoStart: true, + }).inspect, + }); + applicationActor.start(); + const actorSystem: JESActorSystem = applicationActor.system; + + const permissionMonitorActor = actorSystem.get( + ActorSystemIds.permissionMonitoring + )!; + + const countingPermissionReporter = applicationActor.system.get( + 'permissionReportingCounting' + ); + + // @ts-expect-error this means the actor system type is working as expected + permissionMonitorActor.getSnapshot().value === 'foo'; + permissionMonitorActor?.getSnapshot().value.applicationLifecycle === + 'applicationInBackground'; + + // @ts-expect-error this means the actor system type is working as expected + permissionMonitorActor.getSnapshot().context === 'foo'; + + expect(permissionMonitorActor).toBeDefined(); + expect(countingPermissionReporter).toBeDefined(); + + /* Required due to a bug in the initialization of actor systems*/ await new Promise( + (resolve) => setTimeout(resolve, 0) + ); + + const state: PermissionMonitoringSnapshot = + permissionMonitorActor?.getSnapshot(); + + // We should be able to find the permission coordinator for the Counting + // feature in the Permission Monitor's subscription map + const countingMachinePermissionCoordinator = + state.context.permissionSubscribers[Permissions.bluetooth]?.some( + (subscriber) => subscriber.id === 'permissionReportingCounting' ); + expect(countingMachinePermissionCoordinator).toBeDefined(); - // @ts-expect-error this means the actor system type is working as expected - permissionMonitorActor.getSnapshot().value === 'foo'; - permissionMonitorActor?.getSnapshot().value.applicationLifecycle === - 'applicationInBackground'; + const countingActor = applicationActor.system.get( + ActorSystemIds.counting + ); - // @ts-expect-error this means the actor system type is working as expected - permissionMonitorActor.getSnapshot().context === 'foo'; + expect(countingActor?.getSnapshot().value).toStrictEqual('enabled'); - expect(permissionMonitorActor).toBeDefined(); - expect(countingPermissionReporter).toBeDefined(); - - /* Required due to a bug in the initialization of actor systems*/ await new Promise( - (resolve) => setTimeout(resolve, 0) - ); - - const state: PermissionMonitoringSnapshot = - permissionMonitorActor?.getSnapshot(); - - // We should be able to find the permission coordinator for the Counting - // feature in the Permission Monitor's subscription map - const countingMachinePermissionCoordinator = - state.context.permissionSubscribers[Permissions.bluetooth]?.some( - (subscriber) => subscriber.id === 'permissionReportingCounting' - ); - expect(countingMachinePermissionCoordinator).toBeDefined(); - - const countingActor = applicationActor.system.get( - ActorSystemIds.counting - ); - - expect(countingActor?.getSnapshot().value).toStrictEqual('enabled'); + 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( + '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( - 'handlingPermissions' - ); + countingActor.send({ type: 'count.inc' }); + expect(countingActor.getSnapshot().context.count).toBe(3); + expect(countingActor.getSnapshot().value).toStrictEqual( + 'handlingPermissions' + ); - countingActor.send({ type: 'count.inc' }); - expect(countingActor.getSnapshot().context.count).toBe(3); - expect(countingActor.getSnapshot().value).toStrictEqual( - 'handlingPermissions' - ); + // Configure the permission actor to grant permission + const permissionCheckerActor = applicationActor.system.get( + ActorSystemIds.permissionCheckerAndRequester + ); - // Configure the permission actor to grant permission - const permissionCheckerActor = applicationActor.system.get( - ActorSystemIds.permissionCheckerAndRequester - ); + countingActor.send({ type: 'user.didTapBluetoothRequestPermission' }); - countingActor.send({ type: 'user.didTapBluetoothRequestPermission' }); + await waitFor(permissionCheckerActor, (state) => state.value === 'idle'); - await waitFor( - permissionCheckerActor, - (state) => state.value === 'idle' - ); + expect(countingActor.getSnapshot().value).toStrictEqual('enabled'); - expect(countingActor.getSnapshot().value).toStrictEqual('enabled'); + expect(countingActor.getSnapshot().context.permissionStatus).toBe( + PermissionStatuses.granted + ); - expect(countingActor.getSnapshot().context.permissionStatus).toBe( - PermissionStatuses.granted - ); + // Send 'count.inc' events to increment the count to 5 + countingActor.send({ type: 'count.inc' }); + countingActor.send({ type: 'count.inc' }); - // Send 'count.inc' events to increment the count to 5 - countingActor.send({ type: 'count.inc' }); - countingActor.send({ type: 'count.inc' }); - - expect(countingActor.getSnapshot().context.count).toBe(5); - expect(countingActor.getSnapshot().value).toStrictEqual('finished'); - countingActor.send({ type: 'count.inc' }); - expect(countingActor.getSnapshot().context.count).toBe(5); - ///* Required for debugging with stately inspector */ await new Promise((resolve) => setTimeout(resolve, vLongTime)); - }, - vLongTime - ); + expect(countingActor.getSnapshot().context.count).toBe(5); + expect(countingActor.getSnapshot().value).toStrictEqual('finished'); + countingActor.send({ type: 'count.inc' }); + expect(countingActor.getSnapshot().context.count).toBe(5); + // /* Required if you want to debug a test with the stately inspector */ await new Promise((resolve) => setTimeout(resolve, vLongTime)); + }); + // vLongTime }); describe('Actor unit tests', () => { diff --git a/libs/permissions/permissionLogic/src/lib/permission/monitoring/permissionMonitor.machine.ts b/libs/permissions/permissionLogic/src/lib/permission/monitoring/permissionMonitor.machine.ts index 1a199cc..168df08 100644 --- a/libs/permissions/permissionLogic/src/lib/permission/monitoring/permissionMonitor.machine.ts +++ b/libs/permissions/permissionLogic/src/lib/permission/monitoring/permissionMonitor.machine.ts @@ -4,7 +4,6 @@ import { assertEvent, assign, enqueueActions, - log, raise, sendTo, setup, @@ -77,24 +76,22 @@ export const permissionMonitoringMachine = setup({ permissionSubscribers: updatedPermissionSubscribers, }; }), - broadcastPermissionsToListeners: enqueueActions( - ({ context, event, enqueue }) => { - // TODO this should only send permission updates for the recently modified permissions - // and is currently sending updates to all permissions to everyone - Object.keys(context.permissionSubscribers).forEach((permission) => { - context.permissionSubscribers[permission].forEach( - (actorRef: AnyActorRef) => { - enqueue.sendTo(actorRef, { - type: 'permissionStatusChanged', - permission, - status: context.permissionsStatuses[permission], - }); - } - ); - }); - } - ), - assignPermissionRequestResultToContext: assign({ + broadcastPermissionsToListeners: enqueueActions(({ context, enqueue }) => { + // TODO this should only send permission updates for the recently modified permissions + // and is currently sending updates to all permissions to everyone + Object.keys(context.permissionSubscribers).forEach((permission) => { + context.permissionSubscribers[permission].forEach( + (actorRef: AnyActorRef) => { + enqueue.sendTo(actorRef, { + type: 'permissionStatusChanged', + permission, + status: context.permissionsStatuses[permission], + }); + } + ); + }); + }), + savePermissionRequestResult: assign({ permissionsStatuses: ({ event, context }) => { assertEvent(event, 'permissionRequestCompleted'); return { @@ -123,7 +120,6 @@ export const permissionMonitoringMachine = setup({ /** @xstate-layout N4IgpgJg5mDOIC5QCMCWUDSBDAFgVwDssA6LABzIBtUBjLAF1QHsCAZVAMzBoE8bKwAYnJVaDZgQBiTAE5goMpoQiQA2gAYAuolBkmsVIxY6QAD0QBaABwBmdcQCcANicAWKwFY7D165tWAGhAeSwBGf2JQgHYbf3U7J38PACYogF80oLRMXEISEWo6IzZObj4BYQpC8RYAISwaAGsFJQIVCA1tJBA9A2KTcwQLVxjHZISoqz9bcI8gkKHwq0iYmydk9Sso9VdQq2SMrPRsfCJiMjAZAFtUWAMWWGFKSgAFS5u7iVgAYRxuRrUWhMvUMEgGiBsrmSxB2ySsVjcoS8O3Uc2CYSs9lCiSi218DiiHnU6gOmRA2ROeXO71u9wIj3oMnQMBkb2utIkv3+nWB+lBxm6gwsyVCjiRoQcW3iNiivj88zCDhsxBGoXUUQlMQRDg8VkO5OOuTOF3ZnwegkZzMubI+dIASmAAI54OD0HndEH9QWIZLQ4lOKwEgOBqa+tELSHQmW48bhX1rJz6ilGkgm21fQRpjksB3O13fJhXKhgeiArq6Ple0CDGzJVwq2sI6KeDzrcMQjz1zzYkVOGJKvsZMkEJgqeDdZOnLC8vpg71DBzJBwq1K+qFOTa2QLooY2DzLtwIpdwgPJAOJsmTqkFMTFdhcXj8MAz-kEcFDeGiqFRNdnzf+BUhlXYhaw8H8JR2WJXFRJNDSnUgqlvCR7zKJ8ENEIoJAASVgLCpFkeRFGUF8qzMSxbGVb9fw3eEAJ3CwPC-KIlQcJEbFCeN4lgnJ4JvTCWBQx8BHQ6pijw+omhaYiPUrOdq0sVwnGXH8NWJBwHGJCVXEA4VlnUNwnC8RcYy3C8jh4qkszNekSLksiEH2GF-E8fZ3GxSNAJlYhWysCDwkhXZ9yHNIgA */ id: ActorSystemIds.permissionMonitoring, type: 'parallel', - entry: log('monitoring started'), context: { permissionsStatuses: InitialPermissionStatusMap, @@ -178,7 +174,7 @@ export const permissionMonitoringMachine = setup({ }, permissionRequestCompleted: { actions: [ - 'assignPermissionRequestResultToContext', + 'savePermissionRequestResult', 'broadcastPermissionsToListeners', ], }, diff --git a/libs/permissions/permissionLogic/src/lib/permission/reporting/asdf b/libs/permissions/permissionLogic/src/lib/permission/reporting/asdf deleted file mode 100644 index 7524feb..0000000 --- a/libs/permissions/permissionLogic/src/lib/permission/reporting/asdf +++ /dev/null @@ -1,10 +0,0 @@ -Feature needs to control - -in what state permission is required -when the thing is actually requeted - - - -Prereq machine needs to control -subscribe to permission updates from permission monitor machine -checking and updating its own context / context of the parent with up to pre req statuses \ No newline at end of file diff --git a/libs/permissions/permissionLogic/src/lib/permission/reporting/permissionReporting.machine.ts b/libs/permissions/permissionLogic/src/lib/permission/reporting/permissionReporting.machine.ts index 346e3b1..e6e3133 100644 --- a/libs/permissions/permissionLogic/src/lib/permission/reporting/permissionReporting.machine.ts +++ b/libs/permissions/permissionLogic/src/lib/permission/reporting/permissionReporting.machine.ts @@ -3,7 +3,6 @@ import { AnyActorRef, AnyEventObject, enqueueActions, - log, sendTo, setup, } from 'xstate'; @@ -21,6 +20,10 @@ export const permissionReportingMachine = setup({ permissions: Array; parent: AnyActorRef; }, + events: {} as { + type: 'requestPermission'; + permission: Permission; + }, }, actions: { sendSubscriptionRequestForStatusUpdates: sendTo( @@ -40,12 +43,12 @@ export const permissionReportingMachine = setup({ ({ system }) => { return system.get(ActorSystemIds.permissionCheckerAndRequester); }, - ({ event }) => ({ - // TODO: determine how to make this typesafe - // I'm thinking an api like sendToPermissionChecker(event: PermissionCheckerEvent) - type: 'triggerPermissionRequest', - permission: event.permission, - }) + ({ event }) => { + return { + type: 'triggerPermissionRequest', + permission: event.permission, + }; + } ), checkedSendParent: enqueueActions( ({ context, enqueue }, event: AnyEventObject) => { @@ -96,8 +99,10 @@ machine. `, }, permissionStatusChanged: { - description: `Whenever the Permission Monitoring machine reports that a permission - status has changed, we receive this event and can process and share with our siblings.`, + description: ` +Whenever the Permission Monitoring machine reports that a permission +status has changed, we receive this event and can process and share +with the machine in which we are invoked.`, actions: [ { /** diff --git a/libs/permissions/permissionLogic/src/lib/xstate-reference/referenceTests/invoke-and-parallel.spec.ts b/libs/permissions/permissionLogic/src/lib/xstate-reference/referenceTests/invoke-and-parallel.spec.ts index bae5ed7..fc7ccc5 100644 --- a/libs/permissions/permissionLogic/src/lib/xstate-reference/referenceTests/invoke-and-parallel.spec.ts +++ b/libs/permissions/permissionLogic/src/lib/xstate-reference/referenceTests/invoke-and-parallel.spec.ts @@ -1,1980 +1,1979 @@ -it('should pass', () => {}); -// import { -// PromiseActorLogic, -// SpecialTargets, -// StateValue, -// assign, -// createActor, -// createMachine, -// forwardTo, -// fromCallback, -// fromPromise, -// raise, -// sendParent, -// sendTo, -// } from 'xstate'; -// -// const user = { name: 'David' }; -// -// describe('invoke', () => { -// it('should be possible to send immediate events to initially invoked actors', () => { -// const child = createMachine({ -// on: { -// PING: { -// actions: sendParent({ type: 'PONG' }), -// }, -// }, -// }); -// -// const machine = createMachine({ -// initial: 'waiting', -// states: { -// waiting: { -// invoke: { -// id: 'ponger', -// src: child, -// }, -// // entry: sendTo('ponger', { type: 'PING' }), -// on: { -// PONG: 'done', -// sendPing: { -// actions: sendTo('ponger', { type: 'PING' }), -// }, -// }, -// }, -// done: { -// type: 'final', -// }, -// }, -// }); -// -// const service = createActor(machine).start(); -// service.send({ type: 'sendPing' }); -// -// expect(service.getSnapshot().value).toBe('done'); -// }); -// -// it('child can immediately respond to the parent with multiple events', () => { -// const childMachine = createMachine({ -// types: {} as { -// events: { type: 'FORWARD_DEC' }; -// }, -// id: 'child', -// initial: 'init', -// states: { -// init: { -// on: { -// FORWARD_DEC: { -// actions: [ -// sendParent({ type: 'DEC' }), -// sendParent({ type: 'DEC' }), -// sendParent({ type: 'DEC' }), -// ], -// }, -// }, -// }, -// }, -// }); -// -// const someParentMachine = createMachine( -// { -// id: 'parent', -// types: {} as { -// context: { count: number }; -// actors: { -// src: 'child'; -// id: 'someService'; -// logic: typeof childMachine; -// }; -// }, -// context: { count: 0 }, -// initial: 'start', -// states: { -// start: { -// invoke: { -// src: 'child', -// id: 'someService', -// }, -// always: { -// target: 'stop', -// guard: ({ context }) => context.count === -3, -// }, -// on: { -// DEC: { -// actions: assign({ count: ({ context }) => context.count - 1 }), -// }, -// FORWARD_DEC: { -// actions: sendTo('someService', { type: 'FORWARD_DEC' }), -// }, -// }, -// }, -// stop: { -// type: 'final', -// }, -// }, -// }, -// { -// actors: { -// child: childMachine, -// }, -// } -// ); -// -// const actorRef = createActor(someParentMachine).start(); -// actorRef.send({ type: 'FORWARD_DEC' }); -// -// // 1. The 'parent' machine will not do anything (inert transition) -// // 2. The 'FORWARD_DEC' event will be "forwarded" to the child machine -// // 3. On the child machine, the 'FORWARD_DEC' event sends the 'DEC' action to the parent thrice -// // 4. The context of the 'parent' machine will be updated from 0 to -3 -// expect(actorRef.getSnapshot().context).toEqual({ count: -3 }); -// }); -// -// it('should start services (explicit machine, invoke = config)', (done) => { -// const childMachine = createMachine({ -// id: 'fetch', -// types: {} as { -// context: { userId: string | undefined; user?: typeof user | undefined }; -// events: { -// type: 'RESOLVE'; -// user: typeof user; -// }; -// input: { userId: string }; -// }, -// context: ({ input }) => ({ -// userId: input.userId, -// }), -// initial: 'pending', -// states: { -// pending: { -// entry: raise({ type: 'RESOLVE', user }), -// on: { -// RESOLVE: { -// target: 'success', -// guard: ({ context }) => { -// return context.userId !== undefined; -// }, -// }, -// }, -// }, -// success: { -// type: 'final', -// entry: assign({ -// user: ({ event }) => event.user, -// }), -// }, -// failure: { -// entry: sendParent({ type: 'REJECT' }), -// }, -// }, -// output: ({ context }) => ({ user: context.user }), -// }); -// -// const machine = createMachine({ -// types: {} as { -// context: { -// selectedUserId: string; -// user?: typeof user; -// }; -// }, -// id: 'fetcher', -// initial: 'idle', -// context: { -// selectedUserId: '42', -// user: undefined, -// }, -// states: { -// idle: { -// on: { -// GO_TO_WAITING: 'waiting', -// }, -// }, -// waiting: { -// invoke: { -// src: childMachine, -// input: ({ context }: any) => ({ -// userId: context.selectedUserId, -// }), -// onDone: { -// target: 'received', -// guard: ({ event }) => { -// // Should receive { user: { name: 'David' } } as event data -// return (event.output as any).user.name === 'David'; -// }, -// }, -// }, -// }, -// received: { -// type: 'final', -// }, -// }, -// }); -// -// const actor = createActor(machine); -// actor.subscribe({ -// complete: () => { -// done(); -// }, -// }); -// actor.start(); -// actor.send({ type: 'GO_TO_WAITING' }); -// }); -// -// it('should start services (explicit machine, invoke = machine)', (done) => { -// const childMachine = createMachine({ -// types: {} as { -// events: { type: 'RESOLVE' }; -// input: { userId: string }; -// }, -// initial: 'pending', -// states: { -// pending: { -// entry: raise({ type: 'RESOLVE' }), -// on: { -// RESOLVE: { -// target: 'success', -// }, -// }, -// }, -// success: { -// type: 'final', -// }, -// }, -// }); -// -// const machine = createMachine({ -// initial: 'idle', -// states: { -// idle: { -// on: { -// GO_TO_WAITING: 'waiting', -// }, -// }, -// waiting: { -// invoke: { -// src: childMachine, -// onDone: 'received', -// }, -// }, -// received: { -// type: 'final', -// }, -// }, -// }); -// const actor = createActor(machine); -// actor.subscribe({ -// complete: () => { -// done(); -// }, -// }); -// actor.start(); -// actor.send({ type: 'GO_TO_WAITING' }); -// }); -// -// it('should start services (machine as invoke config)', (done) => { -// const machineInvokeMachine = createMachine({ -// types: {} as { -// events: { -// type: 'SUCCESS'; -// data: number; -// }; -// }, -// id: 'machine-invoke', -// initial: 'pending', -// states: { -// pending: { -// invoke: { -// src: createMachine({ -// id: 'child', -// initial: 'sending', -// states: { -// sending: { -// entry: sendParent({ type: 'SUCCESS', data: 42 }), -// }, -// }, -// }), -// }, -// on: { -// SUCCESS: { -// target: 'success', -// guard: ({ event }) => { -// return event.data === 42; -// }, -// }, -// }, -// }, -// success: { -// type: 'final', -// }, -// }, -// }); -// const actor = createActor(machineInvokeMachine); -// actor.subscribe({ complete: () => done() }); -// actor.start(); -// }); -// -// it('should start deeply nested service (machine as invoke config)', (done) => { -// const machineInvokeMachine = createMachine({ -// types: {} as { -// events: { -// type: 'SUCCESS'; -// data: number; -// }; -// }, -// id: 'parent', -// initial: 'a', -// states: { -// a: { -// initial: 'b', -// states: { -// b: { -// invoke: { -// src: createMachine({ -// id: 'child', -// initial: 'sending', -// states: { -// sending: { -// entry: sendParent({ type: 'SUCCESS', data: 42 }), -// }, -// }, -// }), -// }, -// }, -// }, -// }, -// success: { -// id: 'success', -// type: 'final', -// }, -// }, -// on: { -// SUCCESS: { -// target: '.success', -// guard: ({ event }) => { -// return event.data === 42; -// }, -// }, -// }, -// }); -// const actor = createActor(machineInvokeMachine); -// actor.subscribe({ complete: () => done() }); -// actor.start(); -// }); -// -// it('should use the service overwritten by .provide(...)', (done) => { -// const childMachine = createMachine({ -// id: 'child', -// initial: 'init', -// states: { -// init: {}, -// }, -// }); -// -// const someParentMachine = createMachine( -// { -// id: 'parent', -// types: {} as { -// context: { count: number }; -// actors: { -// src: 'child'; -// id: 'someService'; -// logic: typeof childMachine; -// }; -// }, -// context: { count: 0 }, -// initial: 'start', -// states: { -// start: { -// invoke: { -// src: 'child', -// id: 'someService', -// }, -// on: { -// STOP: 'stop', -// }, -// }, -// stop: { -// type: 'final', -// }, -// }, -// }, -// { -// actors: { -// child: childMachine, -// }, -// } -// ); -// -// const actor = createActor( -// someParentMachine.provide({ -// actors: { -// child: createMachine({ -// id: 'child', -// initial: 'init', -// states: { -// init: { -// entry: [sendParent({ type: 'STOP' })], -// }, -// }, -// }), -// }, -// }) -// ); -// actor.subscribe({ -// complete: () => { -// done(); -// }, -// }); -// actor.start(); -// }); -// -// describe('parent to child', () => { -// const subMachine = createMachine({ -// id: 'child', -// initial: 'one', -// states: { -// one: { -// on: { NEXT: 'two' }, -// }, -// two: { -// entry: sendParent({ type: 'NEXT' }), -// }, -// }, -// }); -// -// it('should communicate with the child machine (invoke on machine)', (done) => { -// const mainMachine = createMachine({ -// id: 'parent', -// initial: 'one', -// invoke: { -// id: 'foo-child', -// src: subMachine, -// }, -// states: { -// one: { -// entry: sendTo('foo-child', { type: 'NEXT' }), -// on: { NEXT: 'two' }, -// }, -// two: { -// type: 'final', -// }, -// }, -// }); -// -// const actor = createActor(mainMachine); -// actor.subscribe({ -// complete: () => { -// done(); -// }, -// }); -// actor.start(); -// }); -// -// it('should communicate with the child machine (invoke on state)', (done) => { -// const mainMachine = createMachine({ -// id: 'parent', -// initial: 'one', -// states: { -// one: { -// invoke: { -// id: 'foo-child', -// src: subMachine, -// }, -// entry: sendTo('foo-child', { type: 'NEXT' }), -// on: { NEXT: 'two' }, -// }, -// two: { -// type: 'final', -// }, -// }, -// }); -// -// const actor = createActor(mainMachine); -// actor.subscribe({ -// complete: () => { -// done(); -// }, -// }); -// actor.start(); -// }); -// -// it('should transition correctly if child invocation causes it to directly go to final state', () => { -// const doneSubMachine = createMachine({ -// id: 'child', -// initial: 'one', -// states: { -// one: { -// on: { NEXT: 'two' }, -// }, -// two: { -// type: 'final', -// }, -// }, -// }); -// -// const mainMachine = createMachine({ -// id: 'parent', -// initial: 'one', -// states: { -// one: { -// invoke: { -// id: 'foo-child', -// src: doneSubMachine, -// onDone: 'two', -// }, -// entry: sendTo('foo-child', { type: 'NEXT' }), -// }, -// two: { -// on: { NEXT: 'three' }, -// }, -// three: { -// type: 'final', -// }, -// }, -// }); -// -// const actor = createActor(mainMachine).start(); -// -// expect(actor.getSnapshot().value).toBe('two'); -// }); -// -// it('should work with invocations defined in orthogonal state nodes', (done) => { -// const pongMachine = createMachine({ -// id: 'pong', -// initial: 'active', -// states: { -// active: { -// type: 'final', -// }, -// }, -// output: { secret: 'pingpong' }, -// }); -// -// const pingMachine = createMachine({ -// id: 'ping', -// type: 'parallel', -// states: { -// one: { -// initial: 'active', -// states: { -// active: { -// invoke: { -// id: 'pong', -// src: pongMachine, -// onDone: { -// target: 'success', -// guard: ({ event }) => event.output.secret === 'pingpong', -// }, -// }, -// }, -// success: { -// type: 'final', -// }, -// }, -// }, -// }, -// }); -// -// const actor = createActor(pingMachine); -// actor.subscribe({ -// complete: () => { -// done(); -// }, -// }); -// actor.start(); -// }); -// -// it('should not reinvoke root-level invocations on root non-reentering transitions', () => { -// // https://github.com/statelyai/xstate/issues/2147 -// -// let invokeCount = 0; -// let invokeDisposeCount = 0; -// let actionsCount = 0; -// let entryActionsCount = 0; -// -// const machine = createMachine({ -// invoke: { -// src: fromCallback(() => { -// invokeCount++; -// -// return () => { -// invokeDisposeCount++; -// }; -// }), -// }, -// entry: () => entryActionsCount++, -// on: { -// UPDATE: { -// actions: () => { -// actionsCount++; -// }, -// }, -// }, -// }); -// -// const service = createActor(machine).start(); -// expect(entryActionsCount).toEqual(1); -// expect(invokeCount).toEqual(1); -// expect(invokeDisposeCount).toEqual(0); -// expect(actionsCount).toEqual(0); -// -// service.send({ type: 'UPDATE' }); -// expect(entryActionsCount).toEqual(1); -// expect(invokeCount).toEqual(1); -// expect(invokeDisposeCount).toEqual(0); -// expect(actionsCount).toEqual(1); -// -// service.send({ type: 'UPDATE' }); -// expect(entryActionsCount).toEqual(1); -// expect(invokeCount).toEqual(1); -// expect(invokeDisposeCount).toEqual(0); -// expect(actionsCount).toEqual(2); -// }); -// -// it('should stop a child actor when reaching a final state', () => { -// let actorStopped = false; -// -// const machine = createMachine({ -// id: 'machine', -// invoke: { -// src: fromCallback(() => () => (actorStopped = true)), -// }, -// initial: 'running', -// states: { -// running: { -// on: { -// finished: 'complete', -// }, -// }, -// complete: { type: 'final' }, -// }, -// }); -// -// const service = createActor(machine).start(); -// -// service.send({ -// type: 'finished', -// }); -// -// expect(actorStopped).toBe(true); -// }); -// -// it('child should not invoke an actor when it transitions to an invoking state when it gets stopped by its parent', (done) => { -// let invokeCount = 0; -// -// const child = createMachine({ -// id: 'child', -// initial: 'idle', -// states: { -// idle: { -// invoke: { -// src: fromCallback(({ sendBack }) => { -// invokeCount++; -// -// if (invokeCount > 1) { -// // prevent a potential infinite loop -// throw new Error('This should be impossible.'); -// } -// -// // it's important for this test to send the event back when the parent is *not* currently processing an event -// // this ensures that the parent can process the received event immediately and can stop the child immediately -// setTimeout(() => sendBack({ type: 'STARTED' })); -// }), -// }, -// on: { -// STARTED: 'active', -// }, -// }, -// active: { -// invoke: { -// src: fromCallback(({ sendBack }) => { -// sendBack({ type: 'STOPPED' }); -// }), -// }, -// on: { -// STOPPED: { -// target: 'idle', -// actions: forwardTo(SpecialTargets.Parent), -// }, -// }, -// }, -// }, -// }); -// const parent = createMachine({ -// id: 'parent', -// initial: 'idle', -// states: { -// idle: { -// on: { -// START: 'active', -// }, -// }, -// active: { -// invoke: { src: child }, -// on: { -// STOPPED: 'done', -// }, -// }, -// done: { -// type: 'final', -// }, -// }, -// }); -// -// const service = createActor(parent); -// service.subscribe({ -// complete: () => { -// expect(invokeCount).toBe(1); -// done(); -// }, -// }); -// service.start(); -// -// service.send({ type: 'START' }); -// }); -// }); -// -// type PromiseExecutor = ( -// resolve: (value?: any) => void, -// reject: (reason?: any) => void -// ) => void; -// -// const promiseTypes = [ -// { -// type: 'Promise', -// createPromise(executor: PromiseExecutor): Promise { -// return new Promise(executor); -// }, -// }, -// { -// type: 'PromiseLike', -// createPromise(executor: PromiseExecutor): PromiseLike { -// // Simulate a Promise/A+ thenable / polyfilled Promise. -// function createThenable(promise: Promise): PromiseLike { -// return { -// then(onfulfilled, onrejected) { -// return createThenable(promise.then(onfulfilled, onrejected)); -// }, -// }; -// } -// return createThenable(new Promise(executor)); -// }, -// }, -// ]; -// -// promiseTypes.forEach(({ type, createPromise }) => { -// describe(`with promises (${type})`, () => { -// const invokePromiseMachine = createMachine({ -// types: {} as { context: { id: number; succeed: boolean } }, -// id: 'invokePromise', -// initial: 'pending', -// context: ({ -// input, -// }: { -// input: { id?: number; succeed?: boolean }; -// }) => ({ -// id: 42, -// succeed: true, -// ...input, -// }), -// states: { -// pending: { -// invoke: { -// src: fromPromise(({ input }) => -// createPromise((resolve) => { -// if (input.succeed) { -// resolve(input.id); -// } else { -// throw new Error(`failed on purpose for: ${input.id}`); -// } -// }) -// ), -// input: ({ context }: any) => context, -// onDone: { -// target: 'success', -// guard: ({ context, event }) => { -// return event.output === context.id; -// }, -// }, -// onError: 'failure', -// }, -// }, -// success: { -// type: 'final', -// }, -// failure: { -// type: 'final', -// }, -// }, -// }); -// -// it('should be invoked with a promise factory and resolve through onDone', (done) => { -// const machine = createMachine({ -// initial: 'pending', -// states: { -// pending: { -// invoke: { -// src: fromPromise(() => -// createPromise((resolve) => { -// resolve(); -// }) -// ), -// onDone: 'success', -// }, -// }, -// success: { -// type: 'final', -// }, -// }, -// }); -// const service = createActor(machine); -// service.subscribe({ -// complete: () => { -// done(); -// }, -// }); -// service.start(); -// }); -// -// it('should be invoked with a promise factory and reject with ErrorExecution', (done) => { -// const actor = createActor(invokePromiseMachine, { -// input: { id: 31, succeed: false }, -// }); -// actor.subscribe({ complete: () => done() }); -// actor.start(); -// }); -// -// it('should be invoked with a promise factory and surface any unhandled errors', (done) => { -// const promiseMachine = createMachine({ -// id: 'invokePromise', -// initial: 'pending', -// states: { -// pending: { -// invoke: { -// src: fromPromise(() => -// createPromise(() => { -// throw new Error('test'); -// }) -// ), -// onDone: 'success', -// }, -// }, -// success: { -// type: 'final', -// }, -// }, -// }); -// -// const service = createActor(promiseMachine); -// service.subscribe({ -// error(err) { -// expect((err as any).message).toEqual(expect.stringMatching(/test/)); -// done(); -// }, -// }); -// -// service.start(); -// }); -// -// it('should be invoked with a promise factory and stop on unhandled onError target', (done) => { -// const completeSpy = jest.fn(); -// -// const promiseMachine = createMachine({ -// id: 'invokePromise', -// initial: 'pending', -// states: { -// pending: { -// invoke: { -// src: fromPromise(() => -// createPromise(() => { -// throw new Error('test'); -// }) -// ), -// onDone: 'success', -// }, -// }, -// success: { -// type: 'final', -// }, -// }, -// }); -// -// const actor = createActor(promiseMachine); -// -// actor.subscribe({ -// error: (err) => { -// expect(err).toBeInstanceOf(Error); -// expect((err as any).message).toBe('test'); -// expect(completeSpy).not.toHaveBeenCalled(); -// done(); -// }, -// complete: completeSpy, -// }); -// actor.start(); -// }); -// -// it('should be invoked with a promise factory and resolve through onDone for compound state nodes', (done) => { -// const promiseMachine = createMachine({ -// id: 'promise', -// initial: 'parent', -// states: { -// parent: { -// initial: 'pending', -// states: { -// pending: { -// invoke: { -// src: fromPromise(() => -// createPromise((resolve) => resolve()) -// ), -// onDone: 'success', -// }, -// }, -// success: { -// type: 'final', -// }, -// }, -// onDone: 'success', -// }, -// success: { -// type: 'final', -// }, -// }, -// }); -// const actor = createActor(promiseMachine); -// actor.subscribe({ complete: () => done() }); -// actor.start(); -// }); -// -// it('should be invoked with a promise service and resolve through onDone for compound state nodes', (done) => { -// const promiseMachine = createMachine( -// { -// id: 'promise', -// initial: 'parent', -// states: { -// parent: { -// initial: 'pending', -// states: { -// pending: { -// invoke: { -// src: 'somePromise', -// onDone: 'success', -// }, -// }, -// success: { -// type: 'final', -// }, -// }, -// onDone: 'success', -// }, -// success: { -// type: 'final', -// }, -// }, -// }, -// { -// actors: { -// somePromise: fromPromise(() => -// createPromise((resolve) => resolve()) -// ), -// }, -// } -// ); -// const actor = createActor(promiseMachine); -// actor.subscribe({ complete: () => done() }); -// actor.start(); -// }); -// it('should assign the resolved data when invoked with a promise factory', (done) => { -// const promiseMachine = createMachine({ -// types: {} as { context: { count: number } }, -// id: 'promise', -// context: { count: 0 }, -// initial: 'pending', -// states: { -// pending: { -// invoke: { -// src: fromPromise(() => -// createPromise((resolve) => resolve({ count: 1 })) -// ), -// onDone: { -// target: 'success', -// actions: assign({ -// count: ({ event }) => event.output.count, -// }), -// }, -// }, -// }, -// success: { -// type: 'final', -// }, -// }, -// }); -// -// const actor = createActor(promiseMachine); -// actor.subscribe({ -// complete: () => { -// expect(actor.getSnapshot().context.count).toEqual(1); -// done(); -// }, -// }); -// actor.start(); -// }); -// -// it('should assign the resolved data when invoked with a promise service', (done) => { -// const promiseMachine = createMachine( -// { -// types: {} as { context: { count: number } }, -// id: 'promise', -// context: { count: 0 }, -// initial: 'pending', -// states: { -// pending: { -// invoke: { -// src: 'somePromise', -// onDone: { -// target: 'success', -// actions: assign({ -// count: ({ event }) => event.output.count, -// }), -// }, -// }, -// }, -// success: { -// type: 'final', -// }, -// }, -// }, -// { -// actors: { -// somePromise: fromPromise(() => -// createPromise((resolve) => resolve({ count: 1 })) -// ), -// }, -// } -// ); -// -// const actor = createActor(promiseMachine); -// actor.subscribe({ -// complete: () => { -// expect(actor.getSnapshot().context.count).toEqual(1); -// done(); -// }, -// }); -// actor.start(); -// }); -// -// it('should provide the resolved data when invoked with a promise factory', (done) => { -// let count = 0; -// -// const promiseMachine = createMachine({ -// id: 'promise', -// context: { count: 0 }, -// initial: 'pending', -// states: { -// pending: { -// invoke: { -// src: fromPromise(() => -// createPromise((resolve) => resolve({ count: 1 })) -// ), -// onDone: { -// target: 'success', -// actions: ({ event }) => { -// count = (event.output as any).count; -// }, -// }, -// }, -// }, -// success: { -// type: 'final', -// }, -// }, -// }); -// -// const actor = createActor(promiseMachine); -// actor.subscribe({ -// complete: () => { -// expect(count).toEqual(1); -// done(); -// }, -// }); -// actor.start(); -// }); -// -// it('should provide the resolved data when invoked with a promise service', (done) => { -// let count = 0; -// -// const promiseMachine = createMachine( -// { -// id: 'promise', -// initial: 'pending', -// states: { -// pending: { -// invoke: { -// src: 'somePromise', -// onDone: { -// target: 'success', -// actions: ({ event }) => { -// count = event.output.count; -// }, -// }, -// }, -// }, -// success: { -// type: 'final', -// }, -// }, -// }, -// { -// actors: { -// somePromise: fromPromise(() => -// createPromise((resolve) => resolve({ count: 1 })) -// ), -// }, -// } -// ); -// -// const actor = createActor(promiseMachine); -// actor.subscribe({ -// complete: () => { -// expect(count).toEqual(1); -// done(); -// }, -// }); -// actor.start(); -// }); -// -// it('should be able to specify a Promise as a service', (done) => { -// interface BeginEvent { -// type: 'BEGIN'; -// payload: boolean; -// } -// -// const promiseActor = fromPromise( -// ({ input }: { input: { foo: boolean; event: { payload: any } } }) => { -// return createPromise((resolve, reject) => { -// input.foo && input.event.payload ? resolve() : reject(); -// }); -// } -// ); -// -// const promiseMachine = createMachine( -// { -// id: 'promise', -// types: {} as { -// context: { foo: boolean }; -// events: BeginEvent; -// actors: { -// src: 'somePromise'; -// logic: typeof promiseActor; -// }; -// }, -// initial: 'pending', -// context: { -// foo: true, -// }, -// states: { -// pending: { -// on: { -// BEGIN: 'first', -// }, -// }, -// first: { -// invoke: { -// src: 'somePromise', -// input: ({ context, event }) => ({ -// foo: context.foo, -// event: event, -// }), -// onDone: 'last', -// }, -// }, -// last: { -// type: 'final', -// }, -// }, -// }, -// { -// actors: { -// somePromise: promiseActor, -// }, -// } -// ); -// -// const actor = createActor(promiseMachine); -// actor.subscribe({ complete: () => done() }); -// actor.start(); -// actor.send({ -// type: 'BEGIN', -// payload: true, -// }); -// }); -// -// it('should be able to reuse the same promise logic multiple times and create unique promise for each created actor', (done) => { -// const machine = createMachine( -// { -// types: {} as { -// context: { -// result1: number | null; -// result2: number | null; -// }; -// actors: { -// src: 'getRandomNumber'; -// logic: PromiseActorLogic<{ result: number }>; -// }; -// }, -// context: { -// result1: null, -// result2: null, -// }, -// initial: 'pending', -// states: { -// pending: { -// type: 'parallel', -// states: { -// state1: { -// initial: 'active', -// states: { -// active: { -// invoke: { -// src: 'getRandomNumber', -// onDone: { -// target: 'success', -// // TODO: we get DoneInvokeEvent here, this gets fixed with https://github.com/microsoft/TypeScript/pull/48838 -// actions: assign(({ event }) => ({ -// result1: event.output.result, -// })), -// }, -// }, -// }, -// success: { -// type: 'final', -// }, -// }, -// }, -// state2: { -// initial: 'active', -// states: { -// active: { -// invoke: { -// src: 'getRandomNumber', -// onDone: { -// target: 'success', -// actions: assign(({ event }) => ({ -// result2: event.output.result, -// })), -// }, -// }, -// }, -// success: { -// type: 'final', -// }, -// }, -// }, -// }, -// onDone: 'done', -// }, -// done: { -// type: 'final', -// }, -// }, -// }, -// { -// actors: { -// // it's important for this actor to be reused, this test shouldn't use a factory or anything like that -// getRandomNumber: fromPromise(() => { -// return createPromise((resolve) => -// resolve({ result: Math.random() }) -// ); -// }), -// }, -// } -// ); -// -// const service = createActor(machine); -// service.subscribe({ -// complete: () => { -// const snapshot = service.getSnapshot(); -// expect(typeof snapshot.context.result1).toBe('number'); -// expect(typeof snapshot.context.result2).toBe('number'); -// expect(snapshot.context.result1).not.toBe(snapshot.context.result2); -// done(); -// }, -// }); -// service.start(); -// }); -// -// it('should not emit onSnapshot if stopped', (done) => { -// const machine = createMachine({ -// initial: 'active', -// states: { -// active: { -// invoke: { -// src: fromPromise(() => -// createPromise((res) => { -// setTimeout(() => res(42), 5); -// }) -// ), -// onSnapshot: {}, -// }, -// on: { -// deactivate: 'inactive', -// }, -// }, -// inactive: { -// on: { -// '*': { -// actions: ({ event }) => { -// if (event.snapshot) { -// throw new Error( -// `Received unexpected event: ${event.type}` -// ); -// } -// }, -// }, -// }, -// }, -// }, -// }); -// -// const actor = createActor(machine).start(); -// actor.send({ type: 'deactivate' }); -// -// setTimeout(() => { -// done(); -// }, 10); -// }); -// }); -// }); -// -// describe('with callbacks', () => { -// it('should be able to specify a callback as a service', (done) => { -// interface BeginEvent { -// type: 'BEGIN'; -// payload: boolean; -// } -// interface CallbackEvent { -// type: 'CALLBACK'; -// data: number; -// } -// -// const someCallback = fromCallback( -// ({ -// sendBack, -// input, -// }: { -// sendBack: (event: BeginEvent | CallbackEvent) => void; -// input: { foo: boolean; event: BeginEvent | CallbackEvent }; -// }) => { -// if (input.foo && input.event.type === 'BEGIN') { -// sendBack({ -// type: 'CALLBACK', -// data: 40, -// }); -// sendBack({ -// type: 'CALLBACK', -// data: 41, -// }); -// sendBack({ -// type: 'CALLBACK', -// data: 42, -// }); -// } -// } -// ); -// -// const callbackMachine = createMachine( -// { -// id: 'callback', -// types: {} as { -// context: { foo: boolean }; -// events: BeginEvent | CallbackEvent; -// actors: { -// src: 'someCallback'; -// logic: typeof someCallback; -// }; -// }, -// initial: 'pending', -// context: { -// foo: true, -// }, -// states: { -// pending: { -// on: { -// BEGIN: 'first', -// }, -// }, -// first: { -// invoke: { -// src: 'someCallback', -// input: ({ context, event }) => ({ -// foo: context.foo, -// event: event, -// }), -// }, -// on: { -// CALLBACK: { -// target: 'last', -// guard: ({ event }) => event.data === 42, -// }, -// }, -// }, -// last: { -// type: 'final', -// }, -// }, -// }, -// { -// actors: { -// someCallback, -// }, -// } -// ); -// -// const actor = createActor(callbackMachine); -// actor.subscribe({ complete: () => done() }); -// actor.start(); -// actor.send({ -// type: 'BEGIN', -// payload: true, -// }); -// }); -// -// it('should transition correctly if callback function sends an event', () => { -// const callbackMachine = createMachine( -// { -// id: 'callback', -// initial: 'pending', -// context: { foo: true }, -// states: { -// pending: { -// on: { BEGIN: 'first' }, -// }, -// first: { -// invoke: { -// src: 'someCallback', -// }, -// on: { CALLBACK: 'intermediate' }, -// }, -// intermediate: { -// on: { NEXT: 'last' }, -// }, -// last: { -// type: 'final', -// }, -// }, -// }, -// { -// actors: { -// someCallback: fromCallback(({ sendBack }) => { -// sendBack({ type: 'CALLBACK' }); -// }), -// }, -// } -// ); -// -// const expectedStateValues = ['pending', 'first', 'intermediate']; -// const stateValues: StateValue[] = []; -// const actor = createActor(callbackMachine); -// actor.subscribe((current) => stateValues.push(current.value)); -// actor.start().send({ type: 'BEGIN' }); -// for (let i = 0; i < expectedStateValues.length; i++) { -// expect(stateValues[i]).toEqual(expectedStateValues[i]); -// } -// }); -// -// it('should transition correctly if callback function invoked from start and sends an event', () => { -// const callbackMachine = createMachine( -// { -// id: 'callback', -// initial: 'idle', -// context: { foo: true }, -// states: { -// idle: { -// invoke: { -// src: 'someCallback', -// }, -// on: { CALLBACK: 'intermediate' }, -// }, -// intermediate: { -// on: { NEXT: 'last' }, -// }, -// last: { -// type: 'final', -// }, -// }, -// }, -// { -// actors: { -// someCallback: fromCallback(({ sendBack }) => { -// sendBack({ type: 'CALLBACK' }); -// }), -// }, -// } -// ); -// -// const expectedStateValues = ['idle', 'intermediate']; -// const stateValues: StateValue[] = []; -// const actor = createActor(callbackMachine); -// actor.subscribe((current) => stateValues.push(current.value)); -// actor.start().send({ type: 'BEGIN' }); -// for (let i = 0; i < expectedStateValues.length; i++) { -// expect(stateValues[i]).toEqual(expectedStateValues[i]); -// } -// }); -// -// // tslint:disable-next-line:max-line-length -// it('should transition correctly if transient transition happens before current state invokes callback function and sends an event', () => { -// const callbackMachine = createMachine( -// { -// id: 'callback', -// initial: 'pending', -// context: { foo: true }, -// states: { -// pending: { -// on: { BEGIN: 'first' }, -// }, -// first: { -// always: 'second', -// }, -// second: { -// invoke: { -// src: 'someCallback', -// }, -// on: { CALLBACK: 'third' }, -// }, -// third: { -// on: { NEXT: 'last' }, -// }, -// last: { -// type: 'final', -// }, -// }, -// }, -// { -// actors: { -// someCallback: fromCallback(({ sendBack }) => { -// sendBack({ type: 'CALLBACK' }); -// }), -// }, -// } -// ); -// -// const expectedStateValues = ['pending', 'second', 'third']; -// const stateValues: StateValue[] = []; -// const actor = createActor(callbackMachine); -// actor.subscribe((current) => { -// stateValues.push(current.value); -// }); -// actor.start().send({ type: 'BEGIN' }); -// -// for (let i = 0; i < expectedStateValues.length; i++) { -// expect(stateValues[i]).toEqual(expectedStateValues[i]); -// } -// }); -// -// it('should treat a callback source as an event stream', (done) => { -// const intervalMachine = createMachine({ -// types: {} as { context: { count: number } }, -// id: 'interval', -// initial: 'counting', -// context: { -// count: 0, -// }, -// states: { -// counting: { -// invoke: { -// id: 'intervalService', -// src: fromCallback(({ sendBack }) => { -// const ivl = setInterval(() => { -// sendBack({ type: 'INC' }); -// }, 10); -// -// return () => clearInterval(ivl); -// }), -// }, -// always: { -// target: 'finished', -// guard: ({ context }) => context.count === 3, -// }, -// on: { -// INC: { -// actions: assign({ count: ({ context }) => context.count + 1 }), -// }, -// }, -// }, -// finished: { -// type: 'final', -// }, -// }, -// }); -// const actor = createActor(intervalMachine); -// actor.subscribe({ complete: () => done() }); -// actor.start(); -// }); -// -// it('should dispose of the callback (if disposal function provided)', () => { -// const spy = jest.fn(); -// const intervalMachine = createMachine({ -// id: 'interval', -// initial: 'counting', -// states: { -// counting: { -// invoke: { -// id: 'intervalService', -// src: fromCallback(() => spy), -// }, -// on: { -// NEXT: 'idle', -// }, -// }, -// idle: {}, -// }, -// }); -// const actorRef = createActor(intervalMachine).start(); -// -// actorRef.send({ type: 'NEXT' }); -// -// expect(spy).toHaveBeenCalled(); -// }); -// -// it('callback should be able to receive messages from parent', (done) => { -// const pingPongMachine = createMachine({ -// id: 'ping-pong', -// initial: 'active', -// states: { -// active: { -// invoke: { -// id: 'child', -// src: fromCallback(({ sendBack, receive }) => { -// receive((e) => { -// if (e.type === 'PING') { -// sendBack({ type: 'PONG' }); -// } -// }); -// }), -// }, -// entry: sendTo('child', { type: 'PING' }), -// on: { -// PONG: 'done', -// }, -// }, -// done: { -// type: 'final', -// }, -// }, -// }); -// const actor = createActor(pingPongMachine); -// actor.subscribe({ complete: () => done() }); -// actor.start(); -// }); -// -// it('should call onError upon error (sync)', (done) => { -// const errorMachine = createMachine({ -// id: 'error', -// initial: 'safe', -// states: { -// safe: { -// invoke: { -// src: fromCallback(() => { -// throw new Error('test'); -// }), -// onError: { -// target: 'failed', -// guard: ({ event }) => { -// return ( -// event.error instanceof Error && -// event.error.message === 'test' -// ); -// }, -// }, -// }, -// }, -// failed: { -// type: 'final', -// }, -// }, -// }); -// const actor = createActor(errorMachine); -// actor.subscribe({ complete: () => done() }); -// actor.start(); -// }); -// -// it('should transition correctly upon error (sync)', () => { -// const errorMachine = createMachine({ -// id: 'error', -// initial: 'safe', -// states: { -// safe: { -// invoke: { -// src: fromCallback(() => { -// throw new Error('test'); -// }), -// onError: 'failed', -// }, -// }, -// failed: { -// on: { RETRY: 'safe' }, -// }, -// }, -// }); -// -// const expectedStateValue = 'failed'; -// const service = createActor(errorMachine).start(); -// expect(service.getSnapshot().value).toEqual(expectedStateValue); -// }); -// -// it('should call onError only on the state which has invoked failed service', () => { -// const errorMachine = createMachine({ -// initial: 'start', -// states: { -// start: { -// on: { -// FETCH: 'fetch', -// }, -// }, -// fetch: { -// type: 'parallel', -// states: { -// first: { -// initial: 'waiting', -// states: { -// waiting: { -// invoke: { -// src: fromCallback(() => { -// throw new Error('test'); -// }), -// onError: { -// target: 'failed', -// }, -// }, -// }, -// failed: {}, -// }, -// }, -// second: { -// initial: 'waiting', -// states: { -// waiting: { -// invoke: { -// src: fromCallback(() => { -// // empty -// return () => {}; -// }), -// onError: { -// target: 'failed', -// }, -// }, -// }, -// failed: {}, -// }, -// }, -// }, -// }, -// }, -// }); -// -// const actorRef = createActor(errorMachine).start(); -// actorRef.send({ type: 'FETCH' }); -// -// expect(actorRef.getSnapshot().value).toEqual({ -// fetch: { first: 'failed', second: 'waiting' }, -// }); -// }); -// -// it('should be able to be stringified', () => { -// const machine = createMachine({ -// initial: 'idle', -// states: { -// idle: { -// on: { -// GO_TO_WAITING: 'waiting', -// }, -// }, -// waiting: { -// invoke: { -// src: fromCallback(() => {}), -// }, -// }, -// }, -// }); -// const actorRef = createActor(machine).start(); -// actorRef.send({ type: 'GO_TO_WAITING' }); -// const waitingState = actorRef.getSnapshot(); -// -// expect(() => { -// JSON.stringify(waitingState); -// }).not.toThrow(); -// }); -// -// it('should result in an error notification if callback actor throws when it starts and the error stays unhandled by the machine', () => { -// const errorMachine = createMachine({ -// initial: 'safe', -// states: { -// safe: { -// invoke: { -// src: fromCallback(() => { -// throw new Error('test'); -// }), -// }, -// }, -// failed: { -// type: 'final', -// }, -// }, -// }); -// const spy = jest.fn(); -// -// const actorRef = createActor(errorMachine); -// actorRef.subscribe({ -// error: spy, -// }); -// actorRef.start(); -// expect(spy.mock.calls).toMatchInlineSnapshot(` -// [ -// [ -// [Error: test], -// ], -// ] -// `); -// }); -// -// it('should work with input', (done) => { -// const machine = createMachine({ -// types: {} as { -// context: { foo: string }; -// }, -// initial: 'start', -// context: { foo: 'bar' }, -// states: { -// start: { -// invoke: { -// src: fromCallback(({ input }) => { -// expect(input).toEqual({ foo: 'bar' }); -// done(); -// }), -// input: ({ context }: any) => context, -// }, -// }, -// }, -// }); -// -// createActor(machine).start(); -// }); -// -// it('sub invoke race condition ends on the completed state', () => { -// const anotherChildMachine = createMachine({ -// id: 'child', -// initial: 'start', -// states: { -// start: { -// on: { STOP: 'end' }, -// }, -// end: { -// type: 'final', -// }, -// }, -// }); -// -// const anotherParentMachine = createMachine({ -// id: 'parent', -// initial: 'begin', -// states: { -// begin: { -// invoke: { -// src: anotherChildMachine, -// id: 'invoked.child', -// onDone: 'completed', -// }, -// on: { -// STOPCHILD: { -// actions: sendTo('invoked.child', { type: 'STOP' }), -// }, -// }, -// }, -// completed: { -// type: 'final', -// }, -// }, -// }); -// -// const actorRef = createActor(anotherParentMachine).start(); -// actorRef.send({ type: 'STOPCHILD' }); -// -// expect(actorRef.getSnapshot().value).toEqual('completed'); -// }); -// }); -// -// describe('invoke input', () => { -// it('should provide input to an actor creator', (done) => { -// const machine = createMachine( -// { -// types: {} as { -// context: { count: number }; -// actors: { -// src: 'stringService'; -// logic: PromiseActorLogic< -// boolean, -// { -// staticVal: string; -// newCount: number; -// } -// >; -// }; -// }, -// initial: 'pending', -// context: { -// count: 42, -// }, -// states: { -// pending: { -// invoke: { -// src: 'stringService', -// input: ({ context }) => ({ -// staticVal: 'hello', -// newCount: context.count * 2, -// }), -// onDone: 'success', -// }, -// }, -// success: { -// type: 'final', -// }, -// }, -// }, -// { -// actors: { -// stringService: fromPromise(({ input }) => { -// expect(input).toEqual({ newCount: 84, staticVal: 'hello' }); -// -// return Promise.resolve(true); -// }), -// }, -// } -// ); -// -// const service = createActor(machine); -// service.subscribe({ -// complete: () => { -// done(); -// }, -// }); -// -// service.start(); -// }); -// -// it('should provide self to input mapper', (done) => { -// const machine = createMachine({ -// invoke: { -// src: fromCallback(({ input }) => { -// expect(input.responder.send).toBeDefined(); -// done(); -// }), -// input: ({ self }) => ({ -// responder: self, -// }), -// }, -// }); -// -// createActor(machine).start(); -// }); -// }); -// }); +import { + PromiseActorLogic, + SpecialTargets, + StateValue, + assign, + createActor, + createMachine, + forwardTo, + fromCallback, + fromPromise, + raise, + sendParent, + sendTo, +} from 'xstate'; + +const user = { name: 'David' }; + +describe('invoke', () => { + it('should be possible to send immediate events to initially invoked actors', () => { + const child = createMachine({ + on: { + PING: { + actions: sendParent({ type: 'PONG' }), + }, + }, + }); + + const machine = createMachine({ + initial: 'waiting', + states: { + waiting: { + invoke: { + id: 'ponger', + src: child, + }, + // entry: sendTo('ponger', { type: 'PING' }), + on: { + PONG: 'done', + sendPing: { + actions: sendTo('ponger', { type: 'PING' }), + }, + }, + }, + done: { + type: 'final', + }, + }, + }); + + const service = createActor(machine).start(); + service.send({ type: 'sendPing' }); + + expect(service.getSnapshot().value).toBe('done'); + }); + + it('child can immediately respond to the parent with multiple events', () => { + const childMachine = createMachine({ + types: {} as { + events: { type: 'FORWARD_DEC' }; + }, + id: 'child', + initial: 'init', + states: { + init: { + on: { + FORWARD_DEC: { + actions: [ + sendParent({ type: 'DEC' }), + sendParent({ type: 'DEC' }), + sendParent({ type: 'DEC' }), + ], + }, + }, + }, + }, + }); + + const someParentMachine = createMachine( + { + id: 'parent', + types: {} as { + context: { count: number }; + actors: { + src: 'child'; + id: 'someService'; + logic: typeof childMachine; + }; + }, + context: { count: 0 }, + initial: 'start', + states: { + start: { + invoke: { + src: 'child', + id: 'someService', + }, + always: { + target: 'stop', + guard: ({ context }) => context.count === -3, + }, + on: { + DEC: { + actions: assign({ count: ({ context }) => context.count - 1 }), + }, + FORWARD_DEC: { + actions: sendTo('someService', { type: 'FORWARD_DEC' }), + }, + }, + }, + stop: { + type: 'final', + }, + }, + }, + { + actors: { + child: childMachine, + }, + } + ); + + const actorRef = createActor(someParentMachine).start(); + actorRef.send({ type: 'FORWARD_DEC' }); + + // 1. The 'parent' machine will not do anything (inert transition) + // 2. The 'FORWARD_DEC' event will be "forwarded" to the child machine + // 3. On the child machine, the 'FORWARD_DEC' event sends the 'DEC' action to the parent thrice + // 4. The context of the 'parent' machine will be updated from 0 to -3 + expect(actorRef.getSnapshot().context).toEqual({ count: -3 }); + }); + + it('should start services (explicit machine, invoke = config)', (done) => { + const childMachine = createMachine({ + id: 'fetch', + types: {} as { + context: { userId: string | undefined; user?: typeof user | undefined }; + events: { + type: 'RESOLVE'; + user: typeof user; + }; + input: { userId: string }; + }, + context: ({ input }) => ({ + userId: input.userId, + }), + initial: 'pending', + states: { + pending: { + entry: raise({ type: 'RESOLVE', user }), + on: { + RESOLVE: { + target: 'success', + guard: ({ context }) => { + return context.userId !== undefined; + }, + }, + }, + }, + success: { + type: 'final', + entry: assign({ + user: ({ event }) => event.user, + }), + }, + failure: { + entry: sendParent({ type: 'REJECT' }), + }, + }, + output: ({ context }) => ({ user: context.user }), + }); + + const machine = createMachine({ + types: {} as { + context: { + selectedUserId: string; + user?: typeof user; + }; + }, + id: 'fetcher', + initial: 'idle', + context: { + selectedUserId: '42', + user: undefined, + }, + states: { + idle: { + on: { + GO_TO_WAITING: 'waiting', + }, + }, + waiting: { + invoke: { + src: childMachine, + input: ({ context }: any) => ({ + userId: context.selectedUserId, + }), + onDone: { + target: 'received', + guard: ({ event }) => { + // Should receive { user: { name: 'David' } } as event data + return (event.output as any).user.name === 'David'; + }, + }, + }, + }, + received: { + type: 'final', + }, + }, + }); + + const actor = createActor(machine); + actor.subscribe({ + complete: () => { + done(); + }, + }); + actor.start(); + actor.send({ type: 'GO_TO_WAITING' }); + }); + + it('should start services (explicit machine, invoke = machine)', (done) => { + const childMachine = createMachine({ + types: {} as { + events: { type: 'RESOLVE' }; + input: { userId: string }; + }, + initial: 'pending', + states: { + pending: { + entry: raise({ type: 'RESOLVE' }), + on: { + RESOLVE: { + target: 'success', + }, + }, + }, + success: { + type: 'final', + }, + }, + }); + + const machine = createMachine({ + initial: 'idle', + states: { + idle: { + on: { + GO_TO_WAITING: 'waiting', + }, + }, + waiting: { + invoke: { + src: childMachine, + onDone: 'received', + }, + }, + received: { + type: 'final', + }, + }, + }); + const actor = createActor(machine); + actor.subscribe({ + complete: () => { + done(); + }, + }); + actor.start(); + actor.send({ type: 'GO_TO_WAITING' }); + }); + + it('should start services (machine as invoke config)', (done) => { + const machineInvokeMachine = createMachine({ + types: {} as { + events: { + type: 'SUCCESS'; + data: number; + }; + }, + id: 'machine-invoke', + initial: 'pending', + states: { + pending: { + invoke: { + src: createMachine({ + id: 'child', + initial: 'sending', + states: { + sending: { + entry: sendParent({ type: 'SUCCESS', data: 42 }), + }, + }, + }), + }, + on: { + SUCCESS: { + target: 'success', + guard: ({ event }) => { + return event.data === 42; + }, + }, + }, + }, + success: { + type: 'final', + }, + }, + }); + const actor = createActor(machineInvokeMachine); + actor.subscribe({ complete: () => done() }); + actor.start(); + }); + + it('should start deeply nested service (machine as invoke config)', (done) => { + const machineInvokeMachine = createMachine({ + types: {} as { + events: { + type: 'SUCCESS'; + data: number; + }; + }, + id: 'parent', + initial: 'a', + states: { + a: { + initial: 'b', + states: { + b: { + invoke: { + src: createMachine({ + id: 'child', + initial: 'sending', + states: { + sending: { + entry: sendParent({ type: 'SUCCESS', data: 42 }), + }, + }, + }), + }, + }, + }, + }, + success: { + id: 'success', + type: 'final', + }, + }, + on: { + SUCCESS: { + target: '.success', + guard: ({ event }) => { + return event.data === 42; + }, + }, + }, + }); + const actor = createActor(machineInvokeMachine); + actor.subscribe({ complete: () => done() }); + actor.start(); + }); + + it('should use the service overwritten by .provide(...)', (done) => { + const childMachine = createMachine({ + id: 'child', + initial: 'init', + states: { + init: {}, + }, + }); + + const someParentMachine = createMachine( + { + id: 'parent', + types: {} as { + context: { count: number }; + actors: { + src: 'child'; + id: 'someService'; + logic: typeof childMachine; + }; + }, + context: { count: 0 }, + initial: 'start', + states: { + start: { + invoke: { + src: 'child', + id: 'someService', + }, + on: { + STOP: 'stop', + }, + }, + stop: { + type: 'final', + }, + }, + }, + { + actors: { + child: childMachine, + }, + } + ); + + const actor = createActor( + someParentMachine.provide({ + actors: { + child: createMachine({ + id: 'child', + initial: 'init', + states: { + init: { + entry: [sendParent({ type: 'STOP' })], + }, + }, + }), + }, + }) + ); + actor.subscribe({ + complete: () => { + done(); + }, + }); + actor.start(); + }); + + describe('parent to child', () => { + const subMachine = createMachine({ + id: 'child', + initial: 'one', + states: { + one: { + on: { NEXT: 'two' }, + }, + two: { + entry: sendParent({ type: 'NEXT' }), + }, + }, + }); + + it('should communicate with the child machine (invoke on machine)', (done) => { + const mainMachine = createMachine({ + id: 'parent', + initial: 'one', + invoke: { + id: 'foo-child', + src: subMachine, + }, + states: { + one: { + entry: sendTo('foo-child', { type: 'NEXT' }), + on: { NEXT: 'two' }, + }, + two: { + type: 'final', + }, + }, + }); + + const actor = createActor(mainMachine); + actor.subscribe({ + complete: () => { + done(); + }, + }); + actor.start(); + }); + + it('should communicate with the child machine (invoke on state)', (done) => { + const mainMachine = createMachine({ + id: 'parent', + initial: 'one', + states: { + one: { + invoke: { + id: 'foo-child', + src: subMachine, + }, + entry: sendTo('foo-child', { type: 'NEXT' }), + on: { NEXT: 'two' }, + }, + two: { + type: 'final', + }, + }, + }); + + const actor = createActor(mainMachine); + actor.subscribe({ + complete: () => { + done(); + }, + }); + actor.start(); + }); + + it('should transition correctly if child invocation causes it to directly go to final state', () => { + const doneSubMachine = createMachine({ + id: 'child', + initial: 'one', + states: { + one: { + on: { NEXT: 'two' }, + }, + two: { + type: 'final', + }, + }, + }); + + const mainMachine = createMachine({ + id: 'parent', + initial: 'one', + states: { + one: { + invoke: { + id: 'foo-child', + src: doneSubMachine, + onDone: 'two', + }, + entry: sendTo('foo-child', { type: 'NEXT' }), + }, + two: { + on: { NEXT: 'three' }, + }, + three: { + type: 'final', + }, + }, + }); + + const actor = createActor(mainMachine).start(); + + expect(actor.getSnapshot().value).toBe('two'); + }); + + it('should work with invocations defined in orthogonal state nodes', (done) => { + const pongMachine = createMachine({ + id: 'pong', + initial: 'active', + states: { + active: { + type: 'final', + }, + }, + output: { secret: 'pingpong' }, + }); + + const pingMachine = createMachine({ + id: 'ping', + type: 'parallel', + states: { + one: { + initial: 'active', + states: { + active: { + invoke: { + id: 'pong', + src: pongMachine, + onDone: { + target: 'success', + guard: ({ event }) => event.output.secret === 'pingpong', + }, + }, + }, + success: { + type: 'final', + }, + }, + }, + }, + }); + + const actor = createActor(pingMachine); + actor.subscribe({ + complete: () => { + done(); + }, + }); + actor.start(); + }); + + it('should not reinvoke root-level invocations on root non-reentering transitions', () => { + // https://github.com/statelyai/xstate/issues/2147 + + let invokeCount = 0; + let invokeDisposeCount = 0; + let actionsCount = 0; + let entryActionsCount = 0; + + const machine = createMachine({ + invoke: { + src: fromCallback(() => { + invokeCount++; + + return () => { + invokeDisposeCount++; + }; + }), + }, + entry: () => entryActionsCount++, + on: { + UPDATE: { + actions: () => { + actionsCount++; + }, + }, + }, + }); + + const service = createActor(machine).start(); + expect(entryActionsCount).toEqual(1); + expect(invokeCount).toEqual(1); + expect(invokeDisposeCount).toEqual(0); + expect(actionsCount).toEqual(0); + + service.send({ type: 'UPDATE' }); + expect(entryActionsCount).toEqual(1); + expect(invokeCount).toEqual(1); + expect(invokeDisposeCount).toEqual(0); + expect(actionsCount).toEqual(1); + + service.send({ type: 'UPDATE' }); + expect(entryActionsCount).toEqual(1); + expect(invokeCount).toEqual(1); + expect(invokeDisposeCount).toEqual(0); + expect(actionsCount).toEqual(2); + }); + + it('should stop a child actor when reaching a final state', () => { + let actorStopped = false; + + const machine = createMachine({ + id: 'machine', + invoke: { + src: fromCallback(() => () => (actorStopped = true)), + }, + initial: 'running', + states: { + running: { + on: { + finished: 'complete', + }, + }, + complete: { type: 'final' }, + }, + }); + + const service = createActor(machine).start(); + + service.send({ + type: 'finished', + }); + + expect(actorStopped).toBe(true); + }); + + it('child should not invoke an actor when it transitions to an invoking state when it gets stopped by its parent', (done) => { + let invokeCount = 0; + + const child = createMachine({ + id: 'child', + initial: 'idle', + states: { + idle: { + invoke: { + src: fromCallback(({ sendBack }) => { + invokeCount++; + + if (invokeCount > 1) { + // prevent a potential infinite loop + throw new Error('This should be impossible.'); + } + + // it's important for this test to send the event back when the parent is *not* currently processing an event + // this ensures that the parent can process the received event immediately and can stop the child immediately + setTimeout(() => sendBack({ type: 'STARTED' })); + }), + }, + on: { + STARTED: 'active', + }, + }, + active: { + invoke: { + src: fromCallback(({ sendBack }) => { + sendBack({ type: 'STOPPED' }); + }), + }, + on: { + STOPPED: { + target: 'idle', + actions: forwardTo(SpecialTargets.Parent), + }, + }, + }, + }, + }); + const parent = createMachine({ + id: 'parent', + initial: 'idle', + states: { + idle: { + on: { + START: 'active', + }, + }, + active: { + invoke: { src: child }, + on: { + STOPPED: 'done', + }, + }, + done: { + type: 'final', + }, + }, + }); + + const service = createActor(parent); + service.subscribe({ + complete: () => { + expect(invokeCount).toBe(1); + done(); + }, + }); + service.start(); + + service.send({ type: 'START' }); + }); + }); + + type PromiseExecutor = ( + resolve: (value?: any) => void, + reject: (reason?: any) => void + ) => void; + + const promiseTypes = [ + { + type: 'Promise', + createPromise(executor: PromiseExecutor): Promise { + return new Promise(executor); + }, + }, + { + type: 'PromiseLike', + createPromise(executor: PromiseExecutor): PromiseLike { + // Simulate a Promise/A+ thenable / polyfilled Promise. + function createThenable(promise: Promise): PromiseLike { + return { + then(onfulfilled, onrejected) { + return createThenable(promise.then(onfulfilled, onrejected)); + }, + }; + } + return createThenable(new Promise(executor)); + }, + }, + ]; + + promiseTypes.forEach(({ type, createPromise }) => { + describe(`with promises (${type})`, () => { + const invokePromiseMachine = createMachine({ + types: {} as { context: { id: number; succeed: boolean } }, + id: 'invokePromise', + initial: 'pending', + context: ({ + input, + }: { + input: { id?: number; succeed?: boolean }; + }) => ({ + id: 42, + succeed: true, + ...input, + }), + states: { + pending: { + invoke: { + src: fromPromise(({ input }) => + createPromise((resolve) => { + if (input.succeed) { + resolve(input.id); + } else { + throw new Error(`failed on purpose for: ${input.id}`); + } + }) + ), + input: ({ context }: any) => context, + onDone: { + target: 'success', + guard: ({ context, event }) => { + return event.output === context.id; + }, + }, + onError: 'failure', + }, + }, + success: { + type: 'final', + }, + failure: { + type: 'final', + }, + }, + }); + + it('should be invoked with a promise factory and resolve through onDone', (done) => { + const machine = createMachine({ + initial: 'pending', + states: { + pending: { + invoke: { + src: fromPromise(() => + createPromise((resolve) => { + resolve(); + }) + ), + onDone: 'success', + }, + }, + success: { + type: 'final', + }, + }, + }); + const service = createActor(machine); + service.subscribe({ + complete: () => { + done(); + }, + }); + service.start(); + }); + + it('should be invoked with a promise factory and reject with ErrorExecution', (done) => { + const actor = createActor(invokePromiseMachine, { + input: { id: 31, succeed: false }, + }); + actor.subscribe({ complete: () => done() }); + actor.start(); + }); + + it('should be invoked with a promise factory and surface any unhandled errors', (done) => { + const promiseMachine = createMachine({ + id: 'invokePromise', + initial: 'pending', + states: { + pending: { + invoke: { + src: fromPromise(() => + createPromise(() => { + throw new Error('test'); + }) + ), + onDone: 'success', + }, + }, + success: { + type: 'final', + }, + }, + }); + + const service = createActor(promiseMachine); + service.subscribe({ + error(err) { + expect((err as any).message).toEqual(expect.stringMatching(/test/)); + done(); + }, + }); + + service.start(); + }); + + it('should be invoked with a promise factory and stop on unhandled onError target', (done) => { + const completeSpy = jest.fn(); + + const promiseMachine = createMachine({ + id: 'invokePromise', + initial: 'pending', + states: { + pending: { + invoke: { + src: fromPromise(() => + createPromise(() => { + throw new Error('test'); + }) + ), + onDone: 'success', + }, + }, + success: { + type: 'final', + }, + }, + }); + + const actor = createActor(promiseMachine); + + actor.subscribe({ + error: (err) => { + expect(err).toBeInstanceOf(Error); + expect((err as any).message).toBe('test'); + expect(completeSpy).not.toHaveBeenCalled(); + done(); + }, + complete: completeSpy, + }); + actor.start(); + }); + + it('should be invoked with a promise factory and resolve through onDone for compound state nodes', (done) => { + const promiseMachine = createMachine({ + id: 'promise', + initial: 'parent', + states: { + parent: { + initial: 'pending', + states: { + pending: { + invoke: { + src: fromPromise(() => + createPromise((resolve) => resolve()) + ), + onDone: 'success', + }, + }, + success: { + type: 'final', + }, + }, + onDone: 'success', + }, + success: { + type: 'final', + }, + }, + }); + const actor = createActor(promiseMachine); + actor.subscribe({ complete: () => done() }); + actor.start(); + }); + + it('should be invoked with a promise service and resolve through onDone for compound state nodes', (done) => { + const promiseMachine = createMachine( + { + id: 'promise', + initial: 'parent', + states: { + parent: { + initial: 'pending', + states: { + pending: { + invoke: { + src: 'somePromise', + onDone: 'success', + }, + }, + success: { + type: 'final', + }, + }, + onDone: 'success', + }, + success: { + type: 'final', + }, + }, + }, + { + actors: { + somePromise: fromPromise(() => + createPromise((resolve) => resolve()) + ), + }, + } + ); + const actor = createActor(promiseMachine); + actor.subscribe({ complete: () => done() }); + actor.start(); + }); + it('should assign the resolved data when invoked with a promise factory', (done) => { + const promiseMachine = createMachine({ + types: {} as { context: { count: number } }, + id: 'promise', + context: { count: 0 }, + initial: 'pending', + states: { + pending: { + invoke: { + src: fromPromise(() => + createPromise((resolve) => resolve({ count: 1 })) + ), + onDone: { + target: 'success', + actions: assign({ + count: ({ event }) => event.output.count, + }), + }, + }, + }, + success: { + type: 'final', + }, + }, + }); + + const actor = createActor(promiseMachine); + actor.subscribe({ + complete: () => { + expect(actor.getSnapshot().context.count).toEqual(1); + done(); + }, + }); + actor.start(); + }); + + it('should assign the resolved data when invoked with a promise service', (done) => { + const promiseMachine = createMachine( + { + types: {} as { context: { count: number } }, + id: 'promise', + context: { count: 0 }, + initial: 'pending', + states: { + pending: { + invoke: { + src: 'somePromise', + onDone: { + target: 'success', + actions: assign({ + count: ({ event }) => event.output.count, + }), + }, + }, + }, + success: { + type: 'final', + }, + }, + }, + { + actors: { + somePromise: fromPromise(() => + createPromise((resolve) => resolve({ count: 1 })) + ), + }, + } + ); + + const actor = createActor(promiseMachine); + actor.subscribe({ + complete: () => { + expect(actor.getSnapshot().context.count).toEqual(1); + done(); + }, + }); + actor.start(); + }); + + it('should provide the resolved data when invoked with a promise factory', (done) => { + let count = 0; + + const promiseMachine = createMachine({ + id: 'promise', + context: { count: 0 }, + initial: 'pending', + states: { + pending: { + invoke: { + src: fromPromise(() => + createPromise((resolve) => resolve({ count: 1 })) + ), + onDone: { + target: 'success', + actions: ({ event }) => { + count = (event.output as any).count; + }, + }, + }, + }, + success: { + type: 'final', + }, + }, + }); + + const actor = createActor(promiseMachine); + actor.subscribe({ + complete: () => { + expect(count).toEqual(1); + done(); + }, + }); + actor.start(); + }); + + it('should provide the resolved data when invoked with a promise service', (done) => { + let count = 0; + + const promiseMachine = createMachine( + { + id: 'promise', + initial: 'pending', + states: { + pending: { + invoke: { + src: 'somePromise', + onDone: { + target: 'success', + actions: ({ event }) => { + count = event.output.count; + }, + }, + }, + }, + success: { + type: 'final', + }, + }, + }, + { + actors: { + somePromise: fromPromise(() => + createPromise((resolve) => resolve({ count: 1 })) + ), + }, + } + ); + + const actor = createActor(promiseMachine); + actor.subscribe({ + complete: () => { + expect(count).toEqual(1); + done(); + }, + }); + actor.start(); + }); + + it('should be able to specify a Promise as a service', (done) => { + interface BeginEvent { + type: 'BEGIN'; + payload: boolean; + } + + const promiseActor = fromPromise( + ({ input }: { input: { foo: boolean; event: { payload: any } } }) => { + return createPromise((resolve, reject) => { + input.foo && input.event.payload ? resolve() : reject(); + }); + } + ); + + const promiseMachine = createMachine( + { + id: 'promise', + types: {} as { + context: { foo: boolean }; + events: BeginEvent; + actors: { + src: 'somePromise'; + logic: typeof promiseActor; + }; + }, + initial: 'pending', + context: { + foo: true, + }, + states: { + pending: { + on: { + BEGIN: 'first', + }, + }, + first: { + invoke: { + src: 'somePromise', + input: ({ context, event }) => ({ + foo: context.foo, + event: event, + }), + onDone: 'last', + }, + }, + last: { + type: 'final', + }, + }, + }, + { + actors: { + somePromise: promiseActor, + }, + } + ); + + const actor = createActor(promiseMachine); + actor.subscribe({ complete: () => done() }); + actor.start(); + actor.send({ + type: 'BEGIN', + payload: true, + }); + }); + + it('should be able to reuse the same promise logic multiple times and create unique promise for each created actor', (done) => { + const machine = createMachine( + { + types: {} as { + context: { + result1: number | null; + result2: number | null; + }; + actors: { + src: 'getRandomNumber'; + logic: PromiseActorLogic<{ result: number }>; + }; + }, + context: { + result1: null, + result2: null, + }, + initial: 'pending', + states: { + pending: { + type: 'parallel', + states: { + state1: { + initial: 'active', + states: { + active: { + invoke: { + src: 'getRandomNumber', + onDone: { + target: 'success', + // TODO: we get DoneInvokeEvent here, this gets fixed with https://github.com/microsoft/TypeScript/pull/48838 + actions: assign(({ event }) => ({ + result1: event.output.result, + })), + }, + }, + }, + success: { + type: 'final', + }, + }, + }, + state2: { + initial: 'active', + states: { + active: { + invoke: { + src: 'getRandomNumber', + onDone: { + target: 'success', + actions: assign(({ event }) => ({ + result2: event.output.result, + })), + }, + }, + }, + success: { + type: 'final', + }, + }, + }, + }, + onDone: 'done', + }, + done: { + type: 'final', + }, + }, + }, + { + actors: { + // it's important for this actor to be reused, this test shouldn't use a factory or anything like that + getRandomNumber: fromPromise(() => { + return createPromise((resolve) => + resolve({ result: Math.random() }) + ); + }), + }, + } + ); + + const service = createActor(machine); + service.subscribe({ + complete: () => { + const snapshot = service.getSnapshot(); + expect(typeof snapshot.context.result1).toBe('number'); + expect(typeof snapshot.context.result2).toBe('number'); + expect(snapshot.context.result1).not.toBe(snapshot.context.result2); + done(); + }, + }); + service.start(); + }); + + it('should not emit onSnapshot if stopped', (done) => { + const machine = createMachine({ + initial: 'active', + states: { + active: { + invoke: { + src: fromPromise(() => + createPromise((res) => { + setTimeout(() => res(42), 5); + }) + ), + onSnapshot: {}, + }, + on: { + deactivate: 'inactive', + }, + }, + inactive: { + on: { + '*': { + actions: ({ event }) => { + if (event.snapshot) { + throw new Error( + `Received unexpected event: ${event.type}` + ); + } + }, + }, + }, + }, + }, + }); + + const actor = createActor(machine).start(); + actor.send({ type: 'deactivate' }); + + setTimeout(() => { + done(); + }, 10); + }); + }); + }); + + describe('with callbacks', () => { + it('should be able to specify a callback as a service', (done) => { + interface BeginEvent { + type: 'BEGIN'; + payload: boolean; + } + interface CallbackEvent { + type: 'CALLBACK'; + data: number; + } + + const someCallback = fromCallback( + ({ + sendBack, + input, + }: { + sendBack: (event: BeginEvent | CallbackEvent) => void; + input: { foo: boolean; event: BeginEvent | CallbackEvent }; + }) => { + if (input.foo && input.event.type === 'BEGIN') { + sendBack({ + type: 'CALLBACK', + data: 40, + }); + sendBack({ + type: 'CALLBACK', + data: 41, + }); + sendBack({ + type: 'CALLBACK', + data: 42, + }); + } + } + ); + + const callbackMachine = createMachine( + { + id: 'callback', + types: {} as { + context: { foo: boolean }; + events: BeginEvent | CallbackEvent; + actors: { + src: 'someCallback'; + logic: typeof someCallback; + }; + }, + initial: 'pending', + context: { + foo: true, + }, + states: { + pending: { + on: { + BEGIN: 'first', + }, + }, + first: { + invoke: { + src: 'someCallback', + input: ({ context, event }) => ({ + foo: context.foo, + event: event, + }), + }, + on: { + CALLBACK: { + target: 'last', + guard: ({ event }) => event.data === 42, + }, + }, + }, + last: { + type: 'final', + }, + }, + }, + { + actors: { + someCallback, + }, + } + ); + + const actor = createActor(callbackMachine); + actor.subscribe({ complete: () => done() }); + actor.start(); + actor.send({ + type: 'BEGIN', + payload: true, + }); + }); + + it('should transition correctly if callback function sends an event', () => { + const callbackMachine = createMachine( + { + id: 'callback', + initial: 'pending', + context: { foo: true }, + states: { + pending: { + on: { BEGIN: 'first' }, + }, + first: { + invoke: { + src: 'someCallback', + }, + on: { CALLBACK: 'intermediate' }, + }, + intermediate: { + on: { NEXT: 'last' }, + }, + last: { + type: 'final', + }, + }, + }, + { + actors: { + someCallback: fromCallback(({ sendBack }) => { + sendBack({ type: 'CALLBACK' }); + }), + }, + } + ); + + const expectedStateValues = ['pending', 'first', 'intermediate']; + const stateValues: StateValue[] = []; + const actor = createActor(callbackMachine); + actor.subscribe((current) => stateValues.push(current.value)); + actor.start().send({ type: 'BEGIN' }); + for (let i = 0; i < expectedStateValues.length; i++) { + expect(stateValues[i]).toEqual(expectedStateValues[i]); + } + }); + + it('should transition correctly if callback function invoked from start and sends an event', () => { + const callbackMachine = createMachine( + { + id: 'callback', + initial: 'idle', + context: { foo: true }, + states: { + idle: { + invoke: { + src: 'someCallback', + }, + on: { CALLBACK: 'intermediate' }, + }, + intermediate: { + on: { NEXT: 'last' }, + }, + last: { + type: 'final', + }, + }, + }, + { + actors: { + someCallback: fromCallback(({ sendBack }) => { + sendBack({ type: 'CALLBACK' }); + }), + }, + } + ); + + const expectedStateValues = ['idle', 'intermediate']; + const stateValues: StateValue[] = []; + const actor = createActor(callbackMachine); + actor.subscribe((current) => stateValues.push(current.value)); + actor.start().send({ type: 'BEGIN' }); + for (let i = 0; i < expectedStateValues.length; i++) { + expect(stateValues[i]).toEqual(expectedStateValues[i]); + } + }); + + // tslint:disable-next-line:max-line-length + it('should transition correctly if transient transition happens before current state invokes callback function and sends an event', () => { + const callbackMachine = createMachine( + { + id: 'callback', + initial: 'pending', + context: { foo: true }, + states: { + pending: { + on: { BEGIN: 'first' }, + }, + first: { + always: 'second', + }, + second: { + invoke: { + src: 'someCallback', + }, + on: { CALLBACK: 'third' }, + }, + third: { + on: { NEXT: 'last' }, + }, + last: { + type: 'final', + }, + }, + }, + { + actors: { + someCallback: fromCallback(({ sendBack }) => { + sendBack({ type: 'CALLBACK' }); + }), + }, + } + ); + + const expectedStateValues = ['pending', 'second', 'third']; + const stateValues: StateValue[] = []; + const actor = createActor(callbackMachine); + actor.subscribe((current) => { + stateValues.push(current.value); + }); + actor.start().send({ type: 'BEGIN' }); + + for (let i = 0; i < expectedStateValues.length; i++) { + expect(stateValues[i]).toEqual(expectedStateValues[i]); + } + }); + + it('should treat a callback source as an event stream', (done) => { + const intervalMachine = createMachine({ + types: {} as { context: { count: number } }, + id: 'interval', + initial: 'counting', + context: { + count: 0, + }, + states: { + counting: { + invoke: { + id: 'intervalService', + src: fromCallback(({ sendBack }) => { + const ivl = setInterval(() => { + sendBack({ type: 'INC' }); + }, 10); + + return () => clearInterval(ivl); + }), + }, + always: { + target: 'finished', + guard: ({ context }) => context.count === 3, + }, + on: { + INC: { + actions: assign({ count: ({ context }) => context.count + 1 }), + }, + }, + }, + finished: { + type: 'final', + }, + }, + }); + const actor = createActor(intervalMachine); + actor.subscribe({ complete: () => done() }); + actor.start(); + }); + + it('should dispose of the callback (if disposal function provided)', () => { + const spy = jest.fn(); + const intervalMachine = createMachine({ + id: 'interval', + initial: 'counting', + states: { + counting: { + invoke: { + id: 'intervalService', + src: fromCallback(() => spy), + }, + on: { + NEXT: 'idle', + }, + }, + idle: {}, + }, + }); + const actorRef = createActor(intervalMachine).start(); + + actorRef.send({ type: 'NEXT' }); + + expect(spy).toHaveBeenCalled(); + }); + + it('callback should be able to receive messages from parent', (done) => { + const pingPongMachine = createMachine({ + id: 'ping-pong', + initial: 'active', + states: { + active: { + invoke: { + id: 'child', + src: fromCallback(({ sendBack, receive }) => { + receive((e) => { + if (e.type === 'PING') { + sendBack({ type: 'PONG' }); + } + }); + }), + }, + entry: sendTo('child', { type: 'PING' }), + on: { + PONG: 'done', + }, + }, + done: { + type: 'final', + }, + }, + }); + const actor = createActor(pingPongMachine); + actor.subscribe({ complete: () => done() }); + actor.start(); + }); + + it('should call onError upon error (sync)', (done) => { + const errorMachine = createMachine({ + id: 'error', + initial: 'safe', + states: { + safe: { + invoke: { + src: fromCallback(() => { + throw new Error('test'); + }), + onError: { + target: 'failed', + guard: ({ event }) => { + return ( + event.error instanceof Error && + event.error.message === 'test' + ); + }, + }, + }, + }, + failed: { + type: 'final', + }, + }, + }); + const actor = createActor(errorMachine); + actor.subscribe({ complete: () => done() }); + actor.start(); + }); + + it('should transition correctly upon error (sync)', () => { + const errorMachine = createMachine({ + id: 'error', + initial: 'safe', + states: { + safe: { + invoke: { + src: fromCallback(() => { + throw new Error('test'); + }), + onError: 'failed', + }, + }, + failed: { + on: { RETRY: 'safe' }, + }, + }, + }); + + const expectedStateValue = 'failed'; + const service = createActor(errorMachine).start(); + expect(service.getSnapshot().value).toEqual(expectedStateValue); + }); + + it('should call onError only on the state which has invoked failed service', () => { + const errorMachine = createMachine({ + initial: 'start', + states: { + start: { + on: { + FETCH: 'fetch', + }, + }, + fetch: { + type: 'parallel', + states: { + first: { + initial: 'waiting', + states: { + waiting: { + invoke: { + src: fromCallback(() => { + throw new Error('test'); + }), + onError: { + target: 'failed', + }, + }, + }, + failed: {}, + }, + }, + second: { + initial: 'waiting', + states: { + waiting: { + invoke: { + src: fromCallback(() => { + // empty + return () => {}; + }), + onError: { + target: 'failed', + }, + }, + }, + failed: {}, + }, + }, + }, + }, + }, + }); + + const actorRef = createActor(errorMachine).start(); + actorRef.send({ type: 'FETCH' }); + + expect(actorRef.getSnapshot().value).toEqual({ + fetch: { first: 'failed', second: 'waiting' }, + }); + }); + + it('should be able to be stringified', () => { + const machine = createMachine({ + initial: 'idle', + states: { + idle: { + on: { + GO_TO_WAITING: 'waiting', + }, + }, + waiting: { + invoke: { + src: fromCallback(() => {}), + }, + }, + }, + }); + const actorRef = createActor(machine).start(); + actorRef.send({ type: 'GO_TO_WAITING' }); + const waitingState = actorRef.getSnapshot(); + + expect(() => { + JSON.stringify(waitingState); + }).not.toThrow(); + }); + + it('should result in an error notification if callback actor throws when it starts and the error stays unhandled by the machine', () => { + const errorMachine = createMachine({ + initial: 'safe', + states: { + safe: { + invoke: { + src: fromCallback(() => { + throw new Error('test'); + }), + }, + }, + failed: { + type: 'final', + }, + }, + }); + const spy = jest.fn(); + + const actorRef = createActor(errorMachine); + actorRef.subscribe({ + error: spy, + }); + actorRef.start(); + expect(spy.mock.calls).toMatchInlineSnapshot(` + [ + [ + [Error: test], + ], + ] + `); + }); + + it('should work with input', (done) => { + const machine = createMachine({ + types: {} as { + context: { foo: string }; + }, + initial: 'start', + context: { foo: 'bar' }, + states: { + start: { + invoke: { + src: fromCallback(({ input }) => { + expect(input).toEqual({ foo: 'bar' }); + done(); + }), + input: ({ context }: any) => context, + }, + }, + }, + }); + + createActor(machine).start(); + }); + + it('sub invoke race condition ends on the completed state', () => { + const anotherChildMachine = createMachine({ + id: 'child', + initial: 'start', + states: { + start: { + on: { STOP: 'end' }, + }, + end: { + type: 'final', + }, + }, + }); + + const anotherParentMachine = createMachine({ + id: 'parent', + initial: 'begin', + states: { + begin: { + invoke: { + src: anotherChildMachine, + id: 'invoked.child', + onDone: 'completed', + }, + on: { + STOPCHILD: { + actions: sendTo('invoked.child', { type: 'STOP' }), + }, + }, + }, + completed: { + type: 'final', + }, + }, + }); + + const actorRef = createActor(anotherParentMachine).start(); + actorRef.send({ type: 'STOPCHILD' }); + + expect(actorRef.getSnapshot().value).toEqual('completed'); + }); + }); + + describe('invoke input', () => { + it('should provide input to an actor creator', (done) => { + const machine = createMachine( + { + types: {} as { + context: { count: number }; + actors: { + src: 'stringService'; + logic: PromiseActorLogic< + boolean, + { + staticVal: string; + newCount: number; + } + >; + }; + }, + initial: 'pending', + context: { + count: 42, + }, + states: { + pending: { + invoke: { + src: 'stringService', + input: ({ context }) => ({ + staticVal: 'hello', + newCount: context.count * 2, + }), + onDone: 'success', + }, + }, + success: { + type: 'final', + }, + }, + }, + { + actors: { + stringService: fromPromise(({ input }) => { + expect(input).toEqual({ newCount: 84, staticVal: 'hello' }); + + return Promise.resolve(true); + }), + }, + } + ); + + const service = createActor(machine); + service.subscribe({ + complete: () => { + done(); + }, + }); + + service.start(); + }); + + it('should provide self to input mapper', (done) => { + const machine = createMachine({ + invoke: { + src: fromCallback(({ input }) => { + expect(input.responder.send).toBeDefined(); + done(); + }), + input: ({ self }) => ({ + responder: self, + }), + }, + }); + + createActor(machine).start(); + }); + }); +}); diff --git a/libs/permissions/permissionLogic/src/lib/xstate-reference/referenceTests/playground.spec.ts b/libs/permissions/permissionLogic/src/lib/xstate-reference/referenceTests/playground.spec.ts index 7c0860e..07955e5 100644 --- a/libs/permissions/permissionLogic/src/lib/xstate-reference/referenceTests/playground.spec.ts +++ b/libs/permissions/permissionLogic/src/lib/xstate-reference/referenceTests/playground.spec.ts @@ -1,1709 +1,1667 @@ -import { createActor, createMachine } from 'xstate'; - -it('meta', () => { - const feedbackMachine = createMachine({ - id: 'feedback', - initial: 'prompt', - meta: { - title: 'Feedback', +import { + ActorRef, + Snapshot, + StateValue, + assign, + createActor, + createMachine, + enqueueActions, + fromPromise, + log, + raise, + sendParent, + sendTo, + setup, +} from 'xstate'; + +const composerMachine = createMachine({ + initial: 'ReadOnly', + states: { + ReadOnly: { + id: 'ReadOnly', + initial: 'StructureEdit', + entry: ['selectNone'], + states: { + StructureEdit: { + id: 'StructureEditRO', + type: 'parallel', + on: { + switchToProjectManagement: [ + { + target: 'ProjectManagement', + }, + ], + }, + states: { + SelectionStatus: { + initial: 'SelectedNone', + on: { + singleClickActivity: [ + { + target: '.SelectedActivity', + actions: ['selectActivity'], + }, + ], + singleClickLink: [ + { + target: '.SelectedLink', + actions: ['selectLink'], + }, + ], + }, + states: { + SelectedNone: { + entry: ['redraw'], + }, + SelectedActivity: { + entry: ['redraw'], + on: { + singleClickCanvas: [ + { + target: 'SelectedNone', + actions: ['selectNone'], + }, + ], + }, + }, + SelectedLink: { + entry: ['redraw'], + on: { + singleClickCanvas: [ + { + target: 'SelectedNone', + actions: ['selectNone'], + }, + ], + }, + }, + }, + }, + ClipboardStatus: { + initial: 'Empty', + states: { + Empty: { + entry: ['emptyClipboard'], + on: { + cutInClipboardSuccess: [ + { + target: 'FilledByCut', + }, + ], + copyInClipboardSuccess: [ + { + target: 'FilledByCopy', + }, + ], + }, + }, + FilledByCopy: { + on: { + cutInClipboardSuccess: [ + { + target: 'FilledByCut', + }, + ], + copyInClipboardSuccess: [ + { + target: 'FilledByCopy', + }, + ], + pasteFromClipboardSuccess: [ + { + target: 'FilledByCopy', + }, + ], + }, + }, + FilledByCut: { + on: { + cutInClipboardSuccess: [ + { + target: 'FilledByCut', + }, + ], + copyInClipboardSuccess: [ + { + target: 'FilledByCopy', + }, + ], + pasteFromClipboardSuccess: [ + { + target: 'Empty', + }, + ], + }, + }, + }, + }, + }, + }, + ProjectManagement: { + id: 'ProjectManagementRO', + type: 'parallel', + on: { + switchToStructureEdit: [ + { + target: 'StructureEdit', + }, + ], + }, + states: { + SelectionStatus: { + initial: 'SelectedNone', + on: { + singleClickActivity: [ + { + target: '.SelectedActivity', + actions: ['selectActivity'], + }, + ], + singleClickLink: [ + { + target: '.SelectedLink', + actions: ['selectLink'], + }, + ], + }, + states: { + SelectedNone: { + entry: ['redraw'], + }, + SelectedActivity: { + entry: ['redraw'], + on: { + singleClickCanvas: [ + { + target: 'SelectedNone', + actions: ['selectNone'], + }, + ], + }, + }, + SelectedLink: { + entry: ['redraw'], + on: { + singleClickCanvas: [ + { + target: 'SelectedNone', + actions: ['selectNone'], + }, + ], + }, + }, + }, + }, + }, + }, + }, }, - states: { - prompt: { - meta: { - content: 'How was your experience?', + }, +}); + +const wordMachine = createMachine({ + id: 'word', + type: 'parallel', + states: { + bold: { + initial: 'off', + states: { + on: { + on: { TOGGLE_BOLD: 'off' }, + }, + off: { + on: { TOGGLE_BOLD: 'on' }, }, }, - form: { - meta: { - content: 'Please fill out the form below.', + }, + underline: { + initial: 'off', + states: { + on: { + on: { TOGGLE_UNDERLINE: 'off' }, + }, + off: { + on: { TOGGLE_UNDERLINE: 'on' }, }, }, - thanks: { - meta: { - content: 'Thank you for your feedback!', + }, + italics: { + initial: 'off', + states: { + on: { + on: { TOGGLE_ITALICS: 'off' }, + }, + off: { + on: { TOGGLE_ITALICS: 'on' }, }, }, - closed: {}, }, - }); + list: { + initial: 'none', + states: { + none: { + on: { BULLETS: 'bullets', NUMBERS: 'numbers' }, + }, + bullets: { + on: { NONE: 'none', NUMBERS: 'numbers' }, + }, + numbers: { + on: { BULLETS: 'bullets', NONE: 'none' }, + }, + }, + }, + }, + on: { + RESET: '#word', // TODO: this should be 'word' or [{ internal: false }] + }, +}); - const feedbackActor = createActor(feedbackMachine).start(); +const flatParallelMachine = createMachine({ + type: 'parallel', + states: { + foo: {}, + bar: {}, + baz: { + initial: 'one', + states: { + one: { on: { E: 'two' } }, + two: {}, + }, + }, + }, +}); - console.log(feedbackActor.getSnapshot().getMeta()); - // logs the object: - // { - // feedback: { - // title: 'Feedback', - // }, - // 'feedback.prompt': { - // content: 'How was your experience?', - // } - // } +const raisingParallelMachine = createMachine({ + /** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOgHkBVAFQFEAlARhIEEBiGgNRoDkqB9SrUZ8AQgG0ADAF1EoAA4B7WLgAuuBflkgAHogYSArAA4SRgOwA2AwE4LEiwGZLNgDQgAnogC0DgCwkJX19jXyMLOyNfBgYAXxi3NCw8QlJBeiY2Th5+NOEAYUkZJBBFZTUNLV0EL2sDEmtrACZfe2sHY0aLBka3TwQnRpIDcIN7XwdHM0ajBziEjBwCYnJqdJIRdi5eAVXhZkKtUtV1TWKq-WNTZ1t7JytrXsRrEl9LC0aph0aDKJm5kESixSKyETA2WW2uQYfAK0kOSmOFTO3gMjSYtQMNiMEgmDAsvh6HkQwSYZgYwwa1hx2Ia-0ByWWUJIeU22R2oL4+zhxSO5VOoCqNTqDWarXaRk63UeCCiFgC5jJRgMfgaCrpCwZqV2TBZEJy2tEBx5CL5lW8tXqTRaFjaHS6hL63wkJDRHzMErMDgcEgkRnVSSWWqEgwAktxuGsyAAxKOsKgUOjcATcI3yE0nM0IBhmZ61BgTfH5yJGaxmaVWOVGfM+zHNKZhf1Axm7UPhyPcVh5AAyNGYdFTJXTSIFehz9QM+fCUQcxdL0rMPpIXoJjkMnQmvkbmpB9FbEbog2jsfjieTA95GeRWbHeYL09nZaJCBLdXaXp+nwnjVm8QBGsDO4HiQYb7oeHYnkmR7nkO-I6KOuYTneRahHOT5KkwMyNIY2bhF8nRxL++AKBAcBaPSgbwmUl4jtUOZ1JiUTuvY4TRA8T5eFhJhmGYPzRL41jmAJ5JbgBUKUYisGCmYrxDLxTHhF0DBsX0XjdEwUQ+hI34fJETQWCJwJMsw4mmleXg2s8DEML4LFaT6yl6HKPyBFWVJksEWmNAZzagusJnUXB1QWbJjHYgprHSg4FrGHithGDMC4+j+8wBoZ2rMv5w6BeZDQhdmYUsdm5ZjhKEoMGEYQ2hYZjeUGu6ZZJiAWNKoSDL6aLmOV0zetYtWAXu6QNZmPzShSLyvDYjjKtxAl9bkA2MOQMZDVejTReYWE5u8vrWeW+IBEElivO05XmHNLbAW2i1kNwK00foDpNV0QyHT8zSluVm6-uRaXBpdoF3YFI1PkdLwKQSNjTRM51-SBu5LVGgPnFp0pKk5jgSl8hi1F8MPw3DQE3UjiDes6tgTBIH02kERiozYAQY9p2PKl5BFAA */ + type: 'parallel', + states: { + OUTER1: { + initial: 'C', + states: { + A: { + entry: [raise({ type: 'TURN_OFF' })], + on: { + EVENT_OUTER1_B: 'B', + EVENT_OUTER1_C: 'C', + }, + }, + B: { + entry: [raise({ type: 'TURN_ON' })], + on: { + EVENT_OUTER1_A: 'A', + EVENT_OUTER1_C: 'C', + }, + }, + C: { + entry: [raise({ type: 'CLEAR' })], + on: { + EVENT_OUTER1_A: 'A', + EVENT_OUTER1_B: 'B', + }, + }, + }, + }, + OUTER2: { + type: 'parallel', + states: { + INNER1: { + initial: 'ON', + states: { + OFF: { + on: { + TURN_ON: 'ON', + }, + }, + ON: { + on: { + CLEAR: 'OFF', + }, + }, + }, + }, + INNER2: { + initial: 'OFF', + states: { + OFF: { + on: { + TURN_ON: 'ON', + }, + }, + ON: { + on: { + TURN_OFF: 'OFF', + }, + }, + }, + }, + }, + }, + }, }); -// import { -// ActorRef, -// Snapshot, -// StateValue, -// assign, -// createActor, -// createMachine, -// enqueueActions, -// fromPromise, -// log, -// raise, -// sendParent, -// sendTo, -// setup, -// } from 'xstate'; -// -// const composerMachine = createMachine({ -// initial: 'ReadOnly', -// states: { -// ReadOnly: { -// id: 'ReadOnly', -// initial: 'StructureEdit', -// entry: ['selectNone'], -// states: { -// StructureEdit: { -// id: 'StructureEditRO', -// type: 'parallel', -// on: { -// switchToProjectManagement: [ -// { -// target: 'ProjectManagement', -// }, -// ], -// }, -// states: { -// SelectionStatus: { -// initial: 'SelectedNone', -// on: { -// singleClickActivity: [ -// { -// target: '.SelectedActivity', -// actions: ['selectActivity'], -// }, -// ], -// singleClickLink: [ -// { -// target: '.SelectedLink', -// actions: ['selectLink'], -// }, -// ], -// }, -// states: { -// SelectedNone: { -// entry: ['redraw'], -// }, -// SelectedActivity: { -// entry: ['redraw'], -// on: { -// singleClickCanvas: [ -// { -// target: 'SelectedNone', -// actions: ['selectNone'], -// }, -// ], -// }, -// }, -// SelectedLink: { -// entry: ['redraw'], -// on: { -// singleClickCanvas: [ -// { -// target: 'SelectedNone', -// actions: ['selectNone'], -// }, -// ], -// }, -// }, -// }, -// }, -// ClipboardStatus: { -// initial: 'Empty', -// states: { -// Empty: { -// entry: ['emptyClipboard'], -// on: { -// cutInClipboardSuccess: [ -// { -// target: 'FilledByCut', -// }, -// ], -// copyInClipboardSuccess: [ -// { -// target: 'FilledByCopy', -// }, -// ], -// }, -// }, -// FilledByCopy: { -// on: { -// cutInClipboardSuccess: [ -// { -// target: 'FilledByCut', -// }, -// ], -// copyInClipboardSuccess: [ -// { -// target: 'FilledByCopy', -// }, -// ], -// pasteFromClipboardSuccess: [ -// { -// target: 'FilledByCopy', -// }, -// ], -// }, -// }, -// FilledByCut: { -// on: { -// cutInClipboardSuccess: [ -// { -// target: 'FilledByCut', -// }, -// ], -// copyInClipboardSuccess: [ -// { -// target: 'FilledByCopy', -// }, -// ], -// pasteFromClipboardSuccess: [ -// { -// target: 'Empty', -// }, -// ], -// }, -// }, -// }, -// }, -// }, -// }, -// ProjectManagement: { -// id: 'ProjectManagementRO', -// type: 'parallel', -// on: { -// switchToStructureEdit: [ -// { -// target: 'StructureEdit', -// }, -// ], -// }, -// states: { -// SelectionStatus: { -// initial: 'SelectedNone', -// on: { -// singleClickActivity: [ -// { -// target: '.SelectedActivity', -// actions: ['selectActivity'], -// }, -// ], -// singleClickLink: [ -// { -// target: '.SelectedLink', -// actions: ['selectLink'], -// }, -// ], -// }, -// states: { -// SelectedNone: { -// entry: ['redraw'], -// }, -// SelectedActivity: { -// entry: ['redraw'], -// on: { -// singleClickCanvas: [ -// { -// target: 'SelectedNone', -// actions: ['selectNone'], -// }, -// ], -// }, -// }, -// SelectedLink: { -// entry: ['redraw'], -// on: { -// singleClickCanvas: [ -// { -// target: 'SelectedNone', -// actions: ['selectNone'], -// }, -// ], -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// }); -// -// const wordMachine = createMachine({ -// id: 'word', -// type: 'parallel', -// states: { -// bold: { -// initial: 'off', -// states: { -// on: { -// on: { TOGGLE_BOLD: 'off' }, -// }, -// off: { -// on: { TOGGLE_BOLD: 'on' }, -// }, -// }, -// }, -// underline: { -// initial: 'off', -// states: { -// on: { -// on: { TOGGLE_UNDERLINE: 'off' }, -// }, -// off: { -// on: { TOGGLE_UNDERLINE: 'on' }, -// }, -// }, -// }, -// italics: { -// initial: 'off', -// states: { -// on: { -// on: { TOGGLE_ITALICS: 'off' }, -// }, -// off: { -// on: { TOGGLE_ITALICS: 'on' }, -// }, -// }, -// }, -// list: { -// initial: 'none', -// states: { -// none: { -// on: { BULLETS: 'bullets', NUMBERS: 'numbers' }, -// }, -// bullets: { -// on: { NONE: 'none', NUMBERS: 'numbers' }, -// }, -// numbers: { -// on: { BULLETS: 'bullets', NONE: 'none' }, -// }, -// }, -// }, -// }, -// on: { -// RESET: '#word', // TODO: this should be 'word' or [{ internal: false }] -// }, -// }); -// -// const flatParallelMachine = createMachine({ -// type: 'parallel', -// states: { -// foo: {}, -// bar: {}, -// baz: { -// initial: 'one', -// states: { -// one: { on: { E: 'two' } }, -// two: {}, -// }, -// }, -// }, -// }); -// -// const raisingParallelMachine = createMachine({ -// /** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOgHkBVAFQFEAlARhIEEBiGgNRoDkqB9SrUZ8AQgG0ADAF1EoAA4B7WLgAuuBflkgAHogYSArAA4SRgOwA2AwE4LEiwGZLNgDQgAnogC0DgCwkJX19jXyMLOyNfBgYAXxi3NCw8QlJBeiY2Th5+NOEAYUkZJBBFZTUNLV0EL2sDEmtrACZfe2sHY0aLBka3TwQnRpIDcIN7XwdHM0ajBziEjBwCYnJqdJIRdi5eAVXhZkKtUtV1TWKq-WNTZ1t7JytrXsRrEl9LC0aph0aDKJm5kESixSKyETA2WW2uQYfAK0kOSmOFTO3gMjSYtQMNiMEgmDAsvh6HkQwSYZgYwwa1hx2Ia-0ByWWUJIeU22R2oL4+zhxSO5VOoCqNTqDWarXaRk63UeCCiFgC5jJRgMfgaCrpCwZqV2TBZEJy2tEBx5CL5lW8tXqTRaFjaHS6hL63wkJDRHzMErMDgcEgkRnVSSWWqEgwAktxuGsyAAxKOsKgUOjcATcI3yE0nM0IBhmZ61BgTfH5yJGaxmaVWOVGfM+zHNKZhf1Axm7UPhyPcVh5AAyNGYdFTJXTSIFehz9QM+fCUQcxdL0rMPpIXoJjkMnQmvkbmpB9FbEbog2jsfjieTA95GeRWbHeYL09nZaJCBLdXaXp+nwnjVm8QBGsDO4HiQYb7oeHYnkmR7nkO-I6KOuYTneRahHOT5KkwMyNIY2bhF8nRxL++AKBAcBaPSgbwmUl4jtUOZ1JiUTuvY4TRA8T5eFhJhmGYPzRL41jmAJ5JbgBUKUYisGCmYrxDLxTHhF0DBsX0XjdEwUQ+hI34fJETQWCJwJMsw4mmleXg2s8DEML4LFaT6yl6HKPyBFWVJksEWmNAZzagusJnUXB1QWbJjHYgprHSg4FrGHithGDMC4+j+8wBoZ2rMv5w6BeZDQhdmYUsdm5ZjhKEoMGEYQ2hYZjeUGu6ZZJiAWNKoSDL6aLmOV0zetYtWAXu6QNZmPzShSLyvDYjjKtxAl9bkA2MOQMZDVejTReYWE5u8vrWeW+IBEElivO05XmHNLbAW2i1kNwK00foDpNV0QyHT8zSluVm6-uRaXBpdoF3YFI1PkdLwKQSNjTRM51-SBu5LVGgPnFp0pKk5jgSl8hi1F8MPw3DQE3UjiDes6tgTBIH02kERiozYAQY9p2PKl5BFAA */ -// type: 'parallel', -// states: { -// OUTER1: { -// initial: 'C', -// states: { -// A: { -// entry: [raise({ type: 'TURN_OFF' })], -// on: { -// EVENT_OUTER1_B: 'B', -// EVENT_OUTER1_C: 'C', -// }, -// }, -// B: { -// entry: [raise({ type: 'TURN_ON' })], -// on: { -// EVENT_OUTER1_A: 'A', -// EVENT_OUTER1_C: 'C', -// }, -// }, -// C: { -// entry: [raise({ type: 'CLEAR' })], -// on: { -// EVENT_OUTER1_A: 'A', -// EVENT_OUTER1_B: 'B', -// }, -// }, -// }, -// }, -// OUTER2: { -// type: 'parallel', -// states: { -// INNER1: { -// initial: 'ON', -// states: { -// OFF: { -// on: { -// TURN_ON: 'ON', -// }, -// }, -// ON: { -// on: { -// CLEAR: 'OFF', -// }, -// }, -// }, -// }, -// INNER2: { -// initial: 'OFF', -// states: { -// OFF: { -// on: { -// TURN_ON: 'ON', -// }, -// }, -// ON: { -// on: { -// TURN_OFF: 'OFF', -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// }); -// -// const nestedParallelState = createMachine({ -// /** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOgHkBVAFQFEAlARhIGUqBBWgfTIDEeBiGgDUaAOSqcAwmQCyABQAyNABoBtAAwBdRKAAOAe1i4ALrn34dIAB6IAtAHYATABYSz+wFZHAZg8eAHPYAnABsXgA0IACeiI4BJN7ODP4hIe4u3kEeIQC+OZFoWHiEpJS0jCzsXLwCwmISzACS8koa2kggBkam5pY2CLZBDExxyR7O3okeid6RMQhx-glJKWlOE1m5+SCFOATE5NT0TKwcNNyilWec4gBS6ldcjQAiSpzqgiLinKdcd+qcADqZDoAGk2pYuiYzBYOv1bGkll5Mv51I4Qmj0SE5ogQt4Hr4JikghMwv5vHkChg9iVDuUTlVzmRLr9zv9HudgWDGqIAOLvT71H6Mm5Ue6cF6tLSQwzQ3pwux4jwkdTTIIuVIkzE4hABB5efwpcb2eyTIJBSk7anFA5lY4ci4Ou4M66S84MQXfVmi24MIEg8HSjpQnqw0DwsKOEhxZzODwmobZEL2HXkoIJRzk9zeRwkyb+S27G2lI4Vb3Mp1il1cLmgnn8j11L0i50S140CHB2WhvqK7xLez+InZBhDBjqZw6wfpjxo-yLPHDcaF637EvlKPlvieiTSFoqTt6bsw3sLDzpkK59TeBiJWOONGT6KIcdLOL2ZOhdFDy8roprul6E3EUah3H5mkUDsgyPboTwVAZxhIc91VNEJQm8NJ1BJHV8XTBg0McBhsmcdUsiHPJtnwfQIDgSwizXGVYPlcM7HHdR7GjDwxgmKYZh1BEQgSQ0GBIhgTQmTMC22ejaTtRhGLlMNrDsLJ01GfxxkmOM+OfBBMjcIYgnsLD1TjYk-xpW1S2rJk+AUnt4NsfwsmWCc0SIvFEx1Bh0RIfwRMNexnHJftgvsCzi0AssQNEey4JYgYgnUQTEjcwjskyIidWcVU-PJNEkvUW9vBNCKALkmzHW9f44uY5SBg0pZUpy9LPKy3SSO8aNVRJJxMnzKSqX-WTrIdCtqrFB5vTdd5aqU+F-IJHKWo8zKPB1AJBMcQdnAfIIpljLYhss9d7XLFkW0mh1a3rWauyY+a7H7ZVz12wznDCWcMI2w0SBNIc9oOj6ypG+kxou65nTm08nPiZr3IyrzdOMqMxPNdjLy4kTnBBqywfOytfQdGaGGhxzFtclbEfa+ZgmVOIfH8nNrwfBhcdO6LrnGy6ie9G6+U4Un7sUmHMrcRJb3GVJkvnbFkaMv7kSZh9mbZ6TV1BoCyYS2xxyjZDtow9C0gYHUHxIfb3GTG9En23MjqtYa8aAsa7OFhyEp8rqiI-AJMiK9VZl04ZXCHVUQiHMTQtvdmouArnRHA-dtfq5IHnRVVb1jLwSQiYOJ3TDShicedHEI5zY7k+PqkTvdIOUFP+mGVH4zJf3Rx8HD9ot9RAjLvwSXnQbHZOuPwakWR6+J0RRGORuXwxEgfbbpKO6D+ZgpGG9EXxGZPHC9WnY56umVryelGUafZ856o3ZgkX4IfJZl791fA42iOkPGb+CumNJK9LCfC4E99yX2mjPM6MV54LGGEJXwppYzYwxHLeYl4OITiSHOQiEdggAI3OPOuF8r5a3dvFVOi8X5pgDp3XSaEOLTAwslPq0w-B4JdudEBU9wHXyATUaBT8l6t1ftQ9eiAdp+WyF9WM6EJhsLoLws+oDiHyPBvw2B-Z4F3iQclVMExljIJ8kkQcAQKI5CAA */ -// type: 'parallel', -// states: { -// OUTER1: { -// initial: 'STATE_OFF', -// states: { -// STATE_OFF: { -// on: { -// EVENT_COMPLEX: 'STATE_ON', -// EVENT_SIMPLE: 'STATE_ON', -// }, -// }, -// STATE_ON: { -// type: 'parallel', -// states: { -// STATE_NTJ0: { -// initial: 'STATE_IDLE_0', -// states: { -// STATE_IDLE_0: { -// on: { -// EVENT_STATE_NTJ0_WORK: 'STATE_WORKING_0', -// }, -// }, -// STATE_WORKING_0: { -// on: { -// EVENT_STATE_NTJ0_IDLE: 'STATE_IDLE_0', -// }, -// }, -// }, -// }, -// STATE_NTJ1: { -// initial: 'STATE_IDLE_1', -// states: { -// STATE_IDLE_1: { -// on: { -// EVENT_STATE_NTJ1_WORK: 'STATE_WORKING_1', -// }, -// }, -// STATE_WORKING_1: { -// on: { -// EVENT_STATE_NTJ1_IDLE: 'STATE_IDLE_1', -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// OUTER2: { -// initial: 'STATE_OFF', -// states: { -// STATE_OFF: { -// on: { -// EVENT_COMPLEX: 'STATE_ON_COMPLEX', -// EVENT_SIMPLE: 'STATE_ON_SIMPLE', -// }, -// }, -// STATE_ON_SIMPLE: {}, -// STATE_ON_COMPLEX: { -// type: 'parallel', -// states: { -// STATE_INNER1: { -// initial: 'STATE_OFF', -// states: { -// STATE_OFF: {}, -// STATE_ON: {}, -// }, -// }, -// STATE_INNER2: { -// initial: 'STATE_OFF', -// states: { -// STATE_OFF: {}, -// STATE_ON: {}, -// }, -// }, -// }, -// }, -// }, -// }, -// }, -// }); -// -// const deepFlatParallelMachine = createMachine({ -// type: 'parallel', -// states: { -// X: {}, -// V: { -// initial: 'A', -// on: { -// a: { -// target: 'V.A', -// }, -// b: { -// target: 'V.B', -// }, -// c: { -// target: 'V.C', -// }, -// }, -// states: { -// A: {}, -// B: { -// initial: 'BB', -// states: { -// BB: { -// type: 'parallel', -// states: { -// BBB_A: {}, -// BBB_B: {}, -// }, -// }, -// }, -// }, -// C: {}, -// }, -// }, -// }, -// }); -// -// describe('parallel states', () => { -// it('should have initial parallel states', () => { -// const initialState = createActor(wordMachine).getSnapshot(); -// -// expect(initialState.value).toEqual({ -// bold: 'off', -// italics: 'off', -// underline: 'off', -// list: 'none', -// }); -// }); -// -// const expected: Record> = { -// '{"bold": "off"}': { -// TOGGLE_BOLD: { -// bold: 'on', -// italics: 'off', -// underline: 'off', -// list: 'none', -// }, -// }, -// '{"bold": "on"}': { -// TOGGLE_BOLD: { -// bold: 'off', -// italics: 'off', -// underline: 'off', -// list: 'none', -// }, -// }, -// [JSON.stringify({ -// bold: 'off', -// italics: 'off', -// underline: 'on', -// list: 'bullets', -// })]: { -// 'TOGGLE_BOLD, TOGGLE_ITALICS': { -// bold: 'on', -// italics: 'on', -// underline: 'on', -// list: 'bullets', -// }, -// RESET: { -// bold: 'off', -// italics: 'off', -// underline: 'off', -// list: 'none', -// }, -// }, -// }; -// -// it('should have all parallel states represented in the state value', () => { -// const machine = createMachine({ + +const nestedParallelState = createMachine({ + /** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOgHkBVAFQFEAlARhIGUqBBWgfTIDEeBiGgDUaAOSqcAwmQCyABQAyNABoBtAAwBdRKAAOAe1i4ALrn34dIAB6IAtAHYATABYSz+wFZHAZg8eAHPYAnABsXgA0IACeiI4BJN7ODP4hIe4u3kEeIQC+OZFoWHiEpJS0jCzsXLwCwmISzACS8koa2kggBkam5pY2CLZBDExxyR7O3okeid6RMQhx-glJKWlOE1m5+SCFOATE5NT0TKwcNNyilWec4gBS6ldcjQAiSpzqgiLinKdcd+qcADqZDoAGk2pYuiYzBYOv1bGkll5Mv51I4Qmj0SE5ogQt4Hr4JikghMwv5vHkChg9iVDuUTlVzmRLr9zv9HudgWDGqIAOLvT71H6Mm5Ue6cF6tLSQwzQ3pwux4jwkdTTIIuVIkzE4hABB5efwpcb2eyTIJBSk7anFA5lY4ci4Ou4M66S84MQXfVmi24MIEg8HSjpQnqw0DwsKOEhxZzODwmobZEL2HXkoIJRzk9zeRwkyb+S27G2lI4Vb3Mp1il1cLmgnn8j11L0i50S140CHB2WhvqK7xLez+InZBhDBjqZw6wfpjxo-yLPHDcaF637EvlKPlvieiTSFoqTt6bsw3sLDzpkK59TeBiJWOONGT6KIcdLOL2ZOhdFDy8roprul6E3EUah3H5mkUDsgyPboTwVAZxhIc91VNEJQm8NJ1BJHV8XTBg0McBhsmcdUsiHPJtnwfQIDgSwizXGVYPlcM7HHdR7GjDwxgmKYZh1BEQgSQ0GBIhgTQmTMC22ejaTtRhGLlMNrDsLJ01GfxxkmOM+OfBBMjcIYgnsLD1TjYk-xpW1S2rJk+AUnt4NsfwsmWCc0SIvFEx1Bh0RIfwRMNexnHJftgvsCzi0AssQNEey4JYgYgnUQTEjcwjskyIidWcVU-PJNEkvUW9vBNCKALkmzHW9f44uY5SBg0pZUpy9LPKy3SSO8aNVRJJxMnzKSqX-WTrIdCtqrFB5vTdd5aqU+F-IJHKWo8zKPB1AJBMcQdnAfIIpljLYhss9d7XLFkW0mh1a3rWauyY+a7H7ZVz12wznDCWcMI2w0SBNIc9oOj6ypG+kxou65nTm08nPiZr3IyrzdOMqMxPNdjLy4kTnBBqywfOytfQdGaGGhxzFtclbEfa+ZgmVOIfH8nNrwfBhcdO6LrnGy6ie9G6+U4Un7sUmHMrcRJb3GVJkvnbFkaMv7kSZh9mbZ6TV1BoCyYS2xxyjZDtow9C0gYHUHxIfb3GTG9En23MjqtYa8aAsa7OFhyEp8rqiI-AJMiK9VZl04ZXCHVUQiHMTQtvdmouArnRHA-dtfq5IHnRVVb1jLwSQiYOJ3TDShicedHEI5zY7k+PqkTvdIOUFP+mGVH4zJf3Rx8HD9ot9RAjLvwSXnQbHZOuPwakWR6+J0RRGORuXwxEgfbbpKO6D+ZgpGG9EXxGZPHC9WnY56umVryelGUafZ856o3ZgkX4IfJZl791fA42iOkPGb+CumNJK9LCfC4E99yX2mjPM6MV54LGGEJXwppYzYwxHLeYl4OITiSHOQiEdggAI3OPOuF8r5a3dvFVOi8X5pgDp3XSaEOLTAwslPq0w-B4JdudEBU9wHXyATUaBT8l6t1ftQ9eiAdp+WyF9WM6EJhsLoLws+oDiHyPBvw2B-Z4F3iQclVMExljIJ8kkQcAQKI5CAA */ + type: 'parallel', + states: { + OUTER1: { + initial: 'STATE_OFF', + states: { + STATE_OFF: { + on: { + EVENT_COMPLEX: 'STATE_ON', + EVENT_SIMPLE: 'STATE_ON', + }, + }, + STATE_ON: { + type: 'parallel', + states: { + STATE_NTJ0: { + initial: 'STATE_IDLE_0', + states: { + STATE_IDLE_0: { + on: { + EVENT_STATE_NTJ0_WORK: 'STATE_WORKING_0', + }, + }, + STATE_WORKING_0: { + on: { + EVENT_STATE_NTJ0_IDLE: 'STATE_IDLE_0', + }, + }, + }, + }, + STATE_NTJ1: { + initial: 'STATE_IDLE_1', + states: { + STATE_IDLE_1: { + on: { + EVENT_STATE_NTJ1_WORK: 'STATE_WORKING_1', + }, + }, + STATE_WORKING_1: { + on: { + EVENT_STATE_NTJ1_IDLE: 'STATE_IDLE_1', + }, + }, + }, + }, + }, + }, + }, + }, + OUTER2: { + initial: 'STATE_OFF', + states: { + STATE_OFF: { + on: { + EVENT_COMPLEX: 'STATE_ON_COMPLEX', + EVENT_SIMPLE: 'STATE_ON_SIMPLE', + }, + }, + STATE_ON_SIMPLE: {}, + STATE_ON_COMPLEX: { + type: 'parallel', + states: { + STATE_INNER1: { + initial: 'STATE_OFF', + states: { + STATE_OFF: {}, + STATE_ON: {}, + }, + }, + STATE_INNER2: { + initial: 'STATE_OFF', + states: { + STATE_OFF: {}, + STATE_ON: {}, + }, + }, + }, + }, + }, + }, + }, +}); + +const deepFlatParallelMachine = createMachine({ + type: 'parallel', + states: { + X: {}, + V: { + initial: 'A', + on: { + a: { + target: 'V.A', + }, + b: { + target: 'V.B', + }, + c: { + target: 'V.C', + }, + }, + states: { + A: {}, + B: { + initial: 'BB', + states: { + BB: { + type: 'parallel', + states: { + BBB_A: {}, + BBB_B: {}, + }, + }, + }, + }, + C: {}, + }, + }, + }, +}); + +describe('parallel states', () => { + it('should have initial parallel states', () => { + const initialState = createActor(wordMachine).getSnapshot(); + + expect(initialState.value).toEqual({ + bold: 'off', + italics: 'off', + underline: 'off', + list: 'none', + }); + }); + + const expected: Record> = { + '{"bold": "off"}': { + TOGGLE_BOLD: { + bold: 'on', + italics: 'off', + underline: 'off', + list: 'none', + }, + }, + '{"bold": "on"}': { + TOGGLE_BOLD: { + bold: 'off', + italics: 'off', + underline: 'off', + list: 'none', + }, + }, + [JSON.stringify({ + bold: 'off', + italics: 'off', + underline: 'on', + list: 'bullets', + })]: { + 'TOGGLE_BOLD, TOGGLE_ITALICS': { + bold: 'on', + italics: 'on', + underline: 'on', + list: 'bullets', + }, + RESET: { + bold: 'off', + italics: 'off', + underline: 'off', + list: 'none', + }, + }, + }; + + it('should have all parallel states represented in the state value', () => { + const machine = createMachine({ + type: 'parallel', + states: { + wak1: { + initial: 'wak1sonA', + states: { + wak1sonA: {}, + wak1sonB: {}, + }, + on: { + WAK1: '.wak1sonB', + }, + }, + wak2: { + initial: 'wak2sonA', + states: { + wak2sonA: {}, + }, + }, + }, + }); + const actorRef = createActor(machine).start(); + actorRef.send({ type: 'WAK1' }); + + expect(actorRef.getSnapshot().value).toEqual({ + wak1: 'wak1sonB', + wak2: 'wak2sonA', + }); + }); + + it('should work with regions without states', () => { + expect(createActor(flatParallelMachine).getSnapshot().value).toEqual({ + foo: {}, + bar: {}, + baz: 'one', + }); + }); + + it('should work with regions without states', () => { + const actorRef = createActor(flatParallelMachine).start(); + actorRef.send({ type: 'E' }); + expect(actorRef.getSnapshot().value).toEqual({ + foo: {}, + bar: {}, + baz: 'two', + }); + }); + + it('should properly transition to relative substate', () => { + const actorRef = createActor(composerMachine).start(); + actorRef.send({ + type: 'singleClickActivity', + }); + + expect(actorRef.getSnapshot().value).toEqual({ + ReadOnly: { + StructureEdit: { + SelectionStatus: 'SelectedActivity', + ClipboardStatus: 'Empty', + }, + }, + }); + }); + + it('should properly transition according to entry events on an initial state', () => { + const machine = createMachine({ + type: 'parallel', + states: { + OUTER1: { + initial: 'B', + states: { + A: {}, + B: { + entry: raise({ type: 'CLEAR' }), + }, + }, + }, + OUTER2: { + type: 'parallel', + states: { + INNER1: { + initial: 'ON', + states: { + OFF: {}, + ON: { + on: { + CLEAR: 'OFF', + }, + }, + }, + }, + INNER2: { + initial: 'OFF', + states: { + OFF: {}, + ON: {}, + }, + }, + }, + }, + }, + }); + expect(createActor(machine).getSnapshot().value).toEqual({ + OUTER1: 'B', + OUTER2: { + INNER1: 'OFF', + INNER2: 'OFF', + }, + }); + }); + + it('should properly transition when raising events for a parallel state', () => { + const actorRef = createActor(raisingParallelMachine).start(); + actorRef.send({ + type: 'EVENT_OUTER1_B', + }); + + expect(actorRef.getSnapshot().value).toEqual({ + OUTER1: 'B', + OUTER2: { + INNER1: 'ON', + INNER2: 'ON', + }, + }); + }); + + it('should handle simultaneous orthogonal transitions', () => { + type Events = { type: 'CHANGE'; value: string } | { type: 'SAVE' }; + const simultaneousMachine = createMachine({ + /** @xstate-layout N4IgpgJg5mDOIC5QE8CGBbANgUQgSwBcB7AJwDpJC8A7KAYgGEAJAQQDkBxbAbQAYBdRKAAORWFSLUhIAB6IAjAA4AzGWWKATMoDs2gCyLtG+b20A2ADQhkiRfLIbTGzXoCc7s670aAvj6toWLiEpGSwBKgEAK6wZFHUsKgAbpB0AMosAGo8AtKi4gR4ktJyCPKuGmQArEpmZvLKeka8plUBiMpVrmRKRoqunVXKyryNfgEYOPjE5OGRMWHJqczsXHyCSCD5ElKbpeWVNYp1DU2Ore0II90aGvV6Xbe88lVVfv4g1EQQcNKBUyESHkxDsSogALQaPSXcFVMiuRQtZ6jcrKCpQ8bWSbBGYUaY0KDAgpFXagUo1MhmXh2MyKB7U+RmNGXEyqJm8LTaDnKMyvHSY-440JzaLwTbbQrFPaIKGXW6VUaKOkabTKeR6RmuMwC7HTYURUVxBJLCBE0HShBGS5quG8bx6dTuZxKxQ6oJ62YGhaJFKm8UgyWk2QdcpkLlVUzPOw8xzKa3ybTVe2Oiou7TvHxAA */ + types: {} as { context: { value: string }; events: Events }, + id: 'yamlEditor', + type: 'parallel', + context: { + value: '', + }, + states: { + editing: { + on: { + CHANGE: { + actions: assign({ + value: ({ event }) => event.value, + }), + }, + }, + }, + status: { + initial: 'unsaved', + states: { + unsaved: { + on: { + SAVE: { + target: 'saved', + actions: 'save', + }, + }, + }, + saved: { + on: { + CHANGE: 'unsaved', + }, + }, + }, + }, + }, + }); + + const actorRef = createActor(simultaneousMachine).start(); + actorRef.send({ + type: 'SAVE', + }); + actorRef.send({ + type: 'CHANGE', + value: 'something', + }); + + expect(actorRef.getSnapshot().value).toEqual({ + editing: {}, + status: 'unsaved', + }); + + expect(actorRef.getSnapshot().context).toEqual({ + value: 'something', + }); + }); + + it('should execute actions of the initial transition of a parallel region when entering the initial state nodes of a machine', () => { + const spy = jest.fn(); + + const machine = createMachine({ + type: 'parallel', + states: { + a: { + initial: { + target: 'a1', + actions: spy, + }, + states: { + a1: {}, + }, + }, + }, + }); + + createActor(machine).start(); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + it('should execute actions of the initial transition of a parallel region when the parallel state is targeted with an explicit transition', () => { + const spy = jest.fn(); + + const machine = createMachine({ + initial: 'a', + states: { + a: { + on: { + NEXT: 'b', + }, + }, + b: { + type: 'parallel', + states: { + c: { + initial: { + target: 'c1', + actions: spy, + }, + states: { + c1: {}, + }, + }, + }, + }, + }, + }); + + const actorRef = createActor(machine).start(); + + actorRef.send({ type: 'NEXT' }); + + expect(spy).toHaveBeenCalledTimes(1); + }); + + describe('transitions with nested parallel states', () => { + it('should properly transition when in a simple nested state', () => { + const actorRef = createActor(nestedParallelState).start(); + actorRef.send({ + type: 'EVENT_SIMPLE', + }); + actorRef.send({ + type: 'EVENT_STATE_NTJ0_WORK', + }); + + expect(actorRef.getSnapshot().value).toEqual({ + OUTER1: { + STATE_ON: { + STATE_NTJ0: 'STATE_WORKING_0', + STATE_NTJ1: 'STATE_IDLE_1', + }, + }, + OUTER2: 'STATE_ON_SIMPLE', + }); + }); + + it('should properly transition when in a complex nested state', () => { + const actorRef = createActor(nestedParallelState).start(); + actorRef.send({ + type: 'EVENT_COMPLEX', + }); + actorRef.send({ + type: 'EVENT_STATE_NTJ0_WORK', + }); + + expect(actorRef.getSnapshot().value).toEqual({ + OUTER1: { + STATE_ON: { + STATE_NTJ0: 'STATE_WORKING_0', + STATE_NTJ1: 'STATE_IDLE_1', + }, + }, + OUTER2: { + STATE_ON_COMPLEX: { + STATE_INNER1: 'STATE_OFF', + STATE_INNER2: 'STATE_OFF', + }, + }, + }); + }); + }); + + // https://github.com/statelyai/xstate/issues/191 + describe('nested flat parallel states', () => { + const machine = createMachine({ + initial: 'A', + states: { + A: { + on: { + 'to-B': 'B', + }, + }, + B: { + type: 'parallel', + states: { + C: {}, + D: {}, + }, + }, + }, + on: { + 'to-A': '.A', + }, + }); + + it('should represent the flat nested parallel states in the state value', () => { + const actorRef = createActor(machine).start(); + actorRef.send({ + type: 'to-B', + }); + + expect(actorRef.getSnapshot().value).toEqual({ + B: { + C: {}, + D: {}, + }, + }); + }); + }); + + describe('deep flat parallel states', () => { + it('should properly evaluate deep flat parallel states', () => { + const actorRef = createActor(deepFlatParallelMachine).start(); + + actorRef.send({ type: 'a' }); + actorRef.send({ type: 'c' }); + actorRef.send({ type: 'b' }); + + expect(actorRef.getSnapshot().value).toEqual({ + V: { + B: { + BB: { + BBB_A: {}, + BBB_B: {}, + }, + }, + }, + X: {}, + }); + }); + + it('should not overlap resolved state nodes in state resolution', () => { + const machine = createMachine({ + /** @xstate-layout N4IgpgJg5mDOIC5QAcCWywBtUDswDoAzAe2IGIBVABQBEBBAFQFEBtABgF1EVjZUAXVMRzcQAD0QBGAJwBmfGwDsbAKwzFc2YoBMbAGwAaEAE9E0yfkUbJbABxrtsvbb3aAvm6NoM2PPgBGAIYATpS0jKycosi8AkIiSOKIbEamCGweniA4xBBw0ehYuGDRsYLCohIIALTa0tIKqnqSLSqysjLaKqmI1ZKKACz4egMqttq24yrOnR5ehb4EJMSlfOUJoFWyKirDI7KTtmxO9T0I2oq77WySevpsDwMD0nMg3kV+QcGrcRWJVbU1I1pi1JG0OtIumdqrZFPhZNo6pInpIJgM2NI9K93osAiF8KgIJgSokYmt4pVerp5A8Qa12p1uiZEDp8NIXCptDIVAN2jNsQtinjgniAF4-daUhC3fAqHSIvRWaQaaQ7RRnLlsfA2S6uCaKPQjaaZNxAA */ + id: 'pipeline', + type: 'parallel', + states: { + foo: { + on: { + UPDATE: { + actions: () => { + /* do nothing */ + }, + }, + }, + }, + bar: { + on: { + UPDATE: '.baz', + }, + initial: 'idle', + states: { + idle: {}, + baz: {}, + }, + }, + }, + }); + + const actorRef = createActor(machine).start(); + expect(() => { + actorRef.send({ + type: 'UPDATE', + }); + }).not.toThrow(); + }); + }); + + describe('other', () => { + // https://github.com/statelyai/xstate/issues/518 + it('regions should be able to transition to orthogonal regions', () => { + const testMachine = createMachine({ + /** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOgFkx8BXEgYQBsB7WSAYgBdGop6wBtAAwBdRKAAOzXO1yN8okAA9EAgDQgAnsoC+WtWix5CpCtRIB5MZTaduvQSKQgJsKTLmOlCVRu26Q+nAJickoaCysIVihGAAJOGIh0WGwAI0Z0ACcIe3lnV1l5TwBGAFYSACYAFhKBEoBOcrqAdgBmFoAOFsrKtU0EEoA2IpJKgXKipvGBJqa69pKdP3xGCDh5AMNiXMlpAo9EAFoWpsqSEqK644aWgaaBZt7DidOB6vby9vfB9ovynT0MIEjCQAAroGDwRx5XbuUCecoDdoVCYDARjeYYlqPBBFUoVOpjKoDcpVBEff7+QGbUhgiEkACCaSo7G2LhhhUQLSKA2RTVR6JKmOx5TGJCGXJaBJaJQRk0qFI2QRp4LgJAAIklUuksqz8rDFJzubz+R9BYKsT4EG0miQimM+QIijd2nMFn5FcCTFRdez9ggjgizhcruUbncHpaDu0bS0SXUit0ih9RnUBgqqUqQqYGMxID63Bz-eUmkjzpdZqHbvcmsKhiRpqiPuVBnbjumDJmveZLIQIPm9nDDsXS8GK2Hq9jJWVBoNKpcSjMus1FlogA */ + type: 'parallel', + states: { + Pages: { + initial: 'About', + states: { + About: { + id: 'About', + }, + Dashboard: { + id: 'Dashboard', + }, + }, + }, + Menu: { + initial: 'Closed', + states: { + Closed: { + id: 'Closed', + on: { + toggle: 'Opened', + }, + }, + Opened: { + id: 'Opened', + on: { + toggle: 'Closed', + 'go to dashboard': { + target: ['#Dashboard', '#Opened'], + }, + }, + }, + }, + }, + }, + }); + + const actorRef = createActor(testMachine).start(); + + actorRef.send({ type: 'toggle' }); + actorRef.send({ type: 'go to dashboard' }); + + expect( + actorRef.getSnapshot().matches({ Menu: 'Opened', Pages: 'Dashboard' }) + ).toBe(true); + }); + + // https://github.com/statelyai/xstate/issues/531 + it('should calculate the entry set for reentering transitions in parallel states', () => { + const testMachine = createMachine({ + types: {} as { context: { log: string[] } }, + id: 'test', + context: { log: [] }, + type: 'parallel', + states: { + foo: { + initial: 'foobar', + states: { + foobar: { + on: { + GOTO_FOOBAZ: 'foobaz', + }, + }, + foobaz: { + entry: assign({ + log: ({ context }) => [...context.log, 'entered foobaz'], + }), + on: { + GOTO_FOOBAZ: { + target: 'foobaz', + reenter: true, + }, + }, + }, + }, + }, + bar: {}, + }, + }); + + const actorRef = createActor(testMachine).start(); + + actorRef.send({ + type: 'GOTO_FOOBAZ', + }); + actorRef.send({ + type: 'GOTO_FOOBAZ', + }); + + expect(actorRef.getSnapshot().context.log.length).toBe(2); + }); + }); + + it('should raise a "xstate.done.state.*" event when all child states reach final state', (done) => { + const machine = createMachine({ + id: 'test', + initial: 'p', + states: { + p: { + type: 'parallel', + states: { + a: { + initial: 'idle', + states: { + idle: { + on: { + FINISH: 'finished', + }, + }, + finished: { + type: 'final', + }, + }, + }, + b: { + initial: 'idle', + states: { + idle: { + on: { + FINISH: 'finished', + }, + }, + finished: { + type: 'final', + }, + }, + }, + c: { + initial: 'idle', + states: { + idle: { + on: { + FINISH: 'finished', + }, + }, + finished: { + type: 'final', + }, + }, + }, + }, + onDone: 'success', + }, + success: { + type: 'final', + }, + }, + }); + + const service = createActor(machine); + service.subscribe({ + complete: () => { + done(); + }, + }); + service.start(); + + service.send({ type: 'FINISH' }); + }); + + it('should raise a "xstate.done.state.*" event when a pseudostate of a history type is directly on a parallel state', () => { + const machine = createMachine({ + initial: 'parallelSteps', + states: { + parallelSteps: { + type: 'parallel', + states: { + hist: { + type: 'history', + }, + one: { + initial: 'wait_one', + states: { + wait_one: { + on: { + finish_one: { + target: 'done', + }, + }, + }, + done: { + type: 'final', + }, + }, + }, + two: { + initial: 'wait_two', + states: { + wait_two: { + on: { + finish_two: { + target: 'done', + }, + }, + }, + done: { + type: 'final', + }, + }, + }, + }, + onDone: 'finished', + }, + finished: {}, + }, + }); + + const service = createActor(machine).start(); + + service.send({ type: 'finish_one' }); + service.send({ type: 'finish_two' }); + + expect(service.getSnapshot().value).toBe('finished'); + }); + + describe('invoke', () => { + it('should gradually become permissions actor system', () => { + const bluetoothPermissionMachine = setup({ + actors: { + checkPermission: fromPromise(async () => { + const result = await Promise.resolve('denied'); + return assign({ result }); + }), + }, + actions: { + spawnFetcher: assign(({ spawn }) => { + return { + child: spawn('checkPermission'), + }; + }), + }, + types: {} as { + events: + | { type: 'FORWARD_DEC' } + | { type: 'triggerPermissionRequest' }; + + context: { + triggered: boolean; + child: /*TODO fix type */ any | undefined; + }; + }, + }).createMachine({ + context: { triggered: false, child: undefined }, + initial: 'init', + states: { + init: { + on: { + triggerPermissionRequest: { + actions: [ + log('triggerPermissionRequest'), + assign({ triggered: true }), + sendTo('child', { type: 'triggerPermissionRequest' }), + // 'checkPermission', + ], + }, + FORWARD_DEC: { + actions: [ + sendParent({ type: 'DEC' }), + sendParent({ type: 'DEC' }), + sendParent({ type: 'DEC' }), + ], + }, + }, + }, + }, + }); + + const permissionMonitoringMachine = createMachine( + { + id: 'parent', + types: {} as { + context: { count: number }; + actors: { + src: 'bluetoothPermission'; + logic: typeof bluetoothPermissionMachine; + }; + }, + context: { count: 0 }, + initial: 'start', + states: { + start: { + invoke: { + src: 'bluetoothPermission', + id: 'bluetoothPermissionId', + }, + always: { + target: 'stop', + guard: ({ context }) => context.count === -3, + }, + on: { + DEC: { + actions: assign({ + count: ({ context }) => context.count - 1, + }), + }, + FORWARD_DEC: { + actions: sendTo('bluetoothPermissionId', { + type: 'triggerPermissionRequest', + }), + }, + }, + }, + stop: { + type: 'final', + }, + }, + }, + { + actors: { + bluetoothPermission: bluetoothPermissionMachine, + }, + } + ); + + const actorRef = createActor(permissionMonitoringMachine).start(); + actorRef.send({ type: 'FORWARD_DEC' }); + + // 1. The 'parent' machine will not do anything (inert transition) + // 2. The 'FORWARD_DEC' event will be "forwarded" to the child machine + // 3. On the child machine, the 'FORWARD_DEC' event sends the 'DEC' action to the parent thrice + // 4. The context of the 'parent' machine will be updated from 0 to -3 + // expect(actorRef.getSnapshot().context).toEqual({ count: -3 }); + // expect( + // actorRef.getSnapshot().children.bluetoothPermissionId?.getSnapshot() + // .context + // ).toEqual({ + // triggered: true, + // result: 'something', + // }); + }); + }); +}); + +/* +Prompt for LLM: + +Given these passing tests for xstate invoke and xstate parallel machines, can you please answer my following questions? + +1) please create a test to see whether it is possible to do the following: +create a parallel machine with 2 parallel states +in state 1, raise an event to the "parent" machine that will be handled by two invoked actors in the second parallel state. +write a complete test in this style, I will report the findings of the test to the LLM and we can iterate. +*/ + +// claude + +// describe('assumptions', () => { +// it('should support parallel states', () => { +// const parallelMachine = createMachine({ +// id: 'parent', // type: 'parallel', // states: { -// wak1: { -// initial: 'wak1sonA', -// states: { -// wak1sonA: {}, -// wak1sonB: {}, -// }, -// on: { -// WAK1: '.wak1sonB', -// }, -// }, -// wak2: { -// initial: 'wak2sonA', -// states: { -// wak2sonA: {}, -// }, -// }, +// state1: {}, +// state2: {}, // }, // }); -// const actorRef = createActor(machine).start(); -// actorRef.send({ type: 'WAK1' }); -// -// expect(actorRef.getSnapshot().value).toEqual({ -// wak1: 'wak1sonB', -// wak2: 'wak2sonA', -// }); -// }); -// -// it('should work with regions without states', () => { -// expect(createActor(flatParallelMachine).getSnapshot().value).toEqual({ -// foo: {}, -// bar: {}, -// baz: 'one', -// }); -// }); -// -// it('should work with regions without states', () => { -// const actorRef = createActor(flatParallelMachine).start(); -// actorRef.send({ type: 'E' }); + +// const actorRef = createActor(parallelMachine).start(); + // expect(actorRef.getSnapshot().value).toEqual({ -// foo: {}, -// bar: {}, -// baz: 'two', +// state1: {}, +// state2: {}, // }); // }); -// -// it('should properly transition to relative substate', () => { -// const actorRef = createActor(composerMachine).start(); -// actorRef.send({ -// type: 'singleClickActivity', -// }); -// -// expect(actorRef.getSnapshot().value).toEqual({ -// ReadOnly: { -// StructureEdit: { -// SelectionStatus: 'SelectedActivity', -// ClipboardStatus: 'Empty', -// }, + +// it('should support invoking child machines', () => { +// const childMachine = createMachine({ +// id: 'child', +// initial: 'idle', +// states: { +// idle: {}, // }, // }); -// }); -// -// it('should properly transition according to entry events on an initial state', () => { -// const machine = createMachine({ -// type: 'parallel', + +// const parentMachine = createMachine({ +// id: 'parent', +// initial: 'active', // states: { -// OUTER1: { -// initial: 'B', -// states: { -// A: {}, -// B: { -// entry: raise({ type: 'CLEAR' }), -// }, -// }, -// }, -// OUTER2: { -// type: 'parallel', -// states: { -// INNER1: { -// initial: 'ON', -// states: { -// OFF: {}, -// ON: { -// on: { -// CLEAR: 'OFF', -// }, -// }, -// }, -// }, -// INNER2: { -// initial: 'OFF', -// states: { -// OFF: {}, -// ON: {}, -// }, -// }, +// active: { +// invoke: { +// id: 'child', +// src: childMachine, // }, // }, // }, // }); -// expect(createActor(machine).getSnapshot().value).toEqual({ -// OUTER1: 'B', -// OUTER2: { -// INNER1: 'OFF', -// INNER2: 'OFF', -// }, -// }); -// }); -// -// it('should properly transition when raising events for a parallel state', () => { -// const actorRef = createActor(raisingParallelMachine).start(); -// actorRef.send({ -// type: 'EVENT_OUTER1_B', -// }); -// + +// const actorRef = createActor(parentMachine).start(); + // expect(actorRef.getSnapshot().value).toEqual({ -// OUTER1: 'B', -// OUTER2: { -// INNER1: 'ON', -// INNER2: 'ON', +// active: { +// child: 'idle', // }, // }); // }); -// -// it('should handle simultaneous orthogonal transitions', () => { -// type Events = { type: 'CHANGE'; value: string } | { type: 'SAVE' }; -// const simultaneousMachine = createMachine({ -// /** @xstate-layout N4IgpgJg5mDOIC5QE8CGBbANgUQgSwBcB7AJwDpJC8A7KAYgGEAJAQQDkBxbAbQAYBdRKAAORWFSLUhIAB6IAjAA4AzGWWKATMoDs2gCyLtG+b20A2ADQhkiRfLIbTGzXoCc7s670aAvj6toWLiEpGSwBKgEAK6wZFHUsKgAbpB0AMosAGo8AtKi4gR4ktJyCPKuGmQArEpmZvLKeka8plUBiMpVrmRKRoqunVXKyryNfgEYOPjE5OGRMWHJqczsXHyCSCD5ElKbpeWVNYp1DU2Ore0II90aGvV6Xbe88lVVfv4g1EQQcNKBUyESHkxDsSogALQaPSXcFVMiuRQtZ6jcrKCpQ8bWSbBGYUaY0KDAgpFXagUo1MhmXh2MyKB7U+RmNGXEyqJm8LTaDnKMyvHSY-440JzaLwTbbQrFPaIKGXW6VUaKOkabTKeR6RmuMwC7HTYURUVxBJLCBE0HShBGS5quG8bx6dTuZxKxQ6oJ62YGhaJFKm8UgyWk2QdcpkLlVUzPOw8xzKa3ybTVe2Oiou7TvHxAA */ -// types: {} as { context: { value: string }; events: Events }, -// id: 'yamlEditor', -// type: 'parallel', -// context: { -// value: '', -// }, + +// it('should support sending events from child to parent', () => { +// const childMachine = createMachine({ +// id: 'child', +// initial: 'idle', // states: { -// editing: { +// idle: { // on: { -// CHANGE: { -// actions: assign({ -// value: ({ event }) => event.value, -// }), -// }, -// }, -// }, -// status: { -// initial: 'unsaved', -// states: { -// unsaved: { -// on: { -// SAVE: { -// target: 'saved', -// actions: 'save', -// }, -// }, -// }, -// saved: { -// on: { -// CHANGE: 'unsaved', -// }, +// PING: { +// actions: sendParent({ type: 'PING_RECEIVED' }), // }, // }, // }, // }, // }); -// -// const actorRef = createActor(simultaneousMachine).start(); -// actorRef.send({ -// type: 'SAVE', -// }); -// actorRef.send({ -// type: 'CHANGE', -// value: 'something', -// }); -// -// expect(actorRef.getSnapshot().value).toEqual({ -// editing: {}, -// status: 'unsaved', -// }); -// -// expect(actorRef.getSnapshot().context).toEqual({ -// value: 'something', -// }); -// }); -// -// it('should execute actions of the initial transition of a parallel region when entering the initial state nodes of a machine', () => { -// const spy = jest.fn(); -// -// const machine = createMachine({ -// type: 'parallel', + +// const parentMachine = createMachine({ +// id: 'parent', +// initial: 'active', // states: { -// a: { -// initial: { -// target: 'a1', -// actions: spy, -// }, -// states: { -// a1: {}, +// active: { +// invoke: { +// id: 'child', +// src: childMachine, // }, -// }, -// }, -// }); -// -// createActor(machine).start(); -// -// expect(spy).toHaveBeenCalledTimes(1); -// }); -// -// it('should execute actions of the initial transition of a parallel region when the parallel state is targeted with an explicit transition', () => { -// const spy = jest.fn(); -// -// const machine = createMachine({ -// initial: 'a', -// states: { -// a: { // on: { -// NEXT: 'b', -// }, -// }, -// b: { -// type: 'parallel', -// states: { -// c: { -// initial: { -// target: 'c1', -// actions: spy, -// }, -// states: { -// c1: {}, -// }, -// }, +// PING_RECEIVED: 'pinged', // }, // }, +// pinged: {}, // }, // }); -// -// const actorRef = createActor(machine).start(); -// -// actorRef.send({ type: 'NEXT' }); -// -// expect(spy).toHaveBeenCalledTimes(1); -// }); -// -// describe('transitions with nested parallel states', () => { -// it('should properly transition when in a simple nested state', () => { -// const actorRef = createActor(nestedParallelState).start(); -// actorRef.send({ -// type: 'EVENT_SIMPLE', -// }); -// actorRef.send({ -// type: 'EVENT_STATE_NTJ0_WORK', -// }); -// -// expect(actorRef.getSnapshot().value).toEqual({ -// OUTER1: { -// STATE_ON: { -// STATE_NTJ0: 'STATE_WORKING_0', -// STATE_NTJ1: 'STATE_IDLE_1', -// }, -// }, -// OUTER2: 'STATE_ON_SIMPLE', -// }); -// }); -// -// it('should properly transition when in a complex nested state', () => { -// const actorRef = createActor(nestedParallelState).start(); -// actorRef.send({ -// type: 'EVENT_COMPLEX', -// }); -// actorRef.send({ -// type: 'EVENT_STATE_NTJ0_WORK', -// }); -// -// expect(actorRef.getSnapshot().value).toEqual({ -// OUTER1: { -// STATE_ON: { -// STATE_NTJ0: 'STATE_WORKING_0', -// STATE_NTJ1: 'STATE_IDLE_1', -// }, -// }, -// OUTER2: { -// STATE_ON_COMPLEX: { -// STATE_INNER1: 'STATE_OFF', -// STATE_INNER2: 'STATE_OFF', -// }, -// }, -// }); -// }); + +// const actorRef = createActor(parentMachine).start(); + +// actorRef.send({ type: 'PING' }); + +// expect(actorRef.getSnapshot().value).toEqual('pinged'); // }); -// -// // https://github.com/statelyai/xstate/issues/191 -// describe('nested flat parallel states', () => { -// const machine = createMachine({ -// initial: 'A', + +// it('should support sending events from parent to child', () => { +// const childMachine = createMachine({ +// id: 'child', +// initial: 'idle', // states: { -// A: { +// idle: { // on: { -// 'to-B': 'B', -// }, -// }, -// B: { -// type: 'parallel', -// states: { -// C: {}, -// D: {}, +// PING: 'pinged', // }, // }, +// pinged: {}, // }, -// on: { -// 'to-A': '.A', -// }, -// }); -// -// it('should represent the flat nested parallel states in the state value', () => { -// const actorRef = createActor(machine).start(); -// actorRef.send({ -// type: 'to-B', -// }); -// -// expect(actorRef.getSnapshot().value).toEqual({ -// B: { -// C: {}, -// D: {}, -// }, -// }); -// }); -// }); -// -// describe('deep flat parallel states', () => { -// it('should properly evaluate deep flat parallel states', () => { -// const actorRef = createActor(deepFlatParallelMachine).start(); -// -// actorRef.send({ type: 'a' }); -// actorRef.send({ type: 'c' }); -// actorRef.send({ type: 'b' }); -// -// expect(actorRef.getSnapshot().value).toEqual({ -// V: { -// B: { -// BB: { -// BBB_A: {}, -// BBB_B: {}, -// }, -// }, -// }, -// X: {}, -// }); -// }); -// -// it('should not overlap resolved state nodes in state resolution', () => { -// const machine = createMachine({ -// /** @xstate-layout N4IgpgJg5mDOIC5QAcCWywBtUDswDoAzAe2IGIBVABQBEBBAFQFEBtABgF1EVjZUAXVMRzcQAD0QBGAJwBmfGwDsbAKwzFc2YoBMbAGwAaEAE9E0yfkUbJbABxrtsvbb3aAvm6NoM2PPgBGAIYATpS0jKycosi8AkIiSOKIbEamCGweniA4xBBw0ehYuGDRsYLCohIIALTa0tIKqnqSLSqysjLaKqmI1ZKKACz4egMqttq24yrOnR5ehb4EJMSlfOUJoFWyKirDI7KTtmxO9T0I2oq77WySevpsDwMD0nMg3kV+QcGrcRWJVbU1I1pi1JG0OtIumdqrZFPhZNo6pInpIJgM2NI9K93osAiF8KgIJgSokYmt4pVerp5A8Qa12p1uiZEDp8NIXCptDIVAN2jNsQtinjgniAF4-daUhC3fAqHSIvRWaQaaQ7RRnLlsfA2S6uCaKPQjaaZNxAA */ -// id: 'pipeline', -// type: 'parallel', -// states: { -// foo: { -// on: { -// UPDATE: { -// actions: () => { -// /* do nothing */ -// }, -// }, -// }, -// }, -// bar: { -// on: { -// UPDATE: '.baz', -// }, -// initial: 'idle', -// states: { -// idle: {}, -// baz: {}, -// }, -// }, -// }, -// }); -// -// const actorRef = createActor(machine).start(); -// expect(() => { -// actorRef.send({ -// type: 'UPDATE', -// }); -// }).not.toThrow(); -// }); -// }); -// -// describe('other', () => { -// // https://github.com/statelyai/xstate/issues/518 -// it('regions should be able to transition to orthogonal regions', () => { -// const testMachine = createMachine({ -// /** @xstate-layout N4IgpgJg5mDOIC5QAoC2BDAxgCwJYDswBKAOgFkx8BXEgYQBsB7WSAYgBdGop6wBtAAwBdRKAAOzXO1yN8okAA9EAgDQgAnsoC+WtWix5CpCtRIB5MZTaduvQSKQgJsKTLmOlCVRu26Q+nAJickoaCysIVihGAAJOGIh0WGwAI0Z0ACcIe3lnV1l5TwBGAFYSACYAFhKBEoBOcrqAdgBmFoAOFsrKtU0EEoA2IpJKgXKipvGBJqa69pKdP3xGCDh5AMNiXMlpAo9EAFoWpsqSEqK644aWgaaBZt7DidOB6vby9vfB9ovynT0MIEjCQAAroGDwRx5XbuUCecoDdoVCYDARjeYYlqPBBFUoVOpjKoDcpVBEff7+QGbUhgiEkACCaSo7G2LhhhUQLSKA2RTVR6JKmOx5TGJCGXJaBJaJQRk0qFI2QRp4LgJAAIklUuksqz8rDFJzubz+R9BYKsT4EG0miQimM+QIijd2nMFn5FcCTFRdez9ggjgizhcruUbncHpaDu0bS0SXUit0ih9RnUBgqqUqQqYGMxID63Bz-eUmkjzpdZqHbvcmsKhiRpqiPuVBnbjumDJmveZLIQIPm9nDDsXS8GK2Hq9jJWVBoNKpcSjMus1FlogA */ -// type: 'parallel', -// states: { -// Pages: { -// initial: 'About', -// states: { -// About: { -// id: 'About', -// }, -// Dashboard: { -// id: 'Dashboard', -// }, -// }, -// }, -// Menu: { -// initial: 'Closed', -// states: { -// Closed: { -// id: 'Closed', -// on: { -// toggle: 'Opened', -// }, -// }, -// Opened: { -// id: 'Opened', -// on: { -// toggle: 'Closed', -// 'go to dashboard': { -// target: ['#Dashboard', '#Opened'], -// }, -// }, -// }, -// }, -// }, -// }, -// }); -// -// const actorRef = createActor(testMachine).start(); -// -// actorRef.send({ type: 'toggle' }); -// actorRef.send({ type: 'go to dashboard' }); -// -// expect( -// actorRef.getSnapshot().matches({ Menu: 'Opened', Pages: 'Dashboard' }) -// ).toBe(true); -// }); -// -// // https://github.com/statelyai/xstate/issues/531 -// it('should calculate the entry set for reentering transitions in parallel states', () => { -// const testMachine = createMachine({ -// types: {} as { context: { log: string[] } }, -// id: 'test', -// context: { log: [] }, -// type: 'parallel', -// states: { -// foo: { -// initial: 'foobar', -// states: { -// foobar: { -// on: { -// GOTO_FOOBAZ: 'foobaz', -// }, -// }, -// foobaz: { -// entry: assign({ -// log: ({ context }) => [...context.log, 'entered foobaz'], -// }), -// on: { -// GOTO_FOOBAZ: { -// target: 'foobaz', -// reenter: true, -// }, -// }, -// }, -// }, -// }, -// bar: {}, -// }, -// }); -// -// const actorRef = createActor(testMachine).start(); -// -// actorRef.send({ -// type: 'GOTO_FOOBAZ', -// }); -// actorRef.send({ -// type: 'GOTO_FOOBAZ', -// }); -// -// expect(actorRef.getSnapshot().context.log.length).toBe(2); // }); -// }); -// -// it('should raise a "xstate.done.state.*" event when all child states reach final state', (done) => { -// const machine = createMachine({ -// id: 'test', -// initial: 'p', + +// const parentMachine = createMachine({ +// id: 'parent', +// initial: 'active', // states: { -// p: { -// type: 'parallel', -// states: { -// a: { -// initial: 'idle', -// states: { -// idle: { -// on: { -// FINISH: 'finished', -// }, -// }, -// finished: { -// type: 'final', -// }, -// }, -// }, -// b: { -// initial: 'idle', -// states: { -// idle: { -// on: { -// FINISH: 'finished', -// }, -// }, -// finished: { -// type: 'final', -// }, -// }, -// }, -// c: { -// initial: 'idle', -// states: { -// idle: { -// on: { -// FINISH: 'finished', -// }, -// }, -// finished: { -// type: 'final', -// }, -// }, -// }, +// active: { +// invoke: { +// id: 'child', +// src: childMachine, // }, -// onDone: 'success', -// }, -// success: { -// type: 'final', -// }, -// }, -// }); -// -// const service = createActor(machine); -// service.subscribe({ -// complete: () => { -// done(); -// }, -// }); -// service.start(); -// -// service.send({ type: 'FINISH' }); -// }); -// -// it('should raise a "xstate.done.state.*" event when a pseudostate of a history type is directly on a parallel state', () => { -// const machine = createMachine({ -// initial: 'parallelSteps', -// states: { -// parallelSteps: { -// type: 'parallel', -// states: { -// hist: { -// type: 'history', -// }, -// one: { -// initial: 'wait_one', -// states: { -// wait_one: { -// on: { -// finish_one: { -// target: 'done', -// }, -// }, -// }, -// done: { -// type: 'final', -// }, -// }, -// }, -// two: { -// initial: 'wait_two', -// states: { -// wait_two: { -// on: { -// finish_two: { -// target: 'done', -// }, -// }, -// }, -// done: { -// type: 'final', -// }, -// }, +// on: { +// SEND_PING: { +// actions: sendTo('child', 'PING'), // }, // }, -// onDone: 'finished', // }, -// finished: {}, // }, // }); -// -// const service = createActor(machine).start(); -// -// service.send({ type: 'finish_one' }); -// service.send({ type: 'finish_two' }); -// -// expect(service.getSnapshot().value).toBe('finished'); -// }); -// -// describe('invoke', () => { -// it('should gradually become permissions actor system', () => { -// const bluetoothPermissionMachine = setup({ -// actors: { -// checkPermission: fromPromise(async () => { -// const result = await Promise.resolve('denied'); -// return assign({ result }); -// }), -// }, -// actions: { -// spawnFetcher: assign(({ spawn }) => { -// return { -// child: spawn('checkPermission'), -// }; -// }), -// }, -// types: {} as { -// events: -// | { type: 'FORWARD_DEC' } -// | { type: 'triggerPermissionRequest' }; -// -// context: { -// triggered: boolean; -// child: /*TODO fix type */ any | undefined; -// }; -// }, -// }).createMachine({ -// context: { triggered: false, child: undefined }, -// initial: 'init', -// states: { -// init: { -// on: { -// triggerPermissionRequest: { -// actions: [ -// log('triggerPermissionRequest'), -// assign({ triggered: true }), -// sendTo('child', { type: 'triggerPermissionRequest' }), -// // 'checkPermission', -// ], -// }, -// FORWARD_DEC: { -// actions: [ -// sendParent({ type: 'DEC' }), -// sendParent({ type: 'DEC' }), -// sendParent({ type: 'DEC' }), -// ], -// }, -// }, -// }, -// }, -// }); -// -// const permissionMonitoringMachine = createMachine( -// { -// id: 'parent', -// types: {} as { -// context: { count: number }; -// actors: { -// src: 'bluetoothPermission'; -// logic: typeof bluetoothPermissionMachine; -// }; -// }, -// context: { count: 0 }, -// initial: 'start', -// states: { -// start: { -// invoke: { -// src: 'bluetoothPermission', -// id: 'bluetoothPermissionId', -// }, -// always: { -// target: 'stop', -// guard: ({ context }) => context.count === -3, -// }, -// on: { -// DEC: { -// actions: assign({ -// count: ({ context }) => context.count - 1, -// }), -// }, -// FORWARD_DEC: { -// actions: sendTo('bluetoothPermissionId', { -// type: 'triggerPermissionRequest', -// }), -// }, -// }, -// }, -// stop: { -// type: 'final', -// }, -// }, -// }, -// { -// actors: { -// bluetoothPermission: bluetoothPermissionMachine, -// }, -// } -// ); -// -// const actorRef = createActor(permissionMonitoringMachine).start(); -// actorRef.send({ type: 'FORWARD_DEC' }); -// -// // 1. The 'parent' machine will not do anything (inert transition) -// // 2. The 'FORWARD_DEC' event will be "forwarded" to the child machine -// // 3. On the child machine, the 'FORWARD_DEC' event sends the 'DEC' action to the parent thrice -// // 4. The context of the 'parent' machine will be updated from 0 to -3 -// // expect(actorRef.getSnapshot().context).toEqual({ count: -3 }); -// // expect( -// // actorRef.getSnapshot().children.bluetoothPermissionId?.getSnapshot() -// // .context -// // ).toEqual({ -// // triggered: true, -// // result: 'something', -// // }); -// }); -// }); -// }); -// -// /* -// Prompt for LLM: -// -// Given these passing tests for xstate invoke and xstate parallel machines, can you please answer my following questions? -// -// 1) please create a test to see whether it is possible to do the following: -// create a parallel machine with 2 parallel states -// in state 1, raise an event to the "parent" machine that will be handled by two invoked actors in the second parallel state. -// write a complete test in this style, I will report the findings of the test to the LLM and we can iterate. -// */ -// -// // claude -// -// // describe('assumptions', () => { -// // it('should support parallel states', () => { -// // const parallelMachine = createMachine({ -// // id: 'parent', -// // type: 'parallel', -// // states: { -// // state1: {}, -// // state2: {}, -// // }, -// // }); -// -// // const actorRef = createActor(parallelMachine).start(); -// -// // expect(actorRef.getSnapshot().value).toEqual({ -// // state1: {}, -// // state2: {}, -// // }); -// // }); -// -// // it('should support invoking child machines', () => { -// // const childMachine = createMachine({ -// // id: 'child', -// // initial: 'idle', -// // states: { -// // idle: {}, -// // }, -// // }); -// -// // const parentMachine = createMachine({ -// // id: 'parent', -// // initial: 'active', -// // states: { -// // active: { -// // invoke: { -// // id: 'child', -// // src: childMachine, -// // }, -// // }, -// // }, -// // }); -// -// // const actorRef = createActor(parentMachine).start(); -// -// // expect(actorRef.getSnapshot().value).toEqual({ -// // active: { -// // child: 'idle', -// // }, -// // }); -// // }); -// -// // it('should support sending events from child to parent', () => { -// // const childMachine = createMachine({ -// // id: 'child', -// // initial: 'idle', -// // states: { -// // idle: { -// // on: { -// // PING: { -// // actions: sendParent({ type: 'PING_RECEIVED' }), -// // }, -// // }, -// // }, -// // }, -// // }); -// -// // const parentMachine = createMachine({ -// // id: 'parent', -// // initial: 'active', -// // states: { -// // active: { -// // invoke: { -// // id: 'child', -// // src: childMachine, -// // }, -// // on: { -// // PING_RECEIVED: 'pinged', -// // }, -// // }, -// // pinged: {}, -// // }, -// // }); -// -// // const actorRef = createActor(parentMachine).start(); -// -// // actorRef.send({ type: 'PING' }); -// -// // expect(actorRef.getSnapshot().value).toEqual('pinged'); -// // }); -// -// // it('should support sending events from parent to child', () => { -// // const childMachine = createMachine({ -// // id: 'child', -// // initial: 'idle', -// // states: { -// // idle: { -// // on: { -// // PING: 'pinged', -// // }, -// // }, -// // pinged: {}, -// // }, -// // }); -// -// // const parentMachine = createMachine({ -// // id: 'parent', -// // initial: 'active', -// // states: { -// // active: { -// // invoke: { -// // id: 'child', -// // src: childMachine, -// // }, -// // on: { -// // SEND_PING: { -// // actions: sendTo('child', 'PING'), -// // }, -// // }, -// // }, -// // }, -// // }); -// -// // const actorRef = createActor(parentMachine).start(); -// -// // actorRef.send({ type: 'SEND_PING' }); -// -// // expect(actorRef.getSnapshot().value).toEqual({ -// // active: { -// // child: 'pinged', -// // }, -// // }); -// // }); -// // }); -// -// it('should be able to communicate with the parent using params', () => { -// type ParentEvent = { type: 'FOO' } | { type: 'triggerFlob' }; -// -// const childMachine = setup({ -// types: {} as { -// input: { -// parent?: ActorRef, ParentEvent>; -// }; -// context: { -// parent?: ActorRef, ParentEvent>; -// }; -// }, -// actions: { -// mySendParent: enqueueActions( -// ({ context, enqueue }, event: ParentEvent) => { -// if (!context.parent) { -// // it's here just for illustration purposes -// console.log( -// 'WARN: an attempt to send an event to a non-existent parent' -// ); -// return; -// } -// enqueue.sendTo(context.parent, event); -// } -// ), -// }, -// }).createMachine({ -// context: ({ input }) => ({ parent: input.parent }), -// -// on: { -// flob: { -// actions: { -// type: 'mySendParent', -// params: { -// type: 'FOO', -// }, -// }, -// }, -// }, -// }); -// -// const spy = jest.fn(); -// -// const parentMachine = setup({ -// types: {} as { context: { foo: string }; events: ParentEvent }, -// actors: { -// childMachine, -// }, -// }).createMachine({ -// context: { foo: 'bar' }, -// on: { -// FOO: { -// actions: spy, -// }, -// triggerFlob: { -// actions: [ -// assign({ foo: 'baz' }), -// /* don't format */ -// sendTo('someChild', { type: 'flob' }), -// ], -// }, -// }, -// invoke: { -// src: 'childMachine', -// id: 'someChild', -// input: ({ self }) => ({ parent: self }), -// }, -// }); -// -// const actorRef = createActor(parentMachine).start(); -// actorRef.send({ type: 'triggerFlob' }); -// expect(actorRef.getSnapshot().context).toEqual({ foo: 'baz' }); -// -// expect(spy).toHaveBeenCalledTimes(1); -// }); -// -// it.skip('should handle events raised from one parallel state by invoking actors in another parallel state', () => { -// const childMachine1 = createMachine({ -// id: 'child1', -// initial: 'idle', -// states: { -// idle: { -// on: { -// PING: { -// actions: [ -// sendParent({ type: 'PING_RECEIVED' }), -// () => console.log('Child1: Sent PING_RECEIVED event to parent'), -// ], -// }, -// }, -// }, -// }, -// }); -// -// const childMachine2 = createMachine({ -// id: 'child2', -// initial: 'waiting', -// states: { -// waiting: { -// on: { -// PING_RECEIVED: { -// target: 'pinged', -// actions: () => -// console.log( -// 'Child2: Received PING_RECEIVED event, transitioning to pinged state' -// ), -// }, -// }, -// }, -// pinged: { -// type: 'final', -// }, -// }, -// }); -// -// const parentMachine = createMachine({ -// /** @xstate-layout N4IgpgJg5mDOIC5QAcCGAnMA7ALgYgAUBJAOQHEB9AJQFEBhGogNRoBEBtABgF1EUB7WAEscQ-lj4gAHogC0ARgCcAJgB0nAGwBWAOwAWZfKVH5OgDQgAnonmctqjY+0Bmec+06jOgL7eLaTFxCUjIuXiQQZEERMQkImQRnZ0VVVz09Uy09LQ0ADi1FDQtrBHks1WU9RWcMjSNOHVy7Xz8QLH4IOEkA7Bxu6NFxSQSFRWLEZ2U1DSnkgvdKnS1nX38MXtVYHFQcMHl+4UG40AS9cytEPU4UxWzXOy15A0UdH1ae3E3t3eUDmKH4ohlGMLggqjoKho9K4ZnYlkYVu91p8tjswGpUABjHD8dD7CJRQ6xYaIV7jBBaXJ6VSUnTKGY6RzOTiTVaRZE4L5ojHY3G-AkDYmAhBk0F5G6aelU5SS7ItbxAA */ -// id: 'parent', -// type: 'parallel', -// states: { -// state1: { -// invoke: { -// id: 'child1', -// src: childMachine1, -// }, -// }, -// state2: { -// type: 'parallel', -// states: { -// actor1: { -// invoke: { -// id: 'child2_1', -// src: childMachine2, -// }, -// }, -// actor2: { -// invoke: { -// id: 'child2_2', -// src: childMachine2, -// }, -// }, -// }, -// }, -// }, -// on: { -// PING_RECEIVED: { -// actions: [ -// sendTo('child1', { type: 'PING_RECEIVED' }), -// sendTo('child2_1', { type: 'PING_RECEIVED' }), -// sendTo('child2_2', { type: 'PING_RECEIVED' }), -// () => -// console.log( -// 'Parent: Received PING_RECEIVED event, sending it to child2 actors' -// ), -// ], -// }, -// PING: { -// // send this to child2_1 and child2_2 -// actions: [ -// () => console.log('Parent: Sent PING event to child2 actors'), -// sendTo('child2_1', { type: 'PING_RECEIVED' }), -// sendTo('child2_2', { type: 'PING_RECEIVED' }), -// ], -// }, -// }, -// }); -// -// const actorRef = createActor(parentMachine).start(); -// -// // Initial state -// console.log('Initial state:', actorRef.getSnapshot().value); -// // Expected output: -// // Initial state: { -// // state1: {}, -// // state2: { -// // actor1: 'waiting', -// // actor2: 'waiting' -// // } -// // } -// -// // Send 'PING' event to child1 -// actorRef.send({ type: 'PING' }); -// -// // After sending 'PING' event -// console.log('After sending PING event:', actorRef.getSnapshot().value); -// // Expected output: -// // After sending PING event: { -// // state1: {}, -// // state2: { -// // actor1: 'pinged', -// // actor2: 'pinged' -// // } -// // } -// -// expect(actorRef.getSnapshot().value).toEqual({ -// state1: {}, -// state2: { -// actor1: 'pinged', -// actor2: 'pinged', -// }, -// }); -// }); -// -// it('should return actions for parallel machines', () => { -// const actual: string[] = []; -// const machine = setup({}).createMachine({ -// type: 'parallel', -// states: { -// permission: { -// on: { -// foo: { -// target: '.a2', -// }, -// }, -// initial: 'a1', -// states: { -// a1: { -// on: { -// CHANGE: { -// target: 'a2', -// actions: [ -// () => actual.push('do_a2'), -// () => actual.push('another_do_a2'), -// ], -// }, -// }, -// entry: () => actual.push('enter_a1'), -// exit: () => actual.push('exit_a1'), -// }, -// a2: { -// entry: () => actual.push('enter_a2'), -// exit: () => actual.push('exit_a2'), -// }, -// }, -// entry: () => actual.push('enter_a'), -// exit: () => actual.push('exit_a'), -// }, -// b: { -// initial: 'b1', -// states: { -// b1: { -// on: { -// CHANGE: { target: 'b2', actions: () => actual.push('do_b2') }, -// }, -// entry: () => actual.push('enter_b1'), -// exit: () => actual.push('exit_b1'), -// }, -// b2: { -// entry: () => actual.push('enter_b2'), -// exit: () => actual.push('exit_b2'), -// }, -// }, -// entry: () => actual.push('enter_b'), -// exit: () => actual.push('exit_b'), + +// const actorRef = createActor(parentMachine).start(); + +// actorRef.send({ type: 'SEND_PING' }); + +// expect(actorRef.getSnapshot().value).toEqual({ +// active: { +// child: 'pinged', // }, -// }, -// }); -// -// const actor = createActor(machine).start(); -// expect(actor.getSnapshot().value).toStrictEqual({ -// permission: 'a1', -// b: 'b1', -// }); -// -// actor.send({ type: 'foo' }); -// expect(actor.getSnapshot().value).toStrictEqual({ -// permission: 'a2', -// b: 'b1', +// }); // }); -// -// // actual.length = 0; -// -// // actor.send({ type: 'CHANGE' }); -// -// // expect(actual).toEqual([ -// // 'exit_b1', // reverse document order -// // 'exit_a1', -// // 'do_a2', -// // 'another_do_a2', -// // 'do_b2', -// // 'enter_a2', -// // 'enter_b2', -// // ]); // }); + +it('should be able to communicate with the parent using params', () => { + type ParentEvent = { type: 'FOO' } | { type: 'triggerFlob' }; + + const childMachine = setup({ + types: {} as { + input: { + parent?: ActorRef, ParentEvent>; + }; + context: { + parent?: ActorRef, ParentEvent>; + }; + }, + actions: { + mySendParent: enqueueActions( + ({ context, enqueue }, event: ParentEvent) => { + if (!context.parent) { + // it's here just for illustration purposes + console.log( + 'WARN: an attempt to send an event to a non-existent parent' + ); + return; + } + enqueue.sendTo(context.parent, event); + } + ), + }, + }).createMachine({ + context: ({ input }) => ({ parent: input.parent }), + + on: { + flob: { + actions: { + type: 'mySendParent', + params: { + type: 'FOO', + }, + }, + }, + }, + }); + + const spy = jest.fn(); + + const parentMachine = setup({ + types: {} as { context: { foo: string }; events: ParentEvent }, + actors: { + childMachine, + }, + }).createMachine({ + context: { foo: 'bar' }, + on: { + FOO: { + actions: spy, + }, + triggerFlob: { + actions: [ + assign({ foo: 'baz' }), + /* don't format */ + sendTo('someChild', { type: 'flob' }), + ], + }, + }, + invoke: { + src: 'childMachine', + id: 'someChild', + input: ({ self }) => ({ parent: self }), + }, + }); + + const actorRef = createActor(parentMachine).start(); + actorRef.send({ type: 'triggerFlob' }); + expect(actorRef.getSnapshot().context).toEqual({ foo: 'baz' }); + + expect(spy).toHaveBeenCalledTimes(1); +}); + +it.skip('should handle events raised from one parallel state by invoking actors in another parallel state', () => { + const childMachine1 = createMachine({ + id: 'child1', + initial: 'idle', + states: { + idle: { + on: { + PING: { + actions: [ + sendParent({ type: 'PING_RECEIVED' }), + () => console.log('Child1: Sent PING_RECEIVED event to parent'), + ], + }, + }, + }, + }, + }); + + const childMachine2 = createMachine({ + id: 'child2', + initial: 'waiting', + states: { + waiting: { + on: { + PING_RECEIVED: { + target: 'pinged', + actions: () => + console.log( + 'Child2: Received PING_RECEIVED event, transitioning to pinged state' + ), + }, + }, + }, + pinged: { + type: 'final', + }, + }, + }); + + const parentMachine = createMachine({ + /** @xstate-layout N4IgpgJg5mDOIC5QAcCGAnMA7ALgYgAUBJAOQHEB9AJQFEBhGogNRoBEBtABgF1EUB7WAEscQ-lj4gAHogC0ARgCcAJgB0nAGwBWAOwAWZfKVH5OgDQgAnonmctqjY+0Bmec+06jOgL7eLaTFxCUjIuXiQQZEERMQkImQRnZ0VVVz09Uy09LQ0ADi1FDQtrBHks1WU9RWcMjSNOHVy7Xz8QLH4IOEkA7Bxu6NFxSQSFRWLEZ2U1DSnkgvdKnS1nX38MXtVYHFQcMHl+4UG40AS9cytEPU4UxWzXOy15A0UdH1ae3E3t3eUDmKH4ohlGMLggqjoKho9K4ZnYlkYVu91p8tjswGpUABjHD8dD7CJRQ6xYaIV7jBBaXJ6VSUnTKGY6RzOTiTVaRZE4L5ojHY3G-AkDYmAhBk0F5G6aelU5SS7ItbxAA */ + id: 'parent', + type: 'parallel', + states: { + state1: { + invoke: { + id: 'child1', + src: childMachine1, + }, + }, + state2: { + type: 'parallel', + states: { + actor1: { + invoke: { + id: 'child2_1', + src: childMachine2, + }, + }, + actor2: { + invoke: { + id: 'child2_2', + src: childMachine2, + }, + }, + }, + }, + }, + on: { + PING_RECEIVED: { + actions: [ + sendTo('child1', { type: 'PING_RECEIVED' }), + sendTo('child2_1', { type: 'PING_RECEIVED' }), + sendTo('child2_2', { type: 'PING_RECEIVED' }), + () => + console.log( + 'Parent: Received PING_RECEIVED event, sending it to child2 actors' + ), + ], + }, + PING: { + // send this to child2_1 and child2_2 + actions: [ + () => console.log('Parent: Sent PING event to child2 actors'), + sendTo('child2_1', { type: 'PING_RECEIVED' }), + sendTo('child2_2', { type: 'PING_RECEIVED' }), + ], + }, + }, + }); + + const actorRef = createActor(parentMachine).start(); + + // Initial state + console.log('Initial state:', actorRef.getSnapshot().value); + // Expected output: + // Initial state: { + // state1: {}, + // state2: { + // actor1: 'waiting', + // actor2: 'waiting' + // } + // } + + // Send 'PING' event to child1 + actorRef.send({ type: 'PING' }); + + // After sending 'PING' event + console.log('After sending PING event:', actorRef.getSnapshot().value); + // Expected output: + // After sending PING event: { + // state1: {}, + // state2: { + // actor1: 'pinged', + // actor2: 'pinged' + // } + // } + + expect(actorRef.getSnapshot().value).toEqual({ + state1: {}, + state2: { + actor1: 'pinged', + actor2: 'pinged', + }, + }); +}); + +it('should return actions for parallel machines', () => { + const actual: string[] = []; + const machine = setup({}).createMachine({ + type: 'parallel', + states: { + permission: { + on: { + foo: { + target: '.a2', + }, + }, + initial: 'a1', + states: { + a1: { + on: { + CHANGE: { + target: 'a2', + actions: [ + () => actual.push('do_a2'), + () => actual.push('another_do_a2'), + ], + }, + }, + entry: () => actual.push('enter_a1'), + exit: () => actual.push('exit_a1'), + }, + a2: { + entry: () => actual.push('enter_a2'), + exit: () => actual.push('exit_a2'), + }, + }, + entry: () => actual.push('enter_a'), + exit: () => actual.push('exit_a'), + }, + b: { + initial: 'b1', + states: { + b1: { + on: { + CHANGE: { target: 'b2', actions: () => actual.push('do_b2') }, + }, + entry: () => actual.push('enter_b1'), + exit: () => actual.push('exit_b1'), + }, + b2: { + entry: () => actual.push('enter_b2'), + exit: () => actual.push('exit_b2'), + }, + }, + entry: () => actual.push('enter_b'), + exit: () => actual.push('exit_b'), + }, + }, + }); + + const actor = createActor(machine).start(); + expect(actor.getSnapshot().value).toStrictEqual({ + permission: 'a1', + b: 'b1', + }); + + actor.send({ type: 'foo' }); + expect(actor.getSnapshot().value).toStrictEqual({ + permission: 'a2', + b: 'b1', + }); + + // actual.length = 0; + + // actor.send({ type: 'CHANGE' }); + + // expect(actual).toEqual([ + // 'exit_b1', // reverse document order + // 'exit_a1', + // 'do_a2', + // 'another_do_a2', + // 'do_b2', + // 'enter_a2', + // 'enter_b2', + // ]); +});