diff --git a/packages/app/cypress/e2e/create-from-component.cy.ts b/packages/app/cypress/e2e/create-from-component.cy.ts new file mode 100644 index 000000000000..5216f35ce907 --- /dev/null +++ b/packages/app/cypress/e2e/create-from-component.cy.ts @@ -0,0 +1,112 @@ +import defaultMessages from '@packages/frontend-shared/src/locales/en-US.json' +import { getPathForPlatform } from '../../src/paths' + +function validateCreateFromComponentCard (beforeEachFn: () => void, expectedSpecPath: string) { + beforeEach(beforeEachFn) + + it('Shows create from component card for Vue projects with default spec patterns', () => { + cy.get('@ComponentCard') + .within(() => { + cy.findByRole('button', { + name: 'Create from component', + }).should('be.visible') + .and('not.be.disabled') + }) + }) + + it('Can be closed with the x button', () => { + cy.get('@ComponentCard').click() + + cy.findByRole('button', { name: 'Close' }).as('DialogCloseButton') + + cy.get('@DialogCloseButton').click() + cy.findByRole('dialog', { + name: 'Choose a component', + }).should('not.exist') + }) + + it('Lists Vue components in the project', () => { + cy.get('@ComponentCard').click() + + cy.findByText('2 Matches').should('be.visible') + + cy.findByText('App').should('be.visible') + cy.findByText('HelloWorld').should('be.visible') + }) + + it('Allows for the user to search through their components', () => { + cy.get('@ComponentCard').click() + + cy.findByText('*.vue').should('be.visible') + cy.findByText('2 Matches').should('be.visible') + cy.findByLabelText('file-name-input').type('HelloWorld') + + cy.findByText('HelloWorld').should('be.visible') + cy.findByText('1 of 2 Matches').should('be.visible') + cy.findByText('App').should('not.exist') + }) + + it('shows success modal when component spec is created', () => { + cy.get('@ComponentCard').click() + + cy.findByText('HelloWorld').should('be.visible').click() + + cy.findByRole('dialog', { + name: defaultMessages.createSpec.successPage.header, + }).as('SuccessDialog').within(() => { + cy.contains(getPathForPlatform(expectedSpecPath)).should('be.visible') + cy.findByRole('button', { name: 'Close' }).should('be.visible') + + cy.findByRole('link', { name: 'Okay, run the spec' }) + .should('have.attr', 'href', `#/specs/runner?file=${expectedSpecPath}`) + + cy.findByRole('button', { name: 'Create another spec' }).click() + }) + + // 'Create from component' card appears again when the user selects "create another spec" + cy.findByText('Create from component').should('be.visible') + }) + + it('runs generated spec', () => { + cy.get('@ComponentCard').click() + + cy.findByText('HelloWorld').should('be.visible').click() + + cy.findByRole('dialog', { + name: defaultMessages.createSpec.successPage.header, + }).as('SuccessDialog').within(() => { + cy.contains(getPathForPlatform(expectedSpecPath)).should('be.visible') + cy.findByRole('button', { name: 'Close' }).should('be.visible') + + cy.findByRole('link', { name: 'Okay, run the spec' }) + .should('have.attr', 'href', `#/specs/runner?file=${expectedSpecPath}`).click() + }) + + cy.findByText('').should('be.visible') + }) +} + +describe('Create from component card', () => { + context('project with default spec pattern', () => { + validateCreateFromComponentCard(() => { + cy.scaffoldProject('no-specs-vue-2') + cy.openProject('no-specs-vue-2') + cy.startAppServer('component') + cy.visitApp() + + cy.findAllByTestId('card').eq(0).as('ComponentCard') + }, 'src/components/HelloWorld.cy.js') + }) + + context('project with custom spec pattern', () => { + validateCreateFromComponentCard(() => { + cy.scaffoldProject('no-specs-vue-2') + cy.openProject('no-specs-vue-2', ['--config-file', 'cypress-custom-spec-pattern.config.js']) + cy.startAppServer('component') + cy.visitApp() + + cy.findByText('New Spec').click() + cy.findAllByTestId('card').eq(0).as('ComponentCard') + }, 'src/specs-folder/HelloWorld.cy.js') + }) +}) diff --git a/packages/app/cypress/e2e/specs.cy.ts b/packages/app/cypress/e2e/specs.cy.ts index 97c5aa101c38..33397bbbd222 100644 --- a/packages/app/cypress/e2e/specs.cy.ts +++ b/packages/app/cypress/e2e/specs.cy.ts @@ -640,98 +640,6 @@ describe('App: Specs', () => { }) }) - context('Create from component card', () => { - beforeEach(() => { - cy.scaffoldProject('no-specs-vue-2') - cy.openProject('no-specs-vue-2') - cy.startAppServer('component') - cy.visitApp() - - cy.findAllByTestId('card').eq(0).as('ComponentCard') - }) - - it('Shows create from component card for Vue projects with default spec patterns', () => { - cy.get('@ComponentCard') - .within(() => { - cy.findByRole('button', { - name: 'Create from component', - }).should('be.visible') - .and('not.be.disabled') - }) - }) - - it('Can be closed with the x button', () => { - cy.get('@ComponentCard').click() - - cy.findByRole('button', { name: 'Close' }).as('DialogCloseButton') - - cy.get('@DialogCloseButton').click() - cy.findByRole('dialog', { - name: 'Choose a component', - }).should('not.exist') - }) - - it('Lists Vue components in the project', () => { - cy.get('@ComponentCard').click() - - cy.findByText('2 Matches').should('be.visible') - - cy.findByText('App').should('be.visible') - cy.findByText('HelloWorld').should('be.visible') - }) - - it('Allows for the user to search through their components', () => { - cy.get('@ComponentCard').click() - - cy.findByText('*.vue').should('be.visible') - cy.findByText('2 Matches').should('be.visible') - cy.findByLabelText('file-name-input').type('HelloWorld') - - cy.findByText('HelloWorld').should('be.visible') - cy.findByText('1 of 2 Matches').should('be.visible') - cy.findByText('App').should('not.exist') - }) - - it('shows success modal when component spec is created', () => { - cy.get('@ComponentCard').click() - - cy.findByText('HelloWorld').should('be.visible').click() - - cy.findByRole('dialog', { - name: defaultMessages.createSpec.successPage.header, - }).as('SuccessDialog').within(() => { - cy.contains(getPathForPlatform('src/components/HelloWorld.cy.js')).should('be.visible') - cy.findByRole('button', { name: 'Close' }).should('be.visible') - - cy.findByRole('link', { name: 'Okay, run the spec' }) - .should('have.attr', 'href', `#/specs/runner?file=src/components/HelloWorld.cy.js`) - - cy.findByRole('button', { name: 'Create another spec' }).click() - }) - - // 'Create from component' card appears again when the user selects "create another spec" - cy.findByText('Create from component').should('be.visible') - }) - - it('runs generated spec', () => { - cy.get('@ComponentCard').click() - - cy.findByText('HelloWorld').should('be.visible').click() - - cy.findByRole('dialog', { - name: defaultMessages.createSpec.successPage.header, - }).as('SuccessDialog').within(() => { - cy.contains(getPathForPlatform('src/components/HelloWorld.cy.js')).should('be.visible') - cy.findByRole('button', { name: 'Close' }).should('be.visible') - - cy.findByRole('link', { name: 'Okay, run the spec' }) - .should('have.attr', 'href', `#/specs/runner?file=src/components/HelloWorld.cy.js`).click() - }) - - cy.findByText('').should('be.visible') - }) - }) - context('project with custom spec pattern', () => { beforeEach(() => { cy.scaffoldProject('no-specs-custom-pattern') diff --git a/packages/app/src/specs/CreateSpecModal.cy.tsx b/packages/app/src/specs/CreateSpecModal.cy.tsx index f2f79b26cc6f..4d094591881f 100644 --- a/packages/app/src/specs/CreateSpecModal.cy.tsx +++ b/packages/app/src/specs/CreateSpecModal.cy.tsx @@ -34,7 +34,6 @@ describe('', () => { specPattern: '**/*.cy.{js,jsx,ts,tsx}', }, }], - isDefaultSpecPattern: true, specs: [], fileExtensionToUse: 'js', defaultSpecFileName: 'cypress/e2e/ComponentName.cy.js', @@ -130,7 +129,6 @@ describe('Modal Text Input', () => { specPattern: '**/*.cy.{js,jsx,ts,tsx}', }, }], - isDefaultSpecPattern: true, specs: [], fileExtensionToUse: 'js', defaultSpecFileName: 'cypress/e2e/ComponentName.cy.js', @@ -179,7 +177,6 @@ describe('Modal Text Input', () => { specPattern: '**/*.cy.{js,jsx,ts,tsx}', }, }], - isDefaultSpecPattern: true, specs: [], fileExtensionToUse: 'js', defaultSpecFileName: 'this/path/does/not/produce/regex/match-', @@ -232,7 +229,6 @@ describe('playground', () => { specPattern: '**/*.cy.{js,jsx,ts,tsx}', }, }], - isDefaultSpecPattern: true, specs: [], fileExtensionToUse: 'js', defaultSpecFileName: 'cypress/e2e/ComponentName.cy.js', diff --git a/packages/app/src/specs/CreateSpecModal.vue b/packages/app/src/specs/CreateSpecModal.vue index 13e8479934b6..a7abc4146365 100644 --- a/packages/app/src/specs/CreateSpecModal.vue +++ b/packages/app/src/specs/CreateSpecModal.vue @@ -77,7 +77,6 @@ fragment CreateSpecModal on Query { id fileExtensionToUse defaultSpecFileName - isDefaultSpecPattern ...ComponentGeneratorStepOne_codeGenGlob ...EmptyGenerator } @@ -107,7 +106,7 @@ const specFileName = computed(() => { return getPathForPlatform(props.gql.currentProject?.defaultSpecFileName || '') }) -const filteredGenerators = getFilteredGeneratorList(props.gql.currentProject, props.gql.currentProject?.isDefaultSpecPattern) +const filteredGenerators = getFilteredGeneratorList(props.gql.currentProject) const singleGenerator = computed(() => filteredGenerators.value.length === 1 ? filteredGenerators.value[0] : null) diff --git a/packages/app/src/specs/DefaultSpecPatternNoContent.vue b/packages/app/src/specs/DefaultSpecPatternNoContent.vue index 3f0569ea04a3..d8e2372b9e3e 100644 --- a/packages/app/src/specs/DefaultSpecPatternNoContent.vue +++ b/packages/app/src/specs/DefaultSpecPatternNoContent.vue @@ -63,7 +63,7 @@ const props = defineProps<{ gql: CreateSpecContentFragment }>() -const filteredGenerators = getFilteredGeneratorList(props.gql.currentProject, true) +const filteredGenerators = getFilteredGeneratorList(props.gql.currentProject) const emit = defineEmits<{ (e: 'showCreateSpecModal', id: string): void diff --git a/packages/app/src/specs/generators/EmptyGenerator.vue b/packages/app/src/specs/generators/EmptyGenerator.vue index a90b8b443946..180f1517f748 100644 --- a/packages/app/src/specs/generators/EmptyGenerator.vue +++ b/packages/app/src/specs/generators/EmptyGenerator.vue @@ -146,7 +146,6 @@ const props = defineProps<{ gql: EmptyGeneratorFragment type: 'e2e' | 'component' | 'componentEmpty' specFileName: string - erroredCodegenCandidate?: string /** is there any other generator available when clicking "Back" */ otherGenerators: boolean }>() @@ -168,8 +167,8 @@ mutation EmptyGenerator_MatchSpecFile($specFile: String!) { ` gql` -mutation EmptyGenerator_generateSpec($codeGenCandidate: String!, $type: CodeGenType!, $erroredCodegenCandidate: String) { - generateSpecFromSource(codeGenCandidate: $codeGenCandidate, type: $type, erroredCodegenCandidate: $erroredCodegenCandidate) { +mutation EmptyGenerator_generateSpec($codeGenCandidate: String!, $type: CodeGenType!) { + generateSpecFromSource(codeGenCandidate: $codeGenCandidate, type: $type) { ...GeneratorSuccess } }` @@ -230,7 +229,7 @@ const createSpec = async () => { return } - const { data } = await writeFile.executeMutation({ codeGenCandidate: specFile.value, type: props.type, erroredCodegenCandidate: props.erroredCodegenCandidate ?? null }) + const { data } = await writeFile.executeMutation({ codeGenCandidate: specFile.value, type: props.type }) result.value = data?.generateSpecFromSource?.generatedSpecResult?.__typename === 'ScaffoldedFile' ? data?.generateSpecFromSource?.generatedSpecResult : null } diff --git a/packages/app/src/specs/generators/component/ComponentGenerator.tsx b/packages/app/src/specs/generators/component/ComponentGenerator.tsx index 91b3236bdff1..1937264008b9 100644 --- a/packages/app/src/specs/generators/component/ComponentGenerator.tsx +++ b/packages/app/src/specs/generators/component/ComponentGenerator.tsx @@ -6,11 +6,7 @@ import ComponentGeneratorCard from './ComponentGeneratorCard.vue' export const ComponentGenerator: SpecGenerator = { card: ComponentGeneratorCard, entry: ComponentGeneratorStepOne, - show: (currentProject, isDefaultSpecPattern) => { - if (!isDefaultSpecPattern) { - return false - } - + show: (currentProject) => { return currentProject?.codeGenGlobs?.component === '*.vue' }, matches: filters.matchesCT, diff --git a/packages/app/src/specs/generators/component/ComponentGeneratorStepOne.vue b/packages/app/src/specs/generators/component/ComponentGeneratorStepOne.vue index 5f67b556d3f1..84ea5f54e2c3 100644 --- a/packages/app/src/specs/generators/component/ComponentGeneratorStepOne.vue +++ b/packages/app/src/specs/generators/component/ComponentGeneratorStepOne.vue @@ -7,7 +7,6 @@ type="component" :other-generators="false" :spec-file-name="generatedSpecError.fileName" - :errored-codegen-candidate="generatedSpecError.erroredCodegenCandidate" @restart="cancelSpecNameCreation" @updateTitle="(value) => emits('update:title', value)" /> @@ -143,7 +142,6 @@ mutation ComponentGeneratorStepOne_generateSpec($codeGenCandidate: String!, $typ generatedSpecResult { ... on GeneratedSpecError { fileName - erroredCodegenCandidate } } } diff --git a/packages/app/src/specs/generators/index.ts b/packages/app/src/specs/generators/index.ts index 26bdd0013c30..6cf4061bc794 100644 --- a/packages/app/src/specs/generators/index.ts +++ b/packages/app/src/specs/generators/index.ts @@ -19,8 +19,8 @@ export const generatorList: SpecGenerator[] = [ EmptyGenerator, ] -export const getFilteredGeneratorList = (currentProject, isDefaultSpecPattern) => { - return computed(() => generatorList.filter((g) => g.matches(currentProject.currentTestingType) && (g.show === undefined ? true : g.show(currentProject, isDefaultSpecPattern)))) +export const getFilteredGeneratorList = (currentProject) => { + return computed(() => generatorList.filter((g) => g.matches(currentProject.currentTestingType) && (g.show === undefined ? true : g.show(currentProject)))) } export const generators = keyBy(generatorList, 'id') as Record diff --git a/packages/app/src/specs/generators/types.ts b/packages/app/src/specs/generators/types.ts index ebb70a715e33..e8e3978098b4 100644 --- a/packages/app/src/specs/generators/types.ts +++ b/packages/app/src/specs/generators/types.ts @@ -15,6 +15,6 @@ export interface SpecGenerator { card: Component entry: Component matches: (testingType?: TestingType | null) => boolean - show: (currentProject?: CurrentProject, isDefaultSpecPattern?: boolean) => boolean + show: (currentProject?: CurrentProject) => boolean id: GeneratorId } diff --git a/packages/data-context/src/actions/ProjectActions.ts b/packages/data-context/src/actions/ProjectActions.ts index ef57970327cd..a24a15309c24 100644 --- a/packages/data-context/src/actions/ProjectActions.ts +++ b/packages/data-context/src/actions/ProjectActions.ts @@ -336,15 +336,13 @@ export class ProjectActions { this.api.insertProjectPreferencesToCache(this.ctx.lifecycleManager.projectTitle, args) } - async codeGenSpec (codeGenCandidate: string, codeGenType: CodeGenType, erroredCodegenCandidate?: string | null): Promise { + async codeGenSpec (codeGenCandidate: string, codeGenType: CodeGenType): Promise { const project = this.ctx.currentProject - if (!project) { - throw Error(`Cannot create spec without currentProject.`) - } + assert(project, 'Cannot create spec without currentProject.') const getCodeGenPath = () => { - return codeGenType === 'e2e' || erroredCodegenCandidate + return codeGenType === 'e2e' ? this.ctx.path.join( project, codeGenCandidate, @@ -354,18 +352,22 @@ export class ProjectActions { const codeGenPath = getCodeGenPath() + const { specPattern = [] } = await this.ctx.project.specPatterns() + const newSpecCodeGenOptions = new SpecOptions({ codeGenPath, codeGenType, - erroredCodegenCandidate, framework: this.getWizardFrameworkFromConfig(), isDefaultSpecPattern: await this.ctx.project.getIsDefaultSpecPattern(), + specPattern, + currentProject: this.ctx.currentProject, + specs: this.ctx.project.specs, }) let codeGenOptions = await newSpecCodeGenOptions.getCodeGenOptions() const codeGenResults = await codeGenerator( - { templateDir: templates[codeGenOptions.templateKey], target: path.parse(codeGenPath).dir }, + { templateDir: templates[codeGenOptions.templateKey], target: codeGenOptions.overrideCodeGenDir || path.parse(codeGenPath).dir }, codeGenOptions, ) diff --git a/packages/data-context/src/codegen/spec-options.ts b/packages/data-context/src/codegen/spec-options.ts index c5c6226140b6..d0f0d14a85b0 100644 --- a/packages/data-context/src/codegen/spec-options.ts +++ b/packages/data-context/src/codegen/spec-options.ts @@ -3,14 +3,18 @@ import type { CodeGenType } from '@packages/graphql/src/gen/nxs.gen' import type { WizardFrontendFramework } from '@packages/scaffold-config' import fs from 'fs-extra' import path from 'path' +import { getDefaultSpecFileName } from '../sources/migration/utils' +import { toPosix } from '../util' +import type { FoundSpec } from '@packages/types' interface CodeGenOptions { codeGenPath: string codeGenType: CodeGenType isDefaultSpecPattern: boolean - erroredCodegenCandidate?: string | null - specFileExtension?: string + specPattern: string[] + currentProject: string | null framework?: WizardFrontendFramework + specs?: FoundSpec[] } // Spec file extensions that we will preserve when updating the file name @@ -26,14 +30,9 @@ type ComponentExtension = `.cy.${'js' | 'ts' | 'jsx' | 'tsx'}` type TemplateKey = 'e2e' | 'componentEmpty' | 'vueComponent' export class SpecOptions { private parsedPath: ParsedPath; - private parsedErroredCodegenCandidate?: ParsedPath constructor (private options: CodeGenOptions) { this.parsedPath = path.parse(options.codeGenPath) - - if (options.erroredCodegenCandidate) { - this.parsedErroredCodegenCandidate = path.parse(options.erroredCodegenCandidate) - } } async getCodeGenOptions () { @@ -45,6 +44,7 @@ export class SpecOptions { codeGenType: this.options.codeGenType, fileName: await this.buildFileName(), templateKey: this.options.codeGenType as TemplateKey, + overrideCodeGenDir: '', } } @@ -53,12 +53,13 @@ export class SpecOptions { throw new Error('Cannot generate a spec without a framework') } - // This only works for Vue projects with default spec patterns right now. If the framework is not Vue, we're generating an empty component test - if (this.options.framework.codeGenFramework !== 'vue' || !this.options.isDefaultSpecPattern) { + // This only works for Vue projects right now. If the framework is not Vue, we're generating an empty component test + if (this.options.framework.codeGenFramework !== 'vue') { return { codeGenType: this.options.codeGenType, fileName: await this.buildFileName(), templateKey: 'componentEmpty' as TemplateKey, + overrideCodeGenDir: '', } } @@ -67,29 +68,46 @@ export class SpecOptions { return frameworkOptions } - private relativePath () { - if (!this.parsedErroredCodegenCandidate?.base) { - return `./${this.parsedPath.base}` - } + private getRelativePathToComponent (specParsedPath?: ParsedPath) { + if (specParsedPath) { + const componentPathRelative = path.relative(specParsedPath.dir, this.parsedPath.dir) - const componentPathRelative = path.relative(this.parsedPath.dir, this.parsedErroredCodegenCandidate.dir) + const componentPath = path.join(componentPathRelative, this.parsedPath.base) - const componentPath = path.join(componentPathRelative, this.parsedErroredCodegenCandidate.base) + return toPosix(componentPath.startsWith('.') ? componentPath : `./${componentPath}`) + } - return componentPath.startsWith('.') ? componentPath : `./${componentPath}` + return `./${this.parsedPath.base}` } private async getFrameworkComponentOptions () { - const componentName = this.parsedErroredCodegenCandidate?.name ?? this.parsedPath.name + const componentName = this.parsedPath.name + + const extension = await this.getVueExtension() + + let parsedSpecPath: ParsedPath | undefined + + // If we have a custom spec pattern, write the spec to a path that matches the pattern instead of the component directory + if (!this.options.isDefaultSpecPattern) { + parsedSpecPath = path.parse(await getDefaultSpecFileName({ + currentProject: this.options.currentProject, + testingType: this.options.codeGenType === 'componentEmpty' || this.options.codeGenType === 'component' ? 'component' : 'e2e', + fileExtensionToUse: (extension === '.cy.ts' || extension === '.cy.tsx') ? 'ts' : 'js', + specPattern: this.options.specPattern, + name: componentName, + specs: this.options.specs })) + } - const componentPath = this.relativePath() + // The path to import the component from + const componentPath = this.getRelativePathToComponent(parsedSpecPath) return { codeGenType: this.options.codeGenType, componentName, componentPath, - fileName: await this.buildComponentSpecFilename(await this.getVueExtension()), + fileName: await this.buildComponentSpecFilename(extension, parsedSpecPath), templateKey: 'vueComponent' as TemplateKey, + overrideCodeGenDir: parsedSpecPath?.dir, } } @@ -111,24 +129,24 @@ export class SpecOptions { } } - private getSpecExtension = () => { - if (this.options.erroredCodegenCandidate) { - return '' - } - + private getSpecExtension = (filePath?: ParsedPath) => { const foundSpecExtension = expectedSpecExtensions.find((specExtension) => { - return this.parsedPath.base.endsWith(specExtension + this.parsedPath.ext) + return filePath ? filePath.base.endsWith(specExtension + filePath.ext) : + this.parsedPath.base.endsWith(specExtension + this.parsedPath.ext) }) return foundSpecExtension || '' } - private async buildComponentSpecFilename (specExt: string) { - const { dir, base, ext } = this.parsedPath - const cyWithExt = this.getSpecExtension() + specExt - const name = base.slice(0, -ext.length) + private buildComponentSpecFilename (specExt: string, filePath?: ParsedPath) { + const { dir, base, ext } = filePath || this.parsedPath + const cyWithExt = this.getSpecExtension(filePath) + ext - return this.getFinalFileName(dir, name, cyWithExt, path.join(dir, `${name}${cyWithExt}`)) + const name = base.slice(0, base.indexOf('.')) + + const finalExtension = filePath ? cyWithExt : specExt + + return this.getFinalFileName(dir, name, finalExtension, path.join(dir, `${name}${finalExtension}`)) } private async buildFileName () { diff --git a/packages/data-context/src/codegen/templates/vue-component/vue-component.ejs b/packages/data-context/src/codegen/templates/vue-component/vue-component.ejs index 48335fd9b5d4..1666e3edb517 100644 --- a/packages/data-context/src/codegen/templates/vue-component/vue-component.ejs +++ b/packages/data-context/src/codegen/templates/vue-component/vue-component.ejs @@ -2,7 +2,7 @@ fileName: <%= fileName %> --- -import <%- componentName %> from "<%- componentPath %>" +import <%- componentName %> from '<%- componentPath %>' describe('<<%=componentName%> />', () => { it('renders', () => { diff --git a/packages/data-context/src/sources/ProjectDataSource.ts b/packages/data-context/src/sources/ProjectDataSource.ts index 441dc4c17e2f..fab5fd97d693 100644 --- a/packages/data-context/src/sources/ProjectDataSource.ts +++ b/packages/data-context/src/sources/ProjectDataSource.ts @@ -21,6 +21,7 @@ import { toPosix } from '../util/file' import type { FilePartsShape } from '@packages/graphql/src/schemaTypes/objectTypes/gql-FileParts' import type { ProjectShape } from '../data' import type { FindSpecs } from '../actions' +import { getDefaultSpecFileName } from './migration/utils' export type SpecWithRelativeRoot = FoundSpec & { relativeToCommonRoot: string } @@ -134,7 +135,15 @@ export function getLongestCommonPrefixFromPaths (paths: string[]): string { return lcp.slice(0, endIndex).join(path.sep) } -export function getPathFromSpecPattern (specPattern: string, testingType: TestingType, fileExtensionToUse?: 'js' | 'ts') { +export function getPathFromSpecPattern ({ + specPattern, + testingType, + fileExtensionToUse, + name = '' }: +{ specPattern: string + testingType: TestingType + fileExtensionToUse?: 'js' | 'ts' + name?: string}) { function replaceWildCard (s: string, fallback: string) { return s.replace(/\*/g, fallback) } @@ -152,7 +161,7 @@ export function getPathFromSpecPattern (specPattern: string, testingType: Testin if (dirname.startsWith('**')) dirname = dirname.replace('**', 'cypress') const splittedDirname = dirname.split('/').filter((s) => s !== '**').map((x) => replaceWildCard(x, testingType)).join('/') - const fileName = replaceWildCard(parsedGlob.path.filename, testingType === 'e2e' ? 'spec' : 'ComponentName') + const fileName = replaceWildCard(parsedGlob.path.filename, name ? name : testingType === 'e2e' ? 'spec' : 'ComponentName') const extnameWithoutExt = parsedGlob.path.extname.replace(parsedGlob.path.ext, '') || `.cy.${fileExtensionToUse}` @@ -386,49 +395,15 @@ export class ProjectDataSource { } async defaultSpecFileName (): Promise { - const defaultFilename = `${this.ctx.coreData.currentTestingType === 'e2e' ? 'spec' : 'ComponentName'}.cy.${this.ctx.lifecycleManager.fileExtensionToUse}` - const defaultPathname = path.join('cypress', this.ctx.coreData.currentTestingType ?? 'e2e', defaultFilename) - - if (!this.ctx.currentProject || !this.ctx.coreData.currentTestingType) { - throw new Error('Failed to get default spec filename, missing currentProject/currentTestingType') - } - - try { - let specPatternSet: string | undefined - const { specPattern = [] } = await this.ctx.project.specPatterns() + const { specPattern = [] } = await this.ctx.project.specPatterns() - if (Array.isArray(specPattern)) { - specPatternSet = specPattern[0] - } - - // 1. If there is no spec pattern, use the default for this testing type. - if (!specPatternSet) { - return defaultPathname - } - - // 2. If the spec pattern is the default spec pattern, return the default for this testing type. - if (specPatternSet === defaultSpecPattern[this.ctx.coreData.currentTestingType]) { - return defaultPathname - } - - const pathFromSpecPattern = getPathFromSpecPattern(specPatternSet, this.ctx.coreData.currentTestingType, this.ctx.lifecycleManager.fileExtensionToUse) - const filename = pathFromSpecPattern ? path.basename(pathFromSpecPattern) : defaultFilename - - // 3. If there are existing specs, return the longest common path prefix between them, if it is non-empty. - const commonPrefixFromSpecs = getLongestCommonPrefixFromPaths(this.specs.map((spec) => spec.relative)) - - if (commonPrefixFromSpecs) return path.join(commonPrefixFromSpecs, filename) - - // 4. Otherwise, return a path that fulfills the spec pattern. - if (pathFromSpecPattern) return pathFromSpecPattern - - // 5. Return the default for this testing type if we cannot decide from the spec pattern. - return defaultPathname - } catch (err) { - debug('Error intelligently detecting default filename, using safe default %o', err) - - return defaultPathname - } + return getDefaultSpecFileName({ + currentProject: this.ctx.currentProject, + testingType: this.ctx.coreData.currentTestingType, + fileExtensionToUse: this.ctx.lifecycleManager.fileExtensionToUse, + specs: this.specs, + specPattern, + }) } async matchesSpecPattern (specFile: string): Promise { diff --git a/packages/data-context/src/sources/migration/utils.ts b/packages/data-context/src/sources/migration/utils.ts index 7afdfc79cf01..af89cbb39d65 100644 --- a/packages/data-context/src/sources/migration/utils.ts +++ b/packages/data-context/src/sources/migration/utils.ts @@ -1,4 +1,9 @@ +import { defaultSpecPattern } from '@packages/config' +import type { TestingType, FoundSpec } from '@packages/types' +import Debug from 'debug' import _ from 'lodash' +import path from 'path' +import { getPathFromSpecPattern, getLongestCommonPrefixFromPaths } from '../ProjectDataSource' export const isDefaultSupportFile = (supportFile: string) => { if (_.isNil(supportFile) || !_.isBoolean(supportFile) && supportFile.match(/(^|\.+\/)cypress\/support($|\/index($|\.(ts|js|coffee)$))/)) { @@ -7,3 +12,55 @@ export const isDefaultSupportFile = (supportFile: string) => { return false } + +export async function getDefaultSpecFileName ( + { currentProject, testingType, fileExtensionToUse, specPattern, specs = [], name }: + { currentProject: string | null, testingType: TestingType | null, fileExtensionToUse: 'js' | 'ts', specPattern: string[], specs?: FoundSpec[], name?: string }, +): Promise { + const debug = Debug('cypress:data-context:sources:migration:utils') + + const defaultFilename = `${name ? name : testingType === 'e2e' ? 'spec' : 'ComponentName'}.cy.${fileExtensionToUse}` + const defaultPathname = path.join('cypress', testingType ?? 'e2e', defaultFilename) + + if (!currentProject || !testingType) { + debug('currentProject or testingType undefined. Error intelligently detecting default filename, using safe default %o', defaultPathname) + + return defaultPathname + } + + try { + let specPatternSet: string | undefined + + if (Array.isArray(specPattern)) { + specPatternSet = specPattern[0] + } + + // 1. If there is no spec pattern, use the default for this testing type. + if (!specPatternSet) { + return defaultPathname + } + + // 2. If the spec pattern is the default spec pattern, return the default for this testing type. + if (specPatternSet === defaultSpecPattern[testingType]) { + return defaultPathname + } + + const pathFromSpecPattern = getPathFromSpecPattern({ specPattern: specPatternSet, testingType, fileExtensionToUse, name }) + const filename = pathFromSpecPattern ? path.basename(pathFromSpecPattern) : defaultFilename + + // 3. If there are existing specs, return the longest common path prefix between them, if it is non-empty. + const commonPrefixFromSpecs = getLongestCommonPrefixFromPaths(specs.map((spec) => spec.relative)) + + if (commonPrefixFromSpecs) return path.join(commonPrefixFromSpecs, filename) + + // 4. Otherwise, return a path that fulfills the spec pattern. + if (pathFromSpecPattern) return pathFromSpecPattern + + // 5. Return the default for this testing type if we cannot decide from the spec pattern. + return defaultPathname + } catch (err) { + debug('Error intelligently detecting default filename, using safe default %o', err) + + return defaultPathname + } +} diff --git a/packages/data-context/test/unit/codegen/code-generator.spec.ts b/packages/data-context/test/unit/codegen/code-generator.spec.ts index dcf1b1a8aeba..b9d8c0bacfdf 100644 --- a/packages/data-context/test/unit/codegen/code-generator.spec.ts +++ b/packages/data-context/test/unit/codegen/code-generator.spec.ts @@ -11,6 +11,7 @@ import { SpecOptions } from '../../../src/codegen/spec-options' import templates from '../../../src/codegen/templates' import { createTestDataContext } from '../helper' import { WIZARD_FRAMEWORKS } from '@packages/scaffold-config' +import { defaultSpecPattern } from '@packages/config' const tmpPath = path.join(__dirname, 'tmp/test-code-gen') @@ -205,7 +206,7 @@ describe('code-generator', () => { type: 'text', status: 'add', file: fileAbsolute, - content: dedent`import ${codeGenArgs.componentName} from "${codeGenArgs.componentPath}" + content: dedent`import ${codeGenArgs.componentName} from '${codeGenArgs.componentPath}' describe('<${codeGenArgs.componentName} />', () => { it('renders', () => { @@ -257,11 +258,12 @@ describe('code-generator', () => { } const newSpecCodeGenOptions = new SpecOptions({ + currentProject: 'path/to/myProject', codeGenPath: path.join(__dirname, 'files', 'react', 'Button.jsx'), codeGenType: 'component', - specFileExtension: '.cy', framework: WIZARD_FRAMEWORKS[1], isDefaultSpecPattern: true, + specPattern: [defaultSpecPattern.component], }) let codeGenOptions = await newSpecCodeGenOptions.getCodeGenOptions() diff --git a/packages/data-context/test/unit/codegen/spec-options.spec.ts b/packages/data-context/test/unit/codegen/spec-options.spec.ts index a15c9543b66c..15fac55bc7ce 100644 --- a/packages/data-context/test/unit/codegen/spec-options.spec.ts +++ b/packages/data-context/test/unit/codegen/spec-options.spec.ts @@ -1,7 +1,9 @@ +import { defaultSpecPattern } from '@packages/config' import { WIZARD_FRAMEWORKS } from '@packages/scaffold-config' import { expect } from 'chai' import fs from 'fs-extra' import path from 'path' +import sinon from 'sinon' import { DataContext } from '../../../src' import { SpecOptions, expectedSpecExtensions } from '../../../src/codegen/spec-options' import { createTestDataContext } from '../helper' @@ -30,9 +32,11 @@ describe('spec-options', () => { for (const specExtension of expectedSpecExtensions) { it(`generates options for names with extension ${specExtension}`, async () => { const testSpecOptions = new SpecOptions({ + currentProject: 'path/to/myProject', codeGenPath: `${tmpPath}/TestName${specExtension}.js`, codeGenType: 'e2e', isDefaultSpecPattern: true, + specPattern: [defaultSpecPattern.e2e], }) const result = await testSpecOptions.getCodeGenOptions() @@ -44,9 +48,11 @@ describe('spec-options', () => { it('generates options for file name without spec extension', async () => { const testSpecOptions = new SpecOptions({ + currentProject: 'path/to/myProject', codeGenPath: `${tmpPath}/TestName.js`, codeGenType: 'e2e', isDefaultSpecPattern: true, + specPattern: [defaultSpecPattern.e2e], }) const result = await testSpecOptions.getCodeGenOptions() @@ -57,9 +63,11 @@ describe('spec-options', () => { it('generates options for file name with multiple extensions', async () => { const testSpecOptions = new SpecOptions({ + currentProject: 'path/to/myProject', codeGenPath: `${tmpPath}/TestName.foo.bar.js`, codeGenType: 'e2e', isDefaultSpecPattern: true, + specPattern: [defaultSpecPattern.e2e], }) const result = await testSpecOptions.getCodeGenOptions() @@ -67,18 +75,93 @@ describe('spec-options', () => { expect(result.codeGenType).to.eq('e2e') expect(result.fileName).to.eq(`TestName.foo.bar.js`) }) + }) - it('generates options with given codeGenType', async () => { - const testSpecOptions = new SpecOptions({ - codeGenPath: `${tmpPath}/TestName.js`, - codeGenType: 'component', - isDefaultSpecPattern: true, - framework: WIZARD_FRAMEWORKS[1], + context('create from Vue component', () => { + afterEach(function () { + sinon.restore() + }) + + context('default spec pattern', () => { + it('generates options for generating a Vue component spec', async () => { + const testSpecOptions = new SpecOptions({ + currentProject: 'path/to/myProject', + codeGenPath: `${tmpPath}/MyComponent.vue`, + codeGenType: 'component', + isDefaultSpecPattern: true, + framework: WIZARD_FRAMEWORKS[1], + specPattern: [defaultSpecPattern.component], + }) + + const result = await testSpecOptions.getCodeGenOptions() + + expect(result.codeGenType).to.eq('component') + expect(result.fileName).to.eq('MyComponent.cy.js') }) - const result = await testSpecOptions.getCodeGenOptions() + it('creates copy file if spec already exists', async () => { + sinon.stub(fs, 'access').onFirstCall().resolves().onSecondCall().rejects() + + const testSpecOptions = new SpecOptions({ + currentProject: 'path/to/myProject', + codeGenPath: `${tmpPath}/MyComponent.vue`, + codeGenType: 'component', + isDefaultSpecPattern: true, + framework: WIZARD_FRAMEWORKS[1], + specPattern: [defaultSpecPattern.component], + }) + + const result = await testSpecOptions.getCodeGenOptions() + + expect(result.codeGenType).to.eq('component') + expect(result.fileName).to.eq('MyComponent-copy-1.cy.js') + }) + }) - expect(result.codeGenType).to.eq('component') + context('custom spec pattern', () => { + [{ testName: 'src/specs-folder/*.cy.{js,jsx}', componentPath: 'ComponentName.vue', specs: [], pattern: 'src/specs-folder/*.cy.{js,jsx}', expectedPath: 'src/specs-folder/ComponentName.cy.js' }, + { testName: 'src/**/*.{spec,cy}.{js,jsx,ts,tsx}', componentPath: 'MyComponent.vue', specs: [], pattern: 'src/**/*.{spec,cy}.{js,jsx,ts,tsx}', expectedPath: 'src/MyComponent.spec.ts', isTypescriptComponent: true }, + { testName: '**/*.test.js', componentPath: 'src/Foo.vue', specs: [], pattern: '**/*.test.js', expectedPath: 'cypress/Foo.test.js' }, + { testName: 'src/**/*.js', componentPath: 'src/Foo.vue', specs: [], pattern: 'src/**/*.js', expectedPath: 'src/Foo.js' }, + { testName: '**/*.js no specs', componentPath: 'src/Foo.vue', specs: [], pattern: '**/*.js', expectedPath: 'cypress/Foo.js' }, + { testName: '**/*.js existing spec', componentPath: 'src/Foo.vue', + specs: [{ specType: 'component' as Cypress.CypressSpecType, name: 'src/Bar.cy.js', baseName: 'Bar.cy.js', fileName: 'Bar', relative: 'src/Bar.cy.js', absolute: `${tmpPath}/src/Bar.cy.js`, fileExtension: '.js', specFileExtension: '.cy.js' }], + pattern: '**/*.js', expectedPath: 'src/Foo.js' }, + { testName: '**/*.js spec already exists', componentPath: 'src/Foo.vue', + specs: [{ specType: 'component' as Cypress.CypressSpecType, name: 'src/Foo.cy.js', baseName: 'Foo.cy.js', fileName: 'Foo', relative: 'src/Foo.cy.js', absolute: `${tmpPath}/src/Foo.cy.js`, fileExtension: '.js', specFileExtension: '.cy.js' }], + pattern: '**/*.cy.js', expectedPath: 'src/Foo-copy-1.cy.js', makeCopy: true }] + .forEach(({ testName, componentPath, specs, pattern, expectedPath, makeCopy, isTypescriptComponent }) => { + it(testName, async () => { + // This stub simulates the spec file already existing the first time we try, which should cause a copy to be created + if (makeCopy) { + sinon.stub(fs, 'access').onFirstCall().resolves().onSecondCall().rejects() + } + + // This stub simulates that the component we are generating a spec from is using Typescript. + if (isTypescriptComponent) { + // @ts-ignore + sinon.stub(fs, 'readFile').resolves('lang="ts"') + } + + const currentProject = 'path/to/myProject' + const specPattern = [pattern] + + const testSpecOptions = new SpecOptions({ + currentProject, + codeGenPath: `${tmpPath}/${componentPath}`, + codeGenType: 'component', + isDefaultSpecPattern: false, + framework: WIZARD_FRAMEWORKS[1], + specPattern, + specs, + }) + + const result = await testSpecOptions.getCodeGenOptions() + + expect(result.codeGenType).to.eq('component') + expect(`${result.overrideCodeGenDir}/${result.fileName}`).to.eq(expectedPath) + }) + }) }) }) @@ -86,9 +169,11 @@ describe('spec-options', () => { for (const specExtension of expectedSpecExtensions) { it(`generates options for file name with extension ${specExtension}`, async () => { const testSpecOptions = new SpecOptions({ + currentProject: 'path/to/myProject', codeGenPath: `${tmpPath}/TestName${specExtension}.js`, codeGenType: 'e2e', isDefaultSpecPattern: true, + specPattern: [defaultSpecPattern.e2e], }) await fs.outputFile(`${tmpPath}/TestName${specExtension}.js`, '// foo') @@ -110,9 +195,11 @@ describe('spec-options', () => { it('generates options for file name without spec extension', async () => { const testSpecOptions = new SpecOptions({ + currentProject: 'path/to/myProject', codeGenPath: `${tmpPath}/TestName.js`, codeGenType: 'e2e', isDefaultSpecPattern: true, + specPattern: [defaultSpecPattern.e2e], }) await fs.outputFile(`${tmpPath}/TestName.js`, '// foo') @@ -133,9 +220,11 @@ describe('spec-options', () => { it('generates options for file name with multiple extensions', async () => { const testSpecOptions = new SpecOptions({ + currentProject: 'path/to/myProject', codeGenPath: `${tmpPath}/TestName.foo.bar.js`, codeGenType: 'e2e', isDefaultSpecPattern: true, + specPattern: [defaultSpecPattern.e2e], }) await fs.outputFile(`${tmpPath}/TestName.foo.bar.js`, '// foo') diff --git a/packages/data-context/test/unit/sources/ProjectDataSource.spec.ts b/packages/data-context/test/unit/sources/ProjectDataSource.spec.ts index 0e903254dabc..ed7dfc16eb22 100644 --- a/packages/data-context/test/unit/sources/ProjectDataSource.spec.ts +++ b/packages/data-context/test/unit/sources/ProjectDataSource.spec.ts @@ -300,31 +300,31 @@ describe('getPathFromSpecPattern', () => { context('dirname', () => { it('returns pattern without change if it is do not a glob', () => { const specPattern = 'cypress/e2e/foo.spec.ts' - const defaultFileName = getPathFromSpecPattern(specPattern, 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern, testingType: 'e2e' }) expect(defaultFileName).to.eq(specPattern) }) it('remove ** from glob if it is not in the beginning', () => { - const defaultFileName = getPathFromSpecPattern('cypress/**/foo.spec.ts', 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/**/foo.spec.ts', testingType: 'e2e' }) expect(defaultFileName).to.eq('cypress/foo.spec.ts') }) it('replace ** for cypress if it starts with **', () => { - const defaultFileName = getPathFromSpecPattern('**/e2e/foo.spec.ts', 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern: '**/e2e/foo.spec.ts', testingType: 'e2e' }) expect(defaultFileName).to.eq('cypress/e2e/foo.spec.ts') }) it('replace ** for cypress if it starts with ** and omit extra **', () => { - const defaultFileName = getPathFromSpecPattern('**/**/foo.spec.ts', 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern: '**/**/foo.spec.ts', testingType: 'e2e' }) expect(defaultFileName).to.eq('cypress/foo.spec.ts') }) it('selects first option if there are multiples possibilities of values', () => { - const defaultFileName = getPathFromSpecPattern('{cypress,tests}/{integration,e2e}/foo.spec.ts', 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern: '{cypress,tests}/{integration,e2e}/foo.spec.ts', testingType: 'e2e' }) expect(defaultFileName).to.eq('cypress/integration/foo.spec.ts') }) @@ -332,13 +332,13 @@ describe('getPathFromSpecPattern', () => { context('filename', () => { it('replace * for filename', () => { - const defaultFileName = getPathFromSpecPattern('cypress/e2e/*.spec.ts', 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/e2e/*.spec.ts', testingType: 'e2e' }) expect(defaultFileName).to.eq('cypress/e2e/spec.spec.ts') }) it('selects first option if there are multiples possibilities of values', () => { - const defaultFileName = getPathFromSpecPattern('cypress/e2e/{foo,filename}.spec.ts', 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/e2e/{foo,filename}.spec.ts', testingType: 'e2e' }) expect(defaultFileName).to.eq('cypress/e2e/foo.spec.ts') }) @@ -346,13 +346,13 @@ describe('getPathFromSpecPattern', () => { context('test extension', () => { it('replace * for filename', () => { - const defaultFileName = getPathFromSpecPattern('cypress/e2e/filename.*.ts', 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/e2e/filename.*.ts', testingType: 'e2e' }) expect(defaultFileName).to.eq('cypress/e2e/filename.cy.ts') }) it('selects first option if there are multiples possibilities of values', () => { - const defaultFileName = getPathFromSpecPattern('cypress/e2e/filename.{spec,cy}.ts', 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/e2e/filename.{spec,cy}.ts', testingType: 'e2e' }) expect(defaultFileName).to.eq('cypress/e2e/filename.spec.ts') }) @@ -360,25 +360,25 @@ describe('getPathFromSpecPattern', () => { context('lang extension', () => { it('if project use TS, set TS as extension if it exists in the glob', () => { - const defaultFileName = getPathFromSpecPattern('cypress/e2e/filename.cy.ts', 'e2e', 'ts') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/e2e/filename.cy.ts', testingType: 'e2e', fileExtensionToUse: 'ts' }) expect(defaultFileName).to.eq('cypress/e2e/filename.cy.ts') }) it('if project use TS, set TS as extension if it exists in the options of extensions', () => { - const defaultFileName = getPathFromSpecPattern('cypress/e2e/filename.cy.{js,ts,tsx}', 'e2e', 'ts') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/e2e/filename.cy.{js,ts,tsx}', testingType: 'e2e', fileExtensionToUse: 'ts' }) expect(defaultFileName).to.eq('cypress/e2e/filename.cy.ts') }) it('if project use TS, do not set TS as extension if it do not exists in the options of extensions', () => { - const defaultFileName = getPathFromSpecPattern('cypress/e2e/filename.cy.{js,jsx}', 'e2e', 'ts') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/e2e/filename.cy.{js,jsx}', testingType: 'e2e', fileExtensionToUse: 'ts' }) expect(defaultFileName).to.eq('cypress/e2e/filename.cy.js') }) it('selects first option if there are multiples possibilities of values', () => { - const defaultFileName = getPathFromSpecPattern('cypress/e2e/filename.cy.{ts,js}', 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/e2e/filename.cy.{ts,js}', testingType: 'e2e' }) expect(defaultFileName).to.eq('cypress/e2e/filename.cy.ts') }) @@ -386,43 +386,43 @@ describe('getPathFromSpecPattern', () => { context('extra cases', () => { it('creates specName for tests/*.js', () => { - const defaultFileName = getPathFromSpecPattern('tests/*.js', 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'tests/*.js', testingType: 'e2e' }) expect(defaultFileName).to.eq('tests/spec.js') }) it('creates specName for src/*-test.js', () => { - const defaultFileName = getPathFromSpecPattern('src/*-test.js', 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'src/*-test.js', testingType: 'e2e' }) expect(defaultFileName).to.eq('src/spec-test.js') }) it('creates specName for src/*.foo.bar.js', () => { - const defaultFileName = getPathFromSpecPattern('src/*.foo.bar.js', 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'src/*.foo.bar.js', testingType: 'e2e' }) expect(defaultFileName).to.eq('src/spec.foo.bar.js') }) it('creates specName for src/prefix.*.test.js', () => { - const defaultFileName = getPathFromSpecPattern('src/prefix.*.test.js', 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'src/prefix.*.test.js', testingType: 'e2e' }) expect(defaultFileName).to.eq('src/prefix.cy.test.js') }) it('creates specName for src/*/*.test.js', () => { - const defaultFileName = getPathFromSpecPattern('src/*/*.test.js', 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'src/*/*.test.js', testingType: 'e2e' }) expect(defaultFileName).to.eq('src/e2e/spec.test.js') }) it('creates specName for src-*/**/*.test.js', () => { - const defaultFileName = getPathFromSpecPattern('src-*/**/*.test.js', 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'src-*/**/*.test.js', testingType: 'e2e' }) expect(defaultFileName).to.eq('src-e2e/spec.test.js') }) it('creates specName for src/*.test.(js|jsx)', () => { - const defaultFileName = getPathFromSpecPattern('src/*.test.(js|jsx)', 'component') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'src/*.test.(js|jsx)', testingType: 'component' }) const possiblesFileNames = ['src/ComponentName.test.jsx', 'src/ComponentName.test.js'] @@ -430,7 +430,7 @@ describe('getPathFromSpecPattern', () => { }) it('creates specName for (src|components)/**/*.test.js', () => { - const defaultFileName = getPathFromSpecPattern('(src|components)/**/*.test.js', 'component') + const defaultFileName = getPathFromSpecPattern({ specPattern: '(src|components)/**/*.test.js', testingType: 'component' }) const possiblesFileNames = ['src/ComponentName.test.js', 'components/ComponentName.test.js'] @@ -438,13 +438,13 @@ describe('getPathFromSpecPattern', () => { }) it('creates specName for e2e/**/*.cy.{js,jsx,ts,tsx}', () => { - const defaultFileName = getPathFromSpecPattern('e2e/**/*.cy.{js,jsx,ts,tsx}', 'e2e') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'e2e/**/*.cy.{js,jsx,ts,tsx}', testingType: 'e2e' }) expect(defaultFileName).to.eq('e2e/spec.cy.js') }) it('creates specName for cypress/component-tests/**/*', () => { - const defaultFileName = getPathFromSpecPattern('cypress/component-tests/**/*', 'component', 'ts') + const defaultFileName = getPathFromSpecPattern({ specPattern: 'cypress/component-tests/**/*', testingType: 'component', fileExtensionToUse: 'ts' }) expect(defaultFileName).to.eq('cypress/component-tests/ComponentName.cy.ts') }) @@ -772,6 +772,8 @@ describe('ProjectDataSource', () => { context('#defaultSpecFilename', () => { it('yields default if no spec pattern is set', async () => { + sinon.stub(ctx.project, 'specPatterns').resolves({ specPattern: [] }) + const defaultSpecFileName = await ctx.project.defaultSpecFileName() expect(defaultSpecFileName).to.equal('cypress/e2e/spec.cy.js') diff --git a/packages/graphql/schemas/schema.graphql b/packages/graphql/schemas/schema.graphql index c6f4110370af..613eebaa0b63 100644 --- a/packages/graphql/schemas/schema.graphql +++ b/packages/graphql/schemas/schema.graphql @@ -1211,7 +1211,7 @@ type Mutation { focusActiveBrowserWindow: Boolean! """Generate spec from source""" - generateSpecFromSource(codeGenCandidate: String!, erroredCodegenCandidate: String, type: CodeGenType!): GenerateSpecResponse + generateSpecFromSource(codeGenCandidate: String!, type: CodeGenType!): GenerateSpecResponse internal_clearAllProjectPreferencesCache: Boolean internal_clearLatestProjectCache: Boolean internal_clearProjectPreferencesCache(projectTitle: String!): Boolean diff --git a/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts b/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts index 3baf389f8087..8c3f4a9770f2 100644 --- a/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts +++ b/packages/graphql/src/schemaTypes/objectTypes/gql-Mutation.ts @@ -246,10 +246,9 @@ export const mutation = mutationType({ args: { codeGenCandidate: nonNull(stringArg()), type: nonNull(CodeGenTypeEnum), - erroredCodegenCandidate: stringArg(), }, resolve: (_, args, ctx) => { - return ctx.actions.project.codeGenSpec(args.codeGenCandidate, args.type, args.erroredCodegenCandidate) + return ctx.actions.project.codeGenSpec(args.codeGenCandidate, args.type) }, }) diff --git a/system-tests/projects/no-specs-vue-2/cypress-custom-spec-pattern.config.js b/system-tests/projects/no-specs-vue-2/cypress-custom-spec-pattern.config.js new file mode 100644 index 000000000000..5910e12bd83b --- /dev/null +++ b/system-tests/projects/no-specs-vue-2/cypress-custom-spec-pattern.config.js @@ -0,0 +1,9 @@ +module.exports = { + component: { + specPattern: 'src/specs-folder/*.cy.{js,jsx}', + devServer: { + framework: 'vue-cli', + bundler: 'webpack' + } + } +}