@storybook/codemod 7.0.0-beta.6 → 7.0.0-beta.60

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. package/README.md +0 -39
  2. package/dist/index.js +1 -1
  3. package/dist/transforms/add-component-parameters.js +1 -1
  4. package/dist/transforms/csf-2-to-3.d.ts +5 -4
  5. package/dist/transforms/csf-2-to-3.js +3 -1
  6. package/dist/transforms/csf-hoist-story-annotations.js +1 -1
  7. package/dist/transforms/mdx-to-csf.d.ts +7 -0
  8. package/dist/transforms/mdx-to-csf.js +55 -0
  9. package/dist/transforms/move-builtin-addons.js +1 -1
  10. package/dist/transforms/storiesof-to-csf.js +1 -1
  11. package/dist/transforms/update-addon-info.js +1 -1
  12. package/dist/transforms/update-organisation-name.js +1 -1
  13. package/dist/transforms/upgrade-deprecated-types.d.ts +10 -0
  14. package/dist/transforms/upgrade-deprecated-types.js +2 -0
  15. package/dist/transforms/upgrade-hierarchy-separators.js +1 -1
  16. package/jest.config.js +2 -0
  17. package/package.json +31 -15
  18. package/project.json +6 -0
  19. package/src/index.js +30 -4
  20. package/src/lib/utils.ts +2 -2
  21. package/src/transforms/__testfixtures__/storiesof-to-csf/decorators.input.js +1 -1
  22. package/src/transforms/__testfixtures__/storiesof-to-csf/export-function.input.js +1 -1
  23. package/src/transforms/__testfixtures__/storiesof-to-csf/story-decorators.input.js +1 -1
  24. package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.input.js +34 -48
  25. package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.output.snapshot +33 -47
  26. package/src/transforms/__tests__/csf-2-to-3.test.ts +170 -57
  27. package/src/transforms/__tests__/mdx-to-csf.test.ts +587 -0
  28. package/src/transforms/__tests__/upgrade-deprecated-types.test.ts +170 -0
  29. package/src/transforms/csf-2-to-3.ts +173 -37
  30. package/src/transforms/mdx-to-csf.ts +342 -0
  31. package/src/transforms/upgrade-deprecated-types.ts +142 -0
  32. package/dist/chunk-3OPQTROG.mjs +0 -1
  33. package/dist/chunk-B5FMQ3BX.mjs +0 -1
  34. package/dist/chunk-CO6EPEMB.mjs +0 -1
  35. package/dist/index.mjs +0 -1
  36. package/dist/transforms/add-component-parameters.mjs +0 -1
  37. package/dist/transforms/csf-2-to-3.mjs +0 -1
  38. package/dist/transforms/csf-hoist-story-annotations.mjs +0 -1
  39. package/dist/transforms/csf-to-mdx.d.ts +0 -29
  40. package/dist/transforms/csf-to-mdx.js +0 -3
  41. package/dist/transforms/csf-to-mdx.mjs +0 -3
  42. package/dist/transforms/move-builtin-addons.mjs +0 -1
  43. package/dist/transforms/storiesof-to-csf.mjs +0 -1
  44. package/dist/transforms/update-addon-info.mjs +0 -1
  45. package/dist/transforms/update-organisation-name.mjs +0 -1
  46. package/dist/transforms/upgrade-hierarchy-separators.mjs +0 -1
  47. package/src/transforms/__testfixtures__/csf-to-mdx/basic.input.js +0 -20
  48. package/src/transforms/__testfixtures__/csf-to-mdx/basic.output.snapshot +0 -18
  49. package/src/transforms/__testfixtures__/csf-to-mdx/component-id.input.js +0 -9
  50. package/src/transforms/__testfixtures__/csf-to-mdx/component-id.output.snapshot +0 -10
  51. package/src/transforms/__testfixtures__/csf-to-mdx/decorators.input.js +0 -13
  52. package/src/transforms/__testfixtures__/csf-to-mdx/decorators.output.snapshot +0 -12
  53. package/src/transforms/__testfixtures__/csf-to-mdx/exclude-stories.input.js +0 -23
  54. package/src/transforms/__testfixtures__/csf-to-mdx/exclude-stories.output.snapshot +0 -22
  55. package/src/transforms/__testfixtures__/csf-to-mdx/parameters.input.js +0 -16
  56. package/src/transforms/__testfixtures__/csf-to-mdx/parameters.output.snapshot +0 -17
  57. package/src/transforms/__testfixtures__/csf-to-mdx/story-function.input.js +0 -19
  58. package/src/transforms/__testfixtures__/csf-to-mdx/story-function.output.snapshot +0 -18
  59. package/src/transforms/__testfixtures__/csf-to-mdx/story-parameters.input.js +0 -24
  60. package/src/transforms/__testfixtures__/csf-to-mdx/story-parameters.output.snapshot +0 -22
  61. package/src/transforms/csf-to-mdx.js +0 -190
@@ -0,0 +1,587 @@
1
+ import * as fs_ from 'node:fs';
2
+ import { expect, test } from '@jest/globals';
3
+ import dedent from 'ts-dedent';
4
+ import jscodeshift, { nameToValidExport } from '../mdx-to-csf';
5
+
6
+ expect.addSnapshotSerializer({
7
+ print: (val: any) => (typeof val === 'string' ? val : JSON.stringify(val, null, 2) ?? ''),
8
+ test: () => true,
9
+ });
10
+
11
+ jest.mock('node:fs');
12
+ const fs = fs_ as jest.Mocked<typeof import('node:fs')>;
13
+
14
+ beforeEach(() => {
15
+ fs.existsSync.mockImplementation(() => false);
16
+ });
17
+
18
+ test('drop invalid story nodes', () => {
19
+ const input = dedent`
20
+ import { Meta } from '@storybook/addon-docs';
21
+
22
+ <Meta title="Foobar" />
23
+
24
+ <Story>No name!</Story>
25
+
26
+ <Story name="Primary">Story</Story>
27
+
28
+ `;
29
+
30
+ const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
31
+
32
+ expect(mdx).toMatchInlineSnapshot(`
33
+ import { Meta } from '@storybook/addon-docs';
34
+
35
+ <Meta of={FoobarStories} />
36
+
37
+
38
+
39
+ <Story of={FoobarStories.Primary} />
40
+
41
+ `);
42
+ });
43
+
44
+ test('convert story re-definition', () => {
45
+ const input = dedent`
46
+ import { Meta, Story } from '@storybook/addon-docs';
47
+ import { Primary } from './Foobar.stories';
48
+
49
+ <Meta title="Foobar" />
50
+
51
+ <Story story={Primary} />
52
+ `;
53
+
54
+ fs.existsSync.mockImplementation((path) => path === 'Foobar.stories.js');
55
+
56
+ const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
57
+
58
+ expect(mdx).toMatchInlineSnapshot(`
59
+ import { Meta, Story } from '@storybook/blocks';
60
+ import { Primary } from './Foobar.stories';
61
+ import * as FoobarStories from './Foobar_.stories';
62
+
63
+ <Meta of={FoobarStories} />
64
+
65
+ <Story of={FoobarStories.Primary} />
66
+
67
+ `);
68
+ const [csfFileName, csf] = fs.writeFileSync.mock.calls[0];
69
+ expect(csfFileName).toMatchInlineSnapshot(`Foobar_.stories.js`);
70
+ expect(csf).toMatchInlineSnapshot(`
71
+ import { Primary } from './Foobar.stories';
72
+
73
+ export default {
74
+ title: 'Foobar',
75
+ };
76
+
77
+ export { Primary };
78
+
79
+ `);
80
+ });
81
+
82
+ test('Comment out story nodes with id', () => {
83
+ const input = dedent`
84
+ import { Meta, Story } from '@storybook/addon-docs';
85
+
86
+ <Meta title="Foobar" />
87
+
88
+ <Story id="button--primary" />
89
+ `;
90
+
91
+ const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
92
+
93
+ expect(mdx).toMatchInlineSnapshot(`
94
+ import { Meta, Story } from '@storybook/blocks';
95
+ import * as FoobarStories from './Foobar.stories';
96
+
97
+ <Meta of={FoobarStories} />
98
+
99
+ {/* <Story id="button--primary" /> is deprecated, please migrate it to <Story of={referenceToStory} /> */}
100
+
101
+ <Story id="button--primary" />
102
+
103
+ `);
104
+ });
105
+
106
+ test('convert correct story nodes', () => {
107
+ const input = dedent`
108
+ import { Meta, Story } from '@storybook/addon-docs';
109
+
110
+ <Meta title="Foobar" />
111
+
112
+ <Story name="Primary">Story</Story>
113
+ `;
114
+
115
+ const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
116
+
117
+ expect(mdx).toMatchInlineSnapshot(`
118
+ import { Meta, Story } from '@storybook/blocks';
119
+ import * as FoobarStories from './Foobar.stories';
120
+
121
+ <Meta of={FoobarStories} />
122
+
123
+ <Story of={FoobarStories.Primary} />
124
+
125
+ `);
126
+
127
+ const [, csf] = fs.writeFileSync.mock.calls[0];
128
+ expect(csf).toMatchInlineSnapshot(`
129
+ export default {
130
+ title: 'Foobar',
131
+ };
132
+
133
+ export const Primary = {
134
+ render: () => 'Story',
135
+ name: 'Primary',
136
+ };
137
+
138
+ `);
139
+ });
140
+
141
+ test('convert story nodes with spaces', () => {
142
+ const input = dedent`
143
+ import { Meta, Story } from '@storybook/addon-docs';
144
+
145
+ <Meta title="Foobar" />
146
+
147
+ <Story name="Primary Space">Story</Story>
148
+ `;
149
+
150
+ const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
151
+
152
+ expect(mdx).toMatchInlineSnapshot(`
153
+ import { Meta, Story } from '@storybook/blocks';
154
+ import * as FoobarStories from './Foobar.stories';
155
+
156
+ <Meta of={FoobarStories} />
157
+
158
+ <Story of={FoobarStories.PrimarySpace} />
159
+
160
+ `);
161
+
162
+ const [, csf] = fs.writeFileSync.mock.calls[0];
163
+ expect(csf).toMatchInlineSnapshot(`
164
+ export default {
165
+ title: 'Foobar',
166
+ };
167
+
168
+ export const PrimarySpace = {
169
+ render: () => 'Story',
170
+ name: 'Primary Space',
171
+ };
172
+
173
+ `);
174
+ });
175
+
176
+ test('extract esm into csf head code', () => {
177
+ const input = dedent`
178
+ import { Meta, Story } from '@storybook/addon-docs';
179
+ import { Button } from './Button';
180
+
181
+ # hello
182
+
183
+ export const args = { bla: 1 };
184
+ export const Template = (args) => <Button {...args} />;
185
+
186
+ <Meta title="foobar" />
187
+
188
+ world {2 + 1}
189
+
190
+ <Story name="foo">bar</Story>
191
+
192
+ <Story
193
+ name="Unchecked"
194
+ args={{
195
+ ...args,
196
+ label: 'Unchecked',
197
+ }}>
198
+ {Template.bind({})}
199
+ </Story>
200
+ `;
201
+
202
+ const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
203
+
204
+ expect(mdx).toMatchInlineSnapshot(`
205
+ import { Meta, Story } from '@storybook/blocks';
206
+ import { Button } from './Button';
207
+ import * as FoobarStories from './Foobar.stories';
208
+
209
+ # hello
210
+
211
+ export const args = { bla: 1 };
212
+ export const Template = (args) => <Button {...args} />;
213
+
214
+ <Meta of={FoobarStories} />
215
+
216
+ world {2 + 1}
217
+
218
+ <Story of={FoobarStories.Foo} />
219
+
220
+ <Story of={FoobarStories.Unchecked} />
221
+
222
+ `);
223
+
224
+ const [csfFileName, csf] = fs.writeFileSync.mock.calls[0];
225
+ expect(csfFileName).toMatchInlineSnapshot(`Foobar.stories.js`);
226
+ expect(csf).toMatchInlineSnapshot(`
227
+ import { Button } from './Button';
228
+
229
+ const args = { bla: 1 };
230
+ const Template = (args) => <Button {...args} />;
231
+
232
+ export default {
233
+ title: 'foobar',
234
+ };
235
+
236
+ export const Foo = {
237
+ render: () => 'bar',
238
+ name: 'foo',
239
+ };
240
+
241
+ export const Unchecked = {
242
+ render: Template.bind({}),
243
+ name: 'Unchecked',
244
+
245
+ args: {
246
+ ...args,
247
+ label: 'Unchecked',
248
+ },
249
+ };
250
+
251
+ `);
252
+ });
253
+
254
+ test('extract all meta parameters', () => {
255
+ const input = dedent`
256
+ import { Meta, Story } from '@storybook/addon-docs';
257
+
258
+ export const args = { bla: 1 };
259
+
260
+ <Meta title="foobar" args={{...args}} parameters={{a: '1'}} />
261
+
262
+ <Story name="foo">bar</Story>
263
+ `;
264
+
265
+ jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
266
+
267
+ const [, csf] = fs.writeFileSync.mock.calls[0];
268
+
269
+ expect(csf).toMatchInlineSnapshot(`
270
+ const args = { bla: 1 };
271
+
272
+ export default {
273
+ title: 'foobar',
274
+
275
+ args: {
276
+ ...args,
277
+ },
278
+
279
+ parameters: {
280
+ a: '1',
281
+ },
282
+ };
283
+
284
+ export const Foo = {
285
+ render: () => 'bar',
286
+ name: 'foo',
287
+ };
288
+
289
+ `);
290
+ });
291
+
292
+ test('extract all story attributes', () => {
293
+ const input = dedent`
294
+ import { Meta, Story } from '@storybook/addon-docs';
295
+ import { Button } from './Button';
296
+
297
+ export const args = { bla: 1 };
298
+ export const Template = (args) => <Button {...args} />;
299
+
300
+ <Meta title="foobar" />
301
+
302
+ <Story name="foo">bar</Story>
303
+
304
+ <Story
305
+ name="Unchecked"
306
+ args={{
307
+ ...args,
308
+ label: 'Unchecked',
309
+ }}>
310
+ {Template.bind({})}
311
+ </Story>
312
+
313
+ <Story name="Second">{Template.bind({})}</Story>
314
+
315
+ `;
316
+
317
+ jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
318
+
319
+ const [, csf] = fs.writeFileSync.mock.calls[0];
320
+
321
+ expect(csf).toMatchInlineSnapshot(`
322
+ import { Button } from './Button';
323
+
324
+ const args = { bla: 1 };
325
+ const Template = (args) => <Button {...args} />;
326
+
327
+ export default {
328
+ title: 'foobar',
329
+ };
330
+
331
+ export const Foo = {
332
+ render: () => 'bar',
333
+ name: 'foo',
334
+ };
335
+
336
+ export const Unchecked = {
337
+ render: Template.bind({}),
338
+ name: 'Unchecked',
339
+
340
+ args: {
341
+ ...args,
342
+ label: 'Unchecked',
343
+ },
344
+ };
345
+
346
+ export const Second = {
347
+ render: Template.bind({}),
348
+ name: 'Second',
349
+ };
350
+
351
+ `);
352
+ });
353
+
354
+ test('duplicate story name', () => {
355
+ const input = dedent`
356
+ import { Meta, Story } from '@storybook/addon-docs';
357
+ import { Button } from './Button';
358
+
359
+ export const Default = (args) => <Button {...args} />;
360
+
361
+ <Meta title="Button" />
362
+
363
+ <Story name="Default">
364
+ {Default.bind({})}
365
+ </Story>
366
+
367
+ <Story name="Second">{Default.bind({})}</Story>
368
+
369
+ `;
370
+
371
+ const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
372
+ const [, csf] = fs.writeFileSync.mock.calls[0];
373
+
374
+ expect(mdx).toMatchInlineSnapshot(`
375
+ import { Meta, Story } from '@storybook/blocks';
376
+ import { Button } from './Button';
377
+ import * as FoobarStories from './Foobar.stories';
378
+
379
+ export const Default = (args) => <Button {...args} />;
380
+
381
+ <Meta of={FoobarStories} />
382
+
383
+ <Story of={FoobarStories.Default_} />
384
+
385
+ <Story of={FoobarStories.Second} />
386
+
387
+ `);
388
+ expect(csf).toMatchInlineSnapshot(`
389
+ import { Button } from './Button';
390
+
391
+ const Default = (args) => <Button {...args} />;
392
+
393
+ export default {
394
+ title: 'Button',
395
+ };
396
+
397
+ export const Default_ = {
398
+ render: Default.bind({}),
399
+ name: 'Default',
400
+ };
401
+
402
+ export const Second = {
403
+ render: Default.bind({}),
404
+ name: 'Second',
405
+ };
406
+
407
+ `);
408
+ });
409
+
410
+ test('kebab case file name', () => {
411
+ const input = dedent`
412
+ import { Meta, Story } from '@storybook/addon-docs';
413
+ import { Kebab } from './my-component/some-kebab-case';
414
+
415
+ export const Template = (args) => <Kebab {...args} />;
416
+
417
+ <Meta title="Kebab" />
418
+
419
+ <Story name="Much-Kebab">
420
+ {Template.bind({})}
421
+ </Story>
422
+
423
+ <Story name="Really-Much-Kebab">{Template.bind({})}</Story>
424
+
425
+ `;
426
+
427
+ const mdx = jscodeshift({ source: input, path: 'some-kebab-case.stories.mdx' });
428
+
429
+ expect(mdx).toMatchInlineSnapshot(`
430
+ import { Meta, Story } from '@storybook/blocks';
431
+ import { Kebab } from './my-component/some-kebab-case';
432
+ import * as SomeKebabCaseStories from './some-kebab-case.stories';
433
+
434
+ export const Template = (args) => <Kebab {...args} />;
435
+
436
+ <Meta of={SomeKebabCaseStories} />
437
+
438
+ <Story of={SomeKebabCaseStories.MuchKebab} />
439
+
440
+ <Story of={SomeKebabCaseStories.ReallyMuchKebab} />
441
+
442
+ `);
443
+
444
+ const [, csf] = fs.writeFileSync.mock.calls[0];
445
+
446
+ expect(csf).toMatchInlineSnapshot(`
447
+ import { Kebab } from './my-component/some-kebab-case';
448
+
449
+ const Template = (args) => <Kebab {...args} />;
450
+
451
+ export default {
452
+ title: 'Kebab',
453
+ };
454
+
455
+ export const MuchKebab = {
456
+ render: Template.bind({}),
457
+ name: 'Much-Kebab',
458
+ };
459
+
460
+ export const ReallyMuchKebab = {
461
+ render: Template.bind({}),
462
+ name: 'Really-Much-Kebab',
463
+ };
464
+
465
+ `);
466
+ });
467
+
468
+ test('story child is jsx', () => {
469
+ const input = dedent`
470
+ import { Canvas, Meta, Story } from '@storybook/addon-docs';
471
+ import { Button } from './button';
472
+
473
+ <Story name="Primary">
474
+ <Button>
475
+ <div>Hello!</div>
476
+ </Button>
477
+ </Story>
478
+ `;
479
+
480
+ jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
481
+
482
+ const [, csf] = fs.writeFileSync.mock.calls[0];
483
+
484
+ expect(csf).toMatchInlineSnapshot(`
485
+ import { Button } from './button';
486
+ export default {};
487
+
488
+ export const Primary = {
489
+ render: () => (
490
+ <Button>
491
+ <div>Hello!</div>
492
+ </Button>
493
+ ),
494
+
495
+ name: 'Primary',
496
+ };
497
+
498
+ `);
499
+ });
500
+
501
+ test('story child is CSF3', () => {
502
+ const input = dedent`
503
+ import { Story } from '@storybook/addon-docs';
504
+ import { Button } from './button';
505
+
506
+ <Story name="Primary" render={(args) => <Button {...args}></Button> } args={{label: 'Hello' }} />
507
+ `;
508
+
509
+ jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
510
+
511
+ const [, csf] = fs.writeFileSync.mock.calls[0];
512
+
513
+ expect(csf).toMatchInlineSnapshot(`
514
+ import { Button } from './button';
515
+ export default {};
516
+
517
+ export const Primary = {
518
+ name: 'Primary',
519
+ render: (args) => <Button {...args}></Button>,
520
+
521
+ args: {
522
+ label: 'Hello',
523
+ },
524
+ };
525
+
526
+ `);
527
+ });
528
+
529
+ test('story child is arrow function', () => {
530
+ const input = dedent`
531
+ import { Canvas, Meta, Story } from '@storybook/addon-docs';
532
+ import { Button } from './button';
533
+
534
+ <Story name="Primary">
535
+ {(args) => <Button />}
536
+ </Story>
537
+ `;
538
+
539
+ jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
540
+
541
+ const [, csf] = fs.writeFileSync.mock.calls[0];
542
+
543
+ expect(csf).toMatchInlineSnapshot(`
544
+ import { Button } from './button';
545
+ export default {};
546
+
547
+ export const Primary = {
548
+ render: (args) => <Button />,
549
+ name: 'Primary',
550
+ };
551
+
552
+ `);
553
+ });
554
+
555
+ test('story child is identifier', () => {
556
+ const input = dedent`
557
+ import { Canvas, Meta, Story } from '@storybook/addon-docs';
558
+ import { Button } from './button';
559
+
560
+ <Story name="Primary">
561
+ {Button}
562
+ </Story>
563
+ `;
564
+
565
+ jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
566
+
567
+ const [, csf] = fs.writeFileSync.mock.calls[0];
568
+
569
+ expect(csf).toMatchInlineSnapshot(`
570
+ import { Button } from './button';
571
+ export default {};
572
+
573
+ export const Primary = {
574
+ render: Button,
575
+ name: 'Primary',
576
+ };
577
+
578
+ `);
579
+ });
580
+
581
+ test('nameToValidExport', () => {
582
+ expect(nameToValidExport('1 starts with digit')).toMatchInlineSnapshot(`$1StartsWithDigit`);
583
+ expect(nameToValidExport('name')).toMatchInlineSnapshot(`Name`);
584
+ expect(nameToValidExport('Multi words')).toMatchInlineSnapshot(`MultiWords`);
585
+ // Unicode is valid in JS variable names
586
+ expect(nameToValidExport('Keep unicode 😅')).toMatchInlineSnapshot(`KeepUnicode😅`);
587
+ });