Skip to content

Commit

Permalink
Core: Preserve kind load order on HMR when no sortFn is provided (#9424)
Browse files Browse the repository at this point in the history
Core: Preserve kind load order on HMR when no sortFn is provided
  • Loading branch information
shilman committed Jan 14, 2020
2 parents 287a3a7 + 8bfd191 commit 0e3ff1d
Show file tree
Hide file tree
Showing 2 changed files with 52 additions and 0 deletions.
39 changes: 39 additions & 0 deletions lib/client-api/src/client_api.test.ts
@@ -1,5 +1,6 @@
import { logger } from '@storybook/client-logger';
import addons, { mockChannel } from '@storybook/addons';
import Events from '@storybook/core-events';
import ClientApi from './client_api';
import ConfigApi from './config_api';
import StoryStore from './story_store';
Expand Down Expand Up @@ -507,6 +508,44 @@ describe('preview.client_api', () => {
expect(stories[1]).toHaveBeenCalled();
expect(logger.warn).not.toHaveBeenCalled();
});

it('should maintain kind order when the module reloads', async () => {
const {
clientApi: { storiesOf, getStorybook },
channel,
} = getContext(undefined);
const module1 = new MockModule();
const module2 = new MockModule();
channel.emit = jest.fn();

expect(getStorybook()).toEqual([]);

storiesOf('kind1', module1).add('story1', jest.fn());
storiesOf('kind2', module2).add('story2', jest.fn());

// storyStore debounces so we need to wait for the next tick
await new Promise(r => setTimeout(r, 0));

let [event, storiesHash] = channel.emit.mock.calls[0];
expect(event).toEqual(Events.SET_STORIES);
expect(Object.values(storiesHash.stories).map(v => v.kind)).toEqual(['kind1', 'kind2']);
expect(getStorybook().map(story => story.kind)).toEqual(['kind1', 'kind2']);

channel.emit.mockClear();

// simulate an HMR of kind1, which would cause it to go to the end
// if the original order is not maintainaed
module1.hot.reload();
storiesOf('kind1', module1).add('story1', jest.fn());

await new Promise(r => setTimeout(r, 0));
// eslint-disable-next-line prefer-destructuring
[event, storiesHash] = channel.emit.mock.calls[0];

expect(event).toEqual(Events.SET_STORIES);
expect(Object.values(storiesHash.stories).map(v => v.kind)).toEqual(['kind1', 'kind2']);
expect(getStorybook().map(story => story.kind)).toEqual(['kind1', 'kind2']);
});
});

describe('parameters', () => {
Expand Down
13 changes: 13 additions & 0 deletions lib/client-api/src/story_store.ts
Expand Up @@ -131,6 +131,19 @@ export default class StoryStore extends EventEmitter {
if (index && this._data[index].parameters.options.storySort) {
const sortFn = this._data[index].parameters.options.storySort;
stable.inplace(stories, sortFn);
} else {
// NOTE: when kinds are HMR'ed they get temporarily removed from the `_data` array
// and thus lose order. However in `_legacydata` they just get zeroed out, meaning
// that the order is preserved. Here we can use this to preserve the load
// order if there is no sort function, although it's a hack.
const kindOrder = Object.keys(this._legacydata).reduce(
(acc: Record<string, number>, kind: string, idx: number) => {
acc[kind] = idx;
return acc;
},
{}
);
stable.inplace(stories, (s1, s2) => kindOrder[s1[1].kind] - kindOrder[s2[1].kind]);
}
}
// removes function values from all stories so they are safe to transport over the channel
Expand Down

0 comments on commit 0e3ff1d

Please sign in to comment.