diff --git a/src/parser/collect-stories.js b/src/parser/collect-stories.js
index 2aa72ac..2e78414 100644
--- a/src/parser/collect-stories.js
+++ b/src/parser/collect-stories.js
@@ -21,7 +21,7 @@ const createFragment = document.createDocumentFragment
? () => document.createDocumentFragment()
: () => document.createElement('div');
-export default (StoriesComponent, stories) => {
+export default (StoriesComponent, { stories = {}, allocatedIds }) => {
const repositories = {
meta: null,
stories: [],
@@ -72,7 +72,7 @@ export default (StoriesComponent, stories) => {
.reduce((all, story) => {
const { id, name, template, component, source = false, ...props } = story;
- const storyId = extractId(story);
+ const storyId = extractId(story, allocatedIds);
if (!storyId) {
return all;
}
diff --git a/src/parser/collect-stories.test.js b/src/parser/collect-stories.test.js
index 61b2289..abbb8e8 100644
--- a/src/parser/collect-stories.test.js
+++ b/src/parser/collect-stories.test.js
@@ -3,7 +3,7 @@ import TestStories from '../components/__tests__/TestStories.svelte';
describe('parse-stories', () => {
test('Extract Stories', () => {
- const data = collectStories(TestStories, { 'tpl:tpl2': 'tpl2src' });
+ const data = collectStories(TestStories, { stories: { 'tpl:tpl2': 'tpl2src' } });
const { stories, meta } = data;
expect(meta).toMatchInlineSnapshot(`
Object {
diff --git a/src/parser/extract-id.test.js b/src/parser/extract-id.test.js
index 0bcaf38..12020a3 100644
--- a/src/parser/extract-id.test.js
+++ b/src/parser/extract-id.test.js
@@ -4,4 +4,7 @@ describe('extract-id', () => {
test('name with spaces', () => {
expect(extractId({ name: 'Name with spaces' })).toBe('NameWithSpaces');
});
+ test('duplicates id', () => {
+ expect(extractId({ name: 'Button' }, ['Button'])).toBe('Button77471352');
+ });
});
diff --git a/src/parser/extract-id.ts b/src/parser/extract-id.ts
index 2e214e2..55f8d3d 100644
--- a/src/parser/extract-id.ts
+++ b/src/parser/extract-id.ts
@@ -1,8 +1,31 @@
+import { logger } from '@storybook/client-logger';
+
// extract a story id
-export function extractId({ id, name }: { id?: string; name?: string }): string {
+export function extractId(
+ {
+ id,
+ name,
+ }: {
+ id?: string;
+ name?: string;
+ },
+ allocatedIds: string[] = []
+): string {
if (id) {
return id;
}
- return name.replace(/\W+(.)/g, (_, chr) => chr.toUpperCase());
+ let generated = name.replace(/\W+(.)/g, (_, chr) => chr.toUpperCase());
+ if (allocatedIds.indexOf(generated) >= 0) {
+ logger.warn(`Story name conflict with exports - Please add an explicit id for story ${name}`);
+ generated += hashCode(name);
+ }
+ return generated;
+}
+
+function hashCode(str: string): string {
+ return str
+ .split('')
+ .reduce((prevHash, currVal) => ((prevHash << 5) - prevHash + currVal.charCodeAt(0)) | 0, 0) // eslint-disable-line
+ .toString(16);
}
diff --git a/src/parser/extract-stories.test.js b/src/parser/extract-stories.test.js
index f546499..0bff61b 100644
--- a/src/parser/extract-stories.test.js
+++ b/src/parser/extract-stories.test.js
@@ -14,11 +14,17 @@ describe('extractSource', () => {
`)
).toMatchInlineSnapshot(`
Object {
- "MyStory": Object {
- "hasArgs": false,
- "name": "MyStory",
- "source": "
a story
",
- "template": false,
+ "allocatedIds": Array [
+ "default",
+ "Story",
+ ],
+ "stories": Object {
+ "MyStory": Object {
+ "hasArgs": false,
+ "name": "MyStory",
+ "source": "a story
",
+ "template": false,
+ },
},
}
`);
@@ -36,11 +42,17 @@ describe('extractSource', () => {
`)
).toMatchInlineSnapshot(`
Object {
- "myId": Object {
- "hasArgs": false,
- "name": "MyStory",
- "source": "a story
",
- "template": false,
+ "allocatedIds": Array [
+ "default",
+ "Story",
+ ],
+ "stories": Object {
+ "myId": Object {
+ "hasArgs": false,
+ "name": "MyStory",
+ "source": "a story
",
+ "template": false,
+ },
},
}
`);
@@ -58,11 +70,17 @@ describe('extractSource', () => {
`)
).toMatchInlineSnapshot(`
Object {
- "MyStory": Object {
- "hasArgs": true,
- "name": "MyStory",
- "source": "a story
",
- "template": false,
+ "allocatedIds": Array [
+ "default",
+ "Story",
+ ],
+ "stories": Object {
+ "MyStory": Object {
+ "hasArgs": true,
+ "name": "MyStory",
+ "source": "a story
",
+ "template": false,
+ },
},
}
`);
@@ -80,11 +98,17 @@ describe('extractSource', () => {
`)
).toMatchInlineSnapshot(`
Object {
- "tpl:MyTemplate": Object {
- "hasArgs": false,
- "name": "MyTemplate",
- "source": "a template
",
- "template": true,
+ "allocatedIds": Array [
+ "default",
+ "Template",
+ ],
+ "stories": Object {
+ "tpl:MyTemplate": Object {
+ "hasArgs": false,
+ "name": "MyTemplate",
+ "source": "a template
",
+ "template": true,
+ },
},
}
`);
@@ -102,11 +126,17 @@ describe('extractSource', () => {
`)
).toMatchInlineSnapshot(`
Object {
- "tpl:default": Object {
- "hasArgs": false,
- "name": "default",
- "source": "a template
",
- "template": true,
+ "allocatedIds": Array [
+ "default",
+ "Template",
+ ],
+ "stories": Object {
+ "tpl:default": Object {
+ "hasArgs": false,
+ "name": "default",
+ "source": "a template
",
+ "template": true,
+ },
},
}
`);
@@ -127,17 +157,23 @@ describe('extractSource', () => {
`)
).toMatchInlineSnapshot(`
Object {
- "Story1": Object {
- "hasArgs": false,
- "name": "Story1",
- "source": "story 1
",
- "template": false,
- },
- "Story2": Object {
- "hasArgs": false,
- "name": "Story2",
- "source": "story 2
",
- "template": false,
+ "allocatedIds": Array [
+ "default",
+ "Template",
+ ],
+ "stories": Object {
+ "Story1": Object {
+ "hasArgs": false,
+ "name": "Story1",
+ "source": "story 1
",
+ "template": false,
+ },
+ "Story2": Object {
+ "hasArgs": false,
+ "name": "Story2",
+ "source": "story 2
",
+ "template": false,
+ },
},
}
`);
@@ -157,11 +193,48 @@ describe('extractSource', () => {
`)
).toMatchInlineSnapshot(`
Object {
- "Story1": Object {
- "hasArgs": false,
- "name": "Story1",
- "source": "story 1
",
- "template": false,
+ "allocatedIds": Array [
+ "default",
+ "SBStory",
+ "SBMeta",
+ ],
+ "stories": Object {
+ "Story1": Object {
+ "hasArgs": false,
+ "name": "Story1",
+ "source": "story 1
",
+ "template": false,
+ },
+ },
+ }
+ `);
+ });
+ test('Duplicate Id', () => {
+ expect(
+ extractStories(`
+
+
+
+ a story
+
+ `)
+ ).toMatchInlineSnapshot(`
+ Object {
+ "allocatedIds": Array [
+ "default",
+ "Story",
+ "Button",
+ ],
+ "stories": Object {
+ "Button77471352": Object {
+ "hasArgs": false,
+ "name": "Button",
+ "source": "a story
",
+ "template": false,
+ },
},
}
`);
diff --git a/src/parser/extract-stories.ts b/src/parser/extract-stories.ts
index b00b6af..0246b45 100644
--- a/src/parser/extract-stories.ts
+++ b/src/parser/extract-stories.ts
@@ -9,6 +9,11 @@ interface StoryDef {
hasArgs: boolean;
}
+interface StoriesDef {
+ stories: Record;
+ allocatedIds: string[];
+}
+
function getStaticAttribute(name: string, node: any): string {
// extract the attribute
const attribute = node.attributes.find(
@@ -33,10 +38,12 @@ function getStaticAttribute(name: string, node: any): string {
* @param component Component Source
* @returns Map of storyName -> source
*/
-export function extractStories(component: string): Record {
+export function extractStories(component: string): StoriesDef {
// compile
const { ast } = svelte.compile(component);
+ const allocatedIds: string[] = ['default'];
+
const localNames = {
Story: 'Story',
Template: 'Template',
@@ -58,6 +65,18 @@ export function extractStories(component: string): Record {
},
});
+ // extracts allocated Ids
+ svelte.walk(ast.instance, {
+ enter(node: any) {
+ if (node.type === 'ImportDeclaration') {
+ node.specifiers
+ .map((n: any) => n.local.name)
+ .forEach((name: string) => allocatedIds.push(name));
+ this.skip();
+ }
+ },
+ });
+
const stories: Record = {};
svelte.walk(ast.html, {
enter(node: any) {
@@ -77,10 +96,13 @@ export function extractStories(component: string): Record {
name = 'default';
}
- const id = extractId({
- id: getStaticAttribute('id', node),
- name,
- });
+ const id = extractId(
+ {
+ id: getStaticAttribute('id', node),
+ name,
+ },
+ isTemplate ? undefined : allocatedIds
+ );
if (name && id) {
// ignore stories without children
@@ -103,5 +125,8 @@ export function extractStories(component: string): Record {
},
});
- return stories;
+ return {
+ stories,
+ allocatedIds,
+ };
}
diff --git a/src/parser/svelte-stories-loader.ts b/src/parser/svelte-stories-loader.ts
index 1fa9987..5367110 100644
--- a/src/parser/svelte-stories-loader.ts
+++ b/src/parser/svelte-stories-loader.ts
@@ -44,7 +44,8 @@ function transformSvelteStories(code: string) {
const source = readFileSync(resource).toString();
- const stories = extractStories(source);
+ const storiesDef = extractStories(source);
+ const { stories } = storiesDef;
const storyDef = Object.entries(stories)
.filter(([, def]) => !def.template)
@@ -55,7 +56,7 @@ function transformSvelteStories(code: string) {
return dedent`${codeWithoutDefaultExport}
const { default: parser } = require('${parser}');
- const __storiesMetaData = parser(${componentName}, ${JSON.stringify(stories)});
+ const __storiesMetaData = parser(${componentName}, ${JSON.stringify(storiesDef)});
export default __storiesMetaData.meta;
${storyDef};
`;
diff --git a/stories/__snapshots__/button.stories.storyshot b/stories/__snapshots__/button.stories.storyshot
index d68c867..1f48a17 100644
--- a/stories/__snapshots__/button.stories.storyshot
+++ b/stories/__snapshots__/button.stories.storyshot
@@ -1,5 +1,38 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
+exports[`Storyshots Button Button 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`;
+
exports[`Storyshots Button Button No Args 1`] = `
@@ -57,6 +92,8 @@ exports[`Storyshots Button Rounded 1`] = `
+
+
`;
@@ -88,6 +125,8 @@ exports[`Storyshots Button Square 1`] = `
+
+
`;
diff --git a/stories/button.stories.svelte b/stories/button.stories.svelte
index ef6e431..bbbb7ab 100644
--- a/stories/button.stories.svelte
+++ b/stories/button.stories.svelte
@@ -17,6 +17,8 @@
+
+