Skip to content

Commit

Permalink
Always update initial args when loading a story
Browse files Browse the repository at this point in the history
And also update the current args with the delta. This means both when we change story (to an HMR-ed story) and when we HMR the current story.
  • Loading branch information
tmeasday committed Oct 27, 2021
1 parent 842d479 commit 0a5466a
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 128 deletions.
72 changes: 72 additions & 0 deletions lib/preview-web/src/PreviewWeb.test.ts
Expand Up @@ -2219,6 +2219,78 @@ describe('PreviewWeb', () => {
});
});

describe('when another (not current) story changes', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
const newComponentOneExports = merge({}, componentOneExports, {
a: { args: { bar: 'edited' }, argTypes: { bar: { type: { name: 'string' } } } },
});
const newImportFn = jest.fn(async (path) => {
return path === './src/ComponentOne.stories.js'
? newComponentOneExports
: componentTwoExports;
});
it('retains the same delta to the args', async () => {
// Start at Story A
document.location.search = '?id=component-one--a';
const preview = await createAndRenderPreview();

// Change A's args
mockChannel.emit.mockClear();
emitter.emit(Events.UPDATE_STORY_ARGS, {
storyId: 'component-one--a',
updatedArgs: { foo: 'updated' },
});
await waitForRender();

// Change to story B
mockChannel.emit.mockClear();
emitter.emit(Events.SET_CURRENT_STORY, {
storyId: 'component-one--b',
viewMode: 'story',
});
await waitForSetCurrentStory();
await waitForRender();
expect(preview.storyStore.args.get('component-one--a')).toEqual({
foo: 'updated',
});

// Update story A's args via HMR
mockChannel.emit.mockClear();
projectAnnotations.renderToDOM.mockClear();
preview.onStoriesChanged({ importFn: newImportFn });
await waitForRender();

// Change back to Story A
mockChannel.emit.mockClear();
emitter.emit(Events.SET_CURRENT_STORY, {
storyId: 'component-one--a',
viewMode: 'story',
});
await waitForSetCurrentStory();
await waitForRender();
expect(preview.storyStore.args.get('component-one--a')).toEqual({
foo: 'updated',
bar: 'edited',
});

expect(projectAnnotations.renderToDOM).toHaveBeenCalledWith(
expect.objectContaining({
forceRemount: true,
storyContext: expect.objectContaining({
id: 'component-one--a',
args: { foo: 'updated', bar: 'edited' },
}),
}),
undefined // this is coming from view.prepareForStory, not super important
);
});
});

