expect/class/to-have-instance-with-slots.js

/** @module expect/class */
import { map, keys, isNil, defaultTo } from 'lodash';
import { matcherHint, printReceived, printExpected } from 'jest-matcher-utils';
import { assertIsMockComponent } from '@utils/assert';
import { hasInstanceWithSlots } from '@check';
import { getSlots } from '@from/instance/get-slots';


/**
 * Passes if a mocked component class has an instance with the specified slots.
 *
 * @param {Class<Component>} Component - A mocked component constructor
 * @param {(Array|Object)} [slots] - The slots to check. Default/unnamed slot
 * is checked if this parameter is not provided.
 *
 * @example <caption>Svelte code (App.svelte)</caption>
 * <script>
 *    import Component from './Component.svelte'
 * </script>
 *
 * <Component>
 *   <span>First</span>
 * </Component>
 * <Component>
 *   <span slot="first">First</span>
 *   <span slot="second">Second</span>
 * </Component>
 *
 *
 * @example <caption>Test code (App.spec.js)</caption>
 * // Import mocked components
 * jest.mock('Component.svelte');
 * import Component from 'Component.svelte';
 * svelteMock.mockImplementation(Component);
 *
 * // Import and render app
 * import App from 'App.svelte';
 * new App();
 *
 * // Check for unnamed slot
 * expect(Component).toHaveInstanceWithSlots();
 * // Check for named slots
 * expect(Component).toHaveInstanceWithSlots(['first']);
 * expect(Component).toHaveInstanceWithSlots(['first', 'second']);
 * expect(Component).not.toHaveInstanceWithSlots(['nonExistent']);
 * expect(Component).toHaveInstanceWithSlots({ first: firstSlot });
 * expect(Component).not.toHaveInstanceWithSlots({ first: wrongSlot });
 *
 */

export function toHaveInstanceWithSlots(Component, slots) {
  assertIsMockComponent(Component);
  const pass = hasInstanceWithSlots(Component, slots);
  return {
    pass,
    message: /* istanbul ignore next */ () => {
      const matcherName = 'toHaveInstanceWithSlots';
      const recievedArgument = 'Component';
      const expectedArgument = isNil(slots) ? null : 'namedSlots';
      const options = {
        isNot: this.isNot, // eslint-disable-line no-invalid-this
        promise: this.promise, // eslint-disable-line no-invalid-this
      };
      const has = pass ? 'has' : 'does not have';
      const found = pass ? 'found' : 'could not find';
      const expectedSlots = defaultTo(slots, ['default']);
      const instanceSlots = map(
        Component.mock.results,
        (result) => keys(getSlots(result.value))
      );
      return [
        matcherHint(matcherName, recievedArgument, expectedArgument, options),
        '',
        `Received component ${has} an instance with slots:`,
        `${found} match for slots`,
        `  ${printExpected(expectedSlots)}`,
        'among',
        `  ${printReceived(instanceSlots)}`,
      ].join('\n');
    },
  };
}

// Aliases
export const toHaveAnInstanceWithSlots = toHaveInstanceWithSlots;