text: import {
    STORY_ARGS_UPDATED,
    UPDATE_STORY_ARGS,
    RESET_STORY_ARGS,
    SET_STORIES,
    STORY_SPECIFIED,
    STORY_PREPARED,
  } from '@storybook/core-events';
  import { EventEmitter } from 'events';
  
  import { getEventMetadata } from '../lib/events';
  
  import { init as initStories } from '../modules/stories';
  
  const mockStories = jest.fn();
  
  jest.mock('../lib/events');
  jest.mock('global', () => ({
    ...jest.requireActual('global'),
    fetch: jest.fn(() => ({ json: () => ({ v: 3, stories: mockStories() }) })),
  }));
  
  beforeEach(() => {
    getEventMetadata.mockReturnValue({ sourceType: 'local' });
    mockStories.mockReset().mockReturnValue({
      'component-a--story-1': {
        title: 'Component A',
        name: 'Story 1',
        importPath: './path/to/component-a.ts',
      },
      'component-a--story-2': {
        title: 'Component A',
        name: 'Story 2',
        importPath: './path/to/component-a.ts',
      },
      'component-b--story-3': {
        title: 'Component B',
        name: 'Story 3',
        importPath: './path/to/component-b.ts',
      },
    });
  });
  
  function createMockStore(initialState) {
    let state = initialState || {};
    return {
      getState: jest.fn(() => state),
      setState: jest.fn((s) => {
        state = { ...state, ...s };
        return Promise.resolve(state);
      }),
    };
  }
  
  const provider = { getConfig: jest.fn() };
  
  beforeEach(() => {
    provider.getConfig.mockReturnValue({});
  });
  
  describe('stories API', () => {
    it('sets a sensible initialState', () => {
      const { state } = initStories({
        storyId: 'id',
        viewMode: 'story',
      });
  
      expect(state).toEqual({
        storiesConfigured: false,
        storiesHash: {},
        storyId: 'id',
        viewMode: 'story',
        hasCalledSetOptions: false,
      });
    });
    const parameters = {};
    const storiesHash = {
      'a--1': { kind: 'a', name: '1', parameters, path: 'a--1', id: 'a--1', args: {} },
      'a--2': { kind: 'a', name: '2', parameters, path: 'a--2', id: 'a--2', args: {} },
      'b-c--1': {
        kind: 'b/c',
        name: '1',
        parameters,
        path: 'b-c--1',
        id: 'b-c--1',
        args: {},
      },
      'b-d--1': {
        kind: 'b/d',
        name: '1',
        parameters,
        path: 'b-d--1',
        id: 'b-d--1',
        args: {},
      },
      'b-d--2': {
        kind: 'b/d',
        name: '2',
        parameters,
        path: 'b-d--2',
        id: 'b-d--2',
        args: { a: 'b' },
      },
      'custom-id--1': {
        kind: 'b/e',
        name: '1',
        parameters,
        path: 'custom-id--1',
        id: 'custom-id--1',
        args: {},
      },
    };
    describe('setStories', () => {
      beforeEach(() => {
        mockStories.mockRejectedValue(new Error('Fetch failed'));
      });
  
      it('stores basic kinds and stories w/ correct keys', () => {
        const navigate = jest.fn();
        const store = createMockStore();
  
        const {
          api: { setStories },
        } = initStories({ store, navigate, provider });
  
        provider.getConfig.mockReturnValue({ sidebar: { showRoots: false } });
        setStories(storiesHash);
  
        const { storiesHash: storedStoriesHash } = store.getState();
  
        // We need exact key ordering, even if in theory JS doesn't guarantee it
        expect(Object.keys(storedStoriesHash)).toEqual([
          'a',
          'a--1',
          'a--2',
          'b',
          'b-c',
          'b-c--1',
          'b-d',
          'b-d--1',
          'b-d--2',
          'b-e',
          'custom-id--1',
        ]);
        expect(storedStoriesHash.a).toMatchObject({
          id: 'a',
          children: ['a--1', 'a--2'],
          isRoot: false,
          isComponent: true,
        });
  
        expect(storedStoriesHash['a--1']).toMatchObject({
          id: 'a--1',
          parent: 'a',
          kind: 'a',
          name: '1',
          parameters,
          args: {},
          prepared: true,
        });
  
        expect(storedStoriesHash['a--2']).toMatchObject({
          id: 'a--2',
          parent: 'a',
          kind: 'a',
          name: '2',
          parameters,
          args: {},
          prepared: true,
        });
  
        expect(storedStoriesHash.b).toMatchObject({
          id: 'b',
          children: ['b-c', 'b-d', 'b-e'],
          isRoot: false,
          isComponent: false,
        });
  
        expect(storedStoriesHash['b-c']).toMatchObject({
          id: 'b-c',
          parent: 'b',
          children: ['b-c--1'],
          isRoot: false,
          isComponent: true,
        });
  
        expect(storedStoriesHash['b-c--1']).toMatchObject({
          id: 'b-c--1',
          parent: 'b-c',
          kind: 'b/c',
          name: '1',
          parameters,
          args: {},
          prepared: true,
        });
  
        expect(storedStoriesHash['b-d']).toMatchObject({
          id: 'b-d',
          parent: 'b',
          children: ['b-d--1', 'b-d--2'],
          isRoot: false,
          isComponent: true,
        });
  
        expect(storedStoriesHash['b-d--1']).toMatchObject({
          id: 'b-d--1',
          parent: 'b-d',
          kind: 'b/d',
          name: '1',
          parameters,
          args: {},
          prepared: true,
        });
  
        expect(storedStoriesHash['b-d--2']).toMatchObject({
          id: 'b-d--2',
          parent: 'b-d',
          kind: 'b/d',
          name: '2',
          parameters,
          args: { a: 'b' },
          prepared: true,
        });
  
        expect(storedStoriesHash['b-e']).toMatchObject({
          id: 'b-e',
          parent: 'b',
          children: ['custom-id--1'],
          isRoot: false,
          isComponent: true,
        });
  
        expect(storedStoriesHash['custom-id--1']).toMatchObject({
          id: 'custom-id--1',
          parent: 'b-e',
          kind: 'b/e',
          name: '1',
          parameters,
          args: {},
          prepared: true,
        });
      });
  
      it('trims whitespace of group/component names (which originate from the kind)', () => {
        const navigate = jest.fn();
        const store = createMockStore();
  
        const {
          api: { setStories },
        } = initStories({ store, navigate, provider });
  
        setStories({
          'design-system-some-component--my-story': {
            kind: '  Design System  /  Some Component  ', // note the leading/trailing whitespace around each part of the path
            name: '  My Story  ', // we only trim the path, so this will be kept as-is (it may intentionally have whitespace)
            parameters,
            path: 'design-system-some-component--my-story',
            id: 'design-system-some-component--my-story',
            args: {},
          },
        });
  
        const { storiesHash: storedStoriesHash } = store.getState();
  
        // We need exact key ordering, even if in theory JS doesn't guarantee it
        expect(Object.keys(storedStoriesHash)).toEqual([
          'design-system',
          'design-system-some-component',
          'design-system-some-component--my-story',
        ]);
        expect(storedStoriesHash['design-system']).toMatchObject({
          isRoot: true,
          name: 'Design System', // root name originates from `kind`, so it gets trimmed
        });
        expect(storedStoriesHash['design-system-some-component']).toMatchObject({
          isComponent: true,
          name: 'Some Component', // component name originates from `kind`, so it gets trimmed
        });
        expect(storedStoriesHash['design-system-some-component--my-story']).toMatchObject({
          isLeaf: true,
          kind: '  Design System  /  Some Component  ', // kind is kept as-is, because it may be used as identifier
          name: '  My Story  ', // story name is kept as-is, because it's set directly on the story
        });
      });
  
      it('sets roots when showRoots = true', () => {
        const navigate = jest.fn();
        const store = createMockStore();
  
        const {
          api: { setStories },
        } = initStories({ store, navigate, provider });
  
        provider.getConfig.mockReturnValue({ sidebar: { showRoots: true } });
        setStories({
          'a-b--1': {
            kind: 'a/b',
            name: '1',
            parameters,
            path: 'a-b--1',
            id: 'a-b--1',
            args: {},
          },
        });
  
        const { storiesHash: storedStoriesHash } = store.getState();
  
        // We need exact key ordering, even if in theory JS doens't guarantee it
        expect(Object.keys(storedStoriesHash)).toEqual(['a', 'a-b', 'a-b--1']);
        expect(storedStoriesHash.a).toMatchObject({
          id: 'a',
          children: ['a-b'],
          isRoot: true,
          isComponent: false,
        });
        expect(storedStoriesHash['a-b']).toMatchObject({
          id: 'a-b',
          parent: 'a',
          children: ['a-b--1'],
          isRoot: false,
          isComponent: true,
        });
        expect(storedStoriesHash['a-b--1']).toMatchObject({
          id: 'a-b--1',
          parent: 'a-b',
          kind: 'a/b',
          name: '1',
          parameters,
          args: {},
        });
      });
  
      it('does not put bare stories into a root when showRoots = true', () => {
        const navigate = jest.fn();
        const store = createMockStore();
  
        const {
          api: { setStories },
        } = initStories({ store, navigate, provider });
  
        provider.getConfig.mockReturnValue({ sidebar: { showRoots: true } });
        setStories({
          'a--1': {
            kind: 'a',
            name: '1',
            parameters,
            path: 'a--1',
            id: 'a--1',
            args: {},
          },
        });
  
        const { storiesHash: storedStoriesHash } = store.getState();
  
        // We need exact key ordering, even if in theory JS doens't guarantee it
        expect(Object.keys(storedStoriesHash)).toEqual(['a', 'a--1']);
        expect(storedStoriesHash.a).toMatchObject({
          id: 'a',
          children: ['a--1'],
          isRoot: false,
          isComponent: true,
        });
        expect(storedStoriesHash['a--1']).toMatchObject({
          id: 'a--1',
          parent: 'a',
          kind: 'a',
          name: '1',
          parameters,
          args: {},
        });
      });
  
      // Stories can get out of order for a few reasons -- see reproductions on
      //   https://github.com/storybookjs/storybook/issues/5518
      it('does the right thing for out of order stories', async () => {
        const navigate = jest.fn();
        const store = createMockStore();
  
        const {
          api: { setStories },
        } = initStories({ store, navigate, provider });
  
        await setStories({
          'a--1': { kind: 'a', name: '1', parameters, path: 'a--1', id: 'a--1', args: {} },
          'b--1': { kind: 'b', name: '1', parameters, path: 'b--1', id: 'b--1', args: {} },
          'a--2': { kind: 'a', name: '2', parameters, path: 'a--2', id: 'a--2', args: {} },
        });
  
        const { storiesHash: storedStoriesHash } = store.getState();
  
        // We need exact key ordering, even if in theory JS doens't guarantee it
        expect(Object.keys(storedStoriesHash)).toEqual(['a', 'a--1', 'a--2', 'b', 'b--1']);
        expect(storedStoriesHash.a).toMatchObject({
          id: 'a',
          children: ['a--1', 'a--2'],
          isRoot: false,
          isComponent: true,
        });
  
        expect(storedStoriesHash.b).toMatchObject({
          id: 'b',
          children: ['b--1'],
          isRoot: false,
          isComponent: true,
        });
      });
    });
  
    // Can't currently run these tests as cannot set this on the events
    describe('STORY_SPECIFIED event', () => {
      it('navigates to the story', async () => {
        const navigate = jest.fn();
        const fullAPI = Object.assign(new EventEmitter(), {
          isSettingsScreenActive() {
            return false;
          },
        });
        const store = createMockStore({});
        const { init, api } = initStories({ store, navigate, provider, fullAPI });
  
        Object.assign(fullAPI, api);
        init();
        fullAPI.emit(STORY_SPECIFIED, { storyId: 'a--1', viewMode: 'story' });
  
        expect(navigate).toHaveBeenCalledWith('/story/a--1');
      });
  
      it('DOES not navigate if the story was already selected', async () => {
        const navigate = jest.fn();
        const fullAPI = Object.assign(new EventEmitter(), {
          isSettingsScreenActive() {
            return true;
          },
        });
        const store = createMockStore({ viewMode: 'story', storyId: 'a--1' });
        initStories({ store, navigate, provider, fullAPI });
  
        fullAPI.emit(STORY_SPECIFIED, { storyId: 'a--1', viewMode: 'story' });
  
        expect(navigate).not.toHaveBeenCalled();
      });
  
      it('DOES not navigate if a settings page was selected', async () => {
        const navigate = jest.fn();
        const fullAPI = Object.assign(new EventEmitter(), {
          isSettingsScreenActive() {
            return true;
          },
        });
        const store = createMockStore({ viewMode: 'settings', storyId: 'about' });
        initStories({ store, navigate, provider, fullAPI });
  
        fullAPI.emit(STORY_SPECIFIED, { storyId: 'a--1', viewMode: 'story' });
  
        expect(navigate).not.toHaveBeenCalled();
      });
    });
  
    describe('args handling', () => {
      it('changes args properly, per story when receiving STORY_ARGS_UPDATED', () => {
        const navigate = jest.fn();
        const store = createMockStore();
        const fullAPI = new EventEmitter();
  
        const { api, init } = initStories({ store, navigate, provider, fullAPI });
  
        Object.assign(fullAPI, api);
        fullAPI.setStories({
          'a--1': { kind: 'a', name: '1', parameters, path: 'a--1', id: 'a--1', args: { a: 'b' } },
          'b--1': { kind: 'b', name: '1', parameters, path: 'b--1', id: 'b--1', args: { x: 'y' } },
        });
  
        const { storiesHash: initialStoriesHash } = store.getState();
        expect(initialStoriesHash['a--1'].args).toEqual({ a: 'b' });
        expect(initialStoriesHash['b--1'].args).toEqual({ x: 'y' });
  
        init();
        fullAPI.emit(STORY_ARGS_UPDATED, { storyId: 'a--1', args: { foo: 'bar' } });
  
        const { storiesHash: changedStoriesHash } = store.getState();
        expect(changedStoriesHash['a--1'].args).toEqual({ foo: 'bar' });
        expect(changedStoriesHash['b--1'].args).toEqual({ x: 'y' });
      });
  
      it('changes reffed args properly, per story when receiving STORY_ARGS_UPDATED', () => {
        const navigate = jest.fn();
        const store = createMockStore();
        const fullAPI = new EventEmitter();
        fullAPI.updateRef = jest.fn();
  
        const { init, api } = initStories({ store, navigate, provider, fullAPI });
        Object.assign(fullAPI, api);
  
        init();
        getEventMetadata.mockReturnValueOnce({
          sourceType: 'external',
          ref: { id: 'refId', stories: { 'a--1': { args: { a: 'b' } } } },
        });
        fullAPI.emit(STORY_ARGS_UPDATED, { storyId: 'a--1', args: { foo: 'bar' } });
        expect(fullAPI.updateRef).toHaveBeenCalledWith('refId', {
          stories: { 'a--1': { args: { foo: 'bar' } } },
        });
      });
  
      it('updateStoryArgs emits UPDATE_STORY_ARGS to the local frame and does not change anything', () => {
        const navigate = jest.fn();
        const emit = jest.fn();
        const on = jest.fn();
        const fullAPI = { emit, on };
        const store = createMockStore();
  
        const { api, init } = initStories({ store, navigate, provider, fullAPI });
  
        api.setStories({
          'a--1': { kind: 'a', name: '1', parameters, path: 'a--1', id: 'a--1', args: { a: 'b' } },
          'b--1': { kind: 'b', name: '1', parameters, path: 'b--1', id: 'b--1', args: { x: 'y' } },
        });
  
        Object.assign(fullAPI, api);
        init();
  
        api.updateStoryArgs({ id: 'a--1' }, { foo: 'bar' });
        expect(emit).toHaveBeenCalledWith(UPDATE_STORY_ARGS, {
          storyId: 'a--1',
          updatedArgs: { foo: 'bar' },
          options: {
            target: 'storybook-preview-iframe',
          },
        });
  
        const { storiesHash: changedStoriesHash } = store.getState();
        expect(changedStoriesHash['a--1'].args).toEqual({ a: 'b' });
        expect(changedStoriesHash['b--1'].args).toEqual({ x: 'y' });
      });
  
      it('updateStoryArgs emits UPDATE_STORY_ARGS to the right frame', () => {
        const navigate = jest.fn();
        const emit = jest.fn();
        const on = jest.fn();
        const fullAPI = { emit, on };
        const store = createMockStore();
  
        const { api, init } = initStories({ store, navigate, provider, fullAPI });
  
        api.setStories({
          'a--1': { kind: 'a', name: '1', parameters, path: 'a--1', id: 'a--1', args: { a: 'b' } },
          'b--1': { kind: 'b', name: '1', parameters, path: 'b--1', id: 'b--1', args: { x: 'y' } },
        });
  
        Object.assign(fullAPI, api);
        init();
  
        api.updateStoryArgs({ id: 'a--1', refId: 'refId' }, { foo: 'bar' });
        expect(emit).toHaveBeenCalledWith(UPDATE_STORY_ARGS, {
          storyId: 'a--1',
          updatedArgs: { foo: 'bar' },
          options: {
            target: 'storybook-ref-refId',
          },
        });
      });
  
      it('resetStoryArgs emits RESET_STORY_ARGS to the local frame and does not change anything', () => {
        const navigate = jest.fn();
        const emit = jest.fn();
        const on = jest.fn();
        const fullAPI = { emit, on };
        const store = createMockStore();
  
        const { api, init } = initStories({ store, navigate, provider, fullAPI });
  
        api.setStories({
          'a--1': { kind: 'a', name: '1', parameters, path: 'a--1', id: 'a--1', args: { a: 'b' } },
          'b--1': { kind: 'b', name: '1', parameters, path: 'b--1', id: 'b--1', args: { x: 'y' } },
        });
  
        Object.assign(fullAPI, api);
        init();
  
        api.resetStoryArgs({ id: 'a--1' }, ['foo']);
        expect(emit).toHaveBeenCalledWith(RESET_STORY_ARGS, {
          storyId: 'a--1',
          argNames: ['foo'],
          options: {
            target: 'storybook-preview-iframe',
          },
        });
  
        const { storiesHash: changedStoriesHash } = store.getState();
        expect(changedStoriesHash['a--1'].args).toEqual({ a: 'b' });
        expect(changedStoriesHash['b--1'].args).toEqual({ x: 'y' });
      });
  
      it('resetStoryArgs emits RESET_STORY_ARGS to the right frame', () => {
        const navigate = jest.fn();
        const emit = jest.fn();
        const on = jest.fn();
        const fullAPI = { emit, on };
        const store = createMockStore();
  
        const { api, init } = initStories({ store, navigate, provider, fullAPI });
  
        api.setStories({
          'a--1': { kind: 'a', name: '1', parameters, path: 'a--1', id: 'a--1', args: { a: 'b' } },
          'b--1': { kind: 'b', name: '1', parameters, path: 'b--1', id: 'b--1', args: { x: 'y' } },
        });
  
        Object.assign(fullAPI, api);
        init();
  
        api.resetStoryArgs({ id: 'a--1', refId: 'refId' }, ['foo']);
        expect(emit).toHaveBeenCalledWith(RESET_STORY_ARGS, {
          storyId: 'a--1',
          argNames: ['foo'],
          options: {
            target: 'storybook-ref-refId',
          },
        });
      });
    });
  
    describe('jumpToStory', () => {
      it('works forward', () => {
        const navigate = jest.fn();
        const store = createMockStore();
  
        const {
          api: { setStories, jumpToStory },
          state,
        } = initStories({ store, navigate, storyId: 'a--1', viewMode: 'story', provider });
        store.setState(state);
        setStories(storiesHash);
  
        jumpToStory(1);
        expect(navigate).toHaveBeenCalledWith('/story/a--2');
      });
  
      it('works backwards', () => {
        const navigate = jest.fn();
        const store = createMockStore();
  
        const {
          api: { setStories, jumpToStory },
          state,
        } = initStories({ store, navigate, storyId: 'a--2', viewMode: 'story', provider });
        store.setState(state);
        setStories(storiesHash);
  
        jumpToStory(-1);
        expect(navigate).toHaveBeenCalledWith('/story/a--1');
      });
  
      it('does nothing if you are at the last story and go forward', () => {
        const navigate = jest.fn();
        const store = createMockStore();
  
        const {
          api: { setStories, jumpToStory },
          state,
        } = initStories({ store, navigate, storyId: 'custom-id--1', viewMode: 'story', provider });
        store.setState(state);
        setStories(storiesHash);
  
        jumpToStory(1);
        expect(navigate).not.toHaveBeenCalled();
      });
  
      it('does nothing if you are at the first story and go backward', () => {
        const navigate = jest.fn();
        const store = createMockStore();
  
        const {
          api: { setStories, jumpToStory },
          state,
        } = initStories({ store, navigate, storyId: 'a--1', viewMode: 'story', provider });
        store.setState(state);
        setStories(storiesHash);
  
        jumpToStory(-1);
        expect(navigate).not.toHaveBeenCalled();
      });
  
      it('does nothing if you have not selected a story', () => {
        const navigate = jest.fn();
        const store = { getState: () => ({ storiesHash }) };
  
        const {
          api: { jumpToStory },
        } = initStories({ store, navigate, provider });
  
        jumpToStory(1);
        expect(navigate).not.toHaveBeenCalled();
      });
    });
  
    describe('jumpToComponent', () => {
      it('works forward', () => {
        const navigate = jest.fn();
        const store = createMockStore();
  
        const {
          api: { setStories, jumpToComponent },
          state,
        } = initStories({ store, navigate, storyId: 'a--1', viewMode: 'story', provider });
        store.setState(state);
        setStories(storiesHash);
  
        jumpToComponent(1);
        expect(navigate).toHaveBeenCalledWith('/story/b-c--1');
      });
  
      it('works backwards', () => {
        const navigate = jest.fn();
        const store = createMockStore();
  
        const {
          api: { setStories, jumpToComponent },
          state,
        } = initStories({ store, navigate, storyId: 'b-c--1', viewMode: 'story', provider });
        store.setState(state);
        setStories(storiesHash);
  
        jumpToComponent(-1);
        expect(navigate).toHaveBeenCalledWith('/story/a--1');
      });
  
      it('does nothing if you are in the last component and go forward', () => {
        const navigate = jest.fn();
        const store = createMockStore();
  
        const {
          api: { setStories, jumpToComponent },
          state,
        } = initStories({ store, navigate, storyId: 'custom-id--1', viewMode: 'story', provider });
        store.setState(state);
        setStories(storiesHash);
  
        jumpToComponent(1);
        expect(navigate).not.toHaveBeenCalled();
      });
  
      it('does nothing if you are at the first component and go backward', () => {
        const navigate = jest.fn();
        const store = createMockStore();
  
        const {
          api: { setStories, jumpToComponent },
          state,
        } = initStories({ store, navigate, storyId: 'a--2', viewMode: 'story', provider });
        store.setState(state);
        setStories(storiesHash);
  
        jumpToComponent(-1);
        expect(navigate).not.toHaveBeenCalled();
      });
    });
  
    describe('selectStory', () => {
      it('navigates', () => {
        const navigate = jest.fn();
        const store = {
          getState: () => ({ viewMode: 'story', storiesHash }),
        };
  
        const {
          api: { selectStory },
        } = initStories({ store, navigate, provider });
  
        selectStory('a--2');
        expect(navigate).toHaveBeenCalledWith('/story/a--2');
      });
  
      it('allows navigating to kind/storyname (legacy api)', () => {
        const navigate = jest.fn();
        const store = {
          getState: () => ({ viewMode: 'story', storiesHash }),
        };
  
        const {
          api: { selectStory },
        } = initStories({ store, navigate, provider });
  
        selectStory('a', '2');
        expect(navigate).toHaveBeenCalledWith('/story/a--2');
      });
  
      it('allows navigating to storyname, without kind (legacy api)', () => {
        const navigate = jest.fn();
        const store = {
          getState: () => ({ viewMode: 'story', storyId: 'a--1', storiesHash }),
        };
  
        const {
          api: { selectStory },
        } = initStories({ store, navigate, provider });
  
        selectStory(null, '2');
        expect(navigate).toHaveBeenCalledWith('/story/a--2');
      });
  
      it('allows navigating away from the settings pages', () => {
        const navigate = jest.fn();
        const store = {
          getState: () => ({ viewMode: 'settings', storyId: 'about', storiesHash }),
        };
  
        const {
          api: { selectStory },
        } = initStories({ store, navigate, provider });
  
        selectStory('a--2');
        expect(navigate).toHaveBeenCalledWith('/story/a--2');
      });
  
      it('allows navigating to first story in kind on call by kind', () => {
        const navigate = jest.fn();
        const store = createMockStore();
  
        const {
          api: { selectStory, setStories },
          state,
        } = initStories({ store, navigate, provider });
        store.setState(state);
        setStories(storiesHash);
  
        selectStory('a');
        expect(navigate).toHaveBeenCalledWith('/story/a--1');
      });
  
      describe('component permalinks', () => {
        it('allows navigating to kind/storyname (legacy api)', () => {
          const navigate = jest.fn();
          const store = createMockStore();
  
          const {
            api: { selectStory, setStories },
            state,
          } = initStories({ store, navigate, provider });
          store.setState(state);
          setStories(storiesHash);
  
          selectStory('b/e', '1');
          expect(navigate).toHaveBeenCalledWith('/story/custom-id--1');
        });
  
        it('allows navigating to component permalink/storyname (legacy api)', () => {
          const navigate = jest.fn();
          const store = createMockStore();
  
          const {
            api: { selectStory, setStories },
            state,
          } = initStories({ store, navigate, provider });
          store.setState(state);
          setStories(storiesHash);
  
          selectStory('custom-id', '1');
          expect(navigate).toHaveBeenCalledWith('/story/custom-id--1');
        });
  
        it('allows navigating to first story in kind on call by kind', () => {
          const navigate = jest.fn();
          const store = createMockStore();
  
          const {
            api: { selectStory, setStories },
            state,
          } = initStories({ store, navigate, provider });
          store.setState(state);
          setStories(storiesHash);
  
          selectStory('b/e');
          expect(navigate).toHaveBeenCalledWith('/story/custom-id--1');
        });
      });
    });
  
    describe('fetchStoryList', () => {
      it('sets the initial set of stories in the stories hash', async () => {
        const navigate = jest.fn();
        const store = createMockStore();
        const fullAPI = Object.assign(new EventEmitter(), {
          setStories: jest.fn(),
        });
  
        const { api, init } = initStories({ store, navigate, provider, fullAPI });
        Object.assign(fullAPI, api);
  
        await init();
  
        const { storiesHash: storedStoriesHash } = store.getState();
  
        // We need exact key ordering, even if in theory JS doesn't guarantee it
        expect(Object.keys(storedStoriesHash)).toEqual([
          'component-a',
          'component-a--story-1',
          'component-a--story-2',
          'component-b',
          'component-b--story-3',
        ]);
        expect(storedStoriesHash['component-a']).toMatchObject({
          id: 'component-a',
          children: ['component-a--story-1', 'component-a--story-2'],
          isRoot: false,
          isComponent: true,
        });
  
        expect(storedStoriesHash['component-a--story-1']).toMatchObject({
          id: 'component-a--story-1',
          parent: 'component-a',
          kind: 'Component A',
          name: 'Story 1',
          prepared: false,
        });
        expect(storedStoriesHash['component-a--story-1'].args).toBeUndefined();
      });
  
      it('infers docs only if there is only one story and it has the name "Page"', async () => {
        mockStories.mockReset().mockReturnValue({
          'component-a--page': {
            title: 'Component A',
            name: 'Page', // Called "Page" but not only story
            importPath: './path/to/component-a.ts',
          },
          'component-a--story-2': {
            title: 'Component A',
            name: 'Story 2',
            importPath: './path/to/component-a.ts',
          },
          'component-b--page': {
            title: 'Component B',
            name: 'Page', // Page and only story
            importPath: './path/to/component-b.ts',
          },
          'component-c--story-4': {
            title: 'Component c',
            name: 'Story 4', // Only story but not page
            importPath: './path/to/component-c.ts',
          },
        });
  
        const navigate = jest.fn();
        const store = createMockStore();
        const fullAPI = Object.assign(new EventEmitter(), {
          setStories: jest.fn(),
        });
  
        const { api, init } = initStories({ store, navigate, provider, fullAPI });
        Object.assign(fullAPI, api);
  
        await init();
  
        const { storiesHash: storedStoriesHash } = store.getState();
  
        // We need exact key ordering, even if in theory JS doesn't guarantee it
        expect(Object.keys(storedStoriesHash)).toEqual([
          'component-a',
          'component-a--page',
          'component-a--story-2',
          'component-b',
          'component-b--page',
          'component-c',
          'component-c--story-4',
        ]);
        expect(storedStoriesHash['component-a--page'].parameters.docsOnly).toBe(false);
        expect(storedStoriesHash['component-a--story-2'].parameters.docsOnly).toBe(false);
        expect(storedStoriesHash['component-b--page'].parameters.docsOnly).toBe(true);
        expect(storedStoriesHash['component-c--story-4'].parameters.docsOnly).toBe(false);
      });
    });
  
    describe('STORY_PREPARED', () => {
      it('prepares the story', async () => {
        const navigate = jest.fn();
        const store = createMockStore();
        const fullAPI = Object.assign(new EventEmitter(), {
          setStories: jest.fn(),
          setOptions: jest.fn(),
        });
  
        const { api, init } = initStories({ store, navigate, provider, fullAPI });
        Object.assign(fullAPI, api);
  
        await init();
        fullAPI.emit(STORY_PREPARED, {
          id: 'component-a--story-1',
          parameters: { a: 'b' },
          args: { c: 'd' },
        });
  
        const { storiesHash: storedStoriesHash } = store.getState();
        expect(storedStoriesHash['component-a--story-1']).toMatchObject({
          id: 'component-a--story-1',
          parent: 'component-a',
          kind: 'Component A',
          name: 'Story 1',
          prepared: true,
          parameters: { a: 'b' },
          args: { c: 'd' },
        });
      });
      it('sets options the first time it is called', async () => {
        const navigate = jest.fn();
        const store = createMockStore();
        const fullAPI = Object.assign(new EventEmitter(), {
          setStories: jest.fn(),
          setOptions: jest.fn(),
        });
  
        const { api, init } = initStories({ store, navigate, provider, fullAPI });
        Object.assign(fullAPI, api);
  
        await init();
        fullAPI.emit(STORY_PREPARED, {
          id: 'component-a--story-1',
          parameters: { options: 'options' },
        });
  
        expect(fullAPI.setOptions).toHaveBeenCalledWith('options');
  
        fullAPI.setOptions.mockClear();
        fullAPI.emit(STORY_PREPARED, {
          id: 'component-a--story-1',
          parameters: { options: 'options2' },
        });
  
        expect(fullAPI.setOptions).not.toHaveBeenCalled();
      });
    });
  
    describe('v2 SET_STORIES event', () => {
      it('normalizes parameters and calls setStories for local stories', () => {
        const fullAPI = Object.assign(new EventEmitter(), {
          setStories: jest.fn(),
          setOptions: jest.fn(),
          findRef: jest.fn(),
          getCurrentParameter: jest.fn(),
        });
        const navigate = jest.fn();
        const store = createMockStore();
  
        const { init, api } = initStories({ store, navigate, provider, fullAPI });
        Object.assign(fullAPI, api, { setStories: jest.fn() });
        init();
  
        const setStoriesPayload = {
          v: 2,
          globalParameters: { global: 'global' },
          kindParameters: { a: { kind: 'kind' } },
          stories: { 'a--1': { kind: 'a', parameters: { story: 'story' } } },
        };
        fullAPI.emit(SET_STORIES, setStoriesPayload);
  
        expect(fullAPI.setStories).toHaveBeenCalledWith({
          'a--1': { kind: 'a', parameters: { global: 'global', kind: 'kind', story: 'story' } },
        });
      });
  
      it('normalizes parameters and calls setRef for external stories', () => {
        const fullAPI = Object.assign(new EventEmitter());
        const navigate = jest.fn();
        const store = createMockStore();
  
        const { init, api } = initStories({ store, navigate, provider, fullAPI });
        Object.assign(fullAPI, api, {
          setStories: jest.fn(),
          findRef: jest.fn(),
          setRef: jest.fn(),
        });
        init();
  
        getEventMetadata.mockReturnValueOnce({ sourceType: 'external', ref: { id: 'ref' } });
        const setStoriesPayload = {
          v: 2,
          globalParameters: { global: 'global' },
          kindParameters: { a: { kind: 'kind' } },
          stories: { 'a--1': { kind: 'a', parameters: { story: 'story' } } },
        };
        fullAPI.emit(SET_STORIES, setStoriesPayload);
  
        expect(fullAPI.setStories).not.toHaveBeenCalled();
        expect(fullAPI.setRef).toHaveBeenCalledWith(
          'ref',
          {
            id: 'ref',
            v: 2,
            globalParameters: { global: 'global' },
            kindParameters: { a: { kind: 'kind' } },
            stories: {
              'a--1': { kind: 'a', parameters: { global: 'global', kind: 'kind', story: 'story' } },
            },
          },
          true
        );
      });
  
      it('calls setOptions w/ first story parameter', () => {
        const fullAPI = Object.assign(new EventEmitter(), {
          setStories: jest.fn(),
          setOptions: jest.fn(),
          findRef: jest.fn(),
        });
        const navigate = jest.fn();
        const store = createMockStore();
  
        const { init, api } = initStories({ store, navigate, provider, fullAPI });
        Object.assign(fullAPI, api, { getCurrentParameter: jest.fn().mockReturnValue('options') });
        init();
  
        store.setState({});
        const setStoriesPayload = {
          v: 2,
          globalParameters: {},
          kindParameters: { a: {} },
          stories: { 'a--1': { kind: 'a' } },
        };
        fullAPI.emit(SET_STORIES, setStoriesPayload);
  
        expect(fullAPI.setOptions).toHaveBeenCalledWith('options');
      });
    });
    describe('legacy (v1) SET_STORIES event', () => {
      it('calls setRef with stories', () => {
        const fullAPI = Object.assign(new EventEmitter());
        const navigate = jest.fn();
        const store = createMockStore();
  
        const { init, api } = initStories({ store, navigate, provider, fullAPI });
        Object.assign(fullAPI, api, {
          setStories: jest.fn(),
          findRef: jest.fn(),
          setRef: jest.fn(),
        });
        init();
  
        getEventMetadata.mockReturnValueOnce({ sourceType: 'external', ref: { id: 'ref' } });
        const setStoriesPayload = {
          stories: { 'a--1': {} },
        };
        fullAPI.emit(SET_STORIES, setStoriesPayload);
  
        expect(fullAPI.setStories).not.toHaveBeenCalled();
        expect(fullAPI.setRef).toHaveBeenCalledWith(
          'ref',
          {
            id: 'ref',
            stories: {
              'a--1': {},
            },
          },
          true
        );
      });
    });
  });
  