11import type { FoundSpec , FullConfig } from '@packages/types'
2- import { CsfFile , readCsfOrMdx } from '@storybook/csf-tools'
2+ import { readCsfOrMdx } from '@storybook/csf-tools'
33import endent from 'endent'
44import * as path from 'path'
55import type { DataContext } from '..'
@@ -16,82 +16,95 @@ export class StorybookActions {
1616
1717 const config = await this . ctx . project . getConfig ( project . projectRoot )
1818
19- const spec = await this . generate ( storyPath , project . projectRoot , config . componentFolder )
19+ const spec = await this . generateSpec (
20+ storyPath ,
21+ project . projectRoot ,
22+ config . componentFolder ,
23+ )
2024
2125 this . ctx . wizardData . generatedSpec = spec
2226 }
2327
24- private async generate (
28+ private async generateSpec (
2529 storyPath : string ,
2630 projectRoot : string ,
2731 componentFolder : FullConfig [ 'componentFolder' ] ,
2832 ) : Promise < FoundSpec | null > {
29- const storyFile = path . parse ( storyPath )
30- const storyName = storyFile . name . split ( '.' ) [ 0 ]
33+ const specFileExtension = '.cy'
34+ const parsedFile = path . parse ( storyPath )
35+ const fileName = parsedFile . name . split ( '.' ) [ 0 ] as string
3136
32- try {
33- const raw = await readCsfOrMdx ( storyPath , {
34- defaultTitle : storyName || '' ,
35- } )
36- const parsed = raw . parse ( )
37-
38- if (
39- ( ! parsed . meta . title && ! parsed . meta . component ) ||
40- ! parsed . stories . length
41- ) {
42- return null
43- }
37+ let newSpecContent : string | null
4438
45- const specFileExtension = '.cy-spec'
46- const newSpecContent = this . generateSpecFromCsf ( parsed , storyFile )
47- const newSpecPath = path . join (
39+ try {
40+ newSpecContent = await this . generateSpecFromCsf (
4841 storyPath ,
49- '..' ,
50- `${ parsed . meta . component } ${ specFileExtension } ${ storyFile . ext } ` ,
42+ fileName ,
5143 )
5244
53- // If this passes then the file exists and we don't want to overwrite it
54- try {
55- await this . ctx . fs . access ( newSpecPath , this . ctx . fs . constants . F_OK )
56-
45+ if ( ! newSpecContent ) {
5746 return null
58- } catch ( e ) {
59- // eslint-disable-line no-empty
6047 }
48+ } catch ( e ) {
49+ return null
50+ }
6151
62- await this . ctx . fs . outputFileSync ( newSpecPath , newSpecContent )
63-
64- const parsedSpec = path . parse ( newSpecPath )
52+ let newSpecAbsolute = path . join (
53+ parsedFile . dir ,
54+ `${ fileName } ${ specFileExtension } ${ parsedFile . ext } ` ,
55+ )
6556
66- // Can this be obtained from the spec watcher?
67- return {
68- baseName : parsedSpec . base ,
69- fileName : parsedSpec . base . replace ( specFileExtension , '' ) ,
57+ try {
58+ newSpecAbsolute = await this . getFilename (
59+ newSpecAbsolute ,
60+ fileName ,
7061 specFileExtension ,
71- fileExtension : parsedSpec . ext ,
72- name : path . relative ( componentFolder || projectRoot , newSpecPath ) ,
73- relative : path . relative ( projectRoot , newSpecPath ) ,
74- absolute : newSpecPath ,
75- specType : 'component' ,
76- }
62+ )
63+
64+ await this . ctx . fs . outputFileSync ( newSpecAbsolute , newSpecContent )
7765 } catch ( e ) {
7866 return null
7967 }
68+
69+ const parsedNewSpec = path . parse ( newSpecAbsolute )
70+
71+ // Can this be obtained from the spec watcher?
72+ return {
73+ absolute : newSpecAbsolute ,
74+ baseName : parsedNewSpec . base ,
75+ fileExtension : parsedNewSpec . ext ,
76+ fileName,
77+ name : path . relative ( componentFolder || projectRoot , newSpecAbsolute ) ,
78+ relative : path . relative ( projectRoot , newSpecAbsolute ) ,
79+ specFileExtension,
80+ specType : 'component' ,
81+ }
8082 }
8183
82- private generateSpecFromCsf ( parsed : CsfFile , storyFile : path . ParsedPath ) {
83- const isReact = parsed . _ast . program . body . some (
84- ( statement ) => {
85- return statement . type === 'ImportDeclaration' &&
84+ private async generateSpecFromCsf (
85+ absolute : string ,
86+ storyName : string ,
87+ ) : Promise < null | string > {
88+ const csf = await readCsfOrMdx ( absolute , {
89+ defaultTitle : storyName ,
90+ } ) . then ( ( res ) => res . parse ( ) )
91+
92+ if ( ( ! csf . meta . title && ! csf . meta . component ) || ! csf . stories . length ) {
93+ return null
94+ }
95+
96+ const isReact = csf . _ast . program . body . some ( ( statement ) => {
97+ return (
98+ statement . type === 'ImportDeclaration' &&
8699 statement . source . value === 'react'
87- } ,
88- )
89- const isVue = parsed . _ast . program . body . some (
90- ( statement ) => {
91- return statement . type === 'ImportDeclaration' &&
100+ )
101+ } )
102+ const isVue = csf . _ast . program . body . some ( ( statement ) => {
103+ return (
104+ statement . type === 'ImportDeclaration' &&
92105 statement . source . value . endsWith ( '.vue' )
93- } ,
94- )
106+ )
107+ } )
95108
96109 if ( ! isReact && ! isVue ) {
97110 throw new Error ( 'Provided story is not supported' )
@@ -100,12 +113,14 @@ export class StorybookActions {
100113 const getDependency = ( ) => {
101114 return endent `
102115 import { mount } from "@cypress/${ isReact ? 'react' : 'vue' } "
103- import { composeStories } from "@storybook/testing-${ isReact ? 'react' : 'vue3' } "`
116+ import { composeStories } from "@storybook/testing-${
117+ isReact ? 'react' : 'vue3'
118+ } "`
104119 }
105120 const getMountSyntax = ( component : string ) => {
106121 return isReact ? `<${ component } />` : `${ component } ()`
107122 }
108- const itContent = parsed . stories
123+ const itContent = csf . stories
109124 . map ( ( story , i ) => {
110125 const component = story . name . replace ( / \s + / , '' )
111126 let it = endent `
@@ -126,13 +141,38 @@ export class StorybookActions {
126141 . join ( '\n\n' )
127142
128143 return endent `${ isReact ? `import React from "react"` : '' }
129- import * as stories from "./${ storyFile . name } "
144+ import * as stories from "./${ path . parse ( absolute ) . name } "
130145 ${ getDependency ( ) }
131146
132147 const composedStories = composeStories(stories)
133148
134- describe('${ parsed . meta . title || parsed . meta . component } ', () => {
149+ describe('${ csf . meta . title || csf . meta . component } ', () => {
135150 ${ itContent }
136151 })`
137152 }
153+
154+ private async getFilename ( absolute : string , fileName : string , specFileExtension : string ) {
155+ let fileToGenerate = absolute
156+ const { dir, ext } = path . parse ( absolute )
157+ let i = 0
158+
159+ while ( this . fileExists ( fileToGenerate ) ) {
160+ fileToGenerate = path . join (
161+ dir ,
162+ `${ fileName } -copy-${ ++ i } ${ specFileExtension } ${ ext } ` ,
163+ )
164+ }
165+
166+ return fileToGenerate
167+ }
168+
169+ private fileExists ( absolute : string ) {
170+ try {
171+ this . ctx . fs . accessSync ( absolute , this . ctx . fs . constants . F_OK )
172+
173+ return true
174+ } catch ( e ) {
175+ return false
176+ }
177+ }
138178}
0 commit comments