fix(launchpad): scaffold correct config file (#20372)
@@ -63,3 +63,24 @@ npm/cypress-schematic/src/**/*.js
|
||||
/npm/create-cypress-tests/**/*.template.*
|
||||
|
||||
packages/data-context/test/unit/codegen/files
|
||||
|
||||
# community templates we test against, no need to lint
|
||||
system-tests/projects/create-react-app-configured/**/*
|
||||
system-tests/projects/create-react-app-unconfigured/**/*
|
||||
|
||||
system-tests/projects/vueclivue2-unconfigured/**/*
|
||||
system-tests/projects/vueclivue2-configured/**/*
|
||||
|
||||
system-tests/projects/vueclivue3-unconfigured/**/*
|
||||
system-tests/projects/vueclivue3-configured/**/*
|
||||
|
||||
system-tests/projects/vue3-vite-ts-unconfigured/**/*
|
||||
system-tests/projects/vue3-vite-ts-configured/**/*
|
||||
|
||||
system-tests/projects/nextjs-unconfigured/**/*
|
||||
system-tests/projects/nextjs-configured/**/*
|
||||
|
||||
system-tests/projects/nuxtjs-vue2-unconfigured/**/*
|
||||
system-tests/projects/nuxtjs-vue2-configured/**/*
|
||||
|
||||
system-tests/projects/react-app-webpack-5-unconfigured/**/*
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
"prefix": "/* eslint-disable padding-line-between-statements */",
|
||||
"paths": [
|
||||
"packages/graphql/src/**/*",
|
||||
"packages/data-context/src/**/*"
|
||||
"packages/data-context/src/**/*",
|
||||
"packages/scaffold-config/src/**"
|
||||
],
|
||||
"ignore": [
|
||||
"packages/data-context/src/gen",
|
||||
|
||||
@@ -561,8 +561,8 @@ describe('App: Index', () => {
|
||||
it('shows input for file extension filter', () => {
|
||||
cy.get('@CreateFromComponentDialog').within(() => {
|
||||
cy.log('testing builds')
|
||||
cy.findByTestId('file-match-indicator').should('contain', '2 Matches')
|
||||
cy.findByRole('button', { name: '*.{jsx,tsx}' }).click()
|
||||
cy.findByTestId('file-match-indicator').should('contain', '4 Matches')
|
||||
cy.findByRole('button', { name: '*.{js,jsx,tsx}' }).click()
|
||||
cy.findByPlaceholderText(defaultMessages.components.fileSearch.byExtensionInput)
|
||||
.as('ExtensionInput')
|
||||
.clear()
|
||||
@@ -572,9 +572,9 @@ describe('App: Index', () => {
|
||||
|
||||
cy.findByTestId('no-results-clear').click()
|
||||
|
||||
cy.get('@ExtensionInput').should('have.value', '*.{jsx,tsx}')
|
||||
cy.get('@ExtensionInput').should('have.value', '*.{js,jsx,tsx}')
|
||||
|
||||
cy.findByTestId('file-match-indicator').should('contain', '2 Matches')
|
||||
cy.findByTestId('file-match-indicator').should('contain', '4 Matches')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -583,7 +583,7 @@ describe('App: Index', () => {
|
||||
cy.findByLabelText('file-name-input').as('FileNameInput')
|
||||
.should('have.value', '')
|
||||
|
||||
cy.findByTestId('file-match-indicator').should('contain', '2 Matches')
|
||||
cy.findByTestId('file-match-indicator').should('contain', '4 Matches')
|
||||
|
||||
cy.get('@FileNameInput')
|
||||
.type('foobar')
|
||||
@@ -592,18 +592,18 @@ describe('App: Index', () => {
|
||||
|
||||
cy.findByTestId('no-results-clear').click()
|
||||
|
||||
cy.findByTestId('file-match-indicator').should('contain', '2 Matches')
|
||||
cy.findByTestId('file-match-indicator').should('contain', '4 Matches')
|
||||
|
||||
cy.get('@FileNameInput')
|
||||
.type('App.jsx')
|
||||
|
||||
cy.findByTestId('file-match-indicator').should('contain', '1 of 2 Matches')
|
||||
cy.findByTestId('file-match-indicator').should('contain', '1 of 4 Matches')
|
||||
})
|
||||
})
|
||||
|
||||
it('shows success modal when spec is created from component', () => {
|
||||
cy.get('@CreateFromComponentDialog').within(() => {
|
||||
cy.findAllByTestId('file-list-row').eq(0).as('NewSpecFile')
|
||||
cy.findAllByTestId('file-list-row').contains('App.jsx').as('NewSpecFile')
|
||||
|
||||
// TODO: assert visibility of secondary text on hover/focus when
|
||||
// item is made keyboard accessible
|
||||
@@ -732,7 +732,7 @@ describe('App: Index', () => {
|
||||
.and('contain', defaultMessages.createSpec.component.importFromComponent.description).click()
|
||||
})
|
||||
|
||||
cy.get('[data-cy=file-list-row]').first().click()
|
||||
cy.get('[data-cy=file-list-row]').contains('App.jsx').click()
|
||||
|
||||
cy.get('input').invoke('val').should('eq', getPathForPlatform('src/App.cy.jsx'))
|
||||
cy.contains(defaultMessages.createSpec.component.importEmptySpec.header)
|
||||
@@ -750,7 +750,7 @@ describe('App: Index', () => {
|
||||
.and('contain', defaultMessages.createSpec.component.importFromComponent.description).click()
|
||||
})
|
||||
|
||||
cy.get('[data-cy=file-list-row]').first().click()
|
||||
cy.get('[data-cy=file-list-row]').contains('App.jsx').click()
|
||||
|
||||
cy.get('input').invoke('val').should('eq', getPathForPlatform('src/App.cy.jsx'))
|
||||
cy.contains(defaultMessages.createSpec.component.importEmptySpec.header)
|
||||
@@ -824,11 +824,11 @@ describe('App: Index', () => {
|
||||
})
|
||||
|
||||
cy.contains('Create from component').click()
|
||||
const componentGlob = '*.{jsx,tsx}'
|
||||
const componentGlob = '*.{js,jsx,tsx}'
|
||||
|
||||
cy.findByTestId('file-match-button').contains(componentGlob)
|
||||
cy.percySnapshot('Component Generator')
|
||||
checkCodeGenCandidates(['App.cy.jsx', 'App.jsx', 'index.jsx', 'Button.jsx', 'Button.stories.jsx'])
|
||||
checkCodeGenCandidates(['cypress.config.js', 'App.cy.jsx', 'App.jsx', 'index.jsx', 'support.js', 'Button.jsx', 'Button.stories.jsx'])
|
||||
|
||||
cy.intercept('query-ComponentGeneratorStepOne').as('code-gen-candidates')
|
||||
cy.findByTestId('file-match-button').click()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import path from 'path'
|
||||
import assert from 'assert'
|
||||
import type { DataContext } from '..'
|
||||
import {
|
||||
cleanUpIntegrationFolder,
|
||||
@@ -127,4 +128,24 @@ export class MigrationActions {
|
||||
throw Error(`Expected ${actual} to equal ${expected}`)
|
||||
}
|
||||
}
|
||||
|
||||
async assertSuccessfulConfigScaffold (configFile: `cypress.config.${'js'|'ts'}`) {
|
||||
assert(this.ctx.currentProject)
|
||||
|
||||
// we assert the generated configuration file against one from a project that has
|
||||
// been verified to run correctly.
|
||||
// each project has an `unconfigured` and `configured` variant in `system-tests/projects`
|
||||
// for example vueclivue2-configured and vueclivue2-unconfigured.
|
||||
// after setting the project up with the launchpad, the two projects should contain the same files.
|
||||
|
||||
const configuredProject = this.ctx.project.projectTitle(this.ctx.currentProject).replace('unconfigured', 'configured')
|
||||
const expectedProjectConfig = path.join(__dirname, '..', '..', '..', '..', 'system-tests', 'projects', configuredProject, configFile)
|
||||
|
||||
const actual = formatConfig(await this.ctx.actions.file.readFileInProject(configFile))
|
||||
const expected = formatConfig(await this.ctx.fs.readFile(expectedProjectConfig, 'utf8'))
|
||||
|
||||
if (actual !== expected) {
|
||||
throw Error(`Expected ${actual} to equal ${expected}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import type { CodeLanguageEnum, NexusGenEnums, NexusGenObjects } from '@packages/graphql/src/gen/nxs.gen'
|
||||
import { Bundler, CodeLanguage, CODE_LANGUAGES, FrontendFramework, FRONTEND_FRAMEWORKS } from '@packages/types'
|
||||
import { CodeLanguage, CODE_LANGUAGES } from '@packages/types'
|
||||
import { Bundler, FrontendFramework, FRONTEND_FRAMEWORKS, detect } from '@packages/scaffold-config'
|
||||
import assert from 'assert'
|
||||
import dedent from 'dedent'
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
import Debug from 'debug'
|
||||
|
||||
const debug = Debug('cypress:data-context:wizard-actions')
|
||||
@@ -12,7 +14,6 @@ import type { DataContext } from '..'
|
||||
interface WizardGetCodeComponent {
|
||||
chosenLanguage: CodeLanguage
|
||||
chosenFramework: FrontendFramework
|
||||
chosenBundler: Bundler
|
||||
}
|
||||
|
||||
export class WizardActions {
|
||||
@@ -28,28 +29,33 @@ export class WizardActions {
|
||||
return this.ctx.wizardData
|
||||
}
|
||||
|
||||
setFramework (framework: NexusGenEnums['FrontendFrameworkEnum'] | null) {
|
||||
const prevFramework = this.ctx.coreData.wizard.chosenFramework || ''
|
||||
setFramework (framework: typeof FRONTEND_FRAMEWORKS[number]['type'] | null): void {
|
||||
const next = FRONTEND_FRAMEWORKS.find((x) => x.type === framework)
|
||||
|
||||
this.ctx.coreData.wizard.chosenFramework = framework
|
||||
|
||||
if (framework !== 'react' && framework !== 'vue') {
|
||||
return this.setBundler('webpack')
|
||||
if (next?.supportedBundlers?.length === 1) {
|
||||
this.setBundler(next?.supportedBundlers?.[0].type)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
const { chosenBundler } = this.ctx.coreData.wizard
|
||||
|
||||
// if the previous bundler was incompatible with the
|
||||
// new framework, we need to reset it
|
||||
if ((chosenBundler && !this.ctx.wizard.chosenFramework?.supportedBundlers.includes(chosenBundler))
|
||||
|| !['react', 'vue'].includes(prevFramework)) {
|
||||
return this.setBundler(null)
|
||||
}
|
||||
// new framework that was selected, we need to reset it
|
||||
const doesNotSupportChosenBundler = (chosenBundler && !new Set(
|
||||
this.ctx.wizard.chosenFramework?.supportedBundlers.map((x) => x.type) || [],
|
||||
).has(chosenBundler)) ?? false
|
||||
|
||||
return
|
||||
const prevFramework = this.ctx.coreData.wizard.chosenFramework || ''
|
||||
|
||||
if (doesNotSupportChosenBundler || !['react', 'vue'].includes(prevFramework)) {
|
||||
this.setBundler(null)
|
||||
}
|
||||
}
|
||||
|
||||
setBundler (bundler: NexusGenEnums['SupportedBundlers'] | null) {
|
||||
setBundler (bundler: Bundler | null) {
|
||||
this.ctx.coreData.wizard.chosenBundler = bundler
|
||||
|
||||
return this.data
|
||||
@@ -83,76 +89,40 @@ export class WizardActions {
|
||||
}
|
||||
|
||||
async initialize () {
|
||||
if (this.ctx.currentProject) {
|
||||
this.data.detectedFramework = null
|
||||
this.data.detectedBundler = null
|
||||
this.data.detectedLanguage = null
|
||||
|
||||
await this.detectLanguage()
|
||||
debug('detectedLanguage %s', this.data.detectedLanguage)
|
||||
this.data.chosenLanguage = this.data.detectedLanguage || 'js'
|
||||
|
||||
let hasPackageJson = true
|
||||
|
||||
try {
|
||||
await this.ctx.fs.access(path.join(this.ctx.currentProject, 'package.json'), this.ctx.fs.constants.R_OK)
|
||||
} catch (e) {
|
||||
debug('Could not read or find package.json: %O', e)
|
||||
hasPackageJson = false
|
||||
}
|
||||
const packageJson: {
|
||||
dependencies?: { [key: string]: string }
|
||||
devDependencies?: { [key: string]: string }
|
||||
} = hasPackageJson ? await this.ctx.fs.readJson(path.join(this.ctx.currentProject, 'package.json')) : {}
|
||||
|
||||
debug('packageJson %O', packageJson)
|
||||
const dependencies = [
|
||||
...Object.keys(packageJson.dependencies || {}),
|
||||
...Object.keys(packageJson.devDependencies || {}),
|
||||
]
|
||||
|
||||
this.detectFramework(dependencies)
|
||||
debug('detectedFramework %s', this.data.detectedFramework)
|
||||
this.detectBundler(dependencies)
|
||||
debug('detectedBundler %s', this.data.detectedBundler)
|
||||
|
||||
this.data.chosenFramework = this.data.detectedFramework || null
|
||||
this.data.chosenBundler = this.data.detectedBundler || null
|
||||
}
|
||||
}
|
||||
|
||||
private detectFramework (dependencies: string[]) {
|
||||
// Detect full featured frameworks
|
||||
if (dependencies.includes('next')) {
|
||||
this.ctx.wizardData.detectedFramework = 'nextjs'
|
||||
} else if (dependencies.includes('react-scripts')) {
|
||||
this.ctx.wizardData.detectedFramework = 'cra'
|
||||
} else if (dependencies.includes('nuxt')) {
|
||||
this.ctx.wizardData.detectedFramework = 'nuxtjs'
|
||||
} else if (dependencies.includes('@vue/cli-service')) {
|
||||
this.ctx.wizardData.detectedFramework = 'vuecli'
|
||||
} else if (dependencies.includes('react')) {
|
||||
this.ctx.wizardData.detectedFramework = 'react'
|
||||
} else if (dependencies.includes('vue')) {
|
||||
this.ctx.wizardData.detectedFramework = 'vue'
|
||||
}
|
||||
}
|
||||
|
||||
private detectBundler (dependencies: string[]) {
|
||||
const detectedFrameworkObject = FRONTEND_FRAMEWORKS.find((f) => f.type === this.ctx.wizardData.detectedFramework)
|
||||
|
||||
if (detectedFrameworkObject && detectedFrameworkObject.supportedBundlers.length === 1) {
|
||||
this.ctx.wizardData.detectedBundler = detectedFrameworkObject.supportedBundlers[0] ?? null
|
||||
|
||||
if (!this.ctx.currentProject) {
|
||||
return
|
||||
}
|
||||
|
||||
if (dependencies.includes('webpack')) {
|
||||
this.ctx.wizardData.detectedBundler = 'webpack'
|
||||
}
|
||||
this.ctx.update((coreData) => {
|
||||
coreData.wizard.detectedFramework = null
|
||||
coreData.wizard.detectedBundler = null
|
||||
coreData.wizard.detectedLanguage = null
|
||||
})
|
||||
|
||||
if (dependencies.includes('vite')) {
|
||||
this.ctx.wizardData.detectedBundler = 'vite'
|
||||
await this.detectLanguage()
|
||||
debug('detectedLanguage %s', this.data.detectedLanguage)
|
||||
this.data.chosenLanguage = this.data.detectedLanguage || 'js'
|
||||
|
||||
try {
|
||||
const detected = detect(await fs.readJson(path.join(this.ctx.currentProject, 'package.json')))
|
||||
|
||||
debug('detected %o', detected)
|
||||
|
||||
if (detected) {
|
||||
this.ctx.update((coreData) => {
|
||||
coreData.wizard.detectedFramework = detected.framework?.type ?? null
|
||||
coreData.wizard.chosenFramework = detected.framework?.type ?? null
|
||||
|
||||
if (!detected.framework?.supportedBundlers[0]) {
|
||||
return
|
||||
}
|
||||
|
||||
coreData.wizard.detectedBundler = detected.bundler || detected.framework.supportedBundlers[0].type
|
||||
coreData.wizard.chosenBundler = detected.bundler || detected.framework.supportedBundlers[0].type
|
||||
})
|
||||
}
|
||||
} catch {
|
||||
// Could not detect anything - no problem, no need to do anything.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +188,6 @@ export class WizardActions {
|
||||
this.scaffoldFixtures(),
|
||||
this.scaffoldSupport('component', chosenLanguage.type),
|
||||
this.getComponentIndexHtml({
|
||||
chosenBundler,
|
||||
chosenFramework,
|
||||
chosenLanguage,
|
||||
}),
|
||||
@@ -252,11 +221,7 @@ export class WizardActions {
|
||||
|
||||
assert(chosenFramework && chosenLanguage && chosenBundler)
|
||||
|
||||
return this.wizardGetConfigCodeComponent({
|
||||
chosenLanguage,
|
||||
chosenFramework,
|
||||
chosenBundler,
|
||||
})
|
||||
return chosenFramework.config[chosenLanguage.type](chosenBundler.type)
|
||||
}
|
||||
|
||||
return this.wizardGetConfigCodeE2E(language)
|
||||
@@ -315,28 +280,6 @@ export class WizardActions {
|
||||
return codeBlocks.join('\n')
|
||||
}
|
||||
|
||||
private wizardGetConfigCodeComponent (opts: WizardGetCodeComponent): string {
|
||||
const codeBlocks: string[] = []
|
||||
const { chosenBundler, chosenFramework, chosenLanguage } = opts
|
||||
|
||||
const requirePath = chosenFramework.defaultPackagePath ?? chosenBundler.package
|
||||
|
||||
codeBlocks.push(chosenLanguage.type === 'ts' ? `import { defineConfig } from 'cypress'` : `const { defineConfig } = require('cypress')`)
|
||||
codeBlocks.push(chosenLanguage.type === 'ts' ? `import { devServer } from '${requirePath}'` : `const { devServer } = require('${requirePath}')`)
|
||||
codeBlocks.push('')
|
||||
codeBlocks.push(chosenLanguage.type === 'ts' ? `export default defineConfig({` : `module.exports = defineConfig({`)
|
||||
codeBlocks.push(` // Component testing, ${chosenLanguage.name}, ${chosenFramework.name}, ${chosenBundler.name}`)
|
||||
|
||||
codeBlocks.push(` ${COMPONENT_SCAFFOLD_BODY({
|
||||
lang: chosenLanguage.type,
|
||||
configOptionsString: '{}',
|
||||
}).replace(/\n/g, '\n ')}`)
|
||||
|
||||
codeBlocks.push(`})\n`)
|
||||
|
||||
return codeBlocks.join('\n')
|
||||
}
|
||||
|
||||
private async getComponentIndexHtml (opts: WizardGetCodeComponent): Promise<NexusGenObjects['ScaffoldedFile']> {
|
||||
const [storybookInfo] = await Promise.all([
|
||||
this.ctx.storybook.loadStorybookInfo(),
|
||||
@@ -442,21 +385,6 @@ const E2E_SCAFFOLD_BODY = dedent`
|
||||
},
|
||||
`
|
||||
|
||||
interface ComponentScaffoldOpts {
|
||||
lang: CodeLanguageEnum
|
||||
configOptionsString: string
|
||||
specPattern?: string
|
||||
}
|
||||
|
||||
const COMPONENT_SCAFFOLD_BODY = (opts: ComponentScaffoldOpts) => {
|
||||
return dedent`
|
||||
component: {
|
||||
devServer,
|
||||
devServerConfig: ${opts.configOptionsString}
|
||||
},
|
||||
`
|
||||
}
|
||||
|
||||
const FIXTURE_DATA = {
|
||||
'name': 'Using fixtures to represent data',
|
||||
'email': 'hello@cypress.io',
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { DataContext } from '../DataContext'
|
||||
import type { ParsedPath } from 'path'
|
||||
import { camelCase, capitalize } from 'lodash'
|
||||
import type { CodeGenType } from '@packages/graphql/src/gen/nxs.gen'
|
||||
import type { CodeGenFramework } from '@packages/types'
|
||||
import type { CodeGenFramework } from '@packages/scaffold-config'
|
||||
import { CsfFile, readCsfOrMdx } from '@storybook/csf-tools'
|
||||
|
||||
interface CodeGenOptions {
|
||||
|
||||
@@ -325,6 +325,8 @@ export class ProjectLifecycleManager {
|
||||
setCurrentTestingType (testingType: TestingType | null) {
|
||||
this.ctx.update((d) => {
|
||||
d.currentTestingType = testingType
|
||||
d.wizard.chosenBundler = null
|
||||
d.wizard.chosenFramework = null
|
||||
})
|
||||
|
||||
if (this._currentTestingType === testingType) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BUNDLERS, FoundBrowser, Editor, Warning, AllowedState, AllModeOptions, TestingType, PACKAGE_MANAGERS, BrowserStatus, AuthStateName } from '@packages/types'
|
||||
import type { FoundBrowser, Editor, Warning, AllowedState, AllModeOptions, TestingType, BrowserStatus, PACKAGE_MANAGERS, AuthStateName } from '@packages/types'
|
||||
import type { Bundler, FRONTEND_FRAMEWORKS } from '@packages/scaffold-config'
|
||||
import type { NexusGenEnums, NexusGenObjects } from '@packages/graphql/src/gen/nxs.gen'
|
||||
import type { App, BrowserWindow } from 'electron'
|
||||
import type { ChildProcess } from 'child_process'
|
||||
@@ -61,14 +62,14 @@ export interface AppDataShape {
|
||||
}
|
||||
|
||||
export interface WizardDataShape {
|
||||
chosenBundler: NexusGenEnums['SupportedBundlers'] | null
|
||||
allBundlers: typeof BUNDLERS
|
||||
chosenFramework: NexusGenEnums['FrontendFrameworkEnum'] | null
|
||||
chosenBundler: Bundler | null
|
||||
chosenFramework: typeof FRONTEND_FRAMEWORKS[number]['type'] | null
|
||||
chosenLanguage: NexusGenEnums['CodeLanguageEnum']
|
||||
chosenManualInstall: boolean
|
||||
detectedLanguage: NexusGenEnums['CodeLanguageEnum'] | null
|
||||
detectedBundler: NexusGenEnums['SupportedBundlers'] | null
|
||||
detectedFramework: NexusGenEnums['FrontendFrameworkEnum'] | null
|
||||
detectedBundler: Bundler | null
|
||||
detectedFramework: typeof FRONTEND_FRAMEWORKS[number]['type'] | null
|
||||
__fakeInstalledPackagesForTesting: string[] | null
|
||||
}
|
||||
|
||||
export interface MigrationDataShape{
|
||||
@@ -168,9 +169,9 @@ export function makeCoreData (modeOptions: Partial<AllModeOptions> = {}): CoreDa
|
||||
chosenFramework: null,
|
||||
chosenLanguage: 'js',
|
||||
chosenManualInstall: false,
|
||||
allBundlers: BUNDLERS,
|
||||
detectedBundler: null,
|
||||
detectedFramework: null,
|
||||
__fakeInstalledPackagesForTesting: null,
|
||||
detectedLanguage: null,
|
||||
},
|
||||
migration: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import os from 'os'
|
||||
import { FrontendFramework, FRONTEND_FRAMEWORKS, ResolvedFromConfig, RESOLVED_FROM, FoundSpec } from '@packages/types'
|
||||
import type { ResolvedFromConfig, RESOLVED_FROM, FoundSpec } from '@packages/types'
|
||||
import { FrontendFramework, FRONTEND_FRAMEWORKS } from '@packages/scaffold-config'
|
||||
import { scanFSForAvailableDependency } from 'create-cypress-tests'
|
||||
import minimatch from 'minimatch'
|
||||
import { debounce, isEqual } from 'lodash'
|
||||
@@ -261,7 +262,7 @@ export class ProjectDataSource {
|
||||
|
||||
private guessFramework (projectRoot: string) {
|
||||
const guess = FRONTEND_FRAMEWORKS.find((framework) => {
|
||||
const lookingForDeps = (framework.deps as readonly string[]).reduce(
|
||||
const lookingForDeps = framework.detectors.map((x) => x.dependency).reduce(
|
||||
(acc, dep) => ({ ...acc, [dep]: '*' }),
|
||||
{},
|
||||
)
|
||||
|
||||
@@ -1,57 +1,84 @@
|
||||
import Debug from 'debug'
|
||||
import { BUNDLERS, CODE_LANGUAGES, FRONTEND_FRAMEWORKS, PACKAGES_DESCRIPTIONS } from '@packages/types'
|
||||
import type { NexusGenObjects } from '@packages/graphql/src/gen/nxs.gen'
|
||||
import { CODE_LANGUAGES } from '@packages/types'
|
||||
import {
|
||||
BUNDLERS,
|
||||
DEPENDENCIES,
|
||||
FRONTEND_FRAMEWORKS,
|
||||
} from '@packages/scaffold-config'
|
||||
import type { DataContext } from '..'
|
||||
import path from 'path'
|
||||
import resolve from 'resolve-from'
|
||||
import assert from 'assert'
|
||||
|
||||
const debug = Debug('cypress:data-context:wizard-data-source')
|
||||
|
||||
interface PackageToInstall {
|
||||
name: typeof DEPENDENCIES[number]['name']
|
||||
installer: typeof DEPENDENCIES[number]['installer']
|
||||
description: typeof DEPENDENCIES[number]['description']
|
||||
package: typeof DEPENDENCIES[number]['package']
|
||||
}
|
||||
|
||||
export class WizardDataSource {
|
||||
constructor (private ctx: DataContext) {}
|
||||
|
||||
async packagesToInstall (): Promise<Array<NexusGenObjects['WizardNpmPackage']> | null> {
|
||||
async packagesToInstall (): Promise<PackageToInstall[] | null> {
|
||||
if (!this.chosenFramework || !this.chosenBundler) {
|
||||
return null
|
||||
}
|
||||
|
||||
const packages = [
|
||||
{
|
||||
name: this.chosenFramework.name as string,
|
||||
description: PACKAGES_DESCRIPTIONS[this.chosenFramework.package],
|
||||
package: this.chosenFramework.package,
|
||||
},
|
||||
{
|
||||
name: this.chosenBundler.name as string,
|
||||
description: PACKAGES_DESCRIPTIONS[this.chosenBundler.package],
|
||||
package: this.chosenBundler.package as string,
|
||||
},
|
||||
]
|
||||
const packages: PackageToInstall[] = [...this.chosenFramework.packages]
|
||||
|
||||
// find the matching dev server
|
||||
// vite -> @cypress/vite-dev-server
|
||||
// webpack -> @cypress/webpack-dev-server
|
||||
const bundler = BUNDLERS.find((x) => x.type === this.chosenBundler?.type)
|
||||
|
||||
assert(bundler)
|
||||
packages.push(...bundler.dependencies)
|
||||
|
||||
const storybookInfo = await this.ctx.storybook.loadStorybookInfo()
|
||||
const { storybookDep } = this.chosenFramework
|
||||
|
||||
if (storybookInfo && storybookDep) {
|
||||
packages.push({
|
||||
name: storybookDep,
|
||||
description: PACKAGES_DESCRIPTIONS[storybookDep],
|
||||
package: storybookDep,
|
||||
})
|
||||
packages.push(storybookDep)
|
||||
}
|
||||
|
||||
return packages
|
||||
}
|
||||
|
||||
async resolvePackagesToInstall (): Promise<string[]> {
|
||||
async installDependenciesCommand () {
|
||||
const commands = {
|
||||
'npm': 'npm install -D',
|
||||
'pnpm': 'pnpm install -D',
|
||||
'yarn': 'yarn add -D',
|
||||
}
|
||||
|
||||
let depsToInstall = (await this.ctx.wizard.packagesToInstall() ?? [])
|
||||
|
||||
if (!depsToInstall?.length) {
|
||||
return null
|
||||
}
|
||||
|
||||
const deps = depsToInstall.map((pack) => `${pack.installer}`).join(' ')
|
||||
|
||||
return `${commands[this.ctx.coreData.packageManager ?? 'npm']} ${deps}`
|
||||
}
|
||||
|
||||
async installedPackages (): Promise<string[]> {
|
||||
if (this.ctx.coreData.wizard.__fakeInstalledPackagesForTesting) {
|
||||
return this.ctx.coreData.wizard.__fakeInstalledPackagesForTesting
|
||||
}
|
||||
|
||||
const packagesInitial = await this.packagesToInstall() || []
|
||||
|
||||
if (!this.ctx.currentProject) {
|
||||
throw Error('currentProject is not defined')
|
||||
}
|
||||
|
||||
debug('packages to install: %O', packagesInitial)
|
||||
debug('packages to install: %O in %s', packagesInitial, this.ctx.currentProject)
|
||||
|
||||
const installedPackages: (string|null)[] = packagesInitial.map((p) => {
|
||||
const installedPackages: Array<string | null> = packagesInitial.map((p) => {
|
||||
if (this.ctx.currentProject) {
|
||||
debug('package checked: %s', p.package)
|
||||
|
||||
@@ -62,6 +89,8 @@ export class WizardDataSource {
|
||||
// `package.json`
|
||||
const packageJsonPath = path.join(p.package, 'package.json')
|
||||
|
||||
debug('package.json path: %s', packageJsonPath)
|
||||
|
||||
try {
|
||||
resolve(this.ctx.currentProject, packageJsonPath)
|
||||
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
import { parse } from '@babel/parser'
|
||||
import { graphqlSchema } from '@packages/graphql/src/schema'
|
||||
import { FRONTEND_FRAMEWORKS } from '@packages/types'
|
||||
import { FRONTEND_FRAMEWORKS } from '@packages/scaffold-config'
|
||||
import { expect } from 'chai'
|
||||
import dedent from 'dedent'
|
||||
import fs from 'fs-extra'
|
||||
import path from 'path'
|
||||
import sinon from 'sinon'
|
||||
import { DataContext } from '../../../src'
|
||||
import { AppApiShape, AuthApiShape, ElectronApiShape, LocalSettingsApiShape, ProjectApiShape } from '../../../src/actions'
|
||||
import {
|
||||
Action, codeGenerator, CodeGenResult, CodeGenResults,
|
||||
} from '../../../src/codegen/code-generator'
|
||||
import { SpecOptions } from '../../../src/codegen/spec-options'
|
||||
import templates from '../../../src/codegen/templates'
|
||||
import { InjectedConfigApi } from '../../../src/data'
|
||||
import { ErrorApiShape } from '../../../src/DataContext'
|
||||
import { BrowserApiShape } from '../../../src/sources'
|
||||
import { createTestDataContext } from '../helper'
|
||||
|
||||
const tmpPath = path.join(__dirname, 'tmp/test-code-gen')
|
||||
|
||||
@@ -29,21 +25,7 @@ describe('code-generator', () => {
|
||||
let ctx: DataContext
|
||||
|
||||
beforeEach(async () => {
|
||||
ctx = new DataContext({
|
||||
schema: graphqlSchema,
|
||||
mode: 'run',
|
||||
modeOptions: {},
|
||||
appApi: {} as AppApiShape,
|
||||
localSettingsApi: {} as LocalSettingsApiShape,
|
||||
authApi: {} as AuthApiShape,
|
||||
errorApi: {} as ErrorApiShape,
|
||||
configApi: {
|
||||
getServerPluginHandlers: () => [],
|
||||
} as InjectedConfigApi,
|
||||
projectApi: {} as ProjectApiShape,
|
||||
electronApi: {} as ElectronApiShape,
|
||||
browserApi: {} as BrowserApiShape,
|
||||
})
|
||||
ctx = createTestDataContext()
|
||||
|
||||
ctx.update((s) => {
|
||||
s.currentProject = tmpPath
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
import 'mocha'
|
||||
import { e2eProjectDirs } from '@packages/frontend-shared/cypress/e2e/support/e2eProjectDirs'
|
||||
import Fixtures from '@tooling/system-tests/lib/fixtures'
|
||||
import { DataContext, DataContextConfig } from '../../src'
|
||||
import { graphqlSchema } from '@packages/graphql/src/schema'
|
||||
import type { BrowserApiShape } from '../../src/sources/BrowserDataSource'
|
||||
import type { AppApiShape, AuthApiShape, ElectronApiShape, LocalSettingsApiShape, ProjectApiShape } from '../../src/actions'
|
||||
import { InjectedConfigApi } from '../../src/data'
|
||||
|
||||
export async function scaffoldMigrationProject (project: typeof e2eProjectDirs[number]) {
|
||||
Fixtures.removeProject(project)
|
||||
@@ -10,3 +15,20 @@ export async function scaffoldMigrationProject (project: typeof e2eProjectDirs[n
|
||||
|
||||
return Fixtures.projectPath(project)
|
||||
}
|
||||
|
||||
export function createTestDataContext (mode: DataContextConfig['mode'] = 'run') {
|
||||
return new DataContext({
|
||||
schema: graphqlSchema,
|
||||
mode,
|
||||
modeOptions: {},
|
||||
appApi: {} as AppApiShape,
|
||||
localSettingsApi: {} as LocalSettingsApiShape,
|
||||
authApi: {} as AuthApiShape,
|
||||
configApi: {
|
||||
getServerPluginHandlers: () => [],
|
||||
} as InjectedConfigApi,
|
||||
projectApi: {} as ProjectApiShape,
|
||||
electronApi: {} as ElectronApiShape,
|
||||
browserApi: {} as BrowserApiShape,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { expect } from 'chai'
|
||||
import { graphqlSchema } from '@packages/graphql/src/schema'
|
||||
import os from 'os'
|
||||
import sinon from 'sinon'
|
||||
import { DataContext } from '../../../src'
|
||||
import { AppApiShape, AuthApiShape, ElectronApiShape, LocalSettingsApiShape, ProjectApiShape } from '../../../src/actions'
|
||||
import { InjectedConfigApi } from '../../../src/data'
|
||||
import { ErrorApiShape } from '../../../src/DataContext'
|
||||
import { BrowserApiShape, VersionsDataSource } from '../../../src/sources'
|
||||
import { VersionsDataSource } from '../../../src/sources'
|
||||
import { createTestDataContext } from '../helper'
|
||||
|
||||
const pkg = require('@packages/root')
|
||||
const nmi = require('node-machine-id')
|
||||
@@ -21,21 +18,7 @@ describe('VersionsDataSource', () => {
|
||||
let currentCypressVersion: string = pkg.version
|
||||
|
||||
before(() => {
|
||||
ctx = new DataContext({
|
||||
schema: graphqlSchema,
|
||||
mode: 'open',
|
||||
modeOptions: {},
|
||||
appApi: {} as AppApiShape,
|
||||
localSettingsApi: {} as LocalSettingsApiShape,
|
||||
authApi: {} as AuthApiShape,
|
||||
errorApi: {} as ErrorApiShape,
|
||||
configApi: {
|
||||
getServerPluginHandlers: () => [],
|
||||
} as InjectedConfigApi,
|
||||
projectApi: {} as ProjectApiShape,
|
||||
electronApi: {} as ElectronApiShape,
|
||||
browserApi: {} as BrowserApiShape,
|
||||
})
|
||||
ctx = createTestDataContext('open')
|
||||
|
||||
ctx.coreData.currentTestingType = 'e2e'
|
||||
|
||||
|
||||
119
packages/data-context/test/unit/sources/WizardDataSource.spec.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { expect } from 'chai'
|
||||
import path from 'path'
|
||||
import { e2eProjectDirs } from '@packages/frontend-shared/cypress/e2e/support/e2eProjectDirs'
|
||||
import { createTestDataContext } from '../helper'
|
||||
|
||||
function getCurrentProject (project: typeof e2eProjectDirs[number]) {
|
||||
return path.join(__dirname, '..', '..', '..', '..', '..', 'system-tests', 'projects', project)
|
||||
}
|
||||
|
||||
describe('packagesToInstall', () => {
|
||||
it('create-react-app-unconfigured', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
ctx.coreData.currentProject = getCurrentProject('create-react-app-unconfigured')
|
||||
ctx.coreData.wizard.chosenFramework = 'crav5'
|
||||
ctx.coreData.wizard.chosenBundler = 'webpack5'
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D @cypress/react@^5.0.0 webpack@^5.0.0 @cypress/webpack-dev-server@latest webpack-dev-server@^4.0.0 html-webpack-plugin@^5.0.0`)
|
||||
})
|
||||
|
||||
it('vueclivue2-unconfigured', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
ctx.coreData.currentProject = getCurrentProject('vueclivue2-unconfigured')
|
||||
ctx.coreData.wizard.chosenFramework = 'vueclivue2'
|
||||
ctx.coreData.wizard.chosenBundler = 'webpack4'
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D @cypress/vue@^2.0.0 webpack@^4.0.0 @cypress/webpack-dev-server@latest webpack-dev-server@^4.0.0 html-webpack-plugin@^4.0.0`)
|
||||
})
|
||||
|
||||
it('vueclivue3-unconfigured', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
ctx.coreData.currentProject = getCurrentProject('vueclivue3-unconfigured')
|
||||
ctx.coreData.wizard.chosenFramework = 'vueclivue3'
|
||||
ctx.coreData.wizard.chosenBundler = 'webpack4'
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D @cypress/vue@^3.0.0 webpack@^4.0.0 @cypress/webpack-dev-server@latest webpack-dev-server@^4.0.0 html-webpack-plugin@^4.0.0`)
|
||||
})
|
||||
|
||||
it('regular react project with vite', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
ctx.coreData.currentProject = getCurrentProject('react-vite-ts-unconfigured')
|
||||
ctx.coreData.wizard.chosenFramework = 'react'
|
||||
ctx.coreData.wizard.chosenBundler = 'vite'
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D @cypress/react@^5.0.0 @cypress/vite-dev-server@latest`)
|
||||
})
|
||||
|
||||
it('regular vue project with vite', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
ctx.coreData.currentProject = getCurrentProject('vue3-vite-ts-unconfigured')
|
||||
ctx.coreData.wizard.chosenFramework = 'vue3'
|
||||
ctx.coreData.wizard.chosenBundler = 'vite'
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D @cypress/vue@^3.0.0 @cypress/vite-dev-server@latest`)
|
||||
})
|
||||
|
||||
it('nextjs-unconfigured', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
ctx.coreData.currentProject = getCurrentProject('nextjs-unconfigured')
|
||||
ctx.coreData.wizard.chosenFramework = 'nextjs'
|
||||
ctx.coreData.wizard.chosenBundler = 'webpack4'
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq(`npm install -D @cypress/react@^5.0.0 webpack@^4.0.0 @cypress/webpack-dev-server@latest webpack-dev-server@^4.0.0 html-webpack-plugin@^4.0.0`)
|
||||
})
|
||||
|
||||
it('nuxtjs-vue2-unconfigured', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
ctx.coreData.currentProject = getCurrentProject('nuxtjs-vue2-unconfigured')
|
||||
ctx.coreData.wizard.chosenFramework = 'nuxtjs'
|
||||
ctx.coreData.wizard.chosenBundler = 'webpack4'
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq('npm install -D @cypress/vue@^2.0.0 webpack@^4.0.0 @cypress/webpack-dev-server@latest webpack-dev-server@^4.0.0 html-webpack-plugin@^4.0.0')
|
||||
})
|
||||
|
||||
it('pristine-with-e2e-testing-and-storybook', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
ctx.coreData.currentProject = getCurrentProject('pristine-with-e2e-testing-and-storybook')
|
||||
ctx.coreData.wizard.chosenFramework = 'react'
|
||||
ctx.coreData.wizard.chosenBundler = 'webpack4'
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.eq('npm install -D @cypress/react@^5.0.0 @cypress/webpack-dev-server@latest webpack-dev-server@^4.0.0 html-webpack-plugin@^4.0.0 @storybook/testing-react@latest')
|
||||
})
|
||||
|
||||
it('framework and bundler are undefined', async () => {
|
||||
const ctx = createTestDataContext()
|
||||
|
||||
// this should never happen!
|
||||
ctx.coreData.currentProject = getCurrentProject('pristine-with-e2e-testing-and-storybook')
|
||||
ctx.coreData.wizard.chosenFramework = undefined
|
||||
ctx.coreData.wizard.chosenBundler = undefined
|
||||
|
||||
const actual = await ctx.wizard.installDependenciesCommand()
|
||||
|
||||
expect(actual).to.be.null
|
||||
})
|
||||
})
|
||||
@@ -13,6 +13,8 @@ export const e2eProjectDirs = [
|
||||
'config-with-short-timeout',
|
||||
'config-with-ts',
|
||||
'cookies',
|
||||
'create-react-app-configured',
|
||||
'create-react-app-unconfigured',
|
||||
'cypress-in-cypress',
|
||||
'default-layout',
|
||||
'devServer-dynamic-import',
|
||||
@@ -49,6 +51,8 @@ export const e2eProjectDirs = [
|
||||
'multiple-config-files-with-json',
|
||||
'multiple-support-files',
|
||||
'multiple-task-registrations',
|
||||
'nextjs-configured',
|
||||
'nextjs-unconfigured',
|
||||
'no-scaffolding',
|
||||
'no-server',
|
||||
'no-specs',
|
||||
@@ -56,6 +60,8 @@ export const e2eProjectDirs = [
|
||||
'no-specs-no-storybook',
|
||||
'non-existent-spec',
|
||||
'non-proxied',
|
||||
'nuxtjs-vue2-configured',
|
||||
'nuxtjs-vue2-unconfigured',
|
||||
'odd-directory-name',
|
||||
'plugin-after-screenshot',
|
||||
'plugin-after-spec-deletes-video',
|
||||
@@ -88,7 +94,10 @@ export const e2eProjectDirs = [
|
||||
'pristine-with-e2e-testing',
|
||||
'pristine-with-e2e-testing-and-storybook',
|
||||
'pristine-yarn',
|
||||
'react-app-webpack-5-unconfigured',
|
||||
'react-code-gen',
|
||||
'react-vite-ts-configured',
|
||||
'react-vite-ts-unconfigured',
|
||||
'read-only-project-root',
|
||||
'record',
|
||||
'remote-debugging-disconnect',
|
||||
@@ -120,6 +129,12 @@ export const e2eProjectDirs = [
|
||||
'unify-plugin-errors',
|
||||
'various-file-types',
|
||||
'vite-ct',
|
||||
'vue3-vite-ts-configured',
|
||||
'vue3-vite-ts-unconfigured',
|
||||
'vueclivue2-configured',
|
||||
'vueclivue2-unconfigured',
|
||||
'vueclivue3-configured',
|
||||
'vueclivue3-unconfigured',
|
||||
'webpack-preprocessor',
|
||||
'webpack-preprocessor-awesome-typescript-loader',
|
||||
'webpack-preprocessor-ts-loader',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { CodegenTypeMap, Wizard } from '../generated/test-graphql-types.gen'
|
||||
import { BUNDLERS, CODE_LANGUAGES, FRONTEND_FRAMEWORKS, PACKAGES_DESCRIPTIONS } from '@packages/types/src/constants'
|
||||
import { CODE_LANGUAGES } from '@packages/types/src/constants'
|
||||
import { BUNDLERS, CYPRESS_REACT_LATEST, CYPRESS_WEBPACK, FRONTEND_FRAMEWORKS } from '@packages/scaffold-config'
|
||||
import type { MaybeResolver } from './clientTestUtils'
|
||||
import { testNodeId } from './clientTestUtils'
|
||||
|
||||
@@ -14,18 +15,15 @@ export const allBundlers = BUNDLERS.map((bundler, idx) => {
|
||||
|
||||
export const stubWizard: MaybeResolver<Wizard> = {
|
||||
__typename: 'Wizard',
|
||||
installDependenciesCommand: 'npm install -D @cypress/react @cypress/webpack-dev-server',
|
||||
packagesToInstall: [
|
||||
{
|
||||
...testNodeId('WizardNpmPackage'),
|
||||
description: PACKAGES_DESCRIPTIONS['@cypress/react'],
|
||||
name: '@cypress/react',
|
||||
package: '@cypress/react',
|
||||
...CYPRESS_REACT_LATEST,
|
||||
},
|
||||
{
|
||||
...testNodeId('WizardNpmPackage'),
|
||||
description: PACKAGES_DESCRIPTIONS['@cypress/webpack-dev-server'],
|
||||
name: '@cypress/webpack-dev-server',
|
||||
package: '@cypress/webpack-dev-server',
|
||||
...CYPRESS_WEBPACK,
|
||||
},
|
||||
],
|
||||
allBundlers,
|
||||
|
||||
@@ -511,12 +511,15 @@ enum FrontendFrameworkCategoryEnum {
|
||||
}
|
||||
|
||||
enum FrontendFrameworkEnum {
|
||||
cra
|
||||
crav4
|
||||
crav5
|
||||
nextjs
|
||||
nuxtjs
|
||||
react
|
||||
vue
|
||||
vuecli
|
||||
vue2
|
||||
vue3
|
||||
vueclivue2
|
||||
vueclivue3
|
||||
}
|
||||
|
||||
"""Error from generated spec"""
|
||||
@@ -1052,6 +1055,13 @@ type Storybook implements Node {
|
||||
|
||||
"""The bundlers that we can use with Cypress"""
|
||||
enum SupportedBundlers {
|
||||
vite
|
||||
webpack4
|
||||
webpack5
|
||||
}
|
||||
|
||||
"""The packages for bundlers that we can use with Cypress"""
|
||||
enum SupportedPackage {
|
||||
vite
|
||||
webpack
|
||||
}
|
||||
@@ -1113,6 +1123,9 @@ type Wizard {
|
||||
"""All of the component testing frameworks to choose from"""
|
||||
frameworks: [WizardFrontendFramework!]!
|
||||
|
||||
"""Command to install required command"""
|
||||
installDependenciesCommand: String
|
||||
|
||||
"""The list of packages to install that are currently installed"""
|
||||
installedPackages: [String!]
|
||||
language: WizardCodeLanguage
|
||||
@@ -1137,8 +1150,8 @@ type WizardBundler implements Node {
|
||||
"""Display name of the bundler"""
|
||||
name: String!
|
||||
|
||||
"""Package to install associated with the bundler"""
|
||||
package: String!
|
||||
"""Name of package on npm"""
|
||||
package: SupportedPackage!
|
||||
|
||||
"""The name of the framework"""
|
||||
type: SupportedBundlers!
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { BUNDLERS, CODE_LANGUAGES, FRONTEND_FRAMEWORKS } from '@packages/types'
|
||||
import { CODE_LANGUAGES } from '@packages/types'
|
||||
import { BUNDLERS, FRONTEND_FRAMEWORKS } from '@packages/scaffold-config'
|
||||
import { enumType } from 'nexus'
|
||||
|
||||
export const SupportedBundlerEnum = enumType({
|
||||
@@ -7,6 +8,12 @@ export const SupportedBundlerEnum = enumType({
|
||||
members: BUNDLERS.map((t) => t.type),
|
||||
})
|
||||
|
||||
export const SupportedPackageEnum = enumType({
|
||||
name: 'SupportedPackage',
|
||||
description: 'The packages for bundlers that we can use with Cypress',
|
||||
members: BUNDLERS.map((t) => t.package),
|
||||
})
|
||||
|
||||
export const WizardConfigFileStatusEnum = enumType({
|
||||
name: 'WizardConfigFileStatusEnum',
|
||||
members: ['changes', 'valid', 'skipped', 'error'],
|
||||
|
||||
@@ -2,7 +2,8 @@ import { WizardBundler } from './gql-WizardBundler'
|
||||
import { WizardFrontendFramework } from './gql-WizardFrontendFramework'
|
||||
import { WizardNpmPackage } from './gql-WizardNpmPackage'
|
||||
import { objectType } from 'nexus'
|
||||
import { BUNDLERS, CODE_LANGUAGES, FRONTEND_FRAMEWORKS } from '@packages/types'
|
||||
import { CODE_LANGUAGES } from '@packages/types'
|
||||
import { BUNDLERS, FRONTEND_FRAMEWORKS } from '@packages/scaffold-config'
|
||||
import { WizardCodeLanguage } from './gql-WizardCodeLanguage'
|
||||
|
||||
export const Wizard = objectType({
|
||||
@@ -50,7 +51,12 @@ export const Wizard = objectType({
|
||||
|
||||
t.list.nonNull.string('installedPackages', {
|
||||
description: 'The list of packages to install that are currently installed',
|
||||
resolve: (source, args, ctx) => ctx.wizard.resolvePackagesToInstall(),
|
||||
resolve: (source, args, ctx) => ctx.wizard.installedPackages(),
|
||||
})
|
||||
|
||||
t.string('installDependenciesCommand', {
|
||||
description: 'Command to install required command',
|
||||
resolve: (source, args, ctx) => ctx.wizard.installDependenciesCommand(),
|
||||
})
|
||||
},
|
||||
sourceType: {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { objectType } from 'nexus'
|
||||
import { SupportedBundlerEnum } from '../enumTypes'
|
||||
import { SupportedBundlerEnum, SupportedPackageEnum } from '../enumTypes'
|
||||
|
||||
export const WizardBundler = objectType({
|
||||
name: 'WizardBundler',
|
||||
description: 'Wizard bundler',
|
||||
node: 'name',
|
||||
node: 'type',
|
||||
definition (t) {
|
||||
t.boolean('isSelected', {
|
||||
description: 'Whether this is the selected framework bundler',
|
||||
@@ -25,8 +25,9 @@ export const WizardBundler = objectType({
|
||||
description: 'Display name of the bundler',
|
||||
})
|
||||
|
||||
t.nonNull.string('package', {
|
||||
description: 'Package to install associated with the bundler',
|
||||
t.nonNull.field('package', {
|
||||
type: SupportedPackageEnum,
|
||||
description: 'Name of package on npm',
|
||||
})
|
||||
},
|
||||
})
|
||||
|
||||
@@ -35,12 +35,13 @@ export const WizardFrontendFramework = objectType({
|
||||
type: WizardBundler,
|
||||
description: 'All of the supported bundlers for this framework',
|
||||
resolve: (source, args, ctx) => {
|
||||
return ctx.wizardData.allBundlers.filter((b) => source.supportedBundlers.some((x) => x === b.type))
|
||||
return [...source.supportedBundlers]
|
||||
},
|
||||
})
|
||||
},
|
||||
|
||||
sourceType: {
|
||||
module: '@packages/types',
|
||||
module: '@packages/scaffold-config',
|
||||
export: 'FrontendFramework',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
import { FRONTEND_FRAMEWORKS, BUNDLERS, CODE_LANGUAGES, PACKAGES_DESCRIPTIONS } from '@packages/types/src/constants'
|
||||
import { BUNDLERS, FRONTEND_FRAMEWORKS, AllPackagePackages } from '@packages/scaffold-config/src'
|
||||
import { CODE_LANGUAGES } from '@packages/types/src'
|
||||
|
||||
function fakeInstalledDeps () {
|
||||
cy.withCtx(async (ctx) => {
|
||||
const deps = (await ctx.wizard.packagesToInstall() ?? []).map((x) => x.package)
|
||||
|
||||
ctx.update((coreData) => {
|
||||
coreData.wizard.__fakeInstalledPackagesForTesting = deps
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
describe('Launchpad: Setup Project', () => {
|
||||
function scaffoldAndOpenProject (name: Parameters<typeof cy.scaffoldProject>[0], args?: Parameters<typeof cy.openProject>[1]) {
|
||||
@@ -349,7 +360,7 @@ describe('Launchpad: Setup Project', () => {
|
||||
cy.findByText('Confirm the front-end framework and bundler used in your project.')
|
||||
|
||||
cy.findByRole('button', { name: 'Front-end Framework Pick a framework' }).click()
|
||||
cy.findByRole('option', { name: 'Create React App' }).click()
|
||||
cy.findByRole('option', { name: 'Create React App (v4)' }).click()
|
||||
|
||||
cy.get('[data-testid="select-bundler"').should('not.exist')
|
||||
cy.findByRole('button', { name: 'Next Step' }).should('not.have.disabled')
|
||||
@@ -363,42 +374,23 @@ describe('Launchpad: Setup Project', () => {
|
||||
cy.findByRole('button', { name: 'Next Step' }).should('have.disabled')
|
||||
|
||||
cy.findByRole('button', { name: 'Bundler(Dev Server) Pick a bundler' }).click()
|
||||
cy.findByRole('option', { name: 'Webpack' }).click()
|
||||
cy.findByRole('option', { name: 'Webpack (v4)' }).click()
|
||||
cy.findByRole('button', { name: 'Next Step' }).should('not.have.disabled')
|
||||
|
||||
cy.findByRole('button', { name: 'Front-end Framework React.js' }).click()
|
||||
cy.findByRole('option', { name: 'Create React App' }).click()
|
||||
cy.findByRole('option', { name: 'Create React App (v4)' }).click()
|
||||
cy.findByRole('button', { name: 'Bundler(Dev Server) Webpack' }).should('not.exist')
|
||||
cy.findByRole('button', { name: 'Next Step' }).should('not.have.disabled')
|
||||
|
||||
cy.findByRole('button', { name: 'TypeScript' }).click()
|
||||
|
||||
let calls = 0
|
||||
|
||||
// simulate progressive installation of modules
|
||||
cy.intercept('query-Wizard_InstalledPackages', (req) => {
|
||||
req.reply({ data: {
|
||||
wizard: {
|
||||
__typename: 'Wizard',
|
||||
installedPackages: ++calls <= 2 ? [] :
|
||||
calls <= 3 ? ['@cypress/react'] :
|
||||
['@cypress/react', '@cypress/webpack-dev-server'],
|
||||
},
|
||||
} })
|
||||
}).as('InstalledPackages')
|
||||
|
||||
cy.findByRole('button', { name: 'Next Step' }).click()
|
||||
cy.findByRole('button', { name: 'Waiting for you to install the dependencies...' })
|
||||
|
||||
cy.wait('@InstalledPackages')
|
||||
fakeInstalledDeps()
|
||||
|
||||
cy.contains('li', '@cypress/react').findByLabelText('installed').should('be.visible')
|
||||
|
||||
cy.wait('@InstalledPackages')
|
||||
cy.contains('li', '@cypress/webpack-dev-server').findByLabelText('installed').should('be.visible').then(() => {
|
||||
expect(calls).to.eq(4)
|
||||
})
|
||||
|
||||
cy.findByRole('button', { name: 'Continue' }).click()
|
||||
|
||||
cy.get('[data-cy=changes]').within(() => {
|
||||
@@ -552,196 +544,149 @@ describe('Launchpad: Setup Project', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('project that has not been configured for component testing', () => {
|
||||
beforeEach(() => {
|
||||
// simulate installation of modules
|
||||
cy.intercept('query-Wizard_InstalledPackages', (req) => {
|
||||
req.reply({ data: {
|
||||
wizard: {
|
||||
__typename: 'Wizard',
|
||||
installedPackages: [
|
||||
'@cypress/react',
|
||||
'@cypress/vue',
|
||||
'@cypress/webpack-dev-server',
|
||||
'@cypress/vite-dev-server',
|
||||
'@storybook/testing-react',
|
||||
'@storybook/testing-vue',
|
||||
'@storybook/testing-vue3',
|
||||
],
|
||||
},
|
||||
} })
|
||||
}).as('InstalledPackages')
|
||||
})
|
||||
beforeEach(() => {
|
||||
fakeInstalledDeps()
|
||||
})
|
||||
|
||||
it('shows the first setup page for configuration when selecting component tests', () => {
|
||||
scaffoldAndOpenProject('pristine-with-e2e-testing')
|
||||
cy.visitLaunchpad()
|
||||
const hasStorybookPermutations = [false, true]
|
||||
|
||||
verifyWelcomePage({ e2eIsConfigured: true, ctIsConfigured: false })
|
||||
FRONTEND_FRAMEWORKS.forEach((framework) => {
|
||||
hasStorybookPermutations.forEach((hasStorybookDep) => {
|
||||
framework.supportedBundlers.forEach((testBundler) => {
|
||||
const bundler = BUNDLERS.find((b) => b.type === testBundler.type)
|
||||
|
||||
cy.get('[data-cy-testingtype="component"]').click()
|
||||
if (!bundler) {
|
||||
throw new Error(`${framework.name} claims to support the bundler, ${testBundler}, however it is not a valid Cypress bundler.`)
|
||||
}
|
||||
|
||||
cy.get('h1').should('contain', 'Project Setup')
|
||||
cy.contains('Confirm the front-end framework and bundler used in your project.')
|
||||
CODE_LANGUAGES.forEach((lang) => {
|
||||
let testTitle = `can setup ${framework.name} + ${lang.name}`
|
||||
|
||||
cy.findByRole('button', {
|
||||
name: 'Front-end Framework Pick a framework',
|
||||
expanded: false,
|
||||
})
|
||||
.should('have.attr', 'aria-haspopup', 'true')
|
||||
|
||||
cy.findByRole('button', { name: 'Next Step' }).should('have.disabled')
|
||||
|
||||
cy.findByRole('button', { name: 'Back' }).click()
|
||||
|
||||
verifyWelcomePage({ e2eIsConfigured: true, ctIsConfigured: false })
|
||||
})
|
||||
|
||||
const hasStorybookPermutations = [false, true]
|
||||
|
||||
FRONTEND_FRAMEWORKS.forEach((framework) => {
|
||||
hasStorybookPermutations.forEach((hasStorybookDep) => {
|
||||
framework.supportedBundlers.forEach((testBundler) => {
|
||||
const bundler = BUNDLERS.find((b) => b.type === testBundler)
|
||||
|
||||
if (!bundler) {
|
||||
throw new Error(`${framework.name} claims to support the bundler, ${testBundler}, however it is not a valid Cypress bundler.`)
|
||||
if (framework.supportedBundlers.length > 1) {
|
||||
testTitle = `can setup ${framework.name} + ${bundler.name} + ${lang.name}`
|
||||
}
|
||||
|
||||
CODE_LANGUAGES.forEach((lang) => {
|
||||
let testTitle = `can setup ${framework.name} + ${lang.name}`
|
||||
if (hasStorybookDep) {
|
||||
testTitle += ` for project using Storybook`
|
||||
}
|
||||
|
||||
it(testTitle, () => {
|
||||
scaffoldAndOpenProject(hasStorybookDep ? 'pristine-with-e2e-testing-and-storybook' : 'pristine-with-e2e-testing')
|
||||
cy.withCtx((ctx) => {
|
||||
ctx.actions.file.writeFileInProject('yarn.lock', '# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.')
|
||||
})
|
||||
|
||||
cy.visitLaunchpad()
|
||||
|
||||
verifyWelcomePage({ e2eIsConfigured: true, ctIsConfigured: false })
|
||||
|
||||
cy.get('[data-cy-testingtype="component"]').click()
|
||||
|
||||
cy.log('Choose project setup')
|
||||
cy.get('h1').should('contain', 'Project Setup')
|
||||
cy.contains('Confirm the front-end framework and bundler used in your project.')
|
||||
|
||||
cy.findByRole('button', { name: 'Next Step' })
|
||||
.should('have.disabled')
|
||||
.as('nextStepButton')
|
||||
|
||||
cy.findByRole('button', {
|
||||
name: 'Front-end Framework Pick a framework',
|
||||
expanded: false,
|
||||
})
|
||||
.click()
|
||||
|
||||
cy.findByRole('option', { name: framework.name }).click()
|
||||
cy.findByRole('button', { name: `Front-end Framework ${framework.name}` }) // ensure selected option updates
|
||||
|
||||
if (framework.supportedBundlers.length > 1) {
|
||||
testTitle = `can setup ${framework.name} + ${bundler.name} + ${lang.name}`
|
||||
}
|
||||
|
||||
if (hasStorybookDep) {
|
||||
testTitle += ` for project using Storybook`
|
||||
}
|
||||
|
||||
it(testTitle, () => {
|
||||
scaffoldAndOpenProject(hasStorybookDep ? 'pristine-with-e2e-testing-and-storybook' : 'pristine-with-e2e-testing')
|
||||
cy.withCtx((ctx) => {
|
||||
ctx.actions.file.writeFileInProject('yarn.lock', '# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.')
|
||||
})
|
||||
|
||||
cy.visitLaunchpad()
|
||||
|
||||
verifyWelcomePage({ e2eIsConfigured: true, ctIsConfigured: false })
|
||||
|
||||
cy.get('[data-cy-testingtype="component"]').click()
|
||||
|
||||
cy.log('Choose project setup')
|
||||
cy.get('h1').should('contain', 'Project Setup')
|
||||
cy.contains('Confirm the front-end framework and bundler used in your project.')
|
||||
|
||||
cy.findByRole('button', { name: 'Next Step' })
|
||||
.should('have.disabled')
|
||||
.as('nextStepButton')
|
||||
|
||||
cy.findByRole('button', {
|
||||
name: 'Front-end Framework Pick a framework',
|
||||
name: 'Bundler(Dev Server) Pick a bundler',
|
||||
expanded: false,
|
||||
})
|
||||
.should('have.attr', 'aria-haspopup', 'true')
|
||||
.click()
|
||||
.should('have.attr', 'aria-expanded', 'true')
|
||||
|
||||
cy.findByRole('option', { name: framework.name }).click()
|
||||
cy.findByRole('button', { name: `Front-end Framework ${framework.name}` }) // ensure selected option updates
|
||||
|
||||
if (framework.supportedBundlers.length > 1) {
|
||||
cy.findByRole('button', {
|
||||
name: 'Bundler(Dev Server) Pick a bundler',
|
||||
expanded: false,
|
||||
})
|
||||
.should('have.attr', 'aria-haspopup', 'true')
|
||||
.click()
|
||||
.should('have.attr', 'aria-expanded', 'true')
|
||||
|
||||
framework.supportedBundlers.forEach((supportedBundler) => {
|
||||
cy.findByRole('option', { name: Cypress._.startCase(supportedBundler) })
|
||||
.find('svg')
|
||||
.should('have.attr', 'data-cy', `${Cypress._.lowerCase(supportedBundler)}-logo`)
|
||||
})
|
||||
|
||||
cy.findByRole('option', { name: bundler.name })
|
||||
framework.supportedBundlers.forEach((supportedBundler) => {
|
||||
cy.findByRole('option', { name: supportedBundler.name })
|
||||
.find('svg')
|
||||
.should('have.attr', 'data-cy', `${Cypress._.lowerCase(bundler.name)}-logo`)
|
||||
.click()
|
||||
.should('have.attr', 'data-cy', `${supportedBundler.package}-logo`)
|
||||
})
|
||||
|
||||
cy.findByRole('button', { name: `Bundler(Dev Server) ${bundler.name}` }) // ensure selected option updates
|
||||
}
|
||||
|
||||
cy.findByRole('button', { name: lang.name }).click()
|
||||
|
||||
cy.log('Go to next step')
|
||||
cy.get('@nextStepButton').should('not.have.disabled').click()
|
||||
|
||||
cy.contains('h1', 'Install Dev Dependencies')
|
||||
cy.contains('p', 'Paste the command below into your terminal to install the required packages.')
|
||||
|
||||
cy.log('Return to previous step')
|
||||
cy.findByRole('button', { name: 'Back' })
|
||||
cy.findByRole('option', { name: bundler.name })
|
||||
.find('svg')
|
||||
.should('have.attr', 'data-cy', `${Cypress._.lowerCase(bundler.package)}-logo`)
|
||||
.click()
|
||||
|
||||
cy.findByRole('button', { name: `Front-end Framework ${framework.name}` })
|
||||
if (framework.supportedBundlers.length > 1) {
|
||||
cy.findByRole('button', { name: `Bundler(Dev Server) ${bundler.name}` })
|
||||
}
|
||||
cy.findByRole('button', { name: `Bundler(Dev Server) ${bundler.name}` }) // ensure selected option updates
|
||||
}
|
||||
|
||||
cy.findByRole('button', { name: lang.name })
|
||||
cy.findByRole('button', { name: 'Next Step' }).click()
|
||||
cy.findByRole('button', { name: lang.name }).click()
|
||||
|
||||
cy.log('Go to next step and verify Install Dev Dependencies page')
|
||||
cy.contains('h1', 'Install Dev Dependencies')
|
||||
cy.log('Go to next step')
|
||||
cy.get('@nextStepButton').should('not.have.disabled').click()
|
||||
|
||||
let installCommand = `npm install -D ${framework.package} ${bundler.package}`
|
||||
cy.contains('h1', 'Install Dev Dependencies')
|
||||
cy.contains('p', 'Paste the command below into your terminal to install the required packages.')
|
||||
|
||||
if (hasStorybookDep) {
|
||||
installCommand += ` ${framework.storybookDep}`
|
||||
}
|
||||
cy.log('Return to previous step')
|
||||
cy.findByRole('button', { name: 'Back' })
|
||||
.click()
|
||||
|
||||
cy.contains('code', installCommand)
|
||||
cy.findByRole('button', { name: `Front-end Framework ${framework.name}` })
|
||||
if (framework.supportedBundlers.length > 1) {
|
||||
cy.findByRole('button', { name: `Bundler(Dev Server) ${bundler.name}` })
|
||||
}
|
||||
|
||||
const validatePackage = (packageName) => {
|
||||
cy.validateExternalLink({
|
||||
name: packageName,
|
||||
href: `https://www.npmjs.com/package/${packageName}`,
|
||||
})
|
||||
cy.findByRole('button', { name: lang.name })
|
||||
cy.findByRole('button', { name: 'Next Step' }).click()
|
||||
|
||||
cy.contains(PACKAGES_DESCRIPTIONS[framework.package].split('<span')[0])
|
||||
}
|
||||
cy.log('Go to next step and verify Install Dev Dependencies page')
|
||||
cy.contains('h1', 'Install Dev Dependencies')
|
||||
|
||||
validatePackage(framework.package)
|
||||
validatePackage(bundler.package)
|
||||
if (hasStorybookDep) {
|
||||
validatePackage(framework.storybookDep)
|
||||
}
|
||||
|
||||
cy.findByRole('button', { name: 'Continue' }).click()
|
||||
|
||||
// Even if user chooses typescript in the previous
|
||||
// step, they already have a config file in js.
|
||||
// We cannot rename this file for them.
|
||||
cy.contains('[data-cy=changes]', `cypress.config.js`)
|
||||
|
||||
cy.get('[data-cy=valid]').within(() => {
|
||||
cy.containsPath('cypress/component/index.html')
|
||||
cy.containsPath(`cypress/support/component.${lang.type}`)
|
||||
cy.containsPath('cypress/fixtures/example.json')
|
||||
const validatePackage = (packageName: AllPackagePackages) => {
|
||||
cy.validateExternalLink({
|
||||
name: packageName,
|
||||
href: `https://www.npmjs.com/package/${packageName}`,
|
||||
})
|
||||
}
|
||||
|
||||
[...framework.packages].forEach((pkg) => {
|
||||
cy.contains(pkg.description.split('<span')[0])
|
||||
validatePackage(pkg.package)
|
||||
})
|
||||
|
||||
fakeInstalledDeps()
|
||||
|
||||
if (hasStorybookDep && framework.storybookDep) {
|
||||
validatePackage(framework.storybookDep.package)
|
||||
}
|
||||
|
||||
cy.findByRole('button', { name: 'Continue' }).click()
|
||||
|
||||
// Even if user chooses typescript in the previous
|
||||
// step, they already have a config file in js.
|
||||
// We cannot rename this file for them.
|
||||
cy.contains('[data-cy=changes]', `cypress.config.js`)
|
||||
|
||||
cy.get('[data-cy=valid]').within(() => {
|
||||
cy.containsPath('cypress/component/index.html')
|
||||
cy.containsPath(`cypress/support/component.${lang.type}`)
|
||||
cy.containsPath('cypress/fixtures/example.json')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('opens to the "choose framework" page when opened via cli with --component flag', () => {
|
||||
scaffoldAndOpenProject('pristine-with-e2e-testing', ['--component'])
|
||||
cy.visitLaunchpad()
|
||||
it('opens to the "choose framework" page when opened via cli with --component flag', () => {
|
||||
scaffoldAndOpenProject('pristine-with-e2e-testing', ['--component'])
|
||||
cy.visitLaunchpad()
|
||||
|
||||
cy.get('h1').should('contain', 'Project Setup')
|
||||
cy.contains('Confirm the front-end framework and bundler used in your project.')
|
||||
})
|
||||
cy.get('h1').should('contain', 'Project Setup')
|
||||
cy.contains('Confirm the front-end framework and bundler used in your project.')
|
||||
})
|
||||
|
||||
describe('project not been configured for cypress', () => {
|
||||
@@ -758,7 +703,7 @@ describe('Launchpad: Setup Project', () => {
|
||||
cy.findByText('Confirm the front-end framework and bundler used in your project.')
|
||||
|
||||
cy.findByRole('button', { name: 'Front-end Framework Pick a framework' }).click()
|
||||
cy.findByRole('option', { name: 'Create React App' }).click()
|
||||
cy.findByRole('option', { name: 'Create React App (v4)' }).click()
|
||||
|
||||
cy.get('[data-testid="select-bundler"').should('not.exist')
|
||||
cy.findByRole('button', { name: 'Next Step' }).should('not.have.disabled')
|
||||
@@ -772,17 +717,20 @@ describe('Launchpad: Setup Project', () => {
|
||||
cy.findByRole('button', { name: 'Next Step' }).should('have.disabled')
|
||||
|
||||
cy.findByRole('button', { name: 'Bundler(Dev Server) Pick a bundler' }).click()
|
||||
cy.findByRole('option', { name: 'Webpack' }).click()
|
||||
cy.findByRole('option', { name: 'Webpack (v4)' }).click()
|
||||
|
||||
cy.findByRole('button', { name: 'TypeScript' }).click()
|
||||
cy.findByRole('button', { name: 'Next Step' }).should('not.have.disabled')
|
||||
|
||||
cy.findByRole('button', { name: 'Front-end Framework React.js' }).click()
|
||||
cy.findByRole('option', { name: 'Create React App' }).click()
|
||||
cy.findByRole('option', { name: 'Create React App (v4)' }).click()
|
||||
cy.findByRole('button', { name: 'Bundler(Dev Server) Webpack' }).should('not.exist')
|
||||
cy.findByRole('button', { name: 'Next Step' }).should('not.have.disabled')
|
||||
|
||||
cy.findByRole('button', { name: 'Next Step' }).click()
|
||||
|
||||
fakeInstalledDeps()
|
||||
|
||||
cy.findByRole('button', { name: 'Continue' }).click()
|
||||
|
||||
cy.get('[data-cy=valid]').within(() => {
|
||||
@@ -809,8 +757,11 @@ describe('Launchpad: Setup Project', () => {
|
||||
cy.findByText('Confirm the front-end framework and bundler used in your project.')
|
||||
|
||||
cy.findByRole('button', { name: 'Front-end Framework Pick a framework' }).click()
|
||||
cy.findByRole('option', { name: 'Create React App' }).click()
|
||||
cy.findByRole('option', { name: 'Create React App (v4)' }).click()
|
||||
cy.findByRole('button', { name: 'TypeScript' }).click()
|
||||
|
||||
fakeInstalledDeps()
|
||||
|
||||
cy.findByRole('button', { name: 'Next Step' }).click()
|
||||
cy.findByRole('button', { name: 'Continue' }).click()
|
||||
|
||||
@@ -835,7 +786,7 @@ describe('Launchpad: Setup Project', () => {
|
||||
|
||||
cy.get('[data-cy-testingtype="component"]').click()
|
||||
cy.get('[data-testid="select-framework"]').click()
|
||||
cy.findByText('Create React App').click()
|
||||
cy.findByText('Create React App (v4)').click()
|
||||
cy.findByText('Next Step').click()
|
||||
cy.get('code').should('contain.text', 'yarn add -D ')
|
||||
})
|
||||
@@ -847,7 +798,7 @@ describe('Launchpad: Setup Project', () => {
|
||||
|
||||
cy.get('[data-cy-testingtype="component"]').click()
|
||||
cy.get('[data-testid="select-framework"]').click()
|
||||
cy.findByText('Create React App').click()
|
||||
cy.findByText('Create React App (v4)').click()
|
||||
cy.findByText('Next Step').click()
|
||||
cy.get('code').should('contain.text', 'pnpm install -D ')
|
||||
})
|
||||
@@ -859,165 +810,9 @@ describe('Launchpad: Setup Project', () => {
|
||||
|
||||
cy.get('[data-cy-testingtype="component"]').click()
|
||||
cy.get('[data-testid="select-framework"]').click()
|
||||
cy.findByText('Create React App').click()
|
||||
cy.findByText('Create React App (v4)').click()
|
||||
cy.findByText('Next Step').click()
|
||||
cy.get('code').should('contain.text', 'npm install -D ')
|
||||
})
|
||||
})
|
||||
|
||||
describe('detect framework, bundler and language', () => {
|
||||
beforeEach(() => {
|
||||
scaffoldAndOpenProject('pristine')
|
||||
})
|
||||
|
||||
context('meta frameworks', () => {
|
||||
it('detects CRA framework', () => {
|
||||
cy.withCtx(async (ctx) => {
|
||||
await ctx.actions.file.writeFileInProject('package.json', `
|
||||
{
|
||||
"dependencies": {
|
||||
"react": "^1.0.0",
|
||||
"react-dom": "^1.0.1",
|
||||
"react-scripts": "1.0.0"
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
cy.visitLaunchpad()
|
||||
|
||||
cy.get('[data-cy-testingtype="component"]').click()
|
||||
cy.get('[data-testid="select-framework"]').findByText('Create React App').should('be.visible')
|
||||
})
|
||||
|
||||
it('detects Next framework', () => {
|
||||
cy.withCtx(async (ctx) => {
|
||||
await ctx.actions.file.writeFileInProject('package.json', `
|
||||
{
|
||||
"dependencies": {
|
||||
"react": "^1.0.0",
|
||||
"react-dom": "^1.0.1",
|
||||
"next": "1.0.0"
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
cy.visitLaunchpad()
|
||||
|
||||
cy.get('[data-cy-testingtype="component"]').click()
|
||||
cy.get('[data-testid="select-framework"]').findByText('Next.js').should('be.visible')
|
||||
})
|
||||
|
||||
it('detects vue-cli framework', () => {
|
||||
cy.withCtx(async (ctx) => {
|
||||
await ctx.actions.file.writeFileInProject('package.json', `
|
||||
{
|
||||
"dependencies": {
|
||||
"vue": "^1.0.0",
|
||||
"@vue/cli-service": "^1.0.1"
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
cy.visitLaunchpad()
|
||||
|
||||
cy.get('[data-cy-testingtype="component"]').click()
|
||||
cy.get('[data-testid="select-framework"]').findByText('Vue CLI').should('be.visible')
|
||||
})
|
||||
|
||||
it('detects nuxtjs framework', () => {
|
||||
cy.withCtx(async (ctx) => {
|
||||
await ctx.actions.file.writeFileInProject('package.json', `
|
||||
{
|
||||
"dependencies": {
|
||||
"vue": "^1.0.0",
|
||||
"nuxt": "^1.0.1"
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
cy.visitLaunchpad()
|
||||
|
||||
cy.get('[data-cy-testingtype="component"]').click()
|
||||
cy.get('[data-testid="select-framework"]').findByText('Nuxt.js').should('be.visible')
|
||||
})
|
||||
})
|
||||
|
||||
context('pure frameworks', () => {
|
||||
it('detects react framework', () => {
|
||||
cy.withCtx(async (ctx) => {
|
||||
await ctx.actions.file.writeFileInProject('package.json', `
|
||||
{
|
||||
"dependencies": {
|
||||
"react": "^1.0.0"
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
cy.visitLaunchpad()
|
||||
|
||||
cy.get('[data-cy-testingtype="component"]').click()
|
||||
cy.get('[data-testid="select-framework"]').findByText('React.js').should('be.visible')
|
||||
})
|
||||
|
||||
it('detects vue framework', () => {
|
||||
cy.withCtx(async (ctx) => {
|
||||
await ctx.actions.file.writeFileInProject('package.json', `
|
||||
{
|
||||
"dependencies": {
|
||||
"vue": "^1.0.0"
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
cy.visitLaunchpad()
|
||||
|
||||
cy.get('[data-cy-testingtype="component"]').click()
|
||||
cy.get('[data-testid="select-framework"]').findByText('Vue.js').should('be.visible')
|
||||
})
|
||||
})
|
||||
|
||||
describe('bundlers', () => {
|
||||
it('detects webpack framework', () => {
|
||||
cy.withCtx(async (ctx) => {
|
||||
await ctx.actions.file.writeFileInProject('package.json', `
|
||||
{
|
||||
"dependencies": {
|
||||
"react": "^1.0.0",
|
||||
"webpack": "^1.0.0"
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
cy.visitLaunchpad()
|
||||
|
||||
cy.get('[data-cy-testingtype="component"]').click()
|
||||
cy.get('[data-testid="select-bundler"]').findByText('Webpack').should('be.visible')
|
||||
})
|
||||
|
||||
it('detects vite framework', () => {
|
||||
cy.withCtx(async (ctx) => {
|
||||
await ctx.actions.file.writeFileInProject('package.json', `
|
||||
{
|
||||
"dependencies": {
|
||||
"react": "^1.0.0",
|
||||
"vite": "^1.0.0"
|
||||
}
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
cy.visitLaunchpad()
|
||||
|
||||
cy.get('[data-cy-testingtype="component"]').click()
|
||||
cy.get('[data-testid="select-bundler"]').findByText('Vite').should('be.visible')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
121
packages/launchpad/cypress/e2e/scaffold-component-testing.cy.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import type { e2eProjectDirs } from '@packages/frontend-shared/cypress/e2e/support/e2eProjectDirs'
|
||||
|
||||
function startSetupFor (project: typeof e2eProjectDirs[number]) {
|
||||
cy.scaffoldProject(project)
|
||||
cy.openProject(project)
|
||||
cy.visitLaunchpad()
|
||||
cy.contains('Component Testing').click()
|
||||
cy.get(`[data-testid="select-framework"]`)
|
||||
}
|
||||
|
||||
function verifyConfigFile (configFile: `cypress.config.${'js' | 'ts'}`) {
|
||||
cy.withCtx(async (ctx, o) => {
|
||||
const configStats = await ctx.actions.file.checkIfFileExists(o.configFile)
|
||||
|
||||
expect(configStats).to.not.be.null.and.not.be.undefined
|
||||
|
||||
await ctx.actions.migration.assertSuccessfulConfigScaffold(o.configFile)
|
||||
}, { configFile })
|
||||
}
|
||||
|
||||
function fakeInstalledDeps () {
|
||||
cy.withCtx(async (ctx) => {
|
||||
const deps = (await ctx.wizard.packagesToInstall() ?? []).map((x) => x.package)
|
||||
|
||||
ctx.update((coreData) => {
|
||||
coreData.wizard.__fakeInstalledPackagesForTesting = deps
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
describe('scaffolding component testing', () => {
|
||||
context('vueclivue2', () => {
|
||||
it('scaffolds component testing for Vue CLI w/ Vue 2 project', () => {
|
||||
startSetupFor('vueclivue2-unconfigured')
|
||||
|
||||
// should detect correctly
|
||||
cy.get('button').should('be.visible').contains('Vue CLI (Vue 2)(detected)')
|
||||
cy.get('button').contains('Next Step').click()
|
||||
|
||||
fakeInstalledDeps()
|
||||
|
||||
cy.findByRole('button', { name: 'Continue' }).click()
|
||||
verifyConfigFile(`cypress.config.js`)
|
||||
})
|
||||
})
|
||||
|
||||
context('vueclivue3', () => {
|
||||
it('scaffolds component testing for Vue CLI w/ Vue 3 project', () => {
|
||||
startSetupFor('vueclivue3-unconfigured')
|
||||
|
||||
// should detect correctly
|
||||
cy.get('button').should('be.visible').contains('Vue CLI (Vue 3)(detected)')
|
||||
cy.get('button').contains('Next Step').click()
|
||||
|
||||
fakeInstalledDeps()
|
||||
|
||||
cy.findByRole('button', { name: 'Continue' }).click()
|
||||
verifyConfigFile(`cypress.config.js`)
|
||||
})
|
||||
})
|
||||
|
||||
context('create-react-app', () => {
|
||||
it('scaffolds component testing for Vue CLI w/ Vue 2 project', () => {
|
||||
startSetupFor('create-react-app-unconfigured')
|
||||
|
||||
// should detect correctly
|
||||
cy.get('button').should('be.visible').contains('Create React App (v5)(detected)')
|
||||
cy.get('button').contains('Next Step').click()
|
||||
|
||||
fakeInstalledDeps()
|
||||
|
||||
cy.findByRole('button', { name: 'Continue' }).click()
|
||||
verifyConfigFile(`cypress.config.js`)
|
||||
})
|
||||
})
|
||||
|
||||
context('react-vite-ts-unconfigured', () => {
|
||||
it('scaffolds component testing for React and Vite', () => {
|
||||
startSetupFor('react-vite-ts-unconfigured')
|
||||
|
||||
// should detect correctly
|
||||
cy.get('button').should('be.visible').contains('React.js(detected)')
|
||||
cy.get('button').contains('Next Step').click()
|
||||
|
||||
fakeInstalledDeps()
|
||||
|
||||
cy.findByRole('button', { name: 'Continue' }).click()
|
||||
verifyConfigFile(`cypress.config.ts`)
|
||||
})
|
||||
})
|
||||
|
||||
context('vue3-vite-ts-unconfigured', () => {
|
||||
it('scaffolds component testing for Vue 3 and Vite', () => {
|
||||
startSetupFor('vue3-vite-ts-unconfigured')
|
||||
|
||||
// should detect correctly
|
||||
cy.get('button').should('be.visible').contains('Vue.js (v3)(detected)')
|
||||
cy.get('button').contains('Next Step').click()
|
||||
|
||||
fakeInstalledDeps()
|
||||
|
||||
cy.findByRole('button', { name: 'Continue' }).click()
|
||||
verifyConfigFile(`cypress.config.ts`)
|
||||
})
|
||||
})
|
||||
|
||||
context('nuxtjs-vue2-unconfigured', () => {
|
||||
it('scaffolds component testing for Nuxt 2', () => {
|
||||
startSetupFor('nuxtjs-vue2-unconfigured')
|
||||
|
||||
// should detect correctly
|
||||
cy.get('button').should('be.visible').contains('Nuxt.js (v2)(detected)')
|
||||
cy.get('button').contains('Next Step').click()
|
||||
|
||||
fakeInstalledDeps()
|
||||
|
||||
cy.findByRole('button', { name: 'Continue' }).click()
|
||||
verifyConfigFile(`cypress.config.js`)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,7 @@
|
||||
import { EnvironmentSetupFragmentDoc } from '../generated/graphql-test'
|
||||
import EnvironmentSetup from './EnvironmentSetup.vue'
|
||||
import { FRONTEND_FRAMEWORKS, CODE_LANGUAGES } from '../../../types/src/constants'
|
||||
import { CODE_LANGUAGES } from '../../../types/src/constants'
|
||||
import { FRONTEND_FRAMEWORKS } from '@packages/scaffold-config'
|
||||
|
||||
describe('<EnvironmentSetup />', { viewportWidth: 800 }, () => {
|
||||
it('default component', () => {
|
||||
@@ -28,6 +29,10 @@ describe('<EnvironmentSetup />', { viewportWidth: 800 }, () => {
|
||||
return 'react-logo'
|
||||
}
|
||||
|
||||
if (frameworkName.includes('Nuxt')) {
|
||||
return 'nuxtjs-logo'
|
||||
}
|
||||
|
||||
if (frameworkName.includes('Vue')) {
|
||||
return 'vue-logo'
|
||||
}
|
||||
@@ -69,7 +74,7 @@ describe('<EnvironmentSetup />', { viewportWidth: 800 }, () => {
|
||||
expanded: false,
|
||||
}).click()
|
||||
|
||||
cy.findByRole('option', { name: 'Create React App (detected)' }).should('be.visible')
|
||||
cy.findByRole('option', { name: 'Create React App (v4) (detected)' }).should('be.visible')
|
||||
})
|
||||
|
||||
it('shows the description of bundler as Dev Server', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { defaultMessages } from '@cy/i18n'
|
||||
import InstallDependencies from './InstallDependencies.vue'
|
||||
import { InstallDependenciesFragmentDoc } from '../generated/graphql-test'
|
||||
import { PACKAGES_DESCRIPTIONS } from '../../../types/src/constants'
|
||||
import { CYPRESS_REACT_LATEST, CYPRESS_WEBPACK } from '@packages/scaffold-config'
|
||||
|
||||
describe('<InstallDependencies />', () => {
|
||||
beforeEach(function () {
|
||||
@@ -24,8 +24,8 @@ describe('<InstallDependencies />', () => {
|
||||
.should('be.visible')
|
||||
.and('have.attr', 'href', 'https://www.npmjs.com/package/@cypress/webpack-dev-server')
|
||||
|
||||
cy.contains(PACKAGES_DESCRIPTIONS['@cypress/react'].split('<span')[0])
|
||||
cy.contains(PACKAGES_DESCRIPTIONS['@cypress/webpack-dev-server'].split('<span')[0])
|
||||
cy.contains(CYPRESS_REACT_LATEST.description.split('<span')[0])
|
||||
cy.contains(CYPRESS_WEBPACK.description.split('<span')[0])
|
||||
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ManualInstallFragmentDoc } from '../generated/graphql-test'
|
||||
import ManualInstall from './ManualInstall.vue'
|
||||
import { PACKAGES_DESCRIPTIONS } from '../../../types/src/constants'
|
||||
import { CYPRESS_REACT_LATEST, CYPRESS_WEBPACK } from '@packages/scaffold-config'
|
||||
|
||||
describe('<ManualInstall />', () => {
|
||||
it('playground', () => {
|
||||
@@ -14,8 +14,8 @@ describe('<ManualInstall />', () => {
|
||||
})
|
||||
|
||||
it('lists packages and can copy install command to clipboard', { viewportWidth: 800, viewportHeight: 600 }, () => {
|
||||
const framework = '@cypress/react'
|
||||
const bundler = '@cypress/webpack-dev-server'
|
||||
const framework = CYPRESS_REACT_LATEST
|
||||
const bundler = CYPRESS_WEBPACK
|
||||
|
||||
cy.mountFragment(ManualInstallFragmentDoc, {
|
||||
render: (gqlVal) => (
|
||||
@@ -25,7 +25,7 @@ describe('<ManualInstall />', () => {
|
||||
),
|
||||
})
|
||||
|
||||
const installCommand = `yarn add -D ${framework} ${bundler}`
|
||||
const installCommand = `npm install -D @cypress/react @cypress/webpack-dev-server`
|
||||
|
||||
// @ts-ignore
|
||||
cy.findByRole('button', { name: 'Copy' }).realClick()
|
||||
@@ -50,15 +50,15 @@ describe('<ManualInstall />', () => {
|
||||
.invoke('readText')
|
||||
.should('equal', installCommand)
|
||||
|
||||
const validatePackage = (packageName) => {
|
||||
const validatePackage = (packageName: string) => {
|
||||
cy.findByRole('link', { name: packageName })
|
||||
.should('have.attr', 'href', `https://www.npmjs.com/package/${packageName}`)
|
||||
|
||||
cy.contains(PACKAGES_DESCRIPTIONS[framework].split('<span')[0])
|
||||
cy.contains(framework.description.split('<span')[0])
|
||||
}
|
||||
|
||||
validatePackage(framework)
|
||||
validatePackage(bundler)
|
||||
validatePackage(framework.package)
|
||||
validatePackage(bundler.package)
|
||||
})
|
||||
|
||||
it('flags packages already installed', () => {
|
||||
|
||||
@@ -1,14 +1,15 @@
|
||||
<template>
|
||||
<TerminalPrompt
|
||||
v-if="props.gql.wizard.installDependenciesCommand"
|
||||
class="m-24px"
|
||||
:command="installDependenciesCode"
|
||||
:command="props.gql.wizard.installDependenciesCommand"
|
||||
:project-folder-name="projectFolder"
|
||||
/>
|
||||
<div class="border-t border-t-gray-100 px-24px">
|
||||
<ul>
|
||||
<li
|
||||
v-for="dep in props.gql.wizard.packagesToInstall"
|
||||
:key="dep.id"
|
||||
:key="dep.package"
|
||||
class="border-b border-b-gray-100 py-16px last-of-type:border-b-0"
|
||||
>
|
||||
<i-cy-status-download-done_x24
|
||||
@@ -55,6 +56,7 @@ fragment ManualInstall on Query {
|
||||
description
|
||||
package
|
||||
}
|
||||
installDependenciesCommand
|
||||
}
|
||||
currentProject {
|
||||
id
|
||||
@@ -70,19 +72,4 @@ const props = defineProps<{
|
||||
gql: ManualInstallFragment
|
||||
packagesInstalled: string[]
|
||||
}>()
|
||||
|
||||
const commands = {
|
||||
'npm': 'npm install -D ',
|
||||
'pnpm': 'pnpm install -D ',
|
||||
'yarn': 'yarn add -D ',
|
||||
}
|
||||
|
||||
const installDependenciesCode = computed(
|
||||
() => {
|
||||
return commands[props.gql.currentProject?.packageManager ?? 'npm'] +
|
||||
(props.gql.wizard.packagesToInstall ?? [])
|
||||
.map((pack) => `${pack.package}`)
|
||||
.join(' ')
|
||||
},
|
||||
)
|
||||
</script>
|
||||
|
||||
@@ -33,7 +33,7 @@ import {
|
||||
} from '../generated/graphql'
|
||||
import WarningList from '../warning/WarningList.vue'
|
||||
import { computed } from 'vue'
|
||||
import type { FrontendFramework, Bundler } from '@packages/types/src/constants'
|
||||
import type { FrontendFramework, Bundler } from '@packages/scaffold-config'
|
||||
import LaunchpadHeader from './LaunchpadHeader.vue'
|
||||
import { useI18n } from '@cy/i18n'
|
||||
import SelectLanguage from './SelectLanguage.vue'
|
||||
@@ -41,7 +41,7 @@ import { useMutation } from '@urql/vue'
|
||||
import WizardLayout from './WizardLayout.vue'
|
||||
|
||||
export interface WizardSetupData {
|
||||
bundler: Bundler['type'] | null
|
||||
bundler: Bundler | null
|
||||
framework: FrontendFramework['type'] | null
|
||||
codeLanguage: CodeLanguageEnum
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { ref } from 'vue'
|
||||
import SelectFwOrBundler from './SelectFwOrBundler.vue'
|
||||
import SelectFwOrBundler, { Option } from './SelectFwOrBundler.vue'
|
||||
|
||||
const manyOptions = [
|
||||
const manyOptions: Readonly<Option[]> = [
|
||||
{
|
||||
name: 'Vue.js',
|
||||
id: 'vue',
|
||||
isSelected: false,
|
||||
type: 'vue',
|
||||
category: 'vue',
|
||||
type: 'vue2',
|
||||
isDetected: true,
|
||||
},
|
||||
{
|
||||
@@ -16,7 +15,6 @@ const manyOptions = [
|
||||
id: 'react',
|
||||
isSelected: false,
|
||||
type: 'react',
|
||||
category: 'react',
|
||||
},
|
||||
] as const
|
||||
|
||||
@@ -65,7 +63,7 @@ describe('<SelectFwOrBundler />', () => {
|
||||
name: 'VueJs',
|
||||
id: 'vue',
|
||||
isSelected: false,
|
||||
type: 'vuecli',
|
||||
type: 'vueclivue3',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
@@ -107,7 +105,7 @@ describe('<SelectFwOrBundler />', () => {
|
||||
cy.mount(() => (
|
||||
<div>
|
||||
<div>click out</div>
|
||||
<SelectFwOrBundler selectorType="framework" label="Front-end Framework" options={manyOptions} value="vue" />
|
||||
<SelectFwOrBundler selectorType="framework" label="Front-end Framework" options={manyOptions} value="vue2" />
|
||||
</div>
|
||||
))
|
||||
|
||||
|
||||
@@ -8,12 +8,16 @@ import LogoReact from '../images/logos/react.svg'
|
||||
import type { FrontendFrameworkEnum, SupportedBundlers } from '../generated/graphql'
|
||||
|
||||
export const FrameworkBundlerLogos: Record<FrontendFrameworkEnum | SupportedBundlers, string> = {
|
||||
webpack: LogoWebpack,
|
||||
webpack4: LogoWebpack,
|
||||
webpack5: LogoWebpack,
|
||||
vite: LogoVite,
|
||||
vue: LogoVue,
|
||||
vuecli: LogoVue,
|
||||
vue2: LogoVue,
|
||||
vue3: LogoVue,
|
||||
vueclivue2: LogoVue,
|
||||
vueclivue3: LogoVue,
|
||||
nextjs: LogoNext,
|
||||
nuxtjs: LogoNuxt,
|
||||
react: LogoReact,
|
||||
cra: LogoReact,
|
||||
crav4: LogoReact,
|
||||
crav5: LogoReact,
|
||||
}
|
||||
|
||||
39
packages/scaffold-config/README.md
Normal file
@@ -0,0 +1,39 @@
|
||||
## @packages/scaffold-config
|
||||
|
||||
Logic related to scaffolding new projects using launchpad, including detecting component frameworks and bundlers, installing dependencies and creating `cypress.config.js` files.
|
||||
|
||||
We have integrations for popular code generators like Create React App, Next.js and Vue CLI, and ship a `cypress.config.js` that will work out of the box for those templates.
|
||||
|
||||
We will also attempt to scaffold a configuration file for projects using React and Vue projects using Vite and Webpack that are not necessarily created using a code generator.
|
||||
|
||||
### Supported Frameworks and Libraries
|
||||
|
||||
| Name | Tool Version | Dev Server | Dev Server Version | Library Version (s) | Component Adaptor Version | Example Project
|
||||
| --- | ---- | ---- | --- | ---- | ---- | --- |
|
||||
| Create React App | 5.x | Webpack | 5.x | React 16, 17 | `@cypress/react@latest` | [TODO]
|
||||
| Create React App | 4.x | Webpack | 4.x | React 16, React 17 | `@cypress/react@latest` | [Link](../../system-tests/projects/create-react-app-configured)
|
||||
| React | - | Vite | 2.x | React 16, React 17 | `@cypress/react@latest` | [Link](../../system-tests/projects/react-vite-ts-configured)
|
||||
| Vue | - | Vite | 2.x | Vue 3 | `@cypress/vue@^3.0.0` | [Link](../../system-tests/projects/vue3-vite-ts-configured)
|
||||
| Vue CLI | 4.x | Webpack | 4.x | Vue 2 | `@cypress/vue@2.0.4` | [Link](../../system-tests/projects/vueclivue2-configured)
|
||||
| Vue CLI | 4.x | Webpack | 4.x | Vue 3 | `@cypress/vue@latest` | [Link](../../system-tests/projects/vueclivue3-configured)
|
||||
| Nuxt.js | 2.x | Webpack | 4.x, 5.x | Vue 2 | `@cypress/vue@2.0.4` | [Link](../../system-tests/projects/pristine-nuxtjs-vue2-configured)
|
||||
|
||||
### Adding More Projects
|
||||
|
||||
The process for adding a new library/framework/bundler is as follows:
|
||||
|
||||
1. Add your framework in [`src/frameworks.ts`](./src/frameworks.ts).
|
||||
2. Any new dependencies are declared in [`src/constants.ts`](./src/constants.ts). Don't forget to add a description.
|
||||
3. Ensure your project has the correct library and bundler detected with a test in [`test/integration/detect.ts`](./test/integration/scaffold-config.spec.ts)
|
||||
3. Add a new project with the correct `cypress.config.js` and `package.json` to [system-tests/projects](../../system-tests/projects). It should be `<name>-configured`, which is a working example with some specs. Ensure it will run on CI by adding it to [`component_testing_spec.ts`](../../system-tests/test/component_testing_spec.ts).
|
||||
4. Add another project called `<name>-unconfigured`, which represents the project prior to having Cypress added. This will be used in step 5.
|
||||
5. Add a test to [`scaffold-component-testing.cy.ts`](../launchpad/cypress/e2e/scaffold-component-testing.cy.ts) to ensure your project has the correct `cypress.config.js` generated. Use an existing test as a template.
|
||||
|
||||
### TODO
|
||||
|
||||
These should be supported but currently are not configured.
|
||||
|
||||
| Name | Tool Version | Dev Server | Dev Server Version | Library Version (s) | Component Adaptor Version | Example Project
|
||||
| --- | ---- | ---- | --- | ---- | ---- | --- |
|
||||
| Next.js | 11.x, 12.x | Webpack | 4.x, 5.x | React 16, 17 | `@cypress/react@latest` | [Link](../../system-tests/projects/nextjs-configured)
|
||||
| Vue CLI | 5.x | Webpack | 5.x | Vue 3 | `@cypress/vue@^3.0.0` | [TODO]
|
||||
5
packages/scaffold-config/index.js
Normal file
@@ -0,0 +1,5 @@
|
||||
if (process.env.CYPRESS_INTERNAL_ENV !== 'production') {
|
||||
require('@packages/ts/register')
|
||||
}
|
||||
|
||||
module.exports = require('./src')
|
||||
26
packages/scaffold-config/package.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "@packages/scaffold-config",
|
||||
"version": "0.0.0-development",
|
||||
"description": "Logic related to scaffolding new projects using launchpad",
|
||||
"main": "index.js",
|
||||
"browser": "src/index.ts",
|
||||
"scripts": {
|
||||
"build-prod": "tsc || echo 'built, with errors'",
|
||||
"check-ts": "tsc --noEmit",
|
||||
"clean": "rm -f ./src/*.js ./src/**/*.js ./src/**/**/*.js ./test/**/*.js || echo 'cleaned'",
|
||||
"clean-deps": "rm -rf node_modules",
|
||||
"test-unit": "mocha -r @packages/ts/register 'test/unit/**' --config ./test/.mocharc.js --exit"
|
||||
},
|
||||
"dependencies": {
|
||||
"compare-versions": "4.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@packages/ts": "0.0.0-development",
|
||||
"chai": "4.2.0",
|
||||
"mocha": "7.0.1"
|
||||
},
|
||||
"files": [
|
||||
"src"
|
||||
],
|
||||
"types": "src/index.ts"
|
||||
}
|
||||
154
packages/scaffold-config/src/constants.ts
Normal file
@@ -0,0 +1,154 @@
|
||||
export const CODE_GEN_FRAMEWORKS = ['react', 'vue'] as const
|
||||
|
||||
export const FRONTEND_FRAMEWORK_CATEGORIES = ['react', 'vue'] as const
|
||||
|
||||
export const STORYBOOK_REACT = {
|
||||
type: 'storybook',
|
||||
name: ' Testing React',
|
||||
package: '@storybook/testing-react',
|
||||
installer: '@storybook/testing-react@latest',
|
||||
description: 'Testing utilities that allow you to reuse your stories in your unit tests',
|
||||
} as const
|
||||
|
||||
export const STORYBOOK_VUE = {
|
||||
type: 'storybook',
|
||||
name: ' Testing Vue 3',
|
||||
package: '@storybook/testing-vue3',
|
||||
installer: '@storybook/testing-vue3@latest',
|
||||
description: 'Testing utilities that allow you to reuse your stories in your unit tests',
|
||||
} as const
|
||||
|
||||
export const STORYBOOK_DEPS = [
|
||||
STORYBOOK_REACT,
|
||||
STORYBOOK_VUE,
|
||||
] as const
|
||||
|
||||
export const CYPRESS_VUE_2 = {
|
||||
type: 'cypress-adapter',
|
||||
name: 'Cypress Vue',
|
||||
package: '@cypress/vue',
|
||||
installer: '@cypress/vue@^2.0.0',
|
||||
description: 'Allows Cypress to mount each Vue component using <span class="text-purple-400">cy.mount()</span>',
|
||||
} as const
|
||||
|
||||
export const CYPRESS_VUE_3 = {
|
||||
type: 'cypress-adapter',
|
||||
name: 'Cypress Vue',
|
||||
package: '@cypress/vue',
|
||||
installer: '@cypress/vue@^3.0.0',
|
||||
description: 'Allows Cypress to mount each Vue component using <span class="text-purple-400">cy.mount()</span>',
|
||||
} as const
|
||||
|
||||
export const WEBPACK_DEV_SERVER_4 = {
|
||||
type: 'dev-server',
|
||||
name: 'Webpack Dev Server',
|
||||
package: 'webpack-dev-server',
|
||||
installer: 'webpack-dev-server@^4.0.0',
|
||||
description: 'Webpack Dev Server to bundle and run your tests',
|
||||
} as const
|
||||
|
||||
export const HTML_WEBPACK_PLUGIN_4 = {
|
||||
type: 'other',
|
||||
name: 'HTML Webpack Plugin',
|
||||
package: 'html-webpack-plugin',
|
||||
installer: 'html-webpack-plugin@^4.0.0',
|
||||
description: 'The HtmlWebpackPlugin simplifies creation of HTML files to serve your webpack bundles',
|
||||
} as const
|
||||
|
||||
export const CYPRESS_WEBPACK = {
|
||||
type: 'webpack',
|
||||
name: 'Cypress Webpack Dev Server',
|
||||
package: '@cypress/webpack-dev-server',
|
||||
installer: '@cypress/webpack-dev-server@latest',
|
||||
description: 'Allows Cypress to use your existing build configuration in order to bundle and run your tests',
|
||||
} as const
|
||||
|
||||
export const CYPRESS_VITE = {
|
||||
type: 'vite',
|
||||
name: 'Cypress Vite Dev Server',
|
||||
package: '@cypress/vite-dev-server',
|
||||
installer: '@cypress/vite-dev-server@latest',
|
||||
description: 'Allows Cypress to use your existing build configuration in order to bundle and run your tests',
|
||||
} as const
|
||||
|
||||
export const CYPRESS_DEV_SERVERS = [
|
||||
CYPRESS_WEBPACK,
|
||||
CYPRESS_VITE,
|
||||
] as const
|
||||
|
||||
export const BUNDLER_WEBPACK_4 = {
|
||||
type: 'webpack4',
|
||||
name: 'Webpack (v4)',
|
||||
package: 'webpack',
|
||||
installer: 'webpack@^4.0.0',
|
||||
description: 'Webpack is a module bundler',
|
||||
dependencies: [
|
||||
CYPRESS_WEBPACK,
|
||||
WEBPACK_DEV_SERVER_4,
|
||||
HTML_WEBPACK_PLUGIN_4,
|
||||
],
|
||||
} as const
|
||||
|
||||
export const HTML_WEBPACK_PLUGIN_5 = {
|
||||
type: 'other',
|
||||
name: 'HTML Webpack Plugin',
|
||||
package: 'html-webpack-plugin',
|
||||
installer: 'html-webpack-plugin@^5.0.0',
|
||||
description: 'The HtmlWebpackPlugin simplifies creation of HTML files to serve your webpack bundles',
|
||||
} as const
|
||||
|
||||
export const BUNDLER_WEBPACK_5 = {
|
||||
type: 'webpack5',
|
||||
name: 'Webpack (v5)',
|
||||
package: 'webpack',
|
||||
installer: 'webpack@^5.0.0',
|
||||
description: 'Webpack is a module bundler',
|
||||
dependencies: [
|
||||
CYPRESS_WEBPACK,
|
||||
WEBPACK_DEV_SERVER_4,
|
||||
HTML_WEBPACK_PLUGIN_5,
|
||||
],
|
||||
} as const
|
||||
|
||||
export const BUNDLER_VITE = {
|
||||
type: 'vite',
|
||||
name: 'Vite',
|
||||
package: 'vite',
|
||||
installer: 'vite@^2.0.0',
|
||||
description: 'Vite is dev server that serves your source files over native ES modules',
|
||||
dependencies: [CYPRESS_VITE],
|
||||
} as const
|
||||
|
||||
export const BUNDLERS = [
|
||||
BUNDLER_WEBPACK_4,
|
||||
BUNDLER_WEBPACK_5,
|
||||
BUNDLER_VITE,
|
||||
]
|
||||
|
||||
export const CYPRESS_REACT_LATEST = {
|
||||
type: 'cypress-adapter',
|
||||
name: 'Cypress React',
|
||||
package: '@cypress/react',
|
||||
installer: '@cypress/react@^5.0.0',
|
||||
description: 'Allows Cypress to mount each React component using <span class="text-purple-400">cy.mount()</span>',
|
||||
} as const
|
||||
|
||||
export const CYPRESS_ADAPTER_DEPS = [
|
||||
CYPRESS_REACT_LATEST,
|
||||
CYPRESS_VUE_2,
|
||||
CYPRESS_VUE_3,
|
||||
] as const
|
||||
|
||||
export const DEV_SERVERS = [
|
||||
WEBPACK_DEV_SERVER_4,
|
||||
]
|
||||
|
||||
export const DEPENDENCIES = [
|
||||
...CYPRESS_ADAPTER_DEPS,
|
||||
...CYPRESS_DEV_SERVERS,
|
||||
...STORYBOOK_DEPS,
|
||||
...DEV_SERVERS,
|
||||
...BUNDLERS,
|
||||
HTML_WEBPACK_PLUGIN_4,
|
||||
HTML_WEBPACK_PLUGIN_5,
|
||||
] as const
|
||||
106
packages/scaffold-config/src/detect.ts
Normal file
@@ -0,0 +1,106 @@
|
||||
import { satisfies } from 'compare-versions'
|
||||
import type { Bundler, PkgJson, FrontendFramework } from './types'
|
||||
import { FRONTEND_FRAMEWORKS } from './frameworks'
|
||||
|
||||
export interface Detector {
|
||||
version: string
|
||||
dependency: string
|
||||
}
|
||||
|
||||
interface DetectFramework {
|
||||
framework?: FrontendFramework
|
||||
bundler?: Bundler
|
||||
}
|
||||
|
||||
const bundlers = [
|
||||
{
|
||||
type: 'vite',
|
||||
detectors: [
|
||||
{
|
||||
dependency: 'vite',
|
||||
version: '>=2.0.0',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'webpack4',
|
||||
detectors: [
|
||||
{
|
||||
dependency: 'webpack',
|
||||
version: '^4.0.0',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'webpack5',
|
||||
detectors: [
|
||||
{
|
||||
dependency: 'webpack',
|
||||
version: '^5.0.0',
|
||||
},
|
||||
],
|
||||
},
|
||||
] as const
|
||||
|
||||
// Detect the framework, which can either be a tool like Create React App,
|
||||
// in which case we just return the framework. The user cannot change the
|
||||
// bundler.
|
||||
|
||||
// If we don't find a specific framework, but we do find a library and/or
|
||||
// bundler, we return both the framework, which might just be "React",
|
||||
// and the bundler, which could be Vite.
|
||||
export function detect (pkg: PkgJson): DetectFramework {
|
||||
const inPkgJson = (detector: Detector) => {
|
||||
const vers = pkg.dependencies?.[detector.dependency] || pkg.devDependencies?.[detector.dependency]
|
||||
const found = (vers && satisfies(vers, detector.version)) ?? false
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
// first see if it's a template
|
||||
for (const framework of FRONTEND_FRAMEWORKS.filter((x) => x.family === 'template')) {
|
||||
const hasAllDeps = [...framework.detectors].every(inPkgJson)
|
||||
|
||||
// so far all the templates we support only have 1 bundler,
|
||||
// for example CRA only works with webpack,
|
||||
// but we want to consider in the future, tools like Nuxt ship
|
||||
// both a webpack and vite dev-env.
|
||||
// if we support this, we will also need to attempt to infer the dev server of choice.
|
||||
if (hasAllDeps && framework.supportedBundlers.length === 1) {
|
||||
return {
|
||||
framework,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// if not a template, they probably just installed/configured on their own.
|
||||
for (const library of FRONTEND_FRAMEWORKS.filter((x) => x.family === 'library')) {
|
||||
// multiple bundlers supported, eg React works with webpack and Vite.
|
||||
// try to infer which one they are using.
|
||||
const hasLibrary = [...library.detectors].every(inPkgJson)
|
||||
|
||||
for (const bundler of bundlers) {
|
||||
const hasBundler = [...bundler.detectors].every(inPkgJson)
|
||||
|
||||
if (hasLibrary && hasBundler) {
|
||||
return {
|
||||
framework: library,
|
||||
bundler: bundler.type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasLibrary) {
|
||||
// unknown bundler, or we couldn't detect it
|
||||
// just return the framework, leave the rest to the user.
|
||||
return {
|
||||
framework: library,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
framework: undefined,
|
||||
bundler: undefined,
|
||||
}
|
||||
}
|
||||
572
packages/scaffold-config/src/frameworks.ts
Normal file
@@ -0,0 +1,572 @@
|
||||
import dedent from 'dedent'
|
||||
import type { Bundler } from './types'
|
||||
import {
|
||||
CODE_GEN_FRAMEWORKS,
|
||||
FRONTEND_FRAMEWORK_CATEGORIES,
|
||||
CYPRESS_REACT_LATEST,
|
||||
CYPRESS_VUE_2,
|
||||
CYPRESS_VUE_3,
|
||||
STORYBOOK_REACT,
|
||||
STORYBOOK_VUE,
|
||||
BUNDLER_WEBPACK_4,
|
||||
} from './constants'
|
||||
import { BUNDLER_VITE, BUNDLER_WEBPACK_5 } from '.'
|
||||
|
||||
const isWebpack = (bundler: Bundler) => ['webpack4', 'webpack5'].includes(bundler)
|
||||
|
||||
export const FRONTEND_FRAMEWORKS = [
|
||||
{
|
||||
type: 'crav4',
|
||||
family: 'template',
|
||||
name: 'Create React App (v4)',
|
||||
supportedBundlers: [BUNDLER_WEBPACK_4],
|
||||
packages: [
|
||||
CYPRESS_REACT_LATEST,
|
||||
BUNDLER_WEBPACK_4,
|
||||
],
|
||||
defaultPackagePath: '@cypress/react/plugins/react-scripts',
|
||||
glob: '*.{js,jsx,tsx}',
|
||||
category: FRONTEND_FRAMEWORK_CATEGORIES[0],
|
||||
codeGenFramework: CODE_GEN_FRAMEWORKS[0],
|
||||
storybookDep: STORYBOOK_REACT,
|
||||
detectors: [
|
||||
{
|
||||
dependency: 'react-scripts',
|
||||
version: '^4.0.0',
|
||||
},
|
||||
],
|
||||
config: {
|
||||
js: () => {
|
||||
return dedent`
|
||||
const { devServer } = require('@cypress/react/plugins/react-scripts')
|
||||
|
||||
module.exports = {
|
||||
component: {
|
||||
devServer,
|
||||
}
|
||||
}`
|
||||
},
|
||||
ts: () => {
|
||||
return dedent`
|
||||
import { defineConfig } from 'cypress'
|
||||
import { devServer } from '@cypress/react/plugins/react-scripts'
|
||||
|
||||
export default defineConfig({
|
||||
component: {
|
||||
devServer,
|
||||
}
|
||||
})`
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'crav5',
|
||||
family: 'template',
|
||||
name: 'Create React App (v5)',
|
||||
supportedBundlers: [BUNDLER_WEBPACK_5],
|
||||
packages: [
|
||||
CYPRESS_REACT_LATEST,
|
||||
BUNDLER_WEBPACK_5,
|
||||
],
|
||||
defaultPackagePath: '@cypress/react/plugins/react-scripts',
|
||||
glob: '*.{js,jsx,tsx}',
|
||||
category: FRONTEND_FRAMEWORK_CATEGORIES[0],
|
||||
codeGenFramework: CODE_GEN_FRAMEWORKS[0],
|
||||
storybookDep: STORYBOOK_REACT,
|
||||
detectors: [
|
||||
{
|
||||
dependency: 'react-scripts',
|
||||
version: '^5.0.0',
|
||||
},
|
||||
],
|
||||
config: {
|
||||
js: () => {
|
||||
return dedent`
|
||||
const { devServer } = require('@cypress/react/plugins/react-scripts')
|
||||
|
||||
module.exports = {
|
||||
component: {
|
||||
devServer,
|
||||
}
|
||||
}`
|
||||
},
|
||||
ts: () => {
|
||||
return dedent`
|
||||
import { defineConfig } from 'cypress'
|
||||
import { devServer } from '@cypress/react/plugins/react-scripts'
|
||||
|
||||
export default defineConfig({
|
||||
component: {
|
||||
devServer,
|
||||
}
|
||||
})`
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'vueclivue2',
|
||||
name: 'Vue CLI (Vue 2)',
|
||||
family: 'template',
|
||||
supportedBundlers: [BUNDLER_WEBPACK_4],
|
||||
packages: [
|
||||
CYPRESS_VUE_2,
|
||||
BUNDLER_WEBPACK_4,
|
||||
],
|
||||
defaultPackagePath: null,
|
||||
glob: '*.vue',
|
||||
detectors: [
|
||||
{
|
||||
dependency: '@vue/cli-service',
|
||||
version: '^4.0.0',
|
||||
},
|
||||
{
|
||||
dependency: 'vue',
|
||||
version: '^2.0.0',
|
||||
},
|
||||
],
|
||||
category: FRONTEND_FRAMEWORK_CATEGORIES[1],
|
||||
codeGenFramework: CODE_GEN_FRAMEWORKS[1],
|
||||
storybookDep: STORYBOOK_VUE,
|
||||
config: {
|
||||
js: (bundler: Bundler) => {
|
||||
return dedent`
|
||||
const { devServer } = require('@cypress/webpack-dev-server')
|
||||
const webpackConfig = require('@vue/cli-service/webpack.config')
|
||||
|
||||
module.exports = {
|
||||
component: {
|
||||
devServer,
|
||||
devServerConfig: {
|
||||
webpackConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
},
|
||||
ts: () => {
|
||||
return dedent`
|
||||
import { defineConfig } from 'cypress'
|
||||
import { devServer } from '@cypress/webpack-dev-server'
|
||||
import webpackConfig from '@vue/cli-service/webpack.config'
|
||||
|
||||
export default defineConfig({
|
||||
component: {
|
||||
devServer,
|
||||
devServerConfig: {
|
||||
webpackConfig
|
||||
}
|
||||
}
|
||||
})`
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'vueclivue3',
|
||||
name: 'Vue CLI (Vue 3)',
|
||||
family: 'template',
|
||||
supportedBundlers: [BUNDLER_WEBPACK_4],
|
||||
packages: [
|
||||
CYPRESS_VUE_3,
|
||||
BUNDLER_WEBPACK_4,
|
||||
],
|
||||
defaultPackagePath: null,
|
||||
glob: '*.vue',
|
||||
detectors: [
|
||||
{
|
||||
dependency: '@vue/cli-service',
|
||||
version: '^4.0.0',
|
||||
},
|
||||
{
|
||||
dependency: 'vue',
|
||||
version: '^3.0.0',
|
||||
},
|
||||
],
|
||||
category: FRONTEND_FRAMEWORK_CATEGORIES[1],
|
||||
codeGenFramework: CODE_GEN_FRAMEWORKS[1],
|
||||
storybookDep: STORYBOOK_VUE,
|
||||
config: {
|
||||
js: (bundler: Bundler) => {
|
||||
return dedent`
|
||||
const { devServer } = require('@cypress/webpack-dev-server')
|
||||
const webpackConfig = require('@vue/cli-service/webpack.config')
|
||||
|
||||
module.exports = {
|
||||
component: {
|
||||
devServer,
|
||||
devServerConfig: {
|
||||
webpackConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
},
|
||||
ts: () => {
|
||||
return dedent`
|
||||
import { defineConfig } from 'cypress'
|
||||
import { devServer } from '@cypress/webpack-dev-server'
|
||||
import webpackConfig from '@vue/cli-service/webpack.config'
|
||||
|
||||
export default defineConfig({
|
||||
component: {
|
||||
devServer,
|
||||
devServerConfig: {
|
||||
webpackConfig
|
||||
}
|
||||
}
|
||||
})`
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'react',
|
||||
name: 'React.js',
|
||||
family: 'library',
|
||||
supportedBundlers: [BUNDLER_WEBPACK_4, BUNDLER_WEBPACK_5, BUNDLER_VITE],
|
||||
packages: [CYPRESS_REACT_LATEST],
|
||||
defaultPackagePath: null,
|
||||
glob: '*.{js,jsx,tsx}',
|
||||
detectors: [
|
||||
{
|
||||
dependency: 'react',
|
||||
version: '>=16.0.0',
|
||||
},
|
||||
{
|
||||
dependency: 'react-dom',
|
||||
version: '>=16.0.0',
|
||||
},
|
||||
],
|
||||
category: FRONTEND_FRAMEWORK_CATEGORIES[0],
|
||||
codeGenFramework: CODE_GEN_FRAMEWORKS[0],
|
||||
storybookDep: STORYBOOK_REACT,
|
||||
config: {
|
||||
js: (bundler: Bundler) => {
|
||||
if (isWebpack(bundler)) {
|
||||
return dedent`
|
||||
const { devServer } = require('@cypress/webpack-dev-server')
|
||||
// NOTE: ensure you are requiring your webpack config from the
|
||||
// correct location.
|
||||
const webpackConfig = require('./webpack.config.js')
|
||||
|
||||
module.exports = {
|
||||
component: {
|
||||
devServer,
|
||||
devServerConfig: {
|
||||
webpackConfig
|
||||
}
|
||||
}
|
||||
}`
|
||||
}
|
||||
|
||||
if (bundler === 'vite') {
|
||||
return dedent`
|
||||
const { devServer } = require('@cypress/vite-dev-server')
|
||||
|
||||
module.exports = {
|
||||
component: {
|
||||
devServer,
|
||||
devServerConfig: {
|
||||
// optionally provide your Vite config overrides.
|
||||
}
|
||||
}
|
||||
}`
|
||||
}
|
||||
|
||||
throw Error(`No config defined for ${bundler}`)
|
||||
},
|
||||
|
||||
ts: (bundler: Bundler) => {
|
||||
if (isWebpack(bundler)) {
|
||||
return dedent`
|
||||
import { defineConfig } from 'cypress'
|
||||
import { devServer } from '@cypress/webpack-dev-server'
|
||||
// NOTE: ensure you are requiring your webpack config from the
|
||||
// correct location.
|
||||
import webpackConfig from './webpack.config.js'
|
||||
|
||||
export default defineConfig({
|
||||
component: {
|
||||
devServer,
|
||||
devServerConfig: {
|
||||
webpackConfig
|
||||
}
|
||||
}
|
||||
})`
|
||||
}
|
||||
|
||||
if (bundler === 'vite') {
|
||||
return dedent`
|
||||
import { defineConfig } from 'cypress'
|
||||
import { devServer } from '@cypress/vite-dev-server'
|
||||
|
||||
export default defineConfig({
|
||||
component: {
|
||||
devServer,
|
||||
// optionally provide your Vite config overrides.
|
||||
devServerConfig: {}
|
||||
}
|
||||
})`
|
||||
}
|
||||
|
||||
throw Error(`No config defined for ${bundler}`)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
type: 'vue2',
|
||||
name: 'Vue.js (v2)',
|
||||
family: 'library',
|
||||
supportedBundlers: [BUNDLER_WEBPACK_4, BUNDLER_WEBPACK_5, BUNDLER_VITE],
|
||||
packages: [CYPRESS_VUE_2],
|
||||
defaultPackagePath: null,
|
||||
glob: '*.vue',
|
||||
detectors: [
|
||||
{
|
||||
dependency: 'vue',
|
||||
version: '^2.0.0',
|
||||
},
|
||||
],
|
||||
category: FRONTEND_FRAMEWORK_CATEGORIES[1],
|
||||
codeGenFramework: CODE_GEN_FRAMEWORKS[1],
|
||||
storybookDep: null,
|
||||
config: {
|
||||
js: (bundler: Bundler) => {
|
||||
if (isWebpack(bundler)) {
|
||||
return dedent`
|
||||
const { devServer } = require('@cypress/webpack-dev-server')
|
||||
// NOTE: ensure you are requiring your webpack config from the
|
||||
// correct location.
|
||||
const webpackConfig = require('./webpack.config.js')
|
||||
|
||||
module.exports = {
|
||||
component: {
|
||||
devServer,
|
||||
devServerConfig: {
|
||||
webpackConfig
|
||||
}
|
||||
}
|
||||
}`
|
||||
}
|
||||
|
||||
if (bundler === 'vite') {
|
||||
return dedent`
|
||||
const { devServer } = require('@cypress/vite-dev-server')
|
||||
|
||||
module.exports = {
|
||||
component: {
|
||||
devServer,
|
||||
devServerConfig: {
|
||||
// optionally provide your Vite config overrides.
|
||||
}
|
||||
}
|
||||
}`
|
||||
}
|
||||
|
||||
throw Error(`No config defined for ${bundler}`)
|
||||
},
|
||||
|
||||
ts: (bundler: Bundler) => {
|
||||
if (isWebpack(bundler)) {
|
||||
return dedent`
|
||||
import { defineConfig } from 'cypress'
|
||||
import { devServer } from '@cypress/webpack-dev-server'
|
||||
import webpackConfig from '@vue/cli-service/webpack.config'
|
||||
|
||||
export default defineConfig({
|
||||
component: {
|
||||
devServer,
|
||||
devServerConfig: {
|
||||
webpackConfig
|
||||
}
|
||||
}
|
||||
})`
|
||||
}
|
||||
|
||||
if (bundler === 'vite') {
|
||||
return dedent`
|
||||
import { defineConfig } from 'cypress'
|
||||
import { devServer } from '@cypress/vite-dev-server'
|
||||
|
||||
export default defineConfig({
|
||||
component: {
|
||||
devServer,
|
||||
// optionally provide your Vite config overrides.
|
||||
devServerConfig: {}
|
||||
}
|
||||
})`
|
||||
}
|
||||
|
||||
throw Error(`No config defined for ${bundler}`)
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
type: 'vue3',
|
||||
name: 'Vue.js (v3)',
|
||||
family: 'library',
|
||||
supportedBundlers: [BUNDLER_WEBPACK_4, BUNDLER_WEBPACK_5, BUNDLER_VITE],
|
||||
packages: [CYPRESS_VUE_3],
|
||||
defaultPackagePath: null,
|
||||
glob: '*.vue',
|
||||
detectors: [
|
||||
{
|
||||
dependency: 'vue',
|
||||
version: '^3.0.0',
|
||||
},
|
||||
],
|
||||
category: FRONTEND_FRAMEWORK_CATEGORIES[1],
|
||||
codeGenFramework: CODE_GEN_FRAMEWORKS[1],
|
||||
storybookDep: STORYBOOK_VUE,
|
||||
config: {
|
||||
js: (bundler: Bundler) => {
|
||||
if (isWebpack(bundler)) {
|
||||
return dedent`
|
||||
const { devServer } = require('@cypress/webpack-dev-server')
|
||||
// NOTE: ensure you are requiring your webpack config from the
|
||||
// correct location.
|
||||
const webpackConfig = require('./webpack.config.js')
|
||||
|
||||
module.exports = {
|
||||
component: {
|
||||
devServer,
|
||||
devServerConfig: {
|
||||
webpackConfig
|
||||
}
|
||||
}
|
||||
}`
|
||||
}
|
||||
|
||||
if (bundler === 'vite') {
|
||||
return dedent`
|
||||
const { devServer } = require('@cypress/vite-dev-server')
|
||||
|
||||
module.exports = {
|
||||
component: {
|
||||
devServer,
|
||||
devServerConfig: {
|
||||
// optionally provide your Vite config overrides.
|
||||
}
|
||||
}
|
||||
}`
|
||||
}
|
||||
|
||||
throw Error(`No config defined for ${bundler}`)
|
||||
},
|
||||
|
||||
ts: (bundler: Bundler) => {
|
||||
if (isWebpack(bundler)) {
|
||||
return dedent`
|
||||
import { defineConfig } from 'cypress'
|
||||
import { devServer } from '@cypress/webpack-dev-server'
|
||||
import webpackConfig from '@vue/cli-service/webpack.config'
|
||||
|
||||
export default defineConfig({
|
||||
component: {
|
||||
devServer,
|
||||
devServerConfig: {
|
||||
webpackConfig
|
||||
}
|
||||
}
|
||||
})`
|
||||
}
|
||||
|
||||
if (bundler === 'vite') {
|
||||
return dedent`
|
||||
import { defineConfig } from 'cypress'
|
||||
import { devServer } from '@cypress/vite-dev-server'
|
||||
|
||||
export default defineConfig({
|
||||
component: {
|
||||
devServer,
|
||||
// optionally provide your Vite config overrides.
|
||||
devServerConfig: {}
|
||||
}
|
||||
})`
|
||||
}
|
||||
|
||||
throw Error(`No config defined for ${bundler}`)
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'nextjs',
|
||||
name: 'Next.js',
|
||||
family: 'template',
|
||||
supportedBundlers: [BUNDLER_WEBPACK_4],
|
||||
packages: [
|
||||
CYPRESS_REACT_LATEST,
|
||||
BUNDLER_WEBPACK_4,
|
||||
],
|
||||
defaultPackagePath: '@cypress/react/plugins/next',
|
||||
glob: '*.{js,jsx,tsx}',
|
||||
category: FRONTEND_FRAMEWORK_CATEGORIES[0],
|
||||
detectors: [
|
||||
{
|
||||
dependency: 'next',
|
||||
version: '>=10.0.0',
|
||||
},
|
||||
],
|
||||
codeGenFramework: CODE_GEN_FRAMEWORKS[0],
|
||||
storybookDep: STORYBOOK_REACT,
|
||||
config: {
|
||||
js: () => ``,
|
||||
ts: () => ``,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'nuxtjs',
|
||||
name: 'Nuxt.js (v2)',
|
||||
family: 'template',
|
||||
supportedBundlers: [BUNDLER_WEBPACK_4],
|
||||
packages: [
|
||||
CYPRESS_VUE_2,
|
||||
BUNDLER_WEBPACK_4,
|
||||
],
|
||||
defaultPackagePath: null,
|
||||
glob: '*.vue',
|
||||
detectors: [
|
||||
{
|
||||
dependency: 'nuxt',
|
||||
version: '^2.0.0',
|
||||
},
|
||||
],
|
||||
category: FRONTEND_FRAMEWORK_CATEGORIES[1],
|
||||
codeGenFramework: CODE_GEN_FRAMEWORKS[1],
|
||||
storybookDep: null,
|
||||
config: {
|
||||
js: () => {
|
||||
return dedent`
|
||||
const { defineConfig } = require("cypress")
|
||||
const { devServer } = require("@cypress/webpack-dev-server")
|
||||
const { getWebpackConfig } = require("nuxt")
|
||||
|
||||
module.exports = defineConfig({
|
||||
component: {
|
||||
async devServer(cypressDevServerConfig) {
|
||||
const webpackConfig = await getWebpackConfig()
|
||||
|
||||
return devServer(cypressDevServerConfig, { webpackConfig })
|
||||
}
|
||||
}
|
||||
})`
|
||||
},
|
||||
ts: () => {
|
||||
return dedent`
|
||||
import { defineConfig } from "cypress"
|
||||
import { devServer } from "@cypress/webpack-dev-server"
|
||||
import { getWebpackConfig } from "nuxt"
|
||||
|
||||
export default defineConfig({
|
||||
component: {
|
||||
async devServer(cypressDevServerConfig) {
|
||||
const webpackConfig = await getWebpackConfig()
|
||||
|
||||
return devServer(cypressDevServerConfig, { webpackConfig })
|
||||
}
|
||||
}
|
||||
})`
|
||||
},
|
||||
},
|
||||
},
|
||||
] as const
|
||||
7
packages/scaffold-config/src/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/* eslint-disable padding-line-between-statements */
|
||||
// created by autobarrel, do not modify directly
|
||||
|
||||
export * from './constants'
|
||||
export * from './detect'
|
||||
export * from './frameworks'
|
||||
export * from './types'
|
||||
16
packages/scaffold-config/src/types.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { BUNDLERS, STORYBOOK_DEPS, CODE_GEN_FRAMEWORKS, DEPENDENCIES } from './constants'
|
||||
import type { FRONTEND_FRAMEWORKS } from './frameworks'
|
||||
|
||||
export type AllPackages = FrontendFramework['packages'][number]
|
||||
| typeof BUNDLERS[number]['package']
|
||||
| typeof STORYBOOK_DEPS[number]
|
||||
|
||||
export type AllPackagePackages = typeof DEPENDENCIES[number]['package']
|
||||
|
||||
export type Bundler = typeof BUNDLERS[number]['type']
|
||||
|
||||
export type CodeGenFramework = typeof CODE_GEN_FRAMEWORKS[number]
|
||||
|
||||
export type FrontendFramework = typeof FRONTEND_FRAMEWORKS[number]
|
||||
|
||||
export type PkgJson = { dependencies?: Record<string, string>, devDependencies?: Record<string, string> }
|
||||
1
packages/scaffold-config/test/.mocharc.js
Normal file
@@ -0,0 +1 @@
|
||||
module.exports = {}
|
||||
95
packages/scaffold-config/test/unit/detect.spec.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
import { expect } from 'chai'
|
||||
import { e2eProjectDirs } from '@packages/frontend-shared/cypress/e2e/support/e2eProjectDirs'
|
||||
import { detect } from '../../src'
|
||||
import Fixtures from '@tooling/system-tests/lib/fixtures'
|
||||
import path from 'path'
|
||||
import fs from 'fs-extra'
|
||||
|
||||
export async function scaffoldMigrationProject (project: typeof e2eProjectDirs[number]) {
|
||||
Fixtures.removeProject(project)
|
||||
|
||||
await Fixtures.scaffoldProject(project)
|
||||
|
||||
return fs.readJSONSync(path.join(Fixtures.projectPath(project), 'package.json'))
|
||||
}
|
||||
|
||||
describe('detect', () => {
|
||||
it('Create React App v4', async () => {
|
||||
const pkg = await scaffoldMigrationProject('create-react-app-unconfigured')
|
||||
const actual = detect({ ...pkg, dependencies: { ...pkg.dependencies, 'react-scripts': '4.0.0' } })
|
||||
|
||||
expect(actual.framework.type).to.eq('crav4')
|
||||
})
|
||||
|
||||
it('Create React App v5', async () => {
|
||||
const pkg = await scaffoldMigrationProject('create-react-app-unconfigured')
|
||||
const actual = detect(pkg)
|
||||
|
||||
expect(actual.framework.type).to.eq('crav5')
|
||||
})
|
||||
|
||||
it('React App with webpack 5', async () => {
|
||||
const pkg = await scaffoldMigrationProject('react-app-webpack-5-unconfigured')
|
||||
const actual = detect(pkg)
|
||||
|
||||
expect(actual.framework.type).to.eq('react')
|
||||
expect(actual.bundler).to.eq('webpack5')
|
||||
})
|
||||
|
||||
it(`Vue CLI w/ Vue 2`, async () => {
|
||||
const pkg = await scaffoldMigrationProject('vueclivue2-unconfigured')
|
||||
const actual = detect(pkg)
|
||||
|
||||
expect(actual.framework.type).to.eq('vueclivue2')
|
||||
})
|
||||
|
||||
it(`Vue CLI w/ Vue 3`, async () => {
|
||||
const pkg = await scaffoldMigrationProject('vueclivue3-unconfigured')
|
||||
const actual = detect(pkg)
|
||||
|
||||
expect(actual.framework.type).to.eq('vueclivue3')
|
||||
})
|
||||
|
||||
it(`React with Vite`, async () => {
|
||||
const pkg = await scaffoldMigrationProject('react-vite-ts-unconfigured')
|
||||
const actual = detect(pkg)
|
||||
|
||||
expect(actual.framework.type).to.eq('react')
|
||||
expect(actual.bundler).to.eq('vite')
|
||||
})
|
||||
|
||||
it(`Vue with Vite`, async () => {
|
||||
const pkg = await scaffoldMigrationProject('vue3-vite-ts-unconfigured')
|
||||
const actual = detect(pkg)
|
||||
|
||||
expect(actual.framework.type).to.eq('vue3')
|
||||
expect(actual.bundler).to.eq('vite')
|
||||
})
|
||||
|
||||
it(`Next.js`, async () => {
|
||||
const pkg = await scaffoldMigrationProject('nextjs-unconfigured')
|
||||
const actual = detect(pkg)
|
||||
|
||||
expect(actual.framework.type).to.eq('nextjs')
|
||||
expect(actual.bundler).to.eq(undefined)
|
||||
})
|
||||
|
||||
;['10', '11', '12'].forEach((v) => {
|
||||
it(`Next.js v${v}`, async () => {
|
||||
const pkg = await scaffoldMigrationProject('nextjs-unconfigured')
|
||||
const actual = detect({ ...pkg, dependencies: {
|
||||
'next': v,
|
||||
} })
|
||||
|
||||
expect(actual.framework.type).to.eq('nextjs')
|
||||
expect(actual.bundler).to.eq(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
it(`no framework or library`, async () => {
|
||||
const actual = detect({})
|
||||
|
||||
expect(actual.framework).to.be.undefined
|
||||
expect(actual.bundler).to.be.undefined
|
||||
})
|
||||
})
|
||||
20
packages/scaffold-config/tsconfig.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"extends": "../ts/tsconfig.json",
|
||||
"include": ["src"],
|
||||
"exclude": [
|
||||
"test",
|
||||
"script"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"lib": ["es2020"],
|
||||
"strict": true,
|
||||
"allowJs": false,
|
||||
"noImplicitAny": true,
|
||||
"resolveJsonModule": true,
|
||||
"experimentalDecorators": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"importsNotUsedAsValues": "error",
|
||||
"types": [],
|
||||
}
|
||||
}
|
||||
@@ -2,109 +2,6 @@ export const PLUGINS_STATE = ['uninitialized', 'initializing', 'initialized', 'e
|
||||
|
||||
export type PluginsState = typeof PLUGINS_STATE[number]
|
||||
|
||||
export const BUNDLERS = [
|
||||
{
|
||||
type: 'webpack',
|
||||
name: 'Webpack',
|
||||
package: '@cypress/webpack-dev-server',
|
||||
},
|
||||
{
|
||||
type: 'vite',
|
||||
name: 'Vite',
|
||||
package: '@cypress/vite-dev-server',
|
||||
},
|
||||
] as const
|
||||
|
||||
export type Bundler = typeof BUNDLERS[number]
|
||||
|
||||
export const CODE_GEN_FRAMEWORKS = ['react', 'vue'] as const
|
||||
|
||||
export type CodeGenFramework = typeof CODE_GEN_FRAMEWORKS[number]
|
||||
|
||||
export const FRONTEND_FRAMEWORK_CATEGORIES = ['react', 'vue'] as const
|
||||
|
||||
export const STORYBOOK_DEPS = [
|
||||
'@storybook/testing-react',
|
||||
'@storybook/testing-vue3',
|
||||
] as const
|
||||
|
||||
export const FRONTEND_FRAMEWORKS = [
|
||||
{
|
||||
type: 'cra',
|
||||
name: 'Create React App',
|
||||
supportedBundlers: ['webpack'] as readonly Bundler['type'][],
|
||||
package: '@cypress/react',
|
||||
defaultPackagePath: '@cypress/react/plugins/react-scripts',
|
||||
glob: '*.{jsx,tsx}',
|
||||
deps: ['react-scripts', 'react', 'react-dom'],
|
||||
category: FRONTEND_FRAMEWORK_CATEGORIES[0],
|
||||
codeGenFramework: CODE_GEN_FRAMEWORKS[0],
|
||||
storybookDep: STORYBOOK_DEPS[0],
|
||||
},
|
||||
{
|
||||
type: 'vuecli',
|
||||
name: 'Vue CLI',
|
||||
supportedBundlers: ['webpack'] as readonly Bundler['type'][],
|
||||
package: '@cypress/vue',
|
||||
defaultPackagePath: null,
|
||||
glob: '*.vue',
|
||||
deps: ['@vue/cli-service', 'vue'],
|
||||
category: FRONTEND_FRAMEWORK_CATEGORIES[1],
|
||||
codeGenFramework: CODE_GEN_FRAMEWORKS[1],
|
||||
storybookDep: STORYBOOK_DEPS[1],
|
||||
},
|
||||
{
|
||||
type: 'react',
|
||||
name: 'React.js',
|
||||
supportedBundlers: ['webpack', 'vite'] as readonly Bundler['type'][],
|
||||
package: '@cypress/react',
|
||||
defaultPackagePath: null,
|
||||
glob: '*.{jsx,tsx}',
|
||||
deps: ['react', 'react-dom'],
|
||||
category: FRONTEND_FRAMEWORK_CATEGORIES[0],
|
||||
codeGenFramework: CODE_GEN_FRAMEWORKS[0],
|
||||
storybookDep: STORYBOOK_DEPS[0],
|
||||
},
|
||||
{
|
||||
type: 'vue',
|
||||
name: 'Vue.js',
|
||||
supportedBundlers: ['webpack', 'vite'] as readonly Bundler['type'][],
|
||||
package: '@cypress/vue',
|
||||
defaultPackagePath: null,
|
||||
glob: '*.vue',
|
||||
deps: ['vue'],
|
||||
category: FRONTEND_FRAMEWORK_CATEGORIES[1],
|
||||
codeGenFramework: CODE_GEN_FRAMEWORKS[1],
|
||||
storybookDep: STORYBOOK_DEPS[1],
|
||||
},
|
||||
{
|
||||
type: 'nextjs',
|
||||
name: 'Next.js',
|
||||
supportedBundlers: ['webpack'] as readonly Bundler['type'][],
|
||||
package: '@cypress/react',
|
||||
defaultPackagePath: '@cypress/react/plugins/next',
|
||||
glob: '*.{jsx,tsx}',
|
||||
deps: ['next', 'react', 'react-dom'],
|
||||
category: FRONTEND_FRAMEWORK_CATEGORIES[0],
|
||||
codeGenFramework: CODE_GEN_FRAMEWORKS[0],
|
||||
storybookDep: STORYBOOK_DEPS[0],
|
||||
},
|
||||
{
|
||||
type: 'nuxtjs',
|
||||
name: 'Nuxt.js',
|
||||
supportedBundlers: ['webpack'] as readonly Bundler['type'][],
|
||||
package: '@cypress/vue',
|
||||
defaultPackagePath: null,
|
||||
glob: '*.vue',
|
||||
deps: ['nuxt'],
|
||||
category: FRONTEND_FRAMEWORK_CATEGORIES[1],
|
||||
codeGenFramework: CODE_GEN_FRAMEWORKS[1],
|
||||
storybookDep: STORYBOOK_DEPS[1],
|
||||
},
|
||||
] as const
|
||||
|
||||
export type FrontendFramework = typeof FRONTEND_FRAMEWORKS[number]
|
||||
|
||||
export const CODE_LANGUAGES = [
|
||||
{
|
||||
type: 'js',
|
||||
@@ -120,17 +17,4 @@ export type CodeLanguage = typeof CODE_LANGUAGES[number]
|
||||
|
||||
export const MIGRATION_STEPS = ['renameAuto', 'renameManual', 'renameSupport', 'configFile', 'setupComponent'] as const
|
||||
|
||||
export type AllPackages = FrontendFramework['package'] | Bundler['package'] | typeof STORYBOOK_DEPS[number]
|
||||
|
||||
export type AllPackageTypes = FrontendFramework['type'] | Bundler['type']
|
||||
|
||||
export const PACKAGES_DESCRIPTIONS: Record<AllPackages, string> = {
|
||||
'@cypress/vue': 'Allows Cypress to mount each Vue component using <span class="text-purple-400">cy.mount()</span>',
|
||||
'@cypress/react': 'Allows Cypress to mount each React component using <span class="text-purple-400">cy.mount()</span>',
|
||||
'@cypress/webpack-dev-server': 'Allows Cypress to use your existing build configuration in order to bundle and run your tests',
|
||||
'@cypress/vite-dev-server': 'Allows Cypress to use your existing build configuration in order to bundle and run your tests',
|
||||
'@storybook/testing-react': 'Testing utilities that allow you to reuse your stories in your unit tests',
|
||||
'@storybook/testing-vue3': 'Testing utilities that allow you to reuse your stories in your unit tests',
|
||||
} as const
|
||||
|
||||
export const PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm'] as const
|
||||
|
||||
@@ -14,11 +14,6 @@ export * from './editors'
|
||||
|
||||
export * from './auth'
|
||||
|
||||
export type {
|
||||
AllPackages,
|
||||
AllPackageTypes,
|
||||
} from './constants'
|
||||
|
||||
export * from './browser'
|
||||
|
||||
export type { PlatformName } from './platform'
|
||||
|
||||
@@ -27,6 +27,7 @@ export const monorepoPaths = {
|
||||
pkgRunner: path.join(__dirname, '../../packages/runner'),
|
||||
pkgRunnerCt: path.join(__dirname, '../../packages/runner-ct'),
|
||||
pkgRunnerShared: path.join(__dirname, '../../packages/runner-shared'),
|
||||
pkgScaffoldConfig: path.join(__dirname, '../../packages/scaffold-config'),
|
||||
pkgServer: path.join(__dirname, '../../packages/server'),
|
||||
pkgSocket: path.join(__dirname, '../../packages/socket'),
|
||||
pkgTs: path.join(__dirname, '../../packages/ts'),
|
||||
|
||||
23
system-tests/projects/create-react-app-configured/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
70
system-tests/projects/create-react-app-configured/README.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `yarn start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
||||
|
||||
The page will reload when you make changes.\
|
||||
You may also see any lint errors in the console.
|
||||
|
||||
### `yarn test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `yarn build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `yarn eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
|
||||
|
||||
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
|
||||
|
||||
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
||||
|
||||
### Deployment
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
||||
|
||||
### `yarn build` fails to minify
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
||||
@@ -0,0 +1,7 @@
|
||||
const { devServer } = require('@cypress/react/plugins/react-scripts')
|
||||
|
||||
module.exports = {
|
||||
component: {
|
||||
devServer,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "create-react-app-unconfigured",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cypress/react": "^5.0.0",
|
||||
"@cypress/webpack-dev-server": "^1.8.1",
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.0.0",
|
||||
"@testing-library/user-event": "^13.2.1",
|
||||
"html-webpack-plugin": "^4.0.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "4.0.0",
|
||||
"webpack": "^4.0.0",
|
||||
"webpack-dev-server": "^4.0.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -0,0 +1,38 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { mount } from '@cypress/react'
|
||||
import React from 'react'
|
||||
import App from './App'
|
||||
|
||||
it('works', () => {
|
||||
mount(<App />)
|
||||
cy.contains('Learn React')
|
||||
})
|
||||
25
system-tests/projects/create-react-app-configured/src/App.js
Normal file
@@ -0,0 +1,25 @@
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.js</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
11934
system-tests/projects/create-react-app-configured/yarn.lock
Normal file
23
system-tests/projects/create-react-app-unconfigured/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
@@ -0,0 +1,70 @@
|
||||
# Getting Started with Create React App
|
||||
|
||||
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
|
||||
|
||||
## Available Scripts
|
||||
|
||||
In the project directory, you can run:
|
||||
|
||||
### `yarn start`
|
||||
|
||||
Runs the app in the development mode.\
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
||||
|
||||
The page will reload when you make changes.\
|
||||
You may also see any lint errors in the console.
|
||||
|
||||
### `yarn test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.\
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `yarn build`
|
||||
|
||||
Builds the app for production to the `build` folder.\
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.\
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
||||
### `yarn eject`
|
||||
|
||||
**Note: this is a one-way operation. Once you `eject`, you can't go back!**
|
||||
|
||||
If you aren't satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
|
||||
|
||||
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you're on your own.
|
||||
|
||||
You don't have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn't feel obligated to use this feature. However we understand that this tool wouldn't be useful if you couldn't customize it when you are ready for it.
|
||||
|
||||
## Learn More
|
||||
|
||||
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
|
||||
|
||||
To learn React, check out the [React documentation](https://reactjs.org/).
|
||||
|
||||
### Code Splitting
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
|
||||
|
||||
### Analyzing the Bundle Size
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
|
||||
|
||||
### Making a Progressive Web App
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
|
||||
|
||||
### Advanced Configuration
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
|
||||
|
||||
### Deployment
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
|
||||
|
||||
### `yarn build` fails to minify
|
||||
|
||||
This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "create-react-app-unconfigured",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.14.1",
|
||||
"@testing-library/react": "^12.0.0",
|
||||
"@testing-library/user-event": "^13.2.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-scripts": "5.0.0",
|
||||
"web-vitals": "^2.1.0"
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"_cySkipYarnInstall": true
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -0,0 +1,38 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
.App-logo {
|
||||
animation: App-logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #61dafb;
|
||||
}
|
||||
|
||||
@keyframes App-logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.js</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import App from './App';
|
||||
|
||||
test('renders learn react link', () => {
|
||||
render(<App />);
|
||||
const linkElement = screen.getByText(/learn react/i);
|
||||
expect(linkElement).toBeInTheDocument();
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
|
||||
ReactDOM.render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,5 @@
|
||||
// jest-dom adds custom jest matchers for asserting on DOM nodes.
|
||||
// allows you to do things like:
|
||||
// expect(element).toHaveTextContent(/react/i)
|
||||
// learn more: https://github.com/testing-library/jest-dom
|
||||
import '@testing-library/jest-dom';
|
||||
0
system-tests/projects/nextjs-configured/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import { Button } from './button'
|
||||
|
||||
it('works', () => {
|
||||
mount(<Button />)
|
||||
cy.get('button').contains('Hello World')
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
import './button.module.css'
|
||||
|
||||
export const Button = () => {
|
||||
return (
|
||||
<button>Hello World</button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
.button {
|
||||
background: black;
|
||||
color: white;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
const { devServer } = require('@cypress/react/plugins/next')
|
||||
|
||||
module.exports = {
|
||||
component: {
|
||||
devServer
|
||||
}
|
||||
}
|
||||
|
||||
7
system-tests/projects/nextjs-configured/next.config.js
Normal file
@@ -0,0 +1,7 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
webpack5: false,
|
||||
reactStrictMode: true,
|
||||
}
|
||||
|
||||
module.exports = nextConfig
|
||||
24
system-tests/projects/nextjs-configured/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "pristine-nextjs-configured",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"start": "next start",
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"next": "^11.0.0",
|
||||
"react": "17.0.2",
|
||||
"react-dom": "17.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@cypress/react": "^5.0.0",
|
||||
"@cypress/webpack-dev-server": "^1.0.0",
|
||||
"eslint": "8.9.0",
|
||||
"eslint-config-next": "12.1.0",
|
||||
"html-webpack-plugin": "^4.0.0",
|
||||
"webpack-dev-server": "^4.0.0"
|
||||
}
|
||||
}
|
||||
7
system-tests/projects/nextjs-configured/pages/_app.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import '../styles/globals.css'
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
return <Component {...pageProps} />
|
||||
}
|
||||
|
||||
export default MyApp
|
||||
@@ -0,0 +1,5 @@
|
||||
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
|
||||
|
||||
export default function handler(req, res) {
|
||||
res.status(200).json({ name: 'John Doe' })
|
||||
}
|
||||
69
system-tests/projects/nextjs-configured/pages/index.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import Head from 'next/head'
|
||||
import Image from 'next/image'
|
||||
import styles from '../styles/Home.module.css'
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Head>
|
||||
<title>Create Next App</title>
|
||||
<meta name="description" content="Generated by create next app" />
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
|
||||
<main className={styles.main}>
|
||||
<h1 className={styles.title}>
|
||||
Welcome to <a href="https://nextjs.org">Next.js!</a>
|
||||
</h1>
|
||||
|
||||
<p className={styles.description}>
|
||||
Get started by editing{' '}
|
||||
<code className={styles.code}>pages/index.js</code>
|
||||
</p>
|
||||
|
||||
<div className={styles.grid}>
|
||||
<a href="https://nextjs.org/docs" className={styles.card}>
|
||||
<h2>Documentation →</h2>
|
||||
<p>Find in-depth information about Next.js features and API.</p>
|
||||
</a>
|
||||
|
||||
<a href="https://nextjs.org/learn" className={styles.card}>
|
||||
<h2>Learn →</h2>
|
||||
<p>Learn about Next.js in an interactive course with quizzes!</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://github.com/vercel/next.js/tree/canary/examples"
|
||||
className={styles.card}
|
||||
>
|
||||
<h2>Examples →</h2>
|
||||
<p>Discover and deploy boilerplate example Next.js projects.</p>
|
||||
</a>
|
||||
|
||||
<a
|
||||
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
||||
className={styles.card}
|
||||
>
|
||||
<h2>Deploy →</h2>
|
||||
<p>
|
||||
Instantly deploy your Next.js site to a public URL with Vercel.
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer className={styles.footer}>
|
||||
<a
|
||||
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Powered by{' '}
|
||||
<span className={styles.logo}>
|
||||
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
|
||||
</span>
|
||||
</a>
|
||||
</footer>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
BIN
system-tests/projects/nextjs-configured/public/favicon.ico
Normal file
|
After Width: | Height: | Size: 25 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
116
system-tests/projects/nextjs-configured/styles/Home.module.css
Normal file
@@ -0,0 +1,116 @@
|
||||
.container {
|
||||
padding: 0 2rem;
|
||||
}
|
||||
|
||||
.main {
|
||||
min-height: 100vh;
|
||||
padding: 4rem 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding: 2rem 0;
|
||||
border-top: 1px solid #eaeaea;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.footer a {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.title a {
|
||||
color: #0070f3;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.title a:hover,
|
||||
.title a:focus,
|
||||
.title a:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0;
|
||||
line-height: 1.15;
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.title,
|
||||
.description {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.description {
|
||||
margin: 4rem 0;
|
||||
line-height: 1.5;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.code {
|
||||
background: #fafafa;
|
||||
border-radius: 5px;
|
||||
padding: 0.75rem;
|
||||
font-size: 1.1rem;
|
||||
font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono,
|
||||
Bitstream Vera Sans Mono, Courier New, monospace;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 1rem;
|
||||
padding: 1.5rem;
|
||||
text-align: left;
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 10px;
|
||||
transition: color 0.15s ease, border-color 0.15s ease;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.card:hover,
|
||||
.card:focus,
|
||||
.card:active {
|
||||
color: #0070f3;
|
||||
border-color: #0070f3;
|
||||
}
|
||||
|
||||
.card h2 {
|
||||
margin: 0 0 1rem 0;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.card p {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 1em;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.grid {
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
16
system-tests/projects/nextjs-configured/styles/globals.css
Normal file
@@ -0,0 +1,16 @@
|
||||
html,
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
5149
system-tests/projects/nextjs-configured/yarn.lock
Normal file
0
system-tests/projects/nextjs-unconfigured/README.md
Normal file
@@ -0,0 +1,8 @@
|
||||
import React from 'react'
|
||||
import { mount } from '@cypress/react'
|
||||
import { Button } from './button'
|
||||
|
||||
it('works', () => {
|
||||
mount(<Button />)
|
||||
cy.get('button').contains('Hello World')
|
||||
})
|
||||
@@ -0,0 +1,7 @@
|
||||
import './button.module.css'
|
||||
|
||||
export const Button = () => {
|
||||
return (
|
||||
<button>Hello World</button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
.button {
|
||||
background: black;
|
||||
color: white;
|
||||
}
|
||||