fix(component-testing): Fix webpack-dev-server deps validation crash (#15708)

Co-authored-by: Lachlan Miller <lachlan.miller.1990@outlook.com>
Co-authored-by: ElevateBart <ledouxb@gmail.com>
Co-authored-by: Barthélémy Ledoux <bart@cypress.io>
This commit is contained in:
Dmitriy Kovalenko
2021-04-02 18:12:57 +03:00
committed by GitHub
parent cd66b05307
commit 254eb47d91
15 changed files with 811 additions and 2761 deletions

View File

@@ -1,4 +1,4 @@
export const MIN_SUPPORTED_VERSION = {
'react-scripts': '^=3.x || ^=4.x',
next: '^=9.x',
next: '^=9.x || ^=10.x',
}

View File

@@ -51,16 +51,22 @@ async function askForComponentTesting () {
return shouldSetupComponentTesting
}
function printCypressCommandsHelper ({ useYarn }: { useYarn: boolean }) {
const displayedCommand = useYarn ? 'yarn' : 'npx'
function printCypressCommandsHelper (options: { shouldSetupComponentTesting: boolean, useYarn: boolean }) {
const printCommand = (command: string, description: string) => {
const displayedRunner = options.useYarn ? 'yarn' : 'npx'
console.log('Inside this directory, you can run several commands:')
console.log()
console.log(chalk.cyan(` ${displayedCommand} cypress open`))
console.log(' Opens cypress local development app.')
console.log()
console.log(chalk.cyan(` ${displayedCommand} cypress run`))
console.log(' Runs tests in headless mode.')
console.log()
console.log(chalk.cyan(` ${displayedRunner} ${command}`))
console.log(` ${description}`)
}
printCommand('cypress open', 'Opens cypress local development app.')
printCommand('cypress run', 'Runs tests in headless mode.')
if (options.shouldSetupComponentTesting) {
printCommand('cypress open-ct', 'Opens cypress component-testing web app.')
printCommand('cypress run', 'Runs component testing in headless mode.')
}
}
export async function main ({ useNpm, ignoreTs, setupComponentTesting, ignoreExamples }: MainArgv) {
@@ -92,7 +98,7 @@ export async function main ({ useNpm, ignoreTs, setupComponentTesting, ignoreExa
}
console.log(`\n👍 Success! Cypress is installed and ready to run tests.`)
printCypressCommandsHelper({ useYarn })
printCypressCommandsHelper({ useYarn, shouldSetupComponentTesting })
console.log(`\nHappy testing with ${chalk.green('cypress.io')} 🌲\n`)
}

View File

@@ -2,7 +2,6 @@
/// <reference types="cypress" />
import * as React from 'react'
import RouterPage from '../../pages/router'
import { createRouter } from 'next/router'
import { RouterContext } from 'next/dist/next-server/lib/router-context'
import { mount } from '@cypress/react'
@@ -22,6 +21,9 @@ describe('Component with router usage', () => {
reload: cy.spy(),
back: cy.spy(),
prefetch: cy.spy(),
isReady: true,
isPreview: false,
isLocaleDomain: false,
beforePopState: cy.spy(),
}
@@ -34,33 +36,6 @@ describe('Component with router usage', () => {
cy.contains('Next.js route /testPath')
})
it('renders the component that uses next.js with parsed query', () => {
// alternatively you can use next's internal `createRouter` function to create a real instance of NextRouter
const router = createRouter(
'/testPath',
{ param1: 'param1' },
'/asTestPath',
{
subscription: cy.spy(),
initialProps: {},
App: cy.spy(),
Component: cy.spy(),
pageLoader: cy.spy(),
initialStyleSheets: [],
wrapApp: cy.spy(),
isFallback: false,
},
)
mount(
<RouterContext.Provider value={router}>
<RouterPage />
</RouterContext.Provider>,
)
cy.contains('My query: {"param1":"param1"}')
})
it('pushes the new route', () => {
const router = {
pathname: '/router',
@@ -76,6 +51,9 @@ describe('Component with router usage', () => {
reload: cy.spy(),
back: cy.spy(),
prefetch: cy.spy(),
isReady: true,
isPreview: false,
isLocaleDomain: false,
beforePopState: cy.spy(),
}

View File

@@ -15,12 +15,11 @@
"devDependencies": {
"@cypress/react": "file:../../dist",
"@mdx-js/loader": "^1.6.16",
"@next/mdx": "10.0.7",
"@next/mdx": "10.1.2",
"@zeit/next-sass": "^1.0.1",
"check-code-coverage": "1.9.2",
"cypress-circleci-reporter": "0.2.0",
"next": "10.0.7",
"webpack": "^4.44.2"
"next": "10.1.2"
},
"license": "MIT"
}

File diff suppressed because it is too large Load Diff

View File

@@ -69,7 +69,7 @@
"lodash": "4.17.15",
"mobx": "6.0.0",
"mobx-react-lite": "3.0.0",
"next": "^9.5.3",
"next": "^10.1.2",
"pretty": "2.0.0",
"prop-types": "15.7.2",
"radioactive-state": "1.3.4",

View File

@@ -1,4 +1,5 @@
// @ts-check
/// <reference types="next" />
const debug = require('debug')('@cypress/react')
const loadConfig = require('next/dist/next-server/server/config').default
const getNextJsBaseWebpackConfig = require('next/dist/build/webpack-config').default
@@ -14,7 +15,7 @@ async function getNextWebpackConfig (config) {
isServer: false,
pagesDir: config.projectRoot,
entrypoints: {},
rewrites: [],
rewrites: { fallback: [], afterFiles: [], beforeFiles: [] },
},
)

View File

@@ -1,5 +0,0 @@
const { validatePeerDependencies } = require('./dist/errors')
validatePeerDependencies()
module.exports = require('./dist')

View File

@@ -2,7 +2,7 @@
"name": "@cypress/webpack-dev-server",
"version": "0.0.0-development",
"description": "Launches Webpack Dev Server for Component Testing",
"main": "index.js",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"build-prod": "tsc",
@@ -25,9 +25,9 @@
"webpack-dev-server": "^3.11.0"
},
"peerDependencies": {
"html-webpack-plugin": "> 3",
"webpack": "> 4",
"webpack-dev-server": "> 3"
"html-webpack-plugin": ">=4",
"webpack": ">=4",
"webpack-dev-server": ">=4"
},
"files": [
"dist",

View File

@@ -1,80 +0,0 @@
export interface MissingDependency {
prettyName: string
packageName: string
}
const peerDeps: MissingDependency[] = [
{
prettyName: 'Webpack Dev Server',
packageName: 'webpack-dev-server',
},
{
prettyName: 'Html Webpack Plugin',
packageName: 'html-webpack-plugin',
},
{
prettyName: 'Webpack',
packageName: 'webpack',
},
]
const makePrettyLibs = (libs) => {
return libs.reduce((acc, curr, idx) => {
acc.prettyNames.push(curr.prettyName)
acc.packageNames.push(curr.packageName)
if (idx >= libs.length) return acc
return {
prettyNames: acc.prettyNames.join(', '),
packageNames: acc.packageNames.join(' '),
}
}, { prettyNames: [], packageNames: [] })
}
export class MissingPeerDependency extends Error {
private libs: { prettyNames: string[], packageNames: string[] }
constructor (message,
libs: MissingDependency[]) {
const prettyLibs = makePrettyLibs(libs)
super(`${message} ${prettyLibs.prettyNames}`)
this.name = 'PeerDependencyMissing'
this.libs = prettyLibs
Object.setPrototypeOf(this, MissingPeerDependency.prototype)
}
get prettyMessage () {
return `❌ Missing ${this.libs.prettyNames}. Please install them with npm or yarn.
npm i ${this.libs.packageNames} -D
yarn add ${this.libs.packageNames} --dev
Updating webpack config is unnecessary
`
}
}
export function validatePeerDependencies () {
const missingPeerDeps = peerDeps.filter((peerDep) => {
try {
require(peerDep.packageName)
} catch (err) {
return true
}
return false
})
if (missingPeerDeps.length) {
const error = new MissingPeerDependency(`@cypress/webpack-dev-server is missing peer dependencies`, missingPeerDeps)
console.error(error.prettyMessage) // eslint-disable-line
throw error
}
return true
}

View File

@@ -1,7 +1,6 @@
import { debug as debugFn } from 'debug'
import { AddressInfo } from 'net'
import { start as createDevServer } from './startServer'
import { UserWebpackDevServerOptions } from './makeWebpackConfig'
import { start as createDevServer, StartDevServer } from './startServer'
const debug = debugFn('cypress:webpack-dev-server:webpack')
@@ -12,12 +11,7 @@ export interface ResolvedDevServerConfig {
close: (done?: DoneCallback) => void
}
export interface StartDevServer extends UserWebpackDevServerOptions {
/* this is the Cypress options object */
options: Cypress.DevServerOptions
/* support passing a path to the user's webpack config */
webpackConfig?: Record<string, any>
}
export { StartDevServer }
export async function startDevServer (startDevServerArgs: StartDevServer, exitProcess = process.exit) {
const webpackDevServer = await createDevServer(startDevServerArgs, exitProcess)

View File

@@ -22,7 +22,7 @@ interface MakeWebpackConfigOptions extends CypressCTOptionsPluginOptions, UserWe
isOpenMode: boolean
}
const mergePublicPath = (baseValue: string, userValue = '/') => {
const mergePublicPath = (baseValue = '/', userValue = '/') => {
return path.join(baseValue, userValue, '/')
}

View File

@@ -1,8 +1,14 @@
import Debug from 'debug'
import webpack from 'webpack'
import WebpackDevServer from 'webpack-dev-server'
import { StartDevServer } from '.'
import { makeWebpackConfig } from './makeWebpackConfig'
import { makeWebpackConfig, UserWebpackDevServerOptions } from './makeWebpackConfig'
export interface StartDevServer extends UserWebpackDevServerOptions {
/* this is the Cypress options object */
options: Cypress.DevServerOptions
/* support passing a path to the user's webpack config */
webpackConfig?: Record<string, any>
}
const debug = Debug('cypress:webpack-dev-server:start')
@@ -40,19 +46,6 @@ export async function start ({ webpackConfig: userWebpackConfig, options, ...use
debug('starting webpack dev server')
// TODO: write a test for how we are NOT modifying publicPath
// here, and instead stripping it out of the cypress proxy layer
//
// ...this prevents a problem if users have a 'before' or 'after'
// function defined in their webpack config, it does NOT
// interfere with their routes... otherwise the public
// path we are prefixing like /__cypress/src/ would be
// prepended to req.url and cause their routing handlers to fail
//
// NOTE: we are merging in webpackConfig.devServer here so
// that user values for the devServer get passed on correctly
// since we are passing in the compiler directly, and these
// devServer options would otherwise get ignored
const webpackDevServerConfig = {
...userWebpackConfig.devServer,
hot: false,

View File

@@ -14,6 +14,6 @@ module.exports = {
path: path.resolve(__dirname, 'dist'),
},
plugins: [new HtmlWebpackPlugin({
template: path.join(__dirname, '../index-template.html'),
template: path.resolve(__dirname, '..', 'index-template.html'),
})],
}

1132
yarn.lock

File diff suppressed because it is too large Load Diff