describe('if the story no longer exists', () => {
const { a, ...componentOneExportsWithoutA } = componentOneExports;
const newImportFn = jest.fn(async (path) => {
Expand Down
2 changes: 0 additions & 2 deletions lib/preview-web/src/PreviewWeb.tsx
Expand Up @@ -328,8 +328,6 @@ export class PreviewWeb<TFramework extends AnyFramework> {

if (persistedArgs) {
this.storyStore.args.updateFromPersisted(story, persistedArgs);
} else if (implementationChanged) {
this.storyStore.args.resetOnImplementationChange(story, this.previousStory);
}

// Don't re-render the story if nothing has changed to justify it
Expand Down
214 changes: 102 additions & 112 deletions lib/store/src/ArgsStore.test.ts
Expand Up @@ -9,28 +9,116 @@ describe('ArgsStore', () => {
describe('setInitial / get', () => {
it('returns in a straightforward way', () => {
const store = new ArgsStore();
store.setInitial('id', { foo: 'bar' });
expect(store.get('id')).toEqual({ foo: 'bar' });
});

it('does not allow re-setting', () => {
const store = new ArgsStore();
store.setInitial('id', { foo: 'bar' });
store.setInitial('id', { foo: 'baz' });
store.setInitial({ id: 'id', initialArgs: { foo: 'bar' } } as any);
expect(store.get('id')).toEqual({ foo: 'bar' });
});

it('throws if you try to get non-existent', () => {
const store = new ArgsStore();
expect(() => store.get('id')).toThrow(/No args known/);
});

describe('on second call for same story', () => {
describe('if initialArgs are unchanged', () => {
it('does nothing if the args are untouched', () => {
const store = new ArgsStore();

const previousStory = {
id: 'id',
initialArgs: { a: '1', b: '1' },
argTypes: { a: stringType, b: stringType },
} as any;
store.setInitial(previousStory);

const story = {
id: 'id',
initialArgs: { a: '1', b: '1' },
argTypes: { a: stringType, b: stringType },
} as any;

store.setInitial(story);
expect(store.get(story.id)).toEqual({ a: '1', b: '1' });
});

it('retains any arg changes', () => {
const store = new ArgsStore();

const previousStory = {
id: 'id',
initialArgs: { a: '1', b: false, c: 'unchanged' },
argTypes: { a: stringType, b: booleanType, c: stringType },
} as any;
store.setInitial(previousStory);

// NOTE: I'm not sure technically you should be allowed to set d here, but
// let's make sure we behave sensibly if you do
store.update('id', { a: 'update', b: true, d: 'update' });

const story = {
id: 'id',
initialArgs: { a: '1', b: false, c: 'unchanged' },
argTypes: { a: stringType, b: booleanType, c: stringType },
} as any;

store.setInitial(story);
// In any case c is not retained.
expect(store.get(story.id)).toEqual({ a: 'update', b: true, c: 'unchanged' });
});
});

describe('when initialArgs change', () => {
it('replaces old args with new if the args are untouched', () => {
const store = new ArgsStore();

const previousStory = {
id: 'id',
initialArgs: { a: '1', b: '1' },
argTypes: { a: stringType, b: stringType },
} as any;
store.setInitial(previousStory);

const story = {
id: 'id',
initialArgs: { a: '1', c: '1' },
argTypes: { a: stringType, c: stringType },
} as any;

store.setInitial(story);
expect(store.get(story.id)).toEqual({ a: '1', c: '1' });
});

it('applies the same delta if the args are changed', () => {
const store = new ArgsStore();

const previousStory = {
id: 'id',
initialArgs: { a: '1', b: '1' },
argTypes: { a: stringType, b: stringType },
} as any;
store.setInitial(previousStory);

// NOTE: I'm not sure technically you should be allowed to set c here
store.update('id', { a: 'update', c: 'update' });

const story = {
id: 'id',
initialArgs: { a: '2', d: '2' },
argTypes: { a: stringType, d: stringType },
} as any;

store.setInitial(story);
// In any case c is not retained.
expect(store.get(story.id)).toEqual({ a: 'update', d: '2' });
});
});
});
});

describe('update', () => {
it('overrides on a per-key basis', () => {
const store = new ArgsStore();

store.setInitial('id', {});
store.setInitial({ id: 'id', initialArgs: {} } as any);

store.update('id', { foo: 'bar' });
expect(store.get('id')).toEqual({ foo: 'bar' });
Expand All @@ -42,7 +130,7 @@ describe('ArgsStore', () => {
it('does not merge objects', () => {
const store = new ArgsStore();

store.setInitial('id', {});
store.setInitial({ id: 'id', initialArgs: {} } as any);

store.update('id', { obj: { foo: 'bar' } });
expect(store.get('id')).toEqual({ obj: { foo: 'bar' } });
Expand All @@ -54,7 +142,7 @@ describe('ArgsStore', () => {
it('does not set keys to undefined, it simply unsets them', () => {
const store = new ArgsStore();

store.setInitial('id', { foo: 'bar' });
store.setInitial({ id: 'id', initialArgs: { foo: 'bar' } } as any);

store.update('id', { foo: undefined });
expect('foo' in store.get('id')).toBe(false);
Expand All @@ -65,7 +153,7 @@ describe('ArgsStore', () => {
it('ensures the types of args are correct', () => {
const store = new ArgsStore();

store.setInitial('id', {});
store.setInitial({ id: 'id', initialArgs: {} } as any);

const story = {
id: 'id',
Expand All @@ -81,10 +169,7 @@ describe('ArgsStore', () => {
it('merges objects and sparse arrays', () => {
const store = new ArgsStore();

store.setInitial('id', {
a: { foo: 'bar' },
b: ['1', '2', '3'],
});
store.setInitial({ id: 'id', initialArgs: { a: { foo: 'bar' }, b: ['1', '2', '3'] } } as any);

const story = {
id: 'id',
Expand All @@ -110,7 +195,7 @@ describe('ArgsStore', () => {
it('checks args are allowed options', () => {
const store = new ArgsStore();

store.setInitial('id', {});
store.setInitial({ id: 'id', initialArgs: {} } as any);

const story = {
id: 'id',
Expand All @@ -123,99 +208,4 @@ describe('ArgsStore', () => {
expect(store.get('id')).toEqual({ a: 'a' });
});
});

describe('resetOnImplementationChange', () => {
describe('if initialArgs are unchanged', () => {
it('does nothing if the args are untouched', () => {
const store = new ArgsStore();

const previousStory = {
id: 'id',
initialArgs: { a: '1', b: '1' },
argTypes: { a: stringType, b: stringType },
} as any;
store.setInitial('id', previousStory.initialArgs);

const story = {
id: 'id',
initialArgs: { a: '1', b: '1' },
argTypes: { a: stringType, b: stringType },
} as any;

store.resetOnImplementationChange(story, previousStory);
expect(store.get(story.id)).toEqual({ a: '1', b: '1' });
});

it('retains any arg changes', () => {
const store = new ArgsStore();

const previousStory = {
id: 'id',
initialArgs: { a: '1', b: false, c: 'unchanged' },
argTypes: { a: stringType, b: booleanType, c: stringType },
} as any;
store.setInitial('id', previousStory.initialArgs);

// NOTE: I'm not sure technically you should be allowed to set d here, but
// let's make sure we behave sensibly if you do
store.update('id', { a: 'update', b: true, d: 'update' });

const story = {
id: 'id',
initialArgs: { a: '1', b: false, c: 'unchanged' },
argTypes: { a: stringType, b: booleanType, c: stringType },
} as any;

store.resetOnImplementationChange(story, previousStory);
// In any case c is not retained.
expect(store.get(story.id)).toEqual({ a: 'update', b: true, c: 'unchanged' });
});
});

describe('when initialArgs change', () => {
it('replaces old args with new if the args are untouched', () => {
const store = new ArgsStore();

const previousStory = {
id: 'id',
initialArgs: { a: '1', b: '1' },
argTypes: { a: stringType, b: stringType },
} as any;
store.setInitial('id', previousStory.initialArgs);

const story = {
id: 'id',
initialArgs: { a: '1', c: '1' },
argTypes: { a: stringType, c: stringType },
} as any;

store.resetOnImplementationChange(story, previousStory);
expect(store.get(story.id)).toEqual({ a: '1', c: '1' });
});

it('applies the same delta if the args are changed', () => {
const store = new ArgsStore();

const previousStory = {
id: 'id',
initialArgs: { a: '1', b: '1' },
argTypes: { a: stringType, b: stringType },
} as any;
store.setInitial('id', previousStory.initialArgs);

// NOTE: I'm not sure technically you should be allowed to set c here
store.update('id', { a: 'update', c: 'update' });

const story = {
id: 'id',
initialArgs: { a: '2', d: '2' },
argTypes: { a: stringType, d: stringType },
} as any;

store.resetOnImplementationChange(story, previousStory);
// In any case c is not retained.
expect(store.get(story.id)).toEqual({ a: 'update', d: '2' });
});
});
});
});

0 comments on commit 0a5466a

Please sign in to comment.