Issue 1042 (#1057)

* server: remove unused file

* server: WIP, start implementing accepting plugin returning promise and yielding config overrides [skip ci]

* server: cleanup the env + environentVariables disaster. simplify and only use 'env'

* server, desktop-gum: rename 'env' to 'cypressEnv' to avoid conflicts, fix failing tests

* server: modify plugins error content to be clearer

* runner, driver: more environmentVariables -> env cleanup

* fixes #509 return the complete configuration object to Cypress.config()

* fixes #1042 enable plugins to return a promise and modify config

* desktop-gui: add 'plugin' override to configuration display

* server: bug fix when plugin cause a project not to open

* desktop-gui: fix for failing e2e test

* server: make errors clearer when plugins crash

* server: fix bug with PLUGINS_FUNCTION_ERROR not sending right arguments

- fix failing tests
- improve plugin error content

* server: fix failing snapshots, preprocessor is invoked for support + spec files

* server: fix tests, don't watch the support file initially

- this was causing a problem where unhandled preprocessor errors were
causing the entire process to hang.
- this was happening because we weren’t properly running support files
through a custom preprocessor initially

* fixes failing tests
This commit is contained in:
Brian Mann
2017-12-11 20:53:23 -05:00
committed by GitHub
parent 6db7a83125
commit c1bcb2f641
38 changed files with 451 additions and 268 deletions

View File

@@ -3,6 +3,7 @@
"plugin:cypress-dev/general"
],
"env": {
"es6": true,
"node": true
}
}

View File

@@ -27,9 +27,9 @@
"clientUrlDisplay": "http://localhost:2020",
"commandTimeout": 4000,
"cypressHostUrl": "http://localhost:2020",
"env": "development",
"environmentVariables": {
"cypressEnv": "development",
"env": {
},
"execTimeout": 60000,
"fileServerFolder": "/Users/jennifer/Dev/Projects/cypress-example-kitchensink",
@@ -40,7 +40,7 @@
"isHeadless": false,
"isNewProject": false,
"javascripts": [
],
"morgan": true,
"namespace": "__cypress",
@@ -108,7 +108,7 @@
"from": "default",
"value": 4000
},
"environmentVariables": {
"env": {
"fileServerFolder": {
"from": "default",
"value": ""

View File

@@ -65,7 +65,7 @@ describe "Settings", ->
cy.contains("Your project's configuration is displayed")
it "displays legend in table", ->
cy.get("table>tbody>tr").should("have.length", 5)
cy.get("table>tbody>tr").should("have.length", 6)
it "wraps config line in proper classes", ->
cy

View File

@@ -20,7 +20,7 @@ class App extends Component {
appApi.listenForMenuClicks()
ipc.getOptions().then((options = {}) => {
appStore.set(_.pick(options, 'env', 'os', 'projectPath', 'version'))
appStore.set(_.pick(options, 'cypressEnv', 'os', 'projectPath', 'version'))
viewStore.showApp()
})

View File

@@ -2,7 +2,7 @@ import { action, computed, observable } from 'mobx'
import localData from '../lib/local-data'
class AppStore {
@observable env
@observable cypressEnv
@observable os
@observable projectPath = null
@observable newVersion
@@ -15,7 +15,7 @@ class AppStore {
}
@computed get isDev () {
return this.env === 'development'
return this.cypressEnv === 'development'
}
@computed get isGlobalMode () {
@@ -27,7 +27,7 @@ class AppStore {
}
@action set (props) {
if (props.env != null) this.env = props.env
if (props.cypressEnv != null) this.cypressEnv = props.cypressEnv
if (props.os != null) this.os = props.os
if (props.projectPath != null) this.projectPath = props.projectPath
if (props.version != null) this.version = this.newVersion = props.version

View File

@@ -102,6 +102,10 @@ const Configuration = observer(({ project }) => (
<td><span className='cli'>CLI</span></td>
<td>set from CLI arguments</td>
</tr>
<tr className='config-keys'>
<td><span className='plugin'>plugin</span></td>
<td>set from plugin file</td>
</tr>
</tbody>
</table>
<pre className='config-vars'>

View File

@@ -53,7 +53,7 @@
padding: 10px;
font-family: $font-mono;
.envFile:hover, .env:hover, .config:hover, .cli:hover, .default:hover {
.envFile:hover, .env:hover, .config:hover, .cli:hover, .plugin:hover, .default:hover {
border-bottom: 1px dotted #777;
}
@@ -83,7 +83,7 @@
}
}
.envFile, .env, .config, .cli, .default {
.envFile, .env, .config, .cli, .plugin, .default {
font-family: $font-mono;
padding: 2px;
@@ -109,6 +109,11 @@
color: #A21313;
}
.plugin {
background-color: #f0e7fc;
color: #134aa2;
}
}
.settings-wrapper {

View File

@@ -119,13 +119,13 @@ class $Cypress
longStackTraces: config.isInteractive
})
{environmentVariables, remote} = config
{env, remote} = config
config = _.omit(config, "environmentVariables", "remote")
config = _.omit(config, "env", "remote", "resolved", "scaffoldedFiles", "javascripts", "state")
@state = $SetterGetter.create({})
@config = $SetterGetter.create(config)
@env = $SetterGetter.create(environmentVariables)
@env = $SetterGetter.create(env)
@Cookies = $Cookies.create(config.namespace, d)

View File

@@ -204,9 +204,6 @@ module.exports = {
invalid_argument: "#{cmd('each')} must be passed a callback function."
non_array: "#{cmd('each')} can only operate on an array like subject. Your subject was: '{{subject}}'"
env:
variables_missing: "Cypress.environmentVariables is not defined. Open an issue if you see this message."
exec:
failed: """#{cmd('exec', '\'{{cmd}}\'')} failed with the following error:

View File

@@ -78,7 +78,7 @@ describe "src/cypress", ->
describe "#env", ->
beforeEach ->
@Cypress.setConfig({
environmentVariables: {foo: "bar"}
env: {foo: "bar"}
})
it "acts as getter", ->
@@ -108,9 +108,9 @@ describe "src/cypress", ->
it "instantiates Cookies", ->
expect(@Cypress.Cookies).to.be.an("object")
it "passes config.environmentVariables", ->
it "passes config.env", ->
@Cypress.setConfig({
environmentVariables: {foo: "bar"}
env: {foo: "bar"}
})
expect(@Cypress.env()).to.deep.eq({foo: "bar"})

View File

@@ -121,7 +121,7 @@ App.propTypes = {
majorVersion: PropTypes.string.isRequired,
version: PropTypes.string.isRequired,
})).isRequired,
env: PropTypes.string.isRequired,
cypressEnv: PropTypes.string.isRequired,
integrationFolder: PropTypes.string.isRequired,
numTestsKeptInMemory: PropTypes.number.isRequired,
projectName: PropTypes.string.isRequired,

View File

@@ -13,7 +13,7 @@ import App from './app'
const createProps = () => ({
config: {
browsers: [],
env: 'tst',
cypressEnv: 'tst',
integrationFolder: '',
numTestsKeptInMemory: 1,
projectName: '',

View File

@@ -14,7 +14,7 @@ import Container, { automationElementId } from './container'
const createProps = () => ({
config: {
browsers: [],
env: 'test',
cypressEnv: 'test',
integrationFolder: '',
numTestsKeptInMemory: 1,
projectName: '',

View File

@@ -62,7 +62,7 @@ const eventManager = {
})
_.each(socketRerunEvents, (event) => {
channel.on(event, this._reRun.bind(this))
channel.on(event, this._reRun.bind(this))
})
reporterBus.on('runner:console:error', (testId) => {
@@ -165,31 +165,7 @@ const eventManager = {
},
setup (config, specPath) {
Cypress = $Cypress.create(
_.pick(config,
'isTextTerminal',
'numTestsKeptInMemory',
'waitForAnimations',
'animationDistanceThreshold',
'defaultCommandTimeout',
'pageLoadTimeout',
'requestTimeout',
'responseTimeout',
'environmentVariables',
'xhrUrl',
'baseUrl',
'viewportWidth',
'viewportHeight',
'execTimeout',
'screenshotOnHeadlessFailure',
'namespace',
'remote',
'version',
'fixturesFolder',
'platform',
'arch'
)
)
Cypress = $Cypress.create(config)
// expose Cypress globally
window.Cypress = Cypress

View File

@@ -3,7 +3,7 @@ Started video recording: /foo/bar/.projects/plugins-async-error/cypress/videos/a
(Tests Starting)
Error: The following error was thrown by a plugin. We've stopped running your tests because this likely interrupts behavior critical to them.
Error: The following error was thrown by a plugin. We've stopped running your tests because a plugin crashed.
Error: Async error from plugins file
at stack trace line
@@ -50,15 +50,16 @@ Started video recording: /foo/bar/.projects/working-preprocessor/cypress/videos/
(Tests Starting)
✓ is another spec
✓ is another spec
1 passing
2 passing
(Tests Finished)
- Tests: 1
- Passes: 1
- Tests: 2
- Passes: 2
- Failures: 0
- Pending: 0
- Duration: 10 seconds
@@ -73,6 +74,40 @@ Started video recording: /foo/bar/.projects/working-preprocessor/cypress/videos/
- Finished processing: /foo/bar/.projects/working-preprocessor/cypress/videos/abc123.mp4 (0 seconds)
(All Done)
`
exports['e2e plugins can modify config from plugins 1'] = `
Started video recording: /foo/bar/.projects/plugin-config/cypress/videos/abc123.mp4
(Tests Starting)
✓ overrides config
✓ overrides env
2 passing
(Tests Finished)
- Tests: 2
- Passes: 2
- Failures: 0
- Pending: 0
- Duration: 10 seconds
- Screenshots: 0
- Video Recorded: true
- Cypress Version: 1.2.3
(Video)
- Started processing: Compressing to 20 CRF
- Finished processing: /foo/bar/.projects/plugin-config/cypress/videos/abc123.mp4 (0 seconds)
(All Done)
`

View File

@@ -2,6 +2,7 @@ _ = require("lodash")
path = require("path")
Promise = require("bluebird")
fs = require("fs-extra")
deepDiff = require("return-deep-diff")
errors = require("./errors")
scaffold = require("./scaffold")
errors = require("./errors")
@@ -12,11 +13,16 @@ v = require("./util/validation")
log = require("debug")("cypress:server:config")
pathHelpers = require("./util/path_helpers")
## cypress following by _
## cypress followed by _
cypressEnvRe = /^(cypress_)/i
dashesOrUnderscoresRe = /^(_-)+/
oneOrMoreSpacesRe = /\s+/
toWords = (str) -> str.trim().split(/\s+/)
toWords = (str) ->
str.trim().split(oneOrMoreSpacesRe)
isCypressEnvLike = (key) ->
cypressEnvRe.test(key) and key isnt "CYPRESS_ENV"
folders = toWords """
fileServerFolder fixturesFolder integrationFolder screenshotsFolder
@@ -27,7 +33,7 @@ configKeys = toWords """
animationDistanceThreshold fileServerFolder
baseUrl fixturesFolder
chromeWebSecurity integrationFolder
environmentVariables pluginsFile
env pluginsFile
hosts screenshotsFolder
numTestsKeptInMemory supportFile
port supportFolder
@@ -45,16 +51,13 @@ configKeys = toWords """
waitForAnimations
"""
isCypressEnvLike = (key) ->
cypressEnvRe.test(key) and key isnt "CYPRESS_ENV"
defaults = {
port: null
hosts: null
morgan: true
baseUrl: null
socketId: null
isTextTerminal: false
isTextTerminal: false
reporter: "spec"
reporterOptions: null
clientRoute: "/__/"
@@ -184,21 +187,25 @@ module.exports = {
_.extend config, _.pick(options, "morgan", "isTextTerminal", "socketId", "report", "browsers")
_.each @whitelist(options), (val, key) ->
_
.chain(@whitelist(options))
.omit("env")
.each (val, key) ->
resolved[key] = "cli"
config[key] = val
return
.value()
if url = config.baseUrl
## always strip trailing slashes
config.baseUrl = _.trimEnd(url, "/")
_.defaults config, defaults
_.defaults(config, defaults)
## split out our own app wide env from user env variables
## and delete envFile
config.environmentVariables = @parseEnv(config, resolved)
config.env = process.env["CYPRESS_ENV"]
config.env = @parseEnv(config, options.env, resolved)
config.cypressEnv = process.env["CYPRESS_ENV"]
delete config.envFile
## when headless
@@ -230,6 +237,32 @@ module.exports = {
return obj
updateWithPluginValues: (cfg, overrides = {}) ->
## diff the overrides with cfg
## including nested objects (env)
diffs = deepDiff(cfg, overrides, true)
setResolvedOn = (resolvedObj, obj) ->
_.each obj, (val, key) ->
if _.isObject(val)
## recurse setting overrides
## inside of this nested objected
setResolvedOn(resolvedObj[key], val)
else
## override the resolved value
resolvedObj[key] = {
value: val
from: "plugin"
}
## for each override go through
## and change the resolved values of cfg
## to point to the plugin
setResolvedOn(cfg.resolved, diffs)
## merge cfg into overrides
_.defaultsDeep(diffs, cfg)
resolveConfigValues: (config, defaults, resolved = {}) ->
## pick out only the keys found in configKeys
_
@@ -418,8 +451,8 @@ module.exports = {
return obj
parseEnv: (cfg, resolved = {}) ->
envVars = resolved.environmentVariables = {}
parseEnv: (cfg, envCLI, resolved = {}) ->
envVars = resolved.env = {}
resolveFrom = (from, obj = {}) ->
_.each obj, (val, key) ->
@@ -431,7 +464,7 @@ module.exports = {
envCfg = cfg.env ? {}
envFile = cfg.envFile ? {}
envProc = @getProcessEnvVars(process.env) ? {}
envCLI = cfg.environmentVariables ? {}
envCLI = envCLI ? {}
matchesConfigKey = (key) ->
if _.has(cfg, key)

View File

@@ -251,7 +251,7 @@ API = {
Your pluginsFile is set to '#{arg1}', but either the file is missing, it contains a syntax error, or threw an error when required. The pluginsFile must be a .js or .coffee file.
Correct your cypress.json, create or fix the file, or set pluginsFile to false if a plugins file is not necessary for your project.
Please fix this, or set 'pluginsFile' to 'false' if a plugins file is not necessary for your project.
#{if arg2 then "The following error was thrown:" else ""}
@@ -273,13 +273,13 @@ API = {
We invoked the function exported by '#{arg1}', but it threw an error.
This is likely an error in the code of the plugins file itself.
The following error was thrown:
#{chalk.yellow(arg2)}
""".trim()
when "PLUGINS_ERROR"
"""
The following error was thrown by a plugin. We've stopped running your tests because this likely interrupts behavior critical to them.
The following error was thrown by a plugin. We've stopped running your tests because a plugin crashed.
#{chalk.yellow(arg1)}
""".trim()

View File

@@ -88,7 +88,6 @@ module.exports = {
Windows.open(@getWindowArgs(state, options))
.then (win) =>
Events.start(_.extend({}, options, {
env: process.env["CYPRESS_ENV"]
onFocusTests: -> win.focus()
os: os.platform()
}), bus)

View File

@@ -1,4 +1,5 @@
const log = require('debug')('cypress:server:plugins:child')
const Promise = require('bluebird')
const preprocessor = require('./preprocessor')
const util = require('../util')
@@ -20,7 +21,7 @@ const sendError = (ipc, err) => {
let plugins
const load = (ipc, config) => {
const load = (ipc, config, pluginsFile) => {
log('run plugins function')
let callbackIdCount = 0
@@ -40,12 +41,16 @@ const load = (ipc, config) => {
})
}
try {
plugins(register, config)
ipc.send('loaded', registrations)
} catch (err) {
ipc.send('load:error', 'PLUGINS_FUNCTION_ERROR', util.serializeError(err))
}
Promise
.try(() => {
return plugins(register, config)
})
.then((modifiedCfg) => {
ipc.send('loaded', modifiedCfg, registrations)
})
.catch((err) => {
ipc.send('load:error', 'PLUGINS_FUNCTION_ERROR', pluginsFile, err.stack)
})
}
const execute = (ipc, event, ids, args = []) => {
@@ -93,7 +98,7 @@ module.exports = (ipc, pluginsFile) => {
}
ipc.on('load', (config) => {
load(ipc, config)
load(ipc, config, pluginsFile)
})
ipc.on('execute', (event, ids, args) => {

View File

@@ -44,7 +44,7 @@ module.exports = {
ipc.send("load", config)
ipc.on "loaded", (registrations) ->
ipc.on "loaded", (newCfg, registrations) ->
_.each registrations, (registration) ->
log("register plugins process event", registration.event, "with id", registration.callbackId)
register registration.event, (args...) ->
@@ -55,7 +55,8 @@ module.exports = {
invocationId: invocationId
}
ipc.send("execute", registration.event, ids, args)
resolve()
resolve(newCfg)
ipc.on "load:error", (type, args...) ->
reject(errors.get(type, args...))

View File

@@ -1,20 +0,0 @@
EE = require("events")
module.exports = (aProcess) ->
emitter = new EE()
aProcess.on "message", (message) ->
emitter.emit(message.event, message.args...)
return {
send: (event, args...) ->
return if aProcess.killed
aProcess.send({
event: event
args
})
on: emitter.on.bind(emitter)
removeListener: emitter.removeListener.bind(emitter)
}

View File

@@ -28,7 +28,7 @@ preprocessor = require("./plugins/preprocessor")
settings = require("./util/settings")
browsers = require("./browsers")
scaffoldLog = require("debug")("cypress:server:scaffold")
log = require("debug")("cypress:server:project")
debug = require("debug")("cypress:server:project")
fs = Promise.promisifyAll(fs)
glob = Promise.promisify(glob)
@@ -53,10 +53,10 @@ class Project extends EE
@cfg = null
@memoryCheck = null
@automation = null
log("Project created %s", @projectRoot)
debug("Project created %s", @projectRoot)
open: (options = {}) ->
log("opening project instance %s", @projectRoot)
debug("opening project instance %s", @projectRoot)
@server = Server()
_.defaults options, {
@@ -68,14 +68,27 @@ class Project extends EE
if process.env.CYPRESS_MEMORY
logMemory = ->
console.log("memory info", process.memoryUsage())
console.debug("memory info", process.memoryUsage())
@memoryCheck = setInterval(logMemory, 1000)
@getConfig(options)
.then (cfg) =>
.tap (cfg) =>
process.chdir(@projectRoot)
## TODO: we currently always scaffold the plugins file
## even when headlessly or else it will cause an error when
## we try to load it and it's not there. We must do this here
## else initialing the plugins will instantly fail.
if cfg.pluginsFile
scaffold.plugins(path.dirname(cfg.pluginsFile), cfg)
.then (cfg) =>
@_initPlugins(cfg, options)
.then (modifiedCfg) ->
debug("plugin config yielded", modifiedCfg)
return config.updateWithPluginValues(cfg, modifiedCfg)
.then (cfg) =>
@server.open(cfg, @)
.spread (port, warning) =>
## if we didnt have a cfg.port
@@ -103,18 +116,24 @@ class Project extends EE
)
.then =>
Promise.join(
@watchSupportFile(cfg)
@checkSupportFile(cfg)
@watchPluginsFile(cfg, options)
)
.then =>
@_initPlugins(cfg, options)
# return our project instance
.return(@)
_initPlugins: (config, options) ->
plugins.init(config, {
_initPlugins: (cfg, options) ->
## only init plugins with the
## whitelisted config values to
## prevent tampering with the
## internals and breaking cypress
cfg = config.whitelist(cfg)
plugins.init(cfg, {
onError: (err) ->
debug('got plugins error', err.stack)
browsers.close()
options.onError(err)
})
@@ -128,13 +147,13 @@ class Project extends EE
api.getProjectRuns(projectId, authToken)
reset: ->
log("resetting project instance %s", @projectRoot)
debug("resetting project instance %s", @projectRoot)
Promise.try =>
@server?.reset()
close: ->
log("closing project instance %s", @projectRoot)
debug("closing project instance %s", @projectRoot)
if @memoryCheck
clearInterval(@memoryCheck)
@@ -149,43 +168,31 @@ class Project extends EE
.then ->
process.chdir(localCwd)
watchSupportFile: (config) ->
if supportFile = config.supportFile
checkSupportFile: (cfg) ->
if supportFile = cfg.supportFile
fs.pathExists(supportFile)
.then (found) =>
if not found
errors.throw("SUPPORT_FILE_NOT_FOUND", supportFile)
relativePath = path.relative(config.projectRoot, config.supportFile)
if config.watchForFileChanges isnt false
options = {
onChange: _.bind(@server.onTestFileChange, @server, relativePath)
}
preprocessor.getFile(relativePath, config, options)
## ignore errors b/c we're just setting up the watching. errors
## are handled by the spec controller
.catch ->
else
Promise.resolve()
watchPluginsFile: (config, options) ->
log("attempt watch plugins file: #{config.pluginsFile}")
if not config.pluginsFile
watchPluginsFile: (cfg, options) ->
debug("attempt watch plugins file: #{cfg.pluginsFile}")
if not cfg.pluginsFile
return Promise.resolve()
fs.pathExists(config.pluginsFile)
fs.pathExists(cfg.pluginsFile)
.then (found) =>
log("plugins file found? #{found}")
debug("plugins file found? #{found}")
## ignore if not found. plugins#init will throw the right error
return if not found
log("watch plugins file")
@watchers.watch(config.pluginsFile, {
debug("watch plugins file")
@watchers.watch(cfg.pluginsFile, {
onChange: =>
## TODO: completely re-open project instead?
log("plugins file changed")
debug("plugins file changed")
## re-init plugins after a change
@_initPlugins(config, options)
@_initPlugins(cfg, options)
.catch (err) ->
options.onError(err)
})
@@ -209,21 +216,21 @@ class Project extends EE
@watchers.watch(settings.pathToCypressJson(@projectRoot), obj)
watchSettingsAndStartWebsockets: (options = {}, config = {}) ->
watchSettingsAndStartWebsockets: (options = {}, cfg = {}) ->
@watchSettings(options.onSettingsChanged)
## if we've passed down reporter
## then record these via mocha reporter
if config.report
if not Reporter.isValidReporterName(config.reporter, config.projectRoot)
paths = Reporter.getSearchPathsForReporter(config.reporter, config.projectRoot)
errors.throw("INVALID_REPORTER_NAME", config.reporter, paths)
if cfg.report
if not Reporter.isValidReporterName(cfg.reporter, cfg.projectRoot)
paths = Reporter.getSearchPathsForReporter(cfg.reporter, cfg.projectRoot)
errors.throw("INVALID_REPORTER_NAME", cfg.reporter, paths)
reporter = Reporter.create(config.reporter, config.reporterOptions, config.projectRoot)
reporter = Reporter.create(cfg.reporter, cfg.reporterOptions, cfg.projectRoot)
@automation = Automation.create(config.namespace, config.socketIoCookie, config.screenshotsFolder)
@automation = Automation.create(cfg.namespace, cfg.socketIoCookie, cfg.screenshotsFolder)
@server.startWebsockets(@automation, config, {
@server.startWebsockets(@automation, cfg, {
onReloadBrowser: options.onReloadBrowser
onFocusTests: options.onFocusTests
@@ -236,12 +243,12 @@ class Project extends EE
@emit("socket:connected", id)
onSetRunnables: (runnables) ->
log("onSetRunnables")
log("runnables", runnables)
debug("onSetRunnables")
debug("runnables", runnables)
reporter?.setRunnables(runnables)
onMocha: (event, runnable) =>
log("onMocha", event)
debug("onMocha", event)
## bail if we dont have a
## reporter instance
return if not reporter
@@ -365,8 +372,8 @@ class Project extends EE
[browserUrl, "#/tests", specUrl].join("/").replace(multipleForwardSlashesRe, replacer)
scaffold: (config) ->
log("scaffolding project %s", @projectRoot)
scaffold: (cfg) ->
debug("scaffolding project %s", @projectRoot)
scaffolds = []
@@ -380,19 +387,13 @@ class Project extends EE
##
## ensure support dir is created
## and example support file if dir doesnt exist
push(scaffold.support(config.supportFolder, config))
## TODO: we currently always scaffold the plugins file
## even when headlessly or else it will cause an error when
## we try to load it and it's not there
if config.pluginsFile
push(scaffold.plugins(path.dirname(config.pluginsFile), config))
push(scaffold.support(cfg.supportFolder, cfg))
## if we're in headed mode add these other scaffolding
## tasks
if not config.isTextTerminal
push(scaffold.integration(config.integrationFolder, config))
push(scaffold.fixture(config.fixturesFolder, config))
if not cfg.isTextTerminal
push(scaffold.integration(cfg.integrationFolder, cfg))
push(scaffold.fixture(cfg.fixturesFolder, cfg))
Promise.all(scaffolds)
@@ -472,13 +473,13 @@ class Project extends EE
_.extend({}, clientProject, {state: state})
@_getProject = (clientProject, authToken) ->
log("get project from api", clientProject.id, clientProject.path)
debug("get project from api", clientProject.id, clientProject.path)
api.getProject(clientProject.id, authToken)
.then (project) ->
log("got project from api")
debug("got project from api")
Project._mergeDetails(clientProject, project)
.catch (err) ->
log("failed to get project from api", err.statusCode)
debug("failed to get project from api", err.statusCode)
switch err.statusCode
when 404
## project doesn't exist
@@ -490,40 +491,40 @@ class Project extends EE
throw err
@getProjectStatuses = (clientProjects = []) ->
log("get project statuses for #{clientProjects.length} projects")
debug("get project statuses for #{clientProjects.length} projects")
user.ensureAuthToken()
.then (authToken) ->
log("got auth token #{authToken}")
debug("got auth token #{authToken}")
api.getProjects(authToken).then (projects = []) ->
log("got #{projects.length} projects")
debug("got #{projects.length} projects")
projectsIndex = _.keyBy(projects, "id")
Promise.all(_.map clientProjects, (clientProject) ->
log("looking at", clientProject.path)
debug("looking at", clientProject.path)
## not a CI project, just mark as valid and return
if not clientProject.id
log("no project id")
debug("no project id")
return Project._mergeState(clientProject, "VALID")
if project = projectsIndex[clientProject.id]
log("found matching:", project)
debug("found matching:", project)
## merge in details for matching project
Project._mergeDetails(clientProject, project)
else
log("did not find matching:", project)
debug("did not find matching:", project)
## project has id, but no matching project found
## check if it doesn't exist or if user isn't authorized
Project._getProject(clientProject, authToken)
)
@getProjectStatus = (clientProject) ->
log("get project status for", clientProject.id, clientProject.path)
debug("get project status for", clientProject.id, clientProject.path)
if not clientProject.id
log("no project id")
debug("no project id")
return Promise.resolve(Project._mergeState(clientProject, "VALID"))
user.ensureAuthToken().then (authToken) ->
log("got auth token #{authToken}")
debug("got auth token #{authToken}")
Project._getProject(clientProject, authToken)
@remove = (path) ->
@@ -579,7 +580,7 @@ class Project extends EE
# Given a path to the project, finds all specs
# returns list of specs with respect to the project root
@findSpecs = (projectPath, specPattern) ->
log("finding specs for project %s", projectPath)
debug("finding specs for project %s", projectPath)
la(check.unemptyString(projectPath), "missing project path", projectPath)
la(check.maybe.unemptyString(specPattern), "invalid spec pattern", specPattern)

View File

@@ -439,7 +439,7 @@ class Server
if fullyQualifiedUrl is "<root>" or not fullyQualifiedRe.test(fullyQualifiedUrl)
@_remoteOrigin = "http://#{DEFAULT_DOMAIN_NAME}:#{@_port()}"
@_remoteStrategy = "file"
@_remoteFileServer = "http://#{DEFAULT_DOMAIN_NAME}:#{@_fileServer.port()}"
@_remoteFileServer = "http://#{DEFAULT_DOMAIN_NAME}:#{@_fileServer?.port()}"
@_remoteDomainName = DEFAULT_DOMAIN_NAME
@_remoteProps = null

View File

@@ -75,7 +75,6 @@ module.exports = {
"run-project": "runProject"
"return-pkg": "returnPkg"
"auto-open": "autoOpen"
"env": "environmentVariables"
"headless": "isTextTerminal"
"exit-with-code": "exitWithCode"
"reporter-options": "reporterOptions"
@@ -89,7 +88,6 @@ module.exports = {
options = _
.chain(options)
.defaults(whitelisted)
.extend({env: process.env["CYPRESS_ENV"]})
.mapValues(coerce)
.value()
@@ -107,9 +105,9 @@ module.exports = {
backup("hosts", options)
options.hosts = parseNestedValues(hosts)
if envs = options.environmentVariables
backup("environmentVariables", options)
options.environmentVariables = parseNestedValues(envs)
if envs = options.env
backup("env", options)
options.env = parseNestedValues(envs)
if ro = options.reporterOptions
backup("reporterOptions", options)

View File

@@ -138,6 +138,7 @@
"randomstring": "^1.1.5",
"request": "2.79.0",
"request-promise": "4.1.1",
"return-deep-diff": "^0.2.9",
"sanitize-filename": "^1.6.1",
"semver": "^5.3.0",
"send": "^0.14.1",

View File

@@ -1,11 +1,12 @@
e2e = require("../support/helpers/e2e")
Fixtures = require("../support/helpers/fixtures")
pluginConfig = Fixtures.projectPath("plugin-config")
workingPreprocessor = Fixtures.projectPath("working-preprocessor")
pluginsAsyncError = Fixtures.projectPath("plugins-async-error")
describe "e2e plugins", ->
e2e.setup({npmInstall: true})
e2e.setup()
it "passes", ->
e2e.exec(@, {
@@ -22,3 +23,13 @@ describe "e2e plugins", ->
snapshot: true
expectedExitCode: 1
})
it "can modify config from plugins", ->
e2e.exec(@, {
spec: "app_spec.coffee"
env: "foo=foo,bar=bar"
config: "pageLoadTimeout=10000"
project: pluginConfig
snapshot: true
expectedExitCode: 0
})

View File

@@ -77,6 +77,7 @@ describe "lib/cypress", ->
@todosPath = Fixtures.projectPath("todos")
@pristinePath = Fixtures.projectPath("pristine")
@noScaffolding = Fixtures.projectPath("no-scaffolding")
@pluginConfig = Fixtures.projectPath("plugin-config")
@idsPath = Fixtures.projectPath("ids")
## force cypress to call directly into main without
@@ -630,6 +631,40 @@ describe "lib/cypress", ->
@expectExitWith(0)
it "can override values in plugins", ->
cypress.start([
"--run-project=#{@pluginConfig}", "--config=requestTimeout=1234,videoCompression=false"
"--env=foo=foo,bar=bar"
])
.then =>
cfg = openProject.getProject().cfg
expect(cfg.videoCompression).to.eq(20)
expect(cfg.defaultCommandTimeout).to.eq(500)
expect(cfg.env).to.deep.eq({
foo: "bar"
bar: "bar"
})
expect(cfg.resolved.videoCompression).to.deep.eq({
value: 20
from: "plugin"
})
expect(cfg.resolved.requestTimeout).to.deep.eq({
value: 1234
from: "cli"
})
expect(cfg.resolved.env.foo).to.deep.eq({
value: "bar"
from: "plugin"
})
expect(cfg.resolved.env.bar).to.deep.eq({
value: "bar"
from: "cli"
})
@expectExitWith(0)
describe "--port", ->
beforeEach ->
headless.listenForProjectEnd.resolves({failures: 0})
@@ -675,7 +710,7 @@ describe "lib/cypress", ->
"version=0.12.1,foo=bar,host=http://localhost:8888,baz=quux=dolor"
])
.then =>
expect(openProject.getProject().cfg.environmentVariables).to.deep.eq({
expect(openProject.getProject().cfg.env).to.deep.eq({
version: "0.12.1"
foo: "bar"
host: "http://localhost:8888"
@@ -1070,7 +1105,7 @@ describe "lib/cypress", ->
port: 2121
pageLoadTimeout: 1000
report: false
environmentVariables: { baz: "baz" }
env: { baz: "baz" }
})
cfg = open.getCall(0).args[0]
@@ -1081,12 +1116,12 @@ describe "lib/cypress", ->
expect(cfg.baseUrl).to.eq("localhost")
expect(cfg.watchForFileChanges).to.be.false
expect(cfg.responseTimeout).to.eq(5555)
expect(cfg.environmentVariables.baz).to.eq("baz")
expect(cfg.environmentVariables).not.to.have.property("fileServerFolder")
expect(cfg.environmentVariables).not.to.have.property("port")
expect(cfg.environmentVariables).not.to.have.property("BASE_URL")
expect(cfg.environmentVariables).not.to.have.property("watchForFileChanges")
expect(cfg.environmentVariables).not.to.have.property("responseTimeout")
expect(cfg.env.baz).to.eq("baz")
expect(cfg.env).not.to.have.property("fileServerFolder")
expect(cfg.env).not.to.have.property("port")
expect(cfg.env).not.to.have.property("BASE_URL")
expect(cfg.env).not.to.have.property("watchForFileChanges")
expect(cfg.env).not.to.have.property("responseTimeout")
expect(cfg.resolved.fileServerFolder).to.deep.eq({
value: "foo"
@@ -1112,7 +1147,7 @@ describe "lib/cypress", ->
value: 5555
from: "env"
})
expect(cfg.resolved.environmentVariables.baz).to.deep.eq({
expect(cfg.resolved.env.baz).to.deep.eq({
value: "baz"
from: "cli"
})

View File

@@ -0,0 +1,14 @@
it "overrides config", ->
## overrides come from plugins
expect(Cypress.config("defaultCommandTimeout")).to.eq(500)
expect(Cypress.config("videoCompression")).to.eq(20)
## overrides come from CLI
expect(Cypress.config("pageLoadTimeout")).to.eq(10000)
it "overrides env", ->
## overrides come from plugins
expect(Cypress.env("foo")).to.eq("bar")
## overrides come from CLI
expect(Cypress.env("bar")).to.eq("bar")

View File

@@ -0,0 +1,12 @@
module.exports = (on, config) => {
return new Promise((resolve) => {
setTimeout(resolve, 100)
})
.then(() => {
config.defaultCommandTimeout = 500
config.videoCompression = 20
config.env.foo = 'bar'
return config
})
}

View File

@@ -150,7 +150,7 @@ module.exports = {
args.push("--hosts=#{options.hosts}")
if options.debug
args.push("--show-headless-gui")
args.push("--headed")
if options.reporter
args.push("--reporter=#{options.reporter}")
@@ -161,6 +161,12 @@ module.exports = {
if browser = (env.BROWSER or options.browser)
args.push("--browser=#{browser}")
if options.config
args.push("--config", options.config)
if options.env
args.push("--env", options.env)
return args
start: (ctx, options = {}) ->

View File

@@ -43,7 +43,7 @@ describe "lib/util/args", ->
context "--env", ->
it "converts to object literal", ->
options = @setup("--env", "foo=bar,version=0.12.1,host=localhost:8888,bar=qux=")
expect(options.environmentVariables).to.deep.eq({
expect(options.env).to.deep.eq({
foo: "bar"
version: "0.12.1"
host: "localhost:8888"
@@ -86,16 +86,15 @@ describe "lib/util/args", ->
expect(@setup("--no-record").record).to.be.false
expect(@setup("--record=false").record).to.be.false
it "backs up hosts + environmentVariables", ->
it "backs up hosts + env", ->
expect(@obj).to.deep.eq({
_: []
env: process.env.NODE_ENV
"get-key": true
getKey: true
_hosts: "*.foobar.com=127.0.0.1"
hosts: {"*.foobar.com": "127.0.0.1"}
_environmentVariables: "foo=bar,baz=quux,bar=foo=quz"
environmentVariables: {
_env: "foo=bar,baz=quux,bar=foo=quz"
env: {
foo: "bar"
baz: "quux"
bar: "foo=quz"
@@ -114,7 +113,7 @@ describe "lib/util/args", ->
"--getKey=true"
"--config=requestTimeout=1234,responseTimeout=9876"
"--hosts=*.foobar.com=127.0.0.1"
"--environmentVariables=foo=bar,baz=quux,bar=foo=quz"
"--env=foo=bar,baz=quux,bar=foo=quz"
"--requestTimeout=1234"
"--responseTimeout=9876"
])
@@ -137,7 +136,6 @@ describe "lib/util/args", ->
"/Applications/Cypress.app"
"/Applications/Cypress.app"
]
env: process.env.NODE_ENV
appPath: "/Applications/Cypress.app"
execPath: "/Applications/Cypress.app"
updating: true
@@ -159,7 +157,6 @@ describe "lib/util/args", ->
"/Applications/Cypress.app1"
"/Applications/Cypress.app2"
]
env: process.env.NODE_ENV
appPath: "a"
execPath: "e"
"app-path": "a"

View File

@@ -29,7 +29,7 @@ describe "lib/config", ->
config.get(@projectPath)
.then (obj) =>
expect(obj.projectRoot).to.eq(@projectPath)
expect(obj.environmentVariables).to.deep.eq({foo: "bar"})
expect(obj.env).to.deep.eq({foo: "bar"})
it "sets projectName", ->
@setup({}, {foo: "bar"})
@@ -481,9 +481,6 @@ describe "lib/config", ->
it "baseUrl=null", ->
@defaults "baseUrl", null
it "env=CYPRESS_ENV", ->
@defaults "env", process.env["CYPRESS_ENV"]
it "defaultCommandTimeout=4000", ->
@defaults "defaultCommandTimeout", 4000
@@ -581,12 +578,12 @@ describe "lib/config", ->
config.mergeDefaults(obj)
.then (cfg) ->
expect(cfg.environmentVariables).to.deep.eq({
expect(cfg.env).to.deep.eq({
foo: "bar"
bar: "baz"
version: "1.0.1"
})
expect(cfg.env).to.eq(process.env["CYPRESS_ENV"])
expect(cfg.cypressEnv).to.eq(process.env["CYPRESS_ENV"])
expect(cfg).not.to.have.property("envFile")
it "merges env into @config.env", ->
@@ -600,7 +597,7 @@ describe "lib/config", ->
}
options = {
environmentVariables: {
env: {
version: "0.13.1"
foo: "bar"
}
@@ -608,7 +605,7 @@ describe "lib/config", ->
config.mergeDefaults(obj, options)
.then (cfg) ->
expect(cfg.environmentVariables).to.deep.eq({
expect(cfg.env).to.deep.eq({
host: "localhost"
user: "brian"
version: "0.13.1"
@@ -629,6 +626,7 @@ describe "lib/config", ->
config.mergeDefaults(obj, options)
.then (cfg) ->
expect(cfg.resolved).to.deep.eq({
env: { }
port: { value: 1234, from: "cli" },
hosts: { value: null, from: "default" }
reporter: { value: "json", from: "cli" },
@@ -651,14 +649,13 @@ describe "lib/config", ->
fileServerFolder: { value: "", from: "default" },
videoRecording: { value: true, from: "default" }
videoCompression: { value: 32, from: "default" }
videoUploadOnPasses: { value: true, from: "default" }
videoUploadOnPasses: { value: true, from: "default" }
videosFolder: { value: "cypress/videos", from: "default" },
supportFile: { value: "cypress/support", from: "default" },
pluginsFile: { value: "cypress/plugins", from: "default" },
fixturesFolder: { value: "cypress/fixtures", from: "default" },
integrationFolder: { value: "cypress/integration", from: "default" },
screenshotsFolder: { value: "cypress/screenshots", from: "default" },
environmentVariables: { }
testFiles: { value: "**/*.*", from: "default" }
})
@@ -675,13 +672,14 @@ describe "lib/config", ->
envFile: {
bar: "bar"
}
environmentVariables: {
}
options = {
env: {
baz: "baz"
}
}
options = {}
config.mergeDefaults(obj, options)
.then (cfg) ->
expect(cfg.resolved).to.deep.eq({
@@ -715,7 +713,7 @@ describe "lib/config", ->
integrationFolder: { value: "cypress/integration", from: "default" },
screenshotsFolder: { value: "cypress/screenshots", from: "default" },
testFiles: { value: "**/*.*", from: "default" }
environmentVariables: {
env: {
foo: {
value: "foo"
from: "config"
@@ -735,6 +733,61 @@ describe "lib/config", ->
}
})
context ".updateWithPluginValues", ->
it "is noop when no overrides", ->
expect(config.updateWithPluginValues({foo: 'bar'}, null)).to.deep.eq({
foo: 'bar'
})
it "updates resolved config values and returns config with overrides", ->
cfg = {
foo: "bar"
baz: "quux"
lol: 1234
env: {
a: "a"
b: "b"
}
resolved: {
foo: { value: "bar", from: "default" }
baz: { value: "quux", from: "cli" }
lol: { value: 1234, from: "env" }
env: {
a: { value: "a", from: "config" }
b: { value: "b", from: "config" }
}
}
}
overrides = {
baz: "baz"
env: {
b: "bb"
c: "c"
}
}
expect(config.updateWithPluginValues(cfg, overrides)).to.deep.eq({
foo: "bar"
baz: "baz"
lol: 1234
env: {
a: "a"
b: "bb"
c: "c"
}
resolved: {
foo: { value: "bar", from: "default" }
baz: { value: "baz", from: "plugin" }
lol: { value: 1234, from: "env" }
env: {
a: { value: "a", from: "config" }
b: { value: "bb", from: "plugin" }
c: { value: "c", from: "plugin" }
}
}
})
context ".parseEnv", ->
it "merges together env from config, env from file, env from process, and env from CLI", ->
@sandbox.stub(config, "getProcessEnvVars").returns({version: "0.12.1", user: "bob"})
@@ -752,14 +805,14 @@ describe "lib/config", ->
user: "brian"
foo: "bar"
}
environmentVariables: {
version: "0.14.0"
project: "pie"
}
}
expect(config.parseEnv(obj)).to.deep.eq({
envCLI = {
version: "0.14.0"
project: "pie"
}
expect(config.parseEnv(obj, envCLI)).to.deep.eq({
version: "0.14.0"
project: "pie"
host: "http://localhost:8888"

View File

@@ -99,17 +99,13 @@ describe "gui/headed", ->
@sandbox.stub(state, "get").resolves(@state)
it "calls Events.start with options, adding env, onFocusTests, and os", ->
env = process.env["CYPRESS_ENV"]
process.env["CYPRESS_ENV"] = "development"
@sandbox.stub(os, "platform").returns("someOs")
opts = {}
headed.ready(opts).then ->
expect(Events.start).to.be.called
expect(Events.start.lastCall.args[0].env).to.equal("development")
expect(Events.start.lastCall.args[0].onFocusTests).to.be.a("function")
expect(Events.start.lastCall.args[0].os).to.equal("someOs")
process.env["CYPRESS_ENV"] = env
it "calls menu.set", ->
headed.ready({}).then ->

View File

@@ -33,7 +33,7 @@ describe "lib/modes/headless", ->
options = {
port: 8080
environmentVariables: {foo: "bar"}
env: {foo: "bar"}
projectPath: "/_test-output/path/to/project/foo"
}
@@ -43,7 +43,7 @@ describe "lib/modes/headless", ->
expect(openProject.create).to.be.calledWithMatch("/_test-output/path/to/project/foo", {
port: 8080
projectPath: "/_test-output/path/to/project/foo"
environmentVariables: {foo: "bar"}
env: {foo: "bar"}
}, {
morgan: false
socketId: 1234

View File

@@ -55,6 +55,21 @@ describe "lib/plugins/child/run_plugins", ->
snapshot(JSON.stringify(@ipc.send.lastCall.args[3]))
describe "on 'load' message", ->
it "sends error if pluginsFile function rejects the promise", (done) ->
err = new Error('foo')
pluginsFn = @sandbox.stub().rejects(err)
mockery.registerMock("plugins-file", pluginsFn)
@ipc.on.withArgs("load").yields({})
runPlugins(@ipc, "plugins-file")
@ipc.send = (event, errorType, pluginsFile, stack) ->
expect(event).to.eq("load:error")
expect(errorType).to.eq("PLUGINS_FUNCTION_ERROR")
expect(pluginsFile).to.eq("plugins-file")
expect(stack).to.eq(err.stack)
done()
it "calls function exported by pluginsFile with register function and config", ->
pluginsFn = @sandbox.spy()
mockery.registerMock("plugins-file", pluginsFn)
@@ -65,12 +80,19 @@ describe "lib/plugins/child/run_plugins", ->
expect(pluginsFn.lastCall.args[0]).to.be.a("function")
expect(pluginsFn.lastCall.args[1]).to.equal(config)
it "sends error if pluginsFile function throws an error", ->
mockery.registerMock("plugins-file", -> foo.bar())
it "sends error if pluginsFile function throws an error", (done) ->
err = new Error('foo')
mockery.registerMock "plugins-file", -> throw err
runPlugins(@ipc, "plugins-file")
@ipc.on.withArgs("load").yield()
expect(@ipc.send).to.be.called
snapshot(withoutStack(@ipc.send.lastCall.args[2]))
@ipc.send = (event, errorType, pluginsFile, stack) ->
expect(event).to.eq("load:error")
expect(errorType).to.eq("PLUGINS_FUNCTION_ERROR")
expect(pluginsFile).to.eq("plugins-file")
expect(stack).to.eq(err.stack)
done()
describe "on 'execute' message", ->
beforeEach ->

View File

@@ -61,7 +61,9 @@ describe "lib/plugins/index", ->
describe "loaded message", ->
beforeEach ->
@ipc.on.withArgs("loaded").yields([{
@config = {}
@ipc.on.withArgs("loaded").yields(@config, [{
event: "some:event"
callbackId: 0
}])
@@ -81,16 +83,29 @@ describe "lib/plugins/index", ->
expect(value).to.equal("value")
describe "load:error message", ->
beforeEach ->
@ipc.on.withArgs("load:error").yields("PLUGINS_FILE_ERROR", "path/to/pluginsFile.js", "error message")
context "PLUGINS_FILE_ERROR", ->
beforeEach ->
@ipc.on.withArgs("load:error").yields("PLUGINS_FILE_ERROR", "path/to/pluginsFile.js", "error message stack")
it "rejects plugins.init", ->
plugins.init({ pluginsFile: "cypress-plugin" })
.catch (err) =>
expect(err.message).to.contain("The plugins file is missing or invalid")
expect(err.message).to.contain("path/to/pluginsFile.js")
expect(err.message).to.contain("The following error was thrown")
expect(err.message).to.contain("error message")
it "rejects plugins.init", ->
plugins.init({ pluginsFile: "cypress-plugin" })
.catch (err) =>
expect(err.message).to.contain("The plugins file is missing or invalid")
expect(err.message).to.contain("path/to/pluginsFile.js")
expect(err.message).to.contain("The following error was thrown")
expect(err.message).to.contain("error message stack")
context "PLUGINS_FUNCTION_ERROR", ->
beforeEach ->
@ipc.on.withArgs("load:error").yields("PLUGINS_FUNCTION_ERROR", "path/to/pluginsFile.js", "error message stack")
it "rejects plugins.init", ->
plugins.init({ pluginsFile: "cypress-plugin" })
.catch (err) =>
expect(err.message).to.contain("The function exported by the plugins file threw an error.")
expect(err.message).to.contain("path/to/pluginsFile.js")
expect(err.message).to.contain("The following error was thrown:")
expect(err.message).to.contain("error message stack")
describe "error message", ->
beforeEach ->

View File

@@ -139,11 +139,13 @@ describe "lib/project", ->
context "#open", ->
beforeEach ->
@sandbox.stub(@project, "watchSettingsAndStartWebsockets").resolves()
@sandbox.stub(@project, "watchSupportFile").resolves()
@sandbox.stub(@project, "checkSupportFile").resolves()
@sandbox.stub(@project, "scaffold").resolves()
@sandbox.stub(@project, "getConfig").resolves(@config)
@sandbox.stub(Server.prototype, "open").resolves([])
@sandbox.stub(Server.prototype, "reset")
@sandbox.stub(config, "updateWithPluginValues").returns(@config)
@sandbox.stub(scaffold, "plugins").resolves()
@sandbox.stub(plugins, "init").resolves()
it "calls #watchSettingsAndStartWebsockets with options + config", ->
@@ -156,9 +158,9 @@ describe "lib/project", ->
@project.open().then =>
expect(@project.scaffold).to.be.calledWith(@config)
it "calls #watchSupportFile with server config when scaffolding is finished", ->
it "calls #checkSupportFile with server config when scaffolding is finished", ->
@project.open().then =>
expect(@project.watchSupportFile).to.be.calledWith(@config)
expect(@project.checkSupportFile).to.be.calledWith(@config)
it "calls #getConfig options", ->
opts = {}
@@ -167,7 +169,11 @@ describe "lib/project", ->
it "initializes the plugins", ->
@project.open({}).then =>
expect(plugins.init).to.be.calledWith(@config)
expect(plugins.init).to.be.called
it "calls support.plugins with pluginsFile directory", ->
@project.open({}).then =>
expect(scaffold.plugins).to.be.calledWith(path.dirname(@config.pluginsFile))
it "calls options.onError with plugins error when there is a plugins error", ->
onError = @sandbox.spy()
@@ -263,10 +269,6 @@ describe "lib/project", ->
@project.scaffold(@obj).then =>
expect(scaffold.support).to.be.calledWith(@obj.supportFolder)
it "calls support.plugins with pluginsFile directory", ->
@project.scaffold(@obj).then =>
expect(scaffold.plugins).to.be.calledWith("pf")
it "does not call support.plugins if config.pluginsFile is falsey", ->
@obj.pluginsFile = false
@project.scaffold(@obj).then =>
@@ -314,7 +316,7 @@ describe "lib/project", ->
expect(stub).to.be.calledOnce
context "#watchSupportFile", ->
context "#checkSupportFile", ->
beforeEach ->
@sandbox.stub(fs, "pathExists").resolves(true)
@project = Project("/_test-output/path/to/project")
@@ -326,30 +328,13 @@ describe "lib/project", ->
}
it "does nothing when {supportFile: false}", ->
@project.watchSupportFile({supportFile: false})
ret = @project.checkSupportFile({supportFile: false})
expect(preprocessor.getFile).not.to.be.called
it "calls preprocessor.getFile with relative path to file", ->
@project.watchSupportFile(@config)
.then () =>
expect(preprocessor.getFile).to.be.calledWith("foo/bar.js", @config)
it "calls server.onTestFileChange when file changes", ->
@project.watchSupportFile(@config)
.then () =>
preprocessor.getFile.firstCall.args[2].onChange()
expect(@project.server.onTestFileChange).to.be.calledWith("foo/bar.js")
it "does not add change listener when {watchForFileChanges: false}", ->
@config.watchForFileChanges = false
@project.watchSupportFile(@config)
.then () =>
expect(preprocessor.getFile.firstCall.args[2]).to.be.undefined
expect(ret).to.be.undefined
it "throws when support file does not exist", ->
fs.pathExists.resolves(false)
@project.watchSupportFile(@config)
@project.checkSupportFile(@config)
.catch (e) ->
expect(e.message).to.include("The support file is missing or invalid.")