Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Core: Single story option in iframe view #14875

Merged
merged 11 commits into from
May 19, 2021
74 changes: 74 additions & 0 deletions lib/client-api/src/story_store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1583,4 +1583,78 @@ describe('preview.story_store', () => {
expect(onStorySpecified).not.toHaveBeenCalled();
});
});

describe('In Single Story mode', () => {
describe('when storySpecifier is story id', () => {
it('adds only one story specified in selection specifier when addStory is called', () => {
const store = new StoryStore({ channel });
store.setSelectionSpecifier({
storySpecifier: toId('kind-1', 'story-1.1'),
viewMode: 'story',
singleStory: true,
});

store.startConfiguring();
addStoryToStore(store, 'kind-1', 'story-1.1', () => 0);
addStoryToStore(store, 'kind-1', 'story-1.2', () => 0);
store.finishConfiguring();

expect(store.fromId(toId('kind-1', 'story-1.1'))).toBeTruthy();
expect(store.fromId(toId('kind-1', 'story-1.2'))).toBeFalsy();
});

it('adds only kind metadata specified in selection specifier when addKindMetadata is called', () => {
const store = new StoryStore({ channel });
store.setSelectionSpecifier({
storySpecifier: toId('kind-1', 'story-1.1'),
viewMode: 'story',
singleStory: true,
});

store.startConfiguring();
store.addKindMetadata('kind-1', {});
store.addKindMetadata('kind-2', {});
store.finishConfiguring();

expect(store._kinds['kind-1']).toBeDefined();
expect(store._kinds['kind-2']).not.toBeDefined();
});
});

describe('when storySpecifier is object', () => {
it('adds only one story specified in selection specifier when addStory is called', () => {
const store = new StoryStore({ channel });
store.setSelectionSpecifier({
storySpecifier: { kind: 'kind-1', name: 'story-1.1' },
viewMode: 'story',
singleStory: true,
});

store.startConfiguring();
addStoryToStore(store, 'kind-1', 'story-1.1', () => 0);
addStoryToStore(store, 'kind-1', 'story-1.2', () => 0);
store.finishConfiguring();

expect(store.fromId(toId('kind-1', 'story-1.1'))).toBeTruthy();
expect(store.fromId(toId('kind-1', 'story-1.2'))).toBeFalsy();
});

it('adds only kind metadata specified in selection specifier when addKindMetadata is called', () => {
const store = new StoryStore({ channel });
store.setSelectionSpecifier({
storySpecifier: { kind: 'kind-1', name: 'story-1.1' },
viewMode: 'story',
singleStory: true,
});

store.startConfiguring();
store.addKindMetadata('kind-1', {});
store.addKindMetadata('kind-2', {});
store.finishConfiguring();

expect(store._kinds['kind-1']).toBeDefined();
expect(store._kinds['kind-2']).not.toBeDefined();
});
});
});
});
50 changes: 50 additions & 0 deletions lib/client-api/src/story_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import deprecate from 'util-deprecate';
import { Channel } from '@storybook/channels';
import Events from '@storybook/core-events';
import { logger } from '@storybook/client-logger';
import { sanitize, toId } from '@storybook/csf';
import {
Comparator,
Parameters,
Expand All @@ -32,6 +33,7 @@ import {
ArgTypesEnhancer,
StoreSelectionSpecifier,
StoreSelection,
StorySpecifier,
} from './types';
import { combineArgs, mapArgsToTypes, validateOptions } from './args';
import { HooksContext } from './hooks';
Expand All @@ -49,6 +51,22 @@ type KindMetadata = StoryMetadata & { order: number };

const STORAGE_KEY = '@storybook/preview/store';

function extractSanitizedKindNameFromStorySpecifier(storySpecifier: StorySpecifier): string {
if (typeof storySpecifier === 'string') {
return storySpecifier.split('--').shift();
}

return sanitize(storySpecifier.kind);
}

function extractIdFromStorySpecifier(storySpecifier: StorySpecifier): string {
if (typeof storySpecifier === 'string') {
return storySpecifier;
}

return toId(storySpecifier.kind, storySpecifier.name);
}

const isStoryDocsOnly = (parameters?: Parameters) => {
return parameters && parameters.docsOnly;
};
Expand Down Expand Up @@ -301,6 +319,10 @@ export default class StoryStore {
}

addKindMetadata(kind: string, { parameters = {}, decorators = [], loaders = [] }: StoryMetadata) {
if (this.shouldBlockAddingKindMetadata(kind)) {
return;
}

this.ensureKind(kind);
if (parameters) {
checkGlobals(parameters);
Expand Down Expand Up @@ -328,6 +350,21 @@ export default class StoryStore {
);
}

shouldBlockAddingStory(id: string): boolean {
return (
this.isSingleStoryMode() &&
id !== extractIdFromStorySpecifier(this._selectionSpecifier.storySpecifier)
);
}

shouldBlockAddingKindMetadata(kind: string): boolean {
return (
this.isSingleStoryMode() &&
sanitize(kind) !==
extractSanitizedKindNameFromStorySpecifier(this._selectionSpecifier.storySpecifier)
);
}

addStory(
{
id,
Expand All @@ -350,6 +387,10 @@ export default class StoryStore {
'Cannot add a story when not configuring, see https://github.com/storybookjs/storybook/blob/next/MIGRATION.md#story-store-immutable-outside-of-configuration'
);

if (this.shouldBlockAddingStory(id)) {
return;
}

checkGlobals(storyParameters);
checkStorySort(storyParameters);

Expand Down Expand Up @@ -658,6 +699,15 @@ export default class StoryStore {
}
}

isSingleStoryMode(): boolean {
if (!this._selectionSpecifier) {
return false;
}

const { singleStory, storySpecifier } = this._selectionSpecifier;
return storySpecifier && storySpecifier !== '*' && singleStory;
}

getSelection = (): StoreSelection => this._selection;

getDataForManager = () => {
Expand Down
3 changes: 2 additions & 1 deletion lib/client-api/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,12 @@ export interface StoryMetadata {
}
export type ArgTypesEnhancer = (context: StoryContext) => ArgTypes;

type StorySpecifier = StoryId | { name: StoryName; kind: StoryKind } | '*';
export type StorySpecifier = StoryId | { name: StoryName; kind: StoryKind } | '*';

export interface StoreSelectionSpecifier {
storySpecifier: StorySpecifier;
viewMode: ViewMode;
singleStory?: boolean;
args?: Args;
}

Expand Down
12 changes: 12 additions & 0 deletions lib/core-client/src/preview/url.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,29 +73,41 @@ describe('url', () => {
expect(getSelectionSpecifierFromPath()).toEqual({
storySpecifier: 'story--id',
viewMode: 'story',
singleStory: false,
});
});
it('should handle id queries with *', () => {
document.location.search = '?id=*';
expect(getSelectionSpecifierFromPath()).toEqual({
storySpecifier: '*',
viewMode: 'story',
singleStory: false,
});
});
it('should redirect legacy queries', () => {
document.location.search = '?selectedKind=kind&selectedStory=story';
expect(getSelectionSpecifierFromPath()).toEqual({
storySpecifier: { kind: 'kind', name: 'story' },
viewMode: 'story',
singleStory: false,
});
});
it('should parse args', () => {
document.location.search = '?id=story--id&args=obj.key:val';
expect(getSelectionSpecifierFromPath()).toEqual({
storySpecifier: 'story--id',
viewMode: 'story',
singleStory: false,
args: { obj: { key: 'val' } },
});
});
it('should handle singleStory param', () => {
document.location.search = '?id=abc&singleStory=true';
expect(getSelectionSpecifierFromPath()).toEqual({
storySpecifier: 'abc',
viewMode: 'story',
singleStory: true,
});
});
});
});
5 changes: 3 additions & 2 deletions lib/core-client/src/preview/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,12 @@ export const getSelectionSpecifierFromPath: () => StoreSelectionSpecifier = () =
viewMode = 'story';
}

const singleStory = getFirstString(query.singleStory) === 'true';
const path = getFirstString(query.path);
const storyId = path ? pathToId(path) : getFirstString(query.id);

if (storyId) {
return { storySpecifier: storyId, args, viewMode };
return { storySpecifier: storyId, args, viewMode, singleStory };
}

// Legacy URL format
Expand All @@ -85,7 +86,7 @@ export const getSelectionSpecifierFromPath: () => StoreSelectionSpecifier = () =

if (kind && name) {
deprecatedLegacyQuery();
return { storySpecifier: { kind, name }, args, viewMode };
return { storySpecifier: { kind, name }, args, viewMode, singleStory };
}
return null;
};