@storybook/codemod 7.0.0-beta.9 → 7.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/README.md +0 -39
  2. package/dist/index.js +1 -1
  3. package/dist/transforms/csf-2-to-3.d.ts +5 -4
  4. package/dist/transforms/csf-2-to-3.js +3 -1
  5. package/dist/transforms/mdx-to-csf.d.ts +7 -0
  6. package/dist/transforms/mdx-to-csf.js +55 -0
  7. package/dist/transforms/storiesof-to-csf.js +1 -1
  8. package/dist/transforms/upgrade-deprecated-types.d.ts +10 -0
  9. package/dist/transforms/upgrade-deprecated-types.js +2 -0
  10. package/jest.config.js +2 -0
  11. package/package.json +31 -16
  12. package/project.json +6 -0
  13. package/src/index.js +30 -4
  14. package/src/lib/utils.ts +2 -2
  15. package/src/transforms/__testfixtures__/storiesof-to-csf/decorators.input.js +1 -1
  16. package/src/transforms/__testfixtures__/storiesof-to-csf/export-function.input.js +1 -1
  17. package/src/transforms/__testfixtures__/storiesof-to-csf/story-decorators.input.js +1 -1
  18. package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.input.js +34 -48
  19. package/src/transforms/__testfixtures__/update-addon-info/update-addon-info.output.snapshot +33 -47
  20. package/src/transforms/__tests__/csf-2-to-3.test.ts +170 -57
  21. package/src/transforms/__tests__/mdx-to-csf.test.ts +611 -0
  22. package/src/transforms/__tests__/upgrade-deprecated-types.test.ts +170 -0
  23. package/src/transforms/csf-2-to-3.ts +173 -37
  24. package/src/transforms/mdx-to-csf.ts +342 -0
  25. package/src/transforms/upgrade-deprecated-types.ts +142 -0
  26. package/dist/chunk-3OPQTROG.mjs +0 -1
  27. package/dist/chunk-B5FMQ3BX.mjs +0 -1
  28. package/dist/chunk-CO6EPEMB.mjs +0 -1
  29. package/dist/index.mjs +0 -1
  30. package/dist/transforms/add-component-parameters.mjs +0 -1
  31. package/dist/transforms/csf-2-to-3.mjs +0 -1
  32. package/dist/transforms/csf-hoist-story-annotations.mjs +0 -1
  33. package/dist/transforms/csf-to-mdx.d.ts +0 -29
  34. package/dist/transforms/csf-to-mdx.js +0 -3
  35. package/dist/transforms/csf-to-mdx.mjs +0 -3
  36. package/dist/transforms/move-builtin-addons.mjs +0 -1
  37. package/dist/transforms/storiesof-to-csf.mjs +0 -1
  38. package/dist/transforms/update-addon-info.mjs +0 -1
  39. package/dist/transforms/update-organisation-name.mjs +0 -1
  40. package/dist/transforms/upgrade-hierarchy-separators.mjs +0 -1
  41. package/src/transforms/__testfixtures__/csf-to-mdx/basic.input.js +0 -20
  42. package/src/transforms/__testfixtures__/csf-to-mdx/basic.output.snapshot +0 -18
  43. package/src/transforms/__testfixtures__/csf-to-mdx/component-id.input.js +0 -9
  44. package/src/transforms/__testfixtures__/csf-to-mdx/component-id.output.snapshot +0 -10
  45. package/src/transforms/__testfixtures__/csf-to-mdx/decorators.input.js +0 -13
  46. package/src/transforms/__testfixtures__/csf-to-mdx/decorators.output.snapshot +0 -12
  47. package/src/transforms/__testfixtures__/csf-to-mdx/exclude-stories.input.js +0 -23
  48. package/src/transforms/__testfixtures__/csf-to-mdx/exclude-stories.output.snapshot +0 -22
  49. package/src/transforms/__testfixtures__/csf-to-mdx/parameters.input.js +0 -16
  50. package/src/transforms/__testfixtures__/csf-to-mdx/parameters.output.snapshot +0 -17
  51. package/src/transforms/__testfixtures__/csf-to-mdx/story-function.input.js +0 -19
  52. package/src/transforms/__testfixtures__/csf-to-mdx/story-function.output.snapshot +0 -18
  53. package/src/transforms/__testfixtures__/csf-to-mdx/story-parameters.input.js +0 -24
  54. package/src/transforms/__testfixtures__/csf-to-mdx/story-parameters.output.snapshot +0 -22
  55. package/src/transforms/csf-to-mdx.js +0 -190
@@ -0,0 +1,611 @@
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 addon-docs imports', () => {
142
+ const input = dedent`
143
+ import { Meta } from '@storybook/addon-docs';
144
+ import { Story } from '@storybook/addon-docs/blocks';
145
+
146
+ <Meta title="Foobar" />
147
+
148
+ <Story name="Primary">Story</Story>
149
+ `;
150
+
151
+ const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
152
+
153
+ expect(mdx).toMatchInlineSnapshot(`
154
+ import { Meta } from '@storybook/blocks';
155
+ import { Story } from '@storybook/blocks';
156
+ import * as FoobarStories from './Foobar.stories';
157
+
158
+ <Meta of={FoobarStories} />
159
+
160
+ <Story of={FoobarStories.Primary} />
161
+
162
+ `);
163
+ });
164
+
165
+ test('convert story nodes with spaces', () => {
166
+ const input = dedent`
167
+ import { Meta, Story } from '@storybook/addon-docs';
168
+
169
+ <Meta title="Foobar" />
170
+
171
+ <Story name="Primary Space">Story</Story>
172
+ `;
173
+
174
+ const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
175
+
176
+ expect(mdx).toMatchInlineSnapshot(`
177
+ import { Meta, Story } from '@storybook/blocks';
178
+ import * as FoobarStories from './Foobar.stories';
179
+
180
+ <Meta of={FoobarStories} />
181
+
182
+ <Story of={FoobarStories.PrimarySpace} />
183
+
184
+ `);
185
+
186
+ const [, csf] = fs.writeFileSync.mock.calls[0];
187
+ expect(csf).toMatchInlineSnapshot(`
188
+ export default {
189
+ title: 'Foobar',
190
+ };
191
+
192
+ export const PrimarySpace = {
193
+ render: () => 'Story',
194
+ name: 'Primary Space',
195
+ };
196
+
197
+ `);
198
+ });
199
+
200
+ test('extract esm into csf head code', () => {
201
+ const input = dedent`
202
+ import { Meta, Story } from '@storybook/addon-docs';
203
+ import { Button } from './Button';
204
+
205
+ # hello
206
+
207
+ export const args = { bla: 1 };
208
+ export const Template = (args) => <Button {...args} />;
209
+
210
+ <Meta title="foobar" />
211
+
212
+ world {2 + 1}
213
+
214
+ <Story name="foo">bar</Story>
215
+
216
+ <Story
217
+ name="Unchecked"
218
+ args={{
219
+ ...args,
220
+ label: 'Unchecked',
221
+ }}>
222
+ {Template.bind({})}
223
+ </Story>
224
+ `;
225
+
226
+ const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
227
+
228
+ expect(mdx).toMatchInlineSnapshot(`
229
+ import { Meta, Story } from '@storybook/blocks';
230
+ import { Button } from './Button';
231
+ import * as FoobarStories from './Foobar.stories';
232
+
233
+ # hello
234
+
235
+ export const args = { bla: 1 };
236
+ export const Template = (args) => <Button {...args} />;
237
+
238
+ <Meta of={FoobarStories} />
239
+
240
+ world {2 + 1}
241
+
242
+ <Story of={FoobarStories.Foo} />
243
+
244
+ <Story of={FoobarStories.Unchecked} />
245
+
246
+ `);
247
+
248
+ const [csfFileName, csf] = fs.writeFileSync.mock.calls[0];
249
+ expect(csfFileName).toMatchInlineSnapshot(`Foobar.stories.js`);
250
+ expect(csf).toMatchInlineSnapshot(`
251
+ import { Button } from './Button';
252
+
253
+ const args = { bla: 1 };
254
+ const Template = (args) => <Button {...args} />;
255
+
256
+ export default {
257
+ title: 'foobar',
258
+ };
259
+
260
+ export const Foo = {
261
+ render: () => 'bar',
262
+ name: 'foo',
263
+ };
264
+
265
+ export const Unchecked = {
266
+ render: Template.bind({}),
267
+ name: 'Unchecked',
268
+
269
+ args: {
270
+ ...args,
271
+ label: 'Unchecked',
272
+ },
273
+ };
274
+
275
+ `);
276
+ });
277
+
278
+ test('extract all meta parameters', () => {
279
+ const input = dedent`
280
+ import { Meta, Story } from '@storybook/addon-docs';
281
+
282
+ export const args = { bla: 1 };
283
+
284
+ <Meta title="foobar" args={{...args}} parameters={{a: '1'}} />
285
+
286
+ <Story name="foo">bar</Story>
287
+ `;
288
+
289
+ jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
290
+
291
+ const [, csf] = fs.writeFileSync.mock.calls[0];
292
+
293
+ expect(csf).toMatchInlineSnapshot(`
294
+ const args = { bla: 1 };
295
+
296
+ export default {
297
+ title: 'foobar',
298
+
299
+ args: {
300
+ ...args,
301
+ },
302
+
303
+ parameters: {
304
+ a: '1',
305
+ },
306
+ };
307
+
308
+ export const Foo = {
309
+ render: () => 'bar',
310
+ name: 'foo',
311
+ };
312
+
313
+ `);
314
+ });
315
+
316
+ test('extract all story attributes', () => {
317
+ const input = dedent`
318
+ import { Meta, Story } from '@storybook/addon-docs';
319
+ import { Button } from './Button';
320
+
321
+ export const args = { bla: 1 };
322
+ export const Template = (args) => <Button {...args} />;
323
+
324
+ <Meta title="foobar" />
325
+
326
+ <Story name="foo">bar</Story>
327
+
328
+ <Story
329
+ name="Unchecked"
330
+ args={{
331
+ ...args,
332
+ label: 'Unchecked',
333
+ }}>
334
+ {Template.bind({})}
335
+ </Story>
336
+
337
+ <Story name="Second">{Template.bind({})}</Story>
338
+
339
+ `;
340
+
341
+ jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
342
+
343
+ const [, csf] = fs.writeFileSync.mock.calls[0];
344
+
345
+ expect(csf).toMatchInlineSnapshot(`
346
+ import { Button } from './Button';
347
+
348
+ const args = { bla: 1 };
349
+ const Template = (args) => <Button {...args} />;
350
+
351
+ export default {
352
+ title: 'foobar',
353
+ };
354
+
355
+ export const Foo = {
356
+ render: () => 'bar',
357
+ name: 'foo',
358
+ };
359
+
360
+ export const Unchecked = {
361
+ render: Template.bind({}),
362
+ name: 'Unchecked',
363
+
364
+ args: {
365
+ ...args,
366
+ label: 'Unchecked',
367
+ },
368
+ };
369
+
370
+ export const Second = {
371
+ render: Template.bind({}),
372
+ name: 'Second',
373
+ };
374
+
375
+ `);
376
+ });
377
+
378
+ test('duplicate story name', () => {
379
+ const input = dedent`
380
+ import { Meta, Story } from '@storybook/addon-docs';
381
+ import { Button } from './Button';
382
+
383
+ export const Default = (args) => <Button {...args} />;
384
+
385
+ <Meta title="Button" />
386
+
387
+ <Story name="Default">
388
+ {Default.bind({})}
389
+ </Story>
390
+
391
+ <Story name="Second">{Default.bind({})}</Story>
392
+
393
+ `;
394
+
395
+ const mdx = jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
396
+ const [, csf] = fs.writeFileSync.mock.calls[0];
397
+
398
+ expect(mdx).toMatchInlineSnapshot(`
399
+ import { Meta, Story } from '@storybook/blocks';
400
+ import { Button } from './Button';
401
+ import * as FoobarStories from './Foobar.stories';
402
+
403
+ export const Default = (args) => <Button {...args} />;
404
+
405
+ <Meta of={FoobarStories} />
406
+
407
+ <Story of={FoobarStories.Default_} />
408
+
409
+ <Story of={FoobarStories.Second} />
410
+
411
+ `);
412
+ expect(csf).toMatchInlineSnapshot(`
413
+ import { Button } from './Button';
414
+
415
+ const Default = (args) => <Button {...args} />;
416
+
417
+ export default {
418
+ title: 'Button',
419
+ };
420
+
421
+ export const Default_ = {
422
+ render: Default.bind({}),
423
+ name: 'Default',
424
+ };
425
+
426
+ export const Second = {
427
+ render: Default.bind({}),
428
+ name: 'Second',
429
+ };
430
+
431
+ `);
432
+ });
433
+
434
+ test('kebab case file name', () => {
435
+ const input = dedent`
436
+ import { Meta, Story } from '@storybook/addon-docs';
437
+ import { Kebab } from './my-component/some-kebab-case';
438
+
439
+ export const Template = (args) => <Kebab {...args} />;
440
+
441
+ <Meta title="Kebab" />
442
+
443
+ <Story name="Much-Kebab">
444
+ {Template.bind({})}
445
+ </Story>
446
+
447
+ <Story name="Really-Much-Kebab">{Template.bind({})}</Story>
448
+
449
+ `;
450
+
451
+ const mdx = jscodeshift({ source: input, path: 'some-kebab-case.stories.mdx' });
452
+
453
+ expect(mdx).toMatchInlineSnapshot(`
454
+ import { Meta, Story } from '@storybook/blocks';
455
+ import { Kebab } from './my-component/some-kebab-case';
456
+ import * as SomeKebabCaseStories from './some-kebab-case.stories';
457
+
458
+ export const Template = (args) => <Kebab {...args} />;
459
+
460
+ <Meta of={SomeKebabCaseStories} />
461
+
462
+ <Story of={SomeKebabCaseStories.MuchKebab} />
463
+
464
+ <Story of={SomeKebabCaseStories.ReallyMuchKebab} />
465
+
466
+ `);
467
+
468
+ const [, csf] = fs.writeFileSync.mock.calls[0];
469
+
470
+ expect(csf).toMatchInlineSnapshot(`
471
+ import { Kebab } from './my-component/some-kebab-case';
472
+
473
+ const Template = (args) => <Kebab {...args} />;
474
+
475
+ export default {
476
+ title: 'Kebab',
477
+ };
478
+
479
+ export const MuchKebab = {
480
+ render: Template.bind({}),
481
+ name: 'Much-Kebab',
482
+ };
483
+
484
+ export const ReallyMuchKebab = {
485
+ render: Template.bind({}),
486
+ name: 'Really-Much-Kebab',
487
+ };
488
+
489
+ `);
490
+ });
491
+
492
+ test('story child is jsx', () => {
493
+ const input = dedent`
494
+ import { Canvas, Meta, Story } from '@storybook/addon-docs';
495
+ import { Button } from './button';
496
+
497
+ <Story name="Primary">
498
+ <Button>
499
+ <div>Hello!</div>
500
+ </Button>
501
+ </Story>
502
+ `;
503
+
504
+ jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
505
+
506
+ const [, csf] = fs.writeFileSync.mock.calls[0];
507
+
508
+ expect(csf).toMatchInlineSnapshot(`
509
+ import { Button } from './button';
510
+ export default {};
511
+
512
+ export const Primary = {
513
+ render: () => (
514
+ <Button>
515
+ <div>Hello!</div>
516
+ </Button>
517
+ ),
518
+
519
+ name: 'Primary',
520
+ };
521
+
522
+ `);
523
+ });
524
+
525
+ test('story child is CSF3', () => {
526
+ const input = dedent`
527
+ import { Story } from '@storybook/addon-docs';
528
+ import { Button } from './button';
529
+
530
+ <Story name="Primary" render={(args) => <Button {...args}></Button> } args={{label: 'Hello' }} />
531
+ `;
532
+
533
+ jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
534
+
535
+ const [, csf] = fs.writeFileSync.mock.calls[0];
536
+
537
+ expect(csf).toMatchInlineSnapshot(`
538
+ import { Button } from './button';
539
+ export default {};
540
+
541
+ export const Primary = {
542
+ name: 'Primary',
543
+ render: (args) => <Button {...args}></Button>,
544
+
545
+ args: {
546
+ label: 'Hello',
547
+ },
548
+ };
549
+
550
+ `);
551
+ });
552
+
553
+ test('story child is arrow function', () => {
554
+ const input = dedent`
555
+ import { Canvas, Meta, Story } from '@storybook/addon-docs';
556
+ import { Button } from './button';
557
+
558
+ <Story name="Primary">
559
+ {(args) => <Button />}
560
+ </Story>
561
+ `;
562
+
563
+ jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
564
+
565
+ const [, csf] = fs.writeFileSync.mock.calls[0];
566
+
567
+ expect(csf).toMatchInlineSnapshot(`
568
+ import { Button } from './button';
569
+ export default {};
570
+
571
+ export const Primary = {
572
+ render: (args) => <Button />,
573
+ name: 'Primary',
574
+ };
575
+
576
+ `);
577
+ });
578
+
579
+ test('story child is identifier', () => {
580
+ const input = dedent`
581
+ import { Canvas, Meta, Story } from '@storybook/addon-docs';
582
+ import { Button } from './button';
583
+
584
+ <Story name="Primary">
585
+ {Button}
586
+ </Story>
587
+ `;
588
+
589
+ jscodeshift({ source: input, path: 'Foobar.stories.mdx' });
590
+
591
+ const [, csf] = fs.writeFileSync.mock.calls[0];
592
+
593
+ expect(csf).toMatchInlineSnapshot(`
594
+ import { Button } from './button';
595
+ export default {};
596
+
597
+ export const Primary = {
598
+ render: Button,
599
+ name: 'Primary',
600
+ };
601
+
602
+ `);
603
+ });
604
+
605
+ test('nameToValidExport', () => {
606
+ expect(nameToValidExport('1 starts with digit')).toMatchInlineSnapshot(`$1StartsWithDigit`);
607
+ expect(nameToValidExport('name')).toMatchInlineSnapshot(`Name`);
608
+ expect(nameToValidExport('Multi words')).toMatchInlineSnapshot(`MultiWords`);
609
+ // Unicode is valid in JS variable names
610
+ expect(nameToValidExport('Keep unicode 😅')).toMatchInlineSnapshot(`KeepUnicode😅`);
611
+ });