Skip to content

Instantly share code, notes, and snippets.

@UberMouse
Created November 2, 2021 02:28
Show Gist options
  • Save UberMouse/2fcfc2d714e0eef68b2b2cc98d194600 to your computer and use it in GitHub Desktop.
Save UberMouse/2fcfc2d714e0eef68b2b2cc98d194600 to your computer and use it in GitHub Desktop.

@xstate/test model generation with zones

What is a zone?

A zone is an isolated portion of an @xstate/test model. A zone defines the events it uses, the event that causes it to be entered and the states within the zone

The zones entryEvent is added as an event handler to the zones parent initial state. If this is a root zone passed to buildTestModel this is the root state, if it's a sub zone (a state pointing at a zone), that is the initial state of the zone that state is contained in

Each state in a zone must contain a test property that validates that the state has been entered. At least one of the states requires it to be marked as the initial state of the zone with initial: true An event handler in a zone state can only refer to events defined inside of the zone

Defining a zone

A zone looks like this

{
  events: {
    EVENT_NAME: (model) => ... // some method call on the model that returns a Promise
    SOME_OTHER_EVENT: (model) => ...,
  },
  entryEvent: "EVENT_NAME", // the name of one of the events in the event object, this event must cause Rimu to transition to the initial state of the zone
  states: {
    stateName: {
      initial: true, // This indicates the initial state of the zone, what running the `entryEvent` handler (EVENT_NAME defined above) in the initial state of the parent zone would cause Rimu to transition to
      test: (model) => ... // Returns a Promise that resolves if the state has been entered correctly and rejects otherwise
      on: {
        SOME_OTHER_EVENT: "someOtherState" // indicate that having the test model run SOME_OTHER_EVENT in this state will cause Rimu to enter the someOtherState, validated by that states test function
      }
    },
    someOtherState: {
      test: (model) => ...
    }
  }
}

A zone can have a state that points at another zone, allowing separation of tests into multiple zones. This is as simple as defining a state like stateName: zone When a state refers to a zone like this the entry event to the zone is added to the initial state of the containing zone, transitioning to the initial state of the sub zone

Zones can be nested infinitely

What do the states in the built test model look like?

Assuming we have a configuration of zones like so

const zone1 = buildTestZone({
  events: {
    ZONE1_ENTRY: () => ...,
    ZONE1_TRANSITION: () => ...,
  },
  entryEvent: "ZONE1_ENTRY",
  states: {
    zone1Initial: {
      test: () => ...,
      initial: true,
      on: {
        ZONE1_TRANSITION: "zone1OtherState"
      }
    },
    zone1OtherState: {
      test: () => ...,
    }
  }
});

const subZone1 = buildTestZone({
  events: {
    SUB_ZONE1_ENTRY: () => ...,
    SUB_ZONE1_TRANSITION: () => ...,
  },
  entryEvent: "SUB_ZONE1_ENTRY",
  states: {
    subZone1Initial: {
      test: () => ...,
      initial: true,
      on: {
        SUB_ZONE1_TRANSITION: "subZone1OtherState"
      }
    },
    subZone1OtherState: {
      test: () => ...,
    }
  }
});

const zone2 = buildTestZone({
  events: {
    ZONE2_ENTRY: () => ...,
  },
  entryEvent: "ZONE2_ENTRY",
  states: {
    zone2Initial: {
      test: () => ...,
      initial: true,
    },
    zone2SubZone: subZone1
  }
});

const model = buildTestModel({
  rootState: {
    test: () => ...
  }
}, [zone1, zone2]);

then the following test model will be created

{
  initial: "rootState",
  states: {
    rootState: {
      on: {
        ZONE1_ENTRY: "zone1Initial",
        ZONE2_ENTRY: "zone2Initial"
      }
    },
    zone1Initial: {
      on: {
        ZONE1_TRANSITION: "zone1OtherState",
      }
    },
    zone1OtherState: {},
    zone2Initial: {
      initial: "zone2Initial",
      states: {
        zone2Initial: {
          on: {
            SUB_ZONE1_ENTRY: "subZone1Initial",
          }
        },
        subZone1Initial: {
          on: {
            SUB_ZONE1_TRANSITION: "subZone1OtherState"
          }
        },
        subZone1OtherState: {}
      }
    }
  }
}

with all of the events extracted from each zone (and recursively from sub zones) and supplied to the models withEvents function automatically

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment