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

Sign up to get free protection for your applications and to get access to all the features.
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
+ });