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: Fix sorting by __namedExportsOrder #16626

Merged
merged 2 commits into from
Nov 8, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 11 additions & 0 deletions examples/react-ts/src/button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default {

export const WithArgs: ComponentStory<typeof Button> = (args) => <Button {...args} />;
WithArgs.args = { label: 'With args' };

export const Basic = () => <Button label="Click me" />;

export const StoryObject = {
Expand All @@ -36,3 +37,13 @@ CSF2StoryWithPlay.play = () => {
console.log('play!!');
userEvent.click(screen.getByRole('button'));
};

// eslint-disable-next-line no-underscore-dangle
export const __namedExportsOrder = [
'Basic',
'WithArgs',
'StoryObject',
'StoryNoRender',
'StoryWithPlay',
'CSF2StoryWithPlay',
];
15 changes: 14 additions & 1 deletion lib/client-api/src/StoryStoreFacade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,20 @@ export class StoryStoreFacade<TFramework extends AnyFramework> {
default: { ...defaultExport, title },
};

Object.entries(namedExports)
let sortedExports = namedExports;

// prefer a user/loader provided `__namedExportsOrder` array if supplied
// we do this as es module exports are always ordered alphabetically
// see https://github.com/storybookjs/storybook/issues/9136
if (Array.isArray(__namedExportsOrder)) {
sortedExports = {};
__namedExportsOrder.forEach((name) => {
const namedExport = namedExports[name];
if (namedExport) sortedExports[name] = namedExport;
});
}

Object.entries(sortedExports)
.filter(([key]) => isExportStory(key, defaultExport))
.forEach(([key, storyExport]: [string, any]) => {
const exportName = storyNameFromExport(key);
Expand Down
31 changes: 31 additions & 0 deletions lib/csf-tools/src/CsfFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,34 @@ describe('CsfFile', () => {
name: B
`);
});

it('named exports order', () => {
expect(
parse(
dedent`
export default { title: 'foo/bar' };
export const A = () => {};
export const B = (args) => {};
export const __namedExportsOrder = ['B', 'A'];
`,
true
)
).toMatchInlineSnapshot(`
meta:
title: foo/bar
stories:
- id: foo-bar--b
name: B
parameters:
__isArgsStory: true
__id: foo-bar--b
- id: foo-bar--a
name: A
parameters:
__isArgsStory: false
__id: foo-bar--a
`);
});
});

describe('error handling', () => {
Expand All @@ -318,6 +346,7 @@ describe('CsfFile', () => {
)
).toThrow('CSF: missing default export');
});

it('no metadata', () => {
expect(() =>
parse(
Expand All @@ -329,6 +358,7 @@ describe('CsfFile', () => {
)
).toThrow('CSF: missing title/component');
});

it('dynamic titles', () => {
expect(() =>
parse(
Expand All @@ -340,6 +370,7 @@ describe('CsfFile', () => {
)
).toThrow('CSF: unexpected dynamic title');
});

it('storiesOf calls', () => {
expect(() =>
parse(
Expand Down
31 changes: 31 additions & 0 deletions lib/csf-tools/src/CsfFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,26 @@ const isArgsStory = (init: t.Expression, parent: t.Node, csf: CsfFile) => {
return false;
};

const parseExportsOrder = (init: t.Expression) => {
if (t.isArrayExpression(init)) {
return init.elements.map((item: t.Expression) => {
if (t.isStringLiteral(item)) {
return item.value;
}
throw new Error(`Expected string literal named export: ${item}`);
});
}
throw new Error(`Expected array of string literals: ${init}`);
};

const sortExports = (exportByName: Record<string, any>, order: string[]) => {
return order.reduce((acc, name) => {
const namedExport = exportByName[name];
if (namedExport) acc[name] = namedExport;
return acc;
}, {} as Record<string, any>);
};

export interface CsfOptions {
defaultTitle: string;
fileName?: string;
Expand Down Expand Up @@ -134,6 +154,8 @@ export class CsfFile {

_templates: Record<string, t.Expression> = {};

_namedExportsOrder?: string[];

constructor(ast: t.File, { defaultTitle, fileName }: CsfOptions) {
this._ast = ast;
this._defaultTitle = defaultTitle;
Expand Down Expand Up @@ -207,6 +229,10 @@ export class CsfFile {
node.declaration.declarations.forEach((decl) => {
if (t.isVariableDeclarator(decl) && t.isIdentifier(decl.id)) {
const { name: exportName } = decl.id;
if (exportName === '__namedExportsOrder') {
self._namedExportsOrder = parseExportsOrder(decl.init);
return;
}
self._storyExports[exportName] = decl;
let name = storyNameFromExport(exportName);
if (self._storyAnnotations[exportName]) {
Expand Down Expand Up @@ -344,6 +370,11 @@ export class CsfFile {
}
});

if (self._namedExportsOrder) {
self._storyExports = sortExports(self._storyExports, self._namedExportsOrder);
self._stories = sortExports(self._stories, self._namedExportsOrder);
}

return self;
}

Expand Down
5 changes: 2 additions & 3 deletions lib/store/src/processCSFFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,8 @@ export function processCSFFile<TFramework extends AnyFramework>(
if (Array.isArray(__namedExportsOrder)) {
exports = {};
__namedExportsOrder.forEach((name) => {
if (namedExports[name]) {
exports[name] = namedExports[name];
}
const namedExport = namedExports[name];
if (namedExport) exports[name] = namedExport;
});
}

Expand Down