Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: create from React component UI #24982

Merged
Merged
Changes from 2 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
6c24bfa
feat: WIP server logic for create from React component
astone123 Nov 29, 2022
2948dd7
feat: add more tests; error handling
astone123 Nov 29, 2022
6468430
feat: WIP create from React UI
astone123 Nov 30, 2022
73cb71f
feat: PR feedback [run CI]
astone123 Dec 1, 2022
0dbd568
Resolve conflicts
astone123 Dec 1, 2022
0f4e836
feat: try committing snapshot cache changes [run ci]
astone123 Dec 1, 2022
d763e1f
feat: try re-generating snapshot [run ci]
astone123 Dec 1, 2022
a2626df
fix build
ryanthemanuel Dec 1, 2022
6eabeb3
regenerate cache on darwin
ryanthemanuel Dec 1, 2022
6ba1695
update caches
ryanthemanuel Dec 1, 2022
9389037
Revert "feat: try re-generating snapshot [run ci]"
astone123 Dec 1, 2022
d4e4c7d
Merge branch 'feature/create-from-react-component' into astone123/cre…
lmiller1990 Dec 2, 2022
a48491d
fix typing error
lmiller1990 Dec 2, 2022
00e7819
types
lmiller1990 Dec 2, 2022
df027ff
fix test
lmiller1990 Dec 2, 2022
b32560f
chore: try using [email protected]
lmiller1990 Dec 2, 2022
89b823a
update test
lmiller1990 Dec 2, 2022
2f3ffcb
regen linux snapshot
lmiller1990 Dec 2, 2022
a631284
update snapshots for darwin
lmiller1990 Dec 2, 2022
dbb5b3e
re-gen linux snapshot
lmiller1990 Dec 2, 2022
4ad474c
Merge branch 'astone123/create-from-react-server' of https://github.c…
lmiller1990 Dec 2, 2022
c0382fa
yarn install
lmiller1990 Dec 2, 2022
7456bb7
update snapshots
lmiller1990 Dec 2, 2022
fadb8d9
update snapshot metadata
lmiller1990 Dec 2, 2022
6d502b1
update snapshots due to babel deps changing slightly
lmiller1990 Dec 2, 2022
f60ac0a
make react docgen a dep
lmiller1990 Dec 2, 2022
bd398ed
update tests
lmiller1990 Dec 2, 2022
9510a85
revert
lmiller1990 Dec 2, 2022
77a4942
snapshots again??
lmiller1990 Dec 2, 2022
f9e8af7
revert
lmiller1990 Dec 2, 2022
68d5c87
update
lmiller1990 Dec 2, 2022
281da0a
update
lmiller1990 Dec 2, 2022
e4410b1
try change snapshot
lmiller1990 Dec 2, 2022
f12efca
change snap
lmiller1990 Dec 2, 2022
e857b8f
update snap
lmiller1990 Dec 2, 2022
c070f96
feat: remove unnecessary ts-ignore
astone123 Dec 2, 2022
cb381e9
Merge branch 'astone123/create-from-react-server' of github.com:cypre…
astone123 Dec 2, 2022
cd65233
feat: add more test cases
astone123 Dec 2, 2022
205e185
Merge branch 'astone123/create-from-react-server' of github.com:cypre…
astone123 Dec 2, 2022
9449d66
feat: create CodegenActions; other minor refactors
astone123 Dec 5, 2022
2b03c33
Merge branch 'astone123/create-from-react-server' of github.com:cypre…
astone123 Dec 5, 2022
f979f48
feat: continue UI work
astone123 Dec 5, 2022
da5bfda
Merge branch 'feature/create-from-react-component' of github.com:cypr…
astone123 Dec 5, 2022
16df37a
feat: ignore config and Cypress-related files
astone123 Dec 6, 2022
19fac18
feat: PR feedback
astone123 Dec 6, 2022
ab6f8c0
Merge branch 'feature/create-from-react-component' of github.com:cypr…
astone123 Dec 7, 2022
c4749b9
update Vue component link
astone123 Dec 7, 2022
544a651
merge in default export work
astone123 Dec 7, 2022
dbea144
consolidate graphql queries
astone123 Dec 7, 2022
50a9c15
other misc feedback
astone123 Dec 7, 2022
367eaa7
use network-only policy to fetch files; include cypress/ dir for code…
astone123 Dec 7, 2022
5debd39
add basic e2e test
astone123 Dec 7, 2022
b469af7
fix app integration tests
astone123 Dec 7, 2022
18d2545
refactor and fix app component and webpack dev server tests
astone123 Dec 8, 2022
1d745e9
add error state; fix unit tests [skip ci]
astone123 Dec 8, 2022
84fb446
simplify generator show logic [skip ci]
astone123 Dec 8, 2022
27ae647
more testing
astone123 Dec 8, 2022
f10b034
fix types
astone123 Dec 8, 2022
2d30c0f
style updates [skip ci]
astone123 Dec 8, 2022
101f964
fix error state [skip ci]
astone123 Dec 9, 2022
595fe0d
fix list padding [skip ci]
astone123 Dec 9, 2022
156c800
use slots (#25079)
lmiller1990 Dec 9, 2022
d504825
add more tests; fix unit tests
astone123 Dec 9, 2022
a44ed45
fix types
astone123 Dec 9, 2022
43d73f0
fix test describe
astone123 Dec 9, 2022
165fbfe
add percy snapshots for new list
astone123 Dec 9, 2022
f1c7a07
update trouble rendering banner link [skip ci]
astone123 Dec 9, 2022
7bf5c7e
use collapsible component
astone123 Dec 9, 2022
766419d
use button for component list items
astone123 Dec 9, 2022
b55c4fa
fix tests
astone123 Dec 9, 2022
48081eb
Merge branch 'feature/create-from-react-component' of github.com:cypr…
astone123 Dec 9, 2022
49c7ed7
build binaries
astone123 Dec 9, 2022
a582866
revert changes to circle config
astone123 Dec 12, 2022
1670c94
remove eslintignore and extra loading div [skip ci] because we know i…
astone123 Dec 13, 2022
d243608
revert changes to framework glob patterns [skip ci]
astone123 Dec 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/data-context/src/DataActions.ts
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ import {
DevActions,
AuthActions,
CohortsActions,
CodegenActions,
} from './actions'
import { ErrorActions } from './actions/ErrorActions'
import { EventCollectorActions } from './actions/EventCollectorActions'
@@ -89,4 +90,9 @@ export class DataActions {
get cohorts () {
return new CohortsActions(this.ctx)
}

@cached
get codegen () {
return new CodegenActions(this.ctx)
}
}
141 changes: 141 additions & 0 deletions packages/data-context/src/actions/CodegenActions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import type { NexusGenObjects, NexusGenUnions } from '@packages/graphql/src/gen/nxs.gen'
import assert from 'assert'
import path from 'path'
import type { DataContext } from '..'
import { SpecOptions, codeGenerator } from '../codegen'
import templates from '../codegen/templates'
import type { CodeGenType } from '../gen/graphcache-config.gen'
import { WizardFrontendFramework, WIZARD_FRAMEWORKS } from '@packages/scaffold-config'
import { parse as parseReactComponent, resolver as reactDocgenResolvers } from 'react-docgen'

interface ReactComponentDescriptor {
displayName: string
}

export class CodegenActions {
constructor (private ctx: DataContext) {}

async getReactComponentsFromFile (filePath: string): Promise<ReactComponentDescriptor[]> {
try {
const src = await this.ctx.fs.readFile(filePath, 'utf8')

const result = parseReactComponent(src, reactDocgenResolvers.findAllExportedComponentDefinitions)
// types appear to be incorrect in [email protected]
// TODO: update when 6.0.0 stable is out for fixed types.
const defs = (Array.isArray(result) ? result : [result]) as ReactComponentDescriptor[]

return defs
} catch (err) {
this.ctx.debug(err)

return []
}
}

async codeGenSpec (codeGenCandidate: string, codeGenType: CodeGenType, componentName?: string): Promise<NexusGenUnions['GeneratedSpecResult']> {
const project = this.ctx.currentProject

assert(project, 'Cannot create spec without currentProject.')

const getCodeGenPath = () => {
return codeGenType === 'e2e'
? this.ctx.path.join(
project,
codeGenCandidate,
)
: codeGenCandidate
}

const codeGenPath = getCodeGenPath()

const { specPattern = [] } = await this.ctx.project.specPatterns()

const newSpecCodeGenOptions = new SpecOptions({
codeGenPath,
codeGenType,
framework: this.getWizardFrameworkFromConfig(),
isDefaultSpecPattern: await this.ctx.project.getIsDefaultSpecPattern(),
specPattern,
currentProject: this.ctx.currentProject,
specs: this.ctx.project.specs,
componentName,
})

let codeGenOptions = await newSpecCodeGenOptions.getCodeGenOptions()

const codeGenResults = await codeGenerator(
{ templateDir: templates[codeGenOptions.templateKey], target: codeGenOptions.overrideCodeGenDir || path.parse(codeGenPath).dir },
codeGenOptions,
)

if (!codeGenResults.files[0] || codeGenResults.failed[0]) {
throw (codeGenResults.failed[0] || 'Unable to generate spec')
}

const [newSpec] = codeGenResults.files

const cfg = await this.ctx.project.getConfig()

if (cfg && this.ctx.currentProject) {
const testingType = (codeGenType === 'component') ? 'component' : 'e2e'

await this.ctx.actions.project.setSpecsFoundBySpecPattern({
projectRoot: this.ctx.currentProject,
testingType,
specPattern: cfg.specPattern ?? [],
configSpecPattern: cfg.specPattern ?? [],
excludeSpecPattern: cfg.excludeSpecPattern,
additionalIgnorePattern: cfg.additionalIgnorePattern,
})
}

return {
status: 'valid',
file: { absolute: newSpec.file, contents: newSpec.content },
description: 'Generated spec',
}
}

get defaultE2EPath () {
const projectRoot = this.ctx.currentProject

assert(projectRoot, `Cannot create e2e directory without currentProject.`)

return path.join(projectRoot, 'cypress', 'e2e')
}

async scaffoldIntegration (): Promise<NexusGenObjects['ScaffoldedFile'][]> {
const projectRoot = this.ctx.currentProject

assert(projectRoot, `Cannot create spec without currentProject.`)

const results = await codeGenerator(
{ templateDir: templates['scaffoldIntegration'], target: this.defaultE2EPath },
{},
)

if (results.failed.length) {
throw new Error(`Failed generating files: ${results.failed.map((e) => `${e}`)}`)
}

return results.files.map(({ status, file, content }) => {
return {
status: (status === 'add' || status === 'overwrite') ? 'valid' : 'skipped',
file: { absolute: file, contents: content },
description: 'Generated spec',
}
})
}

getWizardFrameworkFromConfig (): WizardFrontendFramework | undefined {
const config = this.ctx.lifecycleManager.loadedConfigFile

// If devServer is a function, they are using a custom dev server.
if (!config?.component?.devServer || typeof config?.component?.devServer === 'function') {
return undefined
}

// @ts-ignore - because of the conditional above, we know that devServer isn't a function
return WIZARD_FRAMEWORKS.find((framework) => framework.configFramework === config?.component?.devServer.framework)
}
}
139 changes: 2 additions & 137 deletions packages/data-context/src/actions/ProjectActions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CodeGenType, MutationSetProjectPreferencesInGlobalCacheArgs, NexusGenObjects, NexusGenUnions } from '@packages/graphql/src/gen/nxs.gen'
import type { MutationSetProjectPreferencesInGlobalCacheArgs, NexusGenObjects, NexusGenUnions } from '@packages/graphql/src/gen/nxs.gen'
import { InitializeProjectOptions, FoundBrowser, OpenProjectLaunchOptions, Preferences, TestingType, ReceivedCypressOptions, AddProject, FullConfig, AllowedState, SpecWithRelativeRoot, OpenProjectLaunchOpts, RUN_ALL_SPECS, RUN_ALL_SPECS_KEY } from '@packages/types'
import type { EventEmitter } from 'events'
import execa from 'execa'
@@ -7,17 +7,11 @@ import assert from 'assert'

import type { ProjectShape } from '../data/coreDataShape'
import type { DataContext } from '..'
import { codeGenerator, SpecOptions, hasNonExampleSpec } from '../codegen'
import { hasNonExampleSpec } from '../codegen'
import templates from '../codegen/templates'
import { insertValuesInConfigFile } from '../util'
import { getError } from '@packages/errors'
import { resetIssuedWarnings } from '@packages/config'
import { WizardFrontendFramework, WIZARD_FRAMEWORKS } from '@packages/scaffold-config'
import { parse as parseReactComponent, resolver as reactDocgenResolvers } from 'react-docgen'
import fs from 'fs-extra'
import Debug from 'debug'

const debug = Debug('cypress:data-context:sources:ProjectActions')

export interface ProjectApiShape {
/**
@@ -80,11 +74,6 @@ type SetForceReconfigureProjectByTestingType = {
testingType?: TestingType
}

interface ReactComponentDescriptor {
description: string
displayName: string
}

export class ProjectActions {
constructor (private ctx: DataContext) {}

@@ -354,87 +343,6 @@ export class ProjectActions {
this.api.insertProjectPreferencesToCache(this.ctx.lifecycleManager.projectTitle, args)
}

async getReactComponentsFromFile (filePath: string): Promise<ReactComponentDescriptor[]> {
try {
const src = await fs.readFile(filePath, 'utf8')

const result = parseReactComponent(src, reactDocgenResolvers.findAllExportedComponentDefinitions)
// types appear to be incorrect in [email protected]
// TODO: update when 6.0.0 stable is out for fixed types.
const defs = (Array.isArray(result) ? result : [result]) as ReactComponentDescriptor[]

return defs
} catch (err) {
debug(err)

return []
}
}

async codeGenSpec (codeGenCandidate: string, codeGenType: CodeGenType, componentName?: string): Promise<NexusGenUnions['GeneratedSpecResult']> {
const project = this.ctx.currentProject

assert(project, 'Cannot create spec without currentProject.')

const getCodeGenPath = () => {
return codeGenType === 'e2e'
? this.ctx.path.join(
project,
codeGenCandidate,
)
: codeGenCandidate
}

const codeGenPath = getCodeGenPath()

const { specPattern = [] } = await this.ctx.project.specPatterns()

const newSpecCodeGenOptions = new SpecOptions({
codeGenPath,
codeGenType,
framework: this.getWizardFrameworkFromConfig(),
isDefaultSpecPattern: await this.ctx.project.getIsDefaultSpecPattern(),
specPattern,
currentProject: this.ctx.currentProject,
specs: this.ctx.project.specs,
componentName,
})

let codeGenOptions = await newSpecCodeGenOptions.getCodeGenOptions()

const codeGenResults = await codeGenerator(
{ templateDir: templates[codeGenOptions.templateKey], target: codeGenOptions.overrideCodeGenDir || path.parse(codeGenPath).dir },
codeGenOptions,
)

if (!codeGenResults.files[0] || codeGenResults.failed[0]) {
throw (codeGenResults.failed[0] || 'Unable to generate spec')
}

const [newSpec] = codeGenResults.files

const cfg = await this.ctx.project.getConfig()

if (cfg && this.ctx.currentProject) {
const testingType = (codeGenType === 'component') ? 'component' : 'e2e'

await this.setSpecsFoundBySpecPattern({
projectRoot: this.ctx.currentProject,
testingType,
specPattern: cfg.specPattern ?? [],
configSpecPattern: cfg.specPattern ?? [],
excludeSpecPattern: cfg.excludeSpecPattern,
additionalIgnorePattern: cfg.additionalIgnorePattern,
})
}

return {
status: 'valid',
file: { absolute: newSpec.file, contents: newSpec.content },
description: 'Generated spec',
}
}

async setSpecsFoundBySpecPattern ({ projectRoot, testingType, specPattern, configSpecPattern, excludeSpecPattern, additionalIgnorePattern }: FindSpecs<string | string[] | undefined>) {
const toArray = (val?: string | string[]) => val ? typeof val === 'string' ? [val] : val : []

@@ -494,37 +402,6 @@ export class ProjectActions {
this.ctx.actions.electron.showBrowserWindow()
}

get defaultE2EPath () {
const projectRoot = this.ctx.currentProject

assert(projectRoot, `Cannot create e2e directory without currentProject.`)

return path.join(projectRoot, 'cypress', 'e2e')
}

async scaffoldIntegration (): Promise<NexusGenObjects['ScaffoldedFile'][]> {
const projectRoot = this.ctx.currentProject

assert(projectRoot, `Cannot create spec without currentProject.`)

const results = await codeGenerator(
{ templateDir: templates['scaffoldIntegration'], target: this.defaultE2EPath },
{},
)

if (results.failed.length) {
throw new Error(`Failed generating files: ${results.failed.map((e) => `${e}`)}`)
}

return results.files.map(({ status, file, content }) => {
return {
status: (status === 'add' || status === 'overwrite') ? 'valid' : 'skipped',
file: { absolute: file, contents: content },
description: 'Generated spec',
}
})
}

async hasNonExampleSpec () {
const specs = this.ctx.project.specs?.map((spec) => spec.relativeToCommonRoot)

@@ -572,16 +449,4 @@ export class ProjectActions {
await this.ctx.actions.wizard.scaffoldTestingType()
}
}

getWizardFrameworkFromConfig (): WizardFrontendFramework | undefined {
const config = this.ctx.lifecycleManager.loadedConfigFile

// If devServer is a function, they are using a custom dev server.
if (!config?.component?.devServer || typeof config?.component?.devServer === 'function') {
return undefined
}

// @ts-ignore - because of the conditional above, we know that devServer isn't a function
return WIZARD_FRAMEWORKS.find((framework) => framework.configFramework === config?.component?.devServer.framework)
}
}
1 change: 1 addition & 0 deletions packages/data-context/src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@
export * from './AppActions'
export * from './AuthActions'
export * from './BrowserActions'
export * from './CodegenActions'
export * from './CohortsActions'
export * from './DataEmitterActions'
export * from './DevActions'
2 changes: 1 addition & 1 deletion packages/data-context/src/sources/ProjectDataSource.ts
Original file line number Diff line number Diff line change
@@ -475,7 +475,7 @@ export class ProjectDataSource {

const looseComponentGlob = '*.{js,jsx,ts,tsx,vue}'

const framework = this.ctx.actions.project.getWizardFrameworkFromConfig()
const framework = this.ctx.actions.codegen.getWizardFrameworkFromConfig()

return {
component: framework?.glob ?? looseComponentGlob,
Loading