"Missing config for event" using @xstate/test

155 views Asked by At

I'm testing a state machine using model-based testing using @xstate/test and @testing-library/react.

Basically, I'm testing this machine:

const itemDamagedMachine = createMachine({
  initial: 'newModal',
  context: {
    productScan: '',
    binScan: '',
  },
  states: {
    newModal: {
      initial: 'scanDamagedItem',
      states: {
        scanDamagedItem: {},
        scanDamagedBin: {},
        declareItemDamaged: {},
      },
    },
    closed: {},
  },
  on: {
    UPDATE_PRODUCT_SCAN: {
      actions: assign({
        productScan: 123456,
      }),
    },
    VALIDATE: {
      target: 'newModal.scanDamagedBin',
    },
    UNREADABLE: {
      target: 'newModal.scanDamagedBin',
    },
    CANCEL: {
      target: 'closed',
    },
    UPDATE_DAMAGED_BIN_SCAN: {
      actions: assign({
        binScan: 'PB_DAMAGED',
      }),
    },
  },
});

I'm then configuring the model, and testing it using const testPlans = itemDamagedModel.getSimplePathPlans();.

Everything seems to run smoothly with about 200 passing tests, but I'm having a few issues:

  • For each of my test and each of my event, I'm getting a warning Missing config for event "VALIDATE". I don't understand what it's supposed to mean.
  • All of my tests are validated even if I make typos on purpose in my model event. Sometimes the number of tests is reduced, but I would have hoped to see a few warnings when the model doesn't find a particular input or button.
  • The tests are all passing, even if I'm passing an empty div as my xstate/test rendered component.
1

There are 1 answers

0
luisbar On

I do not get the idea, but I have tested a component as follow:

First I have my machine:

import { createMachine, sendParent } from 'xstate';

export const machineDefinition = {
  id: 'checkbox',
  initial: 'unchecked',
  states: {
    unchecked: {
      on: {
        TOGGLE: [
          {
            actions: [ 'sendParent' ],
            target: 'checked',
          },
        ],
      },
    },
    checked: {
      on: {
        TOGGLE: [
          {
            actions: [ 'sendParent' ],
            target: 'unchecked',
          },
        ],
      },
    },
  },
};

const machineOptions = {
  actions: {
    sendParent: sendParent((context, event) => event.data),
  },
};

export default createMachine(machineDefinition, machineOptions);

Second, I have extended the render method of testing-library

import React from 'react'
import HelmetProvider from 'react-navi-helmet-async'
import SpinnerProvider from '@atoms/GlobalSpinner'
import AlertProvider from '@molecules/GlobalAlert'
import InternationalizationProvider from '@internationalization/InternationalizationProvider'
import { render as originalRender } from '@testing-library/react'

const render = (ui, { locale = 'es', ...renderOptions } = {}) => {
  
  const Wrapper = ({ children }) => {
    return (
      <InternationalizationProvider>
          <AlertProvider>
            <SpinnerProvider>
              <HelmetProvider>
                {children}
              </HelmetProvider>
            </SpinnerProvider>
          </AlertProvider>
      </InternationalizationProvider>
    )
  }

  return originalRender(ui, { wrapper: Wrapper, ...renderOptions })
}

export * from '@testing-library/react'

export { render }

Finally, I have created the test

import React from 'react';
import { produce } from 'immer';
import { machineDefinition } from '@stateMachines/atoms/checkbox';
import { createMachine } from 'xstate';
import { createModel } from '@xstate/test';
import { render, cleanup, fireEvent } from '@root/jest.utils';
import Checkbox from '@atoms/Checkbox';

const getMachineDefinitionWithTests = () => produce(machineDefinition, (draft) => {
  draft.states.unchecked.meta = {
    test: ({ getByTestId }) => {
      expect(getByTestId('checkbox-child-3')).toHaveClass('w-8 h-4 rounded-md duration-500 bg-dark-300 dark:bg-accent-100');
    },
  };
  draft.states.checked.meta = {
    test: ({ getByTestId }) => {
      expect(getByTestId('checkbox-child-3')).toHaveClass('w-8 h-4 rounded-md duration-500 bg-dark-300 dark:bg-accent-100');
      expect(getByTestId('checkbox-child-3.1')).toHaveClass('bg-light-100 w-4 h-4 rounded-full duration-500 dark:transform dark:translate-x-full');
    },
  };
});

const getEvents = () => ({
  TOGGLE: {
    exec: ({ getByTestId }) => {
      fireEvent.click(getByTestId('checkbox-container'));
    },
    cases: [ {} ],
  },
});

describe('checkbox', () => {
  const machine = createMachine(getMachineDefinitionWithTests(), {
    actions: {
      sendParent: () => {},
    },
  });
  const machineModel = createModel(machine)
    .withEvents(getEvents());
  const testPlans = machineModel.getSimplePathPlans();

  testPlans.forEach((plan) => {
    describe(plan.description, () => {
      afterEach(cleanup);

      plan.paths.forEach((path) => {
        it(path.description, () => {
          const rendered = render(
            <Checkbox
              test
              label='main.txt1'
              data={{}}
              machine={machine}
            />,
            { locale: 'en' },
          );

          return path.test(rendered);
        });
      });
    });
  });

  describe('coverage', () => {
    it('should have full coverage', () => {
      machineModel.testCoverage();
    });
  });
});

I have created a react boilerplate which contains XState, there you can find the previous test