mirror of
https://github.com/cypress-io/cypress.git
synced 2026-01-07 06:59:49 -06:00
feat: updated scaffolding, new project & user banners, ability to remove scaffolding (#15826)
* update example build scripts * remove old scaffolding relics * update * Fix some issues with scaffolding * Correctly fix issues with scaffolding * Replace old onboarding with new banner * Add ability to remove scaffolded files * Add banner for new users * Update tests for new scaffolding * Compare file sizes before removing * Add tests for remove file * Save when user opened cypress rather than boolean * Update intro link and add tets for banners * fix small issue * Update design and copy of onboarding banners * Update style of new spec file button * Improve outline button active statE * Update design of new project a bit more * Fix specs list tests * Update banner copy and layout * Update banner copy and layout * Switch to docs style alerts * Fix testing logic * Update banner styles a bit * Update banners * Add confirmation modal for delete specs * Update tests and fix states * Upgrade kitchensink dep * Upgrade kitchen sink version and fix unit tests * Update integration scaffolding test * Add further description to warning modal * Update test for new user and new project case * Remove check to file tree when removing files * Update kitchensink version * Fix edge case where banner could appear when no files have been scaffolded * Fix tests * Update styling for 'note' when deleting files * fix issue with path on windows * Change remove command * Fix rm dir * Fix for windows * Try to use appveyor to test * appveyor please * getting some feedback * Why doesn't this work * more info * I have a feeling this works * maybe its the other path * please * this is the one * this is it * this should work * try reverting that change that might not be needed * remove appveyor testing scaffolding Co-authored-by: Jennifer Shehane <jennifer@cypress.io>
This commit is contained in:
2
cli/types/cypress.d.ts
vendored
2
cli/types/cypress.d.ts
vendored
@@ -2763,8 +2763,6 @@ declare namespace Cypress {
|
||||
clientRoute: string
|
||||
configFile: string
|
||||
cypressEnv: string
|
||||
integrationExampleName: string
|
||||
integrationExamplePath: string
|
||||
isNewProject: boolean
|
||||
isTextTerminal: boolean
|
||||
morgan: boolean
|
||||
|
||||
@@ -128,8 +128,6 @@
|
||||
"execTimeout": 60000,
|
||||
"fileServerFolder": "/Users/jennifer/Dev/Projects/cypress-example-kitchensink",
|
||||
"fixturesFolder": "/Users/jennifer/Dev/Projects/cypress-example-kitchensink/cypress/fixtures",
|
||||
"integrationExamplePath": "/Users/jennifer/Dev/Projects/cypress-example-kitchensink/cypress/integration/examples",
|
||||
"integrationExampleName": "examples",
|
||||
"integrationFolder": "/Users/jennifer/Dev/Projects/cypress-example-kitchensink/cypress/integration",
|
||||
"isHeadless": false,
|
||||
"isNewProject": false,
|
||||
|
||||
@@ -24,10 +24,11 @@ describe('Specs List', function () {
|
||||
cy.stub(this.ipc, 'openFinder')
|
||||
cy.stub(this.ipc, 'openFile')
|
||||
cy.stub(this.ipc, 'externalOpen')
|
||||
cy.stub(this.ipc, 'onboardingClosed')
|
||||
cy.stub(this.ipc, 'hasOpenedCypress').resolves(true)
|
||||
cy.stub(this.ipc, 'onSpecChanged')
|
||||
cy.stub(this.ipc, 'setUserEditor')
|
||||
cy.stub(this.ipc, 'showNewSpecDialog').resolves({ specs: null, path: null })
|
||||
cy.stub(this.ipc, 'removeScaffoldedFiles').resolves()
|
||||
|
||||
this.openProject = this.util.deferred()
|
||||
cy.stub(this.ipc, 'openProject').returns(this.openProject.promise)
|
||||
@@ -58,8 +59,10 @@ describe('Specs List', function () {
|
||||
})
|
||||
})
|
||||
|
||||
it('displays help link', () => {
|
||||
cy.contains('a', 'Need help?')
|
||||
it('launches system save dialog on click of new spec file', function () {
|
||||
cy.contains('New Spec File').click().then(function () {
|
||||
expect(this.ipc.showNewSpecDialog).to.be.called
|
||||
})
|
||||
})
|
||||
|
||||
it('opens link to docs on click of help link', () => {
|
||||
@@ -96,98 +99,76 @@ describe('Specs List', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('first time onboarding specs', function () {
|
||||
describe('new project onboarding', function () {
|
||||
beforeEach(function () {
|
||||
this.config.isNewProject = true
|
||||
|
||||
this.openProject.resolve(this.config)
|
||||
})
|
||||
|
||||
context('modal', () => {
|
||||
it('displays', () => {
|
||||
cy.contains('.modal', 'To help you get started').should('be.visible')
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
it('displays the scaffolded files', () => {
|
||||
cy.get('.folder-preview-onboarding').within(function () {
|
||||
cy.contains('span', 'fixtures').siblings('ul').within(function () {
|
||||
})
|
||||
|
||||
cy.contains('example.json')
|
||||
cy.contains('span', 'integration').siblings('ul').within(() => {
|
||||
cy.contains('examples')
|
||||
})
|
||||
|
||||
cy.contains('span', 'support').siblings('ul').within(function () {
|
||||
cy.contains('commands.js')
|
||||
cy.contains('defaults.js')
|
||||
|
||||
cy.contains('index.js')
|
||||
})
|
||||
|
||||
cy.contains('span', 'plugins').siblings('ul').within(() => {
|
||||
cy.contains('index.js')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('lists folders and files alphabetically', () => {
|
||||
cy.get('.folder-preview-onboarding').within(() => {
|
||||
cy.contains('fixtures').parent().next()
|
||||
.contains('integration')
|
||||
})
|
||||
})
|
||||
|
||||
it('truncates file lists with more than 3 items', () => {
|
||||
cy.get('.folder-preview-onboarding').within(function () {
|
||||
cy.contains('examples').closest('.new-item').find('li')
|
||||
.should('have.length', 3)
|
||||
|
||||
cy.get('.is-more').should('have.text', ' ... 17 more files ...')
|
||||
})
|
||||
})
|
||||
|
||||
it('can dismiss the modal', function () {
|
||||
cy.contains('OK, got it!').click()
|
||||
|
||||
cy.get('.modal').should('not.be.visible')
|
||||
.then(function () {
|
||||
expect(this.ipc.onboardingClosed).to.be.called
|
||||
})
|
||||
})
|
||||
|
||||
it('triggers open:finder on click of example folder', function () {
|
||||
cy.get('.modal').contains('examples').click().then(() => {
|
||||
expect(this.ipc.openFinder).to.be.calledWith(this.config.integrationExamplePath)
|
||||
})
|
||||
})
|
||||
|
||||
it('triggers open:finder on click of text folder', function () {
|
||||
cy.get('.modal').contains('cypress/integration').click().then(() => {
|
||||
expect(this.ipc.openFinder).to.be.calledWith(this.config.integrationFolder)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('banner', function () {
|
||||
beforeEach(function () {
|
||||
cy.get('.modal').find('.btn-success').click()
|
||||
})
|
||||
|
||||
it('displays', function () {
|
||||
cy.get('.first-test-banner')
|
||||
cy.get('.new-project-banner')
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
it('is dismissable', function () {
|
||||
cy.get('.first-test-banner').find('.close').click()
|
||||
cy.get('.first-test-banner').should('not.exist')
|
||||
cy.get('.new-project-banner').find('.close').click()
|
||||
cy.get('.new-project-banner').should('not.exist')
|
||||
})
|
||||
|
||||
it('does not display new user banner even when closed', function () {
|
||||
this.ipc.hasOpenedCypress.resolves(false)
|
||||
|
||||
cy.get('.new-user-banner').should('not.exist')
|
||||
cy.get('.new-project-banner').find('.close').click()
|
||||
cy.get('.new-project-banner').should('not.exist')
|
||||
cy.get('.new-user-banner').should('not.exist')
|
||||
})
|
||||
|
||||
it('opens link to docs on click of help link', function () {
|
||||
cy.contains('a', 'How to write tests').click().then(function () {
|
||||
expect(this.ipc.externalOpen).to.be.calledWith('https://on.cypress.io/writing-first-test')
|
||||
cy.contains('a', 'How to write your first test').click().then(function () {
|
||||
expect(this.ipc.externalOpen).to.be.calledWithMatch({ url: 'https://on.cypress.io/writing-first-test' })
|
||||
})
|
||||
})
|
||||
|
||||
it('removes scaffolded files on click and confirmation', function () {
|
||||
cy.contains('delete example files').click()
|
||||
cy.get('.confirm-remove-scaffolded-files').should('be.visible')
|
||||
cy.contains('Yes, delete files').click().then(function () {
|
||||
expect(this.ipc.removeScaffoldedFiles).to.be.called
|
||||
cy.get('.new-project-banner').should('not.exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('first time user in existing project', function () {
|
||||
beforeEach(function () {
|
||||
this.openProject.resolve(this.config)
|
||||
this.ipc.hasOpenedCypress.resolves(false)
|
||||
})
|
||||
|
||||
context('banner', function () {
|
||||
it('displays', function () {
|
||||
cy.get('.new-user-banner')
|
||||
cy.percySnapshot()
|
||||
})
|
||||
|
||||
it('is dismissable', function () {
|
||||
cy.get('.new-user-banner').find('.close').click()
|
||||
cy.get('.new-user-banner').should('not.exist')
|
||||
})
|
||||
|
||||
it('opens link to docs on click of how to link', function () {
|
||||
cy.contains('a', 'How to write your first test').click().then(function () {
|
||||
expect(this.ipc.externalOpen).to.be.calledWithMatch({ url: 'https://on.cypress.io/writing-first-test' })
|
||||
})
|
||||
})
|
||||
|
||||
it('opens link to intro guide on click of intro link', function () {
|
||||
cy.contains('a', 'Introduction guide to Cypress').click().then(function () {
|
||||
expect(this.ipc.externalOpen).to.be.calledWithMatch({ url: 'https://on.cypress.io/intro-to-cypress' })
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -34,8 +34,8 @@ class Default extends Component {
|
||||
onDrop={this._drop}
|
||||
>
|
||||
<span className="fa-stack fa-lg">
|
||||
<i className="fas fa-folder fa-stack-2x"></i>
|
||||
<i className="fas fa-plus fa-stack-1x"></i>
|
||||
<i className="fas fa-folder fa-stack-2x" />
|
||||
<i className="fas fa-plus fa-stack-1x" />
|
||||
</span>
|
||||
<p>Drag your project here or <a href="#" onClick={this._selectProject}>select manually</a>.</p>
|
||||
</div>
|
||||
@@ -51,7 +51,7 @@ class Default extends Component {
|
||||
return (
|
||||
<div className='local-install-notice alert alert-info alert-dismissible'>
|
||||
<p className='text-center'>
|
||||
<i className='fas fa-info-circle'></i>{' '}
|
||||
<i className='fas fa-info-circle' />{' '}
|
||||
We recommend versioning Cypress per project and{' '}
|
||||
<a onClick={this._openHelp} className='helper-docs-link'>
|
||||
installing it via <span className='mono'>npm</span>
|
||||
|
||||
@@ -71,7 +71,9 @@ register('updater:check', false)
|
||||
register('updater:run', false)
|
||||
register('window:open')
|
||||
register('window:close')
|
||||
register('onboarding:closed')
|
||||
register('new:project:banner:closed')
|
||||
register('has:opened:cypress')
|
||||
register('remove:scaffolded:files')
|
||||
register('set:clipboard:text')
|
||||
register('set:prompt:shown')
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ export default class Browsers extends Component {
|
||||
return (
|
||||
<li className='close-browser'>
|
||||
<button className='btn btn-xs btn-danger' onClick={this._closeBrowser.bind(this)}>
|
||||
<i className='fas fa-fw fa-times'></i>
|
||||
<i className='fas fa-fw fa-times' />
|
||||
Stop
|
||||
</button>
|
||||
</li>
|
||||
|
||||
@@ -12,19 +12,19 @@ export default class ProjectNav extends Component {
|
||||
<ul className='nav left-nav'>
|
||||
<li>
|
||||
<Link to={routes.specs(project)}>
|
||||
<i className='fas fa-code'></i>{' '}
|
||||
<i className='fas fa-code' />{' '}
|
||||
Tests
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to={routes.runs(project)}>
|
||||
<i className='fas fa-database'></i>{' '}
|
||||
<i className='fas fa-database' />{' '}
|
||||
Runs
|
||||
</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link to={routes.settings(project)}>
|
||||
<i className='fas fa-cog'></i>{' '}
|
||||
<i className='fas fa-cog' />{' '}
|
||||
Settings
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
@@ -1,131 +0,0 @@
|
||||
import cs from 'classnames'
|
||||
import _ from 'lodash'
|
||||
import React, { Component } from 'react'
|
||||
import { observer } from 'mobx-react'
|
||||
import BootstrapModal from 'react-bootstrap-modal'
|
||||
|
||||
import ipc from '../lib/ipc'
|
||||
|
||||
@observer
|
||||
class OnBoarding extends Component {
|
||||
componentDidMount () {
|
||||
this._maybeShowModal()
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
this._maybeShowModal()
|
||||
}
|
||||
|
||||
_maybeShowModal () {
|
||||
if (!this.showedModal && this.props.project.isNew) {
|
||||
this.showedModal = true
|
||||
this.props.project.openModal()
|
||||
}
|
||||
}
|
||||
|
||||
render () {
|
||||
const { project } = this.props
|
||||
|
||||
let closeModal = () => {
|
||||
project.closeModal()
|
||||
ipc.onboardingClosed()
|
||||
}
|
||||
|
||||
return (
|
||||
<BootstrapModal
|
||||
show={project.onBoardingModalOpen}
|
||||
onHide={closeModal}
|
||||
backdrop='static'
|
||||
>
|
||||
<div className='modal-body'>
|
||||
<div className='empty-onboarding'>
|
||||
<h1>To help you get started...</h1>
|
||||
<p>
|
||||
We've added some folders and example tests to your project. Try running the tests in the
|
||||
<strong onClick={this._openExampleSpec}>
|
||||
<i className='far fa-folder'></i>{' '}
|
||||
{project.integrationExampleName}{' '}
|
||||
</strong>
|
||||
folder or add your own test files to
|
||||
<strong onClick={this._openIntegrationFolder}>
|
||||
<i className='far fa-folder'></i>{' '}
|
||||
cypress/integration
|
||||
</strong>.
|
||||
</p>
|
||||
<div className='folder-preview-onboarding'>
|
||||
<ul>
|
||||
<li>
|
||||
<span>
|
||||
<i className='far fa-folder-open'></i>{' '}
|
||||
{project.name}
|
||||
</span>
|
||||
<ul>
|
||||
<li className='app-code'>
|
||||
<span >
|
||||
<i className='far fa-folder'></i>{' '}
|
||||
...
|
||||
</span>
|
||||
</li>
|
||||
{this._scaffoldedFiles(project.scaffoldedFiles, 'new-code')}
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div className='helper-line'>
|
||||
<BootstrapModal.Dismiss className='btn btn-success'>
|
||||
OK, got it!
|
||||
</BootstrapModal.Dismiss>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BootstrapModal>
|
||||
)
|
||||
}
|
||||
|
||||
_scaffoldedFiles (files, className) {
|
||||
files = _.sortBy(files, 'name')
|
||||
|
||||
const notFolders = _.every(files, (file) => !file.children)
|
||||
|
||||
if (notFolders && files.length > 3) {
|
||||
const numHidden = files.length - 2
|
||||
|
||||
files = files.slice(0, 2).concat({ name: `... ${numHidden} more files ...`, more: true })
|
||||
}
|
||||
|
||||
return _.map(files, (file) => {
|
||||
if (file.children) {
|
||||
return (
|
||||
<li className={cs(className, 'new-item')} key={file.name}>
|
||||
<span>
|
||||
<i className='far fa-folder-open'></i>{' '}
|
||||
{file.name}
|
||||
</span>
|
||||
<ul>
|
||||
{this._scaffoldedFiles(file.children)}
|
||||
</ul>
|
||||
</li>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<li className={cs(className, 'new-item', { 'is-more': file.more })} key={file.name}>
|
||||
<span>
|
||||
<i className='far fa-file-code'></i>{' '}
|
||||
{file.name}
|
||||
</span>
|
||||
</li>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
_openExampleSpec = () => {
|
||||
ipc.openFinder(this.props.project.integrationExamplePath)
|
||||
}
|
||||
|
||||
_openIntegrationFolder = () => {
|
||||
ipc.openFinder(this.props.project.integrationFolder)
|
||||
}
|
||||
}
|
||||
|
||||
export default OnBoarding
|
||||
@@ -1,137 +0,0 @@
|
||||
.empty-onboarding {
|
||||
margin: 0px auto 0;
|
||||
overflow: auto;
|
||||
|
||||
strong {
|
||||
margin-left: 4px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
strong:hover {
|
||||
cursor: pointer;
|
||||
border-bottom: 1px dotted #333;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: $pass;
|
||||
font-size: 18.5px;
|
||||
margin: 0px 0 10px;
|
||||
}
|
||||
|
||||
img {
|
||||
text-align: center;
|
||||
margin: 20px auto;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 21px;
|
||||
margin-bottom: 0;
|
||||
|
||||
a {
|
||||
border-bottom: 1px dotted lighten($brand-primary, 30%);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.helper-line {
|
||||
bottom: 0;
|
||||
position: inherit;
|
||||
}
|
||||
|
||||
h6 {
|
||||
margin-top: 30px;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
color: #999;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.folder-preview-onboarding {
|
||||
background-color: #fcfcfc;
|
||||
padding: 0;
|
||||
border: 1px solid #e9e9e9;
|
||||
box-shadow: 0 1px 0 0 rgba(0, 0, 0, 0.04);
|
||||
margin: 0 auto 10px;
|
||||
color: #555;
|
||||
margin-top: 10px;
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
margin-left: 0;
|
||||
font-size: 13.5px;
|
||||
-webkit-padding-start: 0;
|
||||
|
||||
|
||||
&>li {
|
||||
position: relative;
|
||||
margin: 3px 0;
|
||||
|
||||
&.app-code {
|
||||
color: #aaa;
|
||||
font-style: italic;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
&.new-code {
|
||||
padding: 2px 0 0 31px;
|
||||
margin-left: -30px;
|
||||
margin-bottom: 0;
|
||||
border-top: 1px solid lighten($pass, 54%);
|
||||
background-color: lighten($pass, 58%);
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
&>ul {
|
||||
padding-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&>ul {
|
||||
margin-bottom: 0;
|
||||
&>li {
|
||||
margin-bottom: 0;
|
||||
padding-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.new-item:after {
|
||||
color: lighten($pass, 5%);
|
||||
content: "+";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.new-item .new-item:after {
|
||||
top: -2px;
|
||||
left: -40px;
|
||||
}
|
||||
|
||||
.new-item .new-item .new-item:after {
|
||||
top: -2px;
|
||||
left: -60px;
|
||||
}
|
||||
|
||||
.new-item .new-item .new-item .new-item:after {
|
||||
top: -2px;
|
||||
left: -80px;
|
||||
}
|
||||
|
||||
.is-more {
|
||||
i {
|
||||
display: none;
|
||||
}
|
||||
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,11 +23,11 @@ const validProps = cacheProps.concat([
|
||||
'isNew',
|
||||
'configFile',
|
||||
'browsers',
|
||||
'onBoardingModalOpen',
|
||||
'newProjectBannerOpen',
|
||||
'newUserBannerOpen',
|
||||
'browserState',
|
||||
'resolvedConfig',
|
||||
'parentTestsFolderDisplay',
|
||||
'integrationExampleName',
|
||||
'scaffoldedFiles',
|
||||
'resolvedNodePath',
|
||||
'resolvedNodeVersion',
|
||||
@@ -55,7 +55,8 @@ export default class Project {
|
||||
@observable isLoading = false
|
||||
@observable isNew = false
|
||||
@observable browsers = []
|
||||
@observable onBoardingModalOpen = false
|
||||
@observable newProjectBannerOpen = false
|
||||
@observable newUserBannerOpen = false
|
||||
@observable browserState = 'closed'
|
||||
@observable resolvedConfig
|
||||
@observable error
|
||||
@@ -63,7 +64,6 @@ export default class Project {
|
||||
@observable _warnings = {}
|
||||
@observable apiError
|
||||
@observable parentTestsFolderDisplay
|
||||
@observable integrationExampleName
|
||||
@observable scaffoldedFiles = []
|
||||
@observable resolvedNodePath
|
||||
@observable resolvedNodeVersion
|
||||
@@ -147,12 +147,9 @@ export default class Project {
|
||||
this.isLoading = isLoading
|
||||
}
|
||||
|
||||
@action openModal () {
|
||||
this.onBoardingModalOpen = true
|
||||
}
|
||||
|
||||
@action closeModal () {
|
||||
this.onBoardingModalOpen = false
|
||||
@action closeBanners () {
|
||||
this.newProjectBannerOpen = false
|
||||
this.newUserBannerOpen = false
|
||||
}
|
||||
|
||||
@action browserOpening () {
|
||||
@@ -206,11 +203,10 @@ export default class Project {
|
||||
|
||||
@action setOnBoardingConfig (config) {
|
||||
this.isNew = config.isNewProject
|
||||
this.newProjectBannerOpen = config.isNewProject
|
||||
this.integrationFolder = config.integrationFolder
|
||||
this.parentTestsFolderDisplay = config.parentTestsFolderDisplay
|
||||
this.fileServerFolder = config.fileServerFolder
|
||||
this.integrationExampleName = config.integrationExampleName
|
||||
this.integrationExamplePath = config.integrationExamplePath
|
||||
this.scaffoldedFiles = config.scaffoldedFiles
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@ import viewStore from '../lib/view-store'
|
||||
import ipc from '../lib/ipc'
|
||||
|
||||
import Settings from '../settings/settings'
|
||||
import OnBoarding from './onboarding'
|
||||
import ProjectNav from '../project-nav/project-nav'
|
||||
import RunsList from '../runs/runs-list'
|
||||
import SpecsList from '../specs/specs-list'
|
||||
@@ -53,7 +52,6 @@ class Project extends Component {
|
||||
{this._renderWarnings()}
|
||||
{this._currentView()}
|
||||
</div>
|
||||
<OnBoarding project={this.props.project}/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ class WarningMessage extends Component {
|
||||
return (
|
||||
<div className='alert alert-warning'>
|
||||
<p className='header'>
|
||||
<i className='fas fa-exclamation-triangle'></i>{' '}
|
||||
<i className='fas fa-exclamation-triangle' />{' '}
|
||||
<strong>Warning</strong>
|
||||
</p>
|
||||
<div>
|
||||
|
||||
@@ -46,7 +46,7 @@ class ProjectsList extends Component {
|
||||
return (
|
||||
<div className='alert alert-danger'>
|
||||
<p>
|
||||
<i className='fas fa-exclamation-triangle'></i>{' '}
|
||||
<i className='fas fa-exclamation-triangle' />{' '}
|
||||
<strong>Error</strong>
|
||||
</p>
|
||||
<p dangerouslySetInnerHTML={{
|
||||
|
||||
@@ -5,6 +5,7 @@ import _ from 'lodash'
|
||||
import React, { Component } from 'react'
|
||||
import { observer } from 'mobx-react'
|
||||
import Loader from 'react-loader'
|
||||
import BootstrapModal from 'react-bootstrap-modal'
|
||||
import Tooltip from '@cypress/react-tooltip'
|
||||
|
||||
import FileOpener from './file-opener'
|
||||
@@ -24,9 +25,7 @@ const formRunButtonLabel = (areTestsAlreadyRunning, specType, specsN) => {
|
||||
return `Running ${specType} tests`
|
||||
}
|
||||
|
||||
const label = specsN === 1 ? `Run 1 ${specType} spec` : `Run ${specsN} ${specType} specs`
|
||||
|
||||
return label
|
||||
return specsN === 1 ? `Run 1 ${specType} spec` : `Run ${specsN} ${specType} specs`
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,6 +59,7 @@ class SpecsList extends Component {
|
||||
super(props)
|
||||
this.state = {
|
||||
isFocused: false,
|
||||
confirmRemoveScaffoldedFiles: false,
|
||||
}
|
||||
|
||||
this.filterRef = React.createRef()
|
||||
@@ -75,10 +75,12 @@ class SpecsList extends Component {
|
||||
// @ts-ignore
|
||||
window.__project = this.props.project
|
||||
}
|
||||
}
|
||||
|
||||
this.state = {
|
||||
firstTestBannerDismissed: false,
|
||||
}
|
||||
componentDidMount () {
|
||||
ipc.hasOpenedCypress().then((opened) => {
|
||||
this.props.project.update({ newUserBannerOpen: !opened })
|
||||
})
|
||||
}
|
||||
|
||||
componentDidUpdate () {
|
||||
@@ -98,7 +100,7 @@ class SpecsList extends Component {
|
||||
}
|
||||
|
||||
render () {
|
||||
if (specsStore.isLoading) return <Loader color='#888' scale={0.5}/>
|
||||
if (specsStore.isLoading) return <Loader color='#888' scale={0.5} />
|
||||
|
||||
const filteredSpecs = specsStore.getFilteredSpecs()
|
||||
|
||||
@@ -121,7 +123,8 @@ class SpecsList extends Component {
|
||||
|
||||
return (
|
||||
<div className='specs'>
|
||||
{this._firstTestBanner()}
|
||||
{this._banners()}
|
||||
{this._confirmRemoveScaffoldedFilesDialog()}
|
||||
<header>
|
||||
<div className={cs('search', {
|
||||
'show-clear-filter': !!specsStore.filter,
|
||||
@@ -151,7 +154,7 @@ class SpecsList extends Component {
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div className='new-file-button'>
|
||||
<button className='btn btn-link' onClick={this._createNewFile}><i className="fa fa-plus"></i> New Spec File</button>
|
||||
<button className='btn btn-link' onClick={this._createNewFile}><i className="fa fa-plus" /> New Spec File</button>
|
||||
</div>
|
||||
</header>
|
||||
{this._specsList()}
|
||||
@@ -415,24 +418,123 @@ class SpecsList extends Component {
|
||||
{this.props.project.integrationFolder}
|
||||
</code>
|
||||
</h5>
|
||||
<a className='helper-docs-link' onClick={this._openHelp}>
|
||||
<i className='fas fa-question-circle' />{' '}
|
||||
Need help?
|
||||
</a>
|
||||
<p>
|
||||
<a onClick={this._createNewFile}>
|
||||
<i className='fas fa-plus' /> New Spec File
|
||||
</a>
|
||||
|
|
||||
<a className='helper-docs-link' onClick={this._openHelp}>
|
||||
<i className='fas fa-question-circle' /> Need help?
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
_firstTestBanner () {
|
||||
if (!this.props.project.isNew || this.state.firstTestBannerDismissed) return
|
||||
_closeBanners = () => {
|
||||
this.props.project.closeBanners()
|
||||
ipc.newProjectBannerClosed()
|
||||
}
|
||||
|
||||
_removeScaffoldedFiles = () => {
|
||||
ipc.removeScaffoldedFiles().then(this._closeBanners)
|
||||
}
|
||||
|
||||
_openRemoveScaffoldedFilesDialog = () => {
|
||||
this.setState({ confirmRemoveScaffoldedFiles: true })
|
||||
}
|
||||
|
||||
_closeRemoveScaffoldedFilesDialog = () => {
|
||||
this.setState({ confirmRemoveScaffoldedFiles: false })
|
||||
}
|
||||
|
||||
_openHowToNewProjectBanner = (e) => {
|
||||
e.preventDefault()
|
||||
ipc.externalOpen({
|
||||
url: 'https://on.cypress.io/writing-first-test',
|
||||
params: {
|
||||
utm_medium: 'New Project Banner',
|
||||
utm_campaign: 'How To',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
_openHowToNewUserBanner = (e) => {
|
||||
e.preventDefault()
|
||||
ipc.externalOpen({
|
||||
url: 'https://on.cypress.io/writing-first-test',
|
||||
params: {
|
||||
utm_medium: 'New User Banner',
|
||||
utm_campaign: 'How To',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
_openIntroNewUserBanner = (e) => {
|
||||
e.preventDefault()
|
||||
ipc.externalOpen({
|
||||
url: 'https://on.cypress.io/intro-to-cypress',
|
||||
params: {
|
||||
utm_medium: 'New User Banner',
|
||||
utm_campaign: 'Intro Guide',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
_banners () {
|
||||
if (this.props.project.newProjectBannerOpen) {
|
||||
return (
|
||||
<div className="onboarding-banner new-project-banner info-box info-box-dismissible">
|
||||
<p className="header">
|
||||
<strong>Welcome to Cypress!</strong>
|
||||
</p>
|
||||
<p>We've created some sample test files that demonstrate key Cypress concepts to help you get started.</p>
|
||||
<p className="action-links">
|
||||
<a onClick={this._openHowToNewProjectBanner}>How to write your first test <i className="fa fa-sm fa-external-link-alt" /></a>
|
||||
|
|
||||
<a className="link-danger" onClick={this._openRemoveScaffoldedFilesDialog}>No thanks, delete example files</a>
|
||||
</p>
|
||||
<button className="close" onClick={this._closeBanners}><span>×</span></button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (this.props.project.newUserBannerOpen) {
|
||||
return (
|
||||
<div className="onboarding-banner new-user-banner info-box info-box-dismissible">
|
||||
<p className="header">
|
||||
<strong>New to Cypress?</strong>
|
||||
</p>
|
||||
<p>We've created some new user guides on key Cypress concepts to help you get started.</p>
|
||||
<p className="action-links">
|
||||
<a onClick={this._openHowToNewUserBanner}>How to write your first test <i className="fa fa-sm fa-external-link-alt" /></a>
|
||||
|
|
||||
<a onClick={this._openIntroNewUserBanner}>Introduction guide to Cypress <i className="fa fa-sm fa-external-link-alt" /></a>
|
||||
</p>
|
||||
<button className="close" onClick={this._closeBanners}><span>×</span></button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
_confirmRemoveScaffoldedFilesDialog = () => {
|
||||
if (!this.props.project.newProjectBannerOpen) return null
|
||||
|
||||
return (
|
||||
<div className="first-test-banner alert alert-info alert-dismissible">
|
||||
<p>We've created some sample tests around key Cypress concepts. Run the first one or create your own test file.</p>
|
||||
<p><a onClick={this._openHelp}>How to write tests</a></p>
|
||||
<button className="close" onClick={this._removeFirstTestBanner}><span>×</span></button>
|
||||
</div>
|
||||
<BootstrapModal show={this.state.confirmRemoveScaffoldedFiles} onHide={this._closeRemoveScaffoldedFilesDialog} backdrop='static'>
|
||||
<div className='modal-body confirm-remove-scaffolded-files'>
|
||||
<BootstrapModal.Dismiss className='btn btn-link close'>×</BootstrapModal.Dismiss>
|
||||
<h4>Are you sure that you want to delete all example spec files?</h4>
|
||||
<h4 className="note">Note: this will not delete any new or edited files.</h4>
|
||||
</div>
|
||||
<div className='modal-footer'>
|
||||
<BootstrapModal.Dismiss className='btn btn-link'>Cancel</BootstrapModal.Dismiss>
|
||||
<button className='btn btn-danger' onClick={this._removeScaffoldedFiles}>Yes, delete files</button>
|
||||
</div>
|
||||
</BootstrapModal>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -454,10 +556,6 @@ class SpecsList extends Component {
|
||||
e.preventDefault()
|
||||
ipc.externalOpen('https://on.cypress.io/writing-first-test')
|
||||
}
|
||||
|
||||
_removeFirstTestBanner = () => {
|
||||
this.setState({ firstTestBannerDismissed: true })
|
||||
}
|
||||
}
|
||||
|
||||
export default SpecsList
|
||||
|
||||
@@ -7,10 +7,18 @@ $max-nesting-level: 14;
|
||||
width: 100%;
|
||||
min-height: 0;
|
||||
|
||||
.empty-well code {
|
||||
display: block;
|
||||
line-height: 1.8;
|
||||
margin-top: 5px;
|
||||
.empty-well {
|
||||
code {
|
||||
color: #666;
|
||||
background: $light-gray;
|
||||
display: block;
|
||||
line-height: 1.8;
|
||||
margin-top: 5px;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
@@ -77,8 +85,13 @@ $max-nesting-level: 14;
|
||||
padding-right: 15px;
|
||||
|
||||
button {
|
||||
color: #637eb9;
|
||||
font-size: 13px;
|
||||
padding: 6px 10px;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: #38589c;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,9 +295,41 @@ $max-nesting-level: 14;
|
||||
}
|
||||
}
|
||||
|
||||
.first-test-banner {
|
||||
.onboarding-banner {
|
||||
margin: 6px;
|
||||
padding-left: 20px;
|
||||
|
||||
p {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.header {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.action-links {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.link-danger {
|
||||
color: #666;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: darken($brand-danger, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.confirm-remove-scaffolded-files {
|
||||
h4 {
|
||||
line-height: 24px;
|
||||
|
||||
&.note {
|
||||
font-style: italic;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
color: #888;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -122,3 +122,17 @@
|
||||
right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.info-box {
|
||||
border-left: 4px solid #2a98b9;
|
||||
background-color: #f2fafd;
|
||||
padding: 10px 14px;
|
||||
position: relative;
|
||||
|
||||
&.info-box-dismissible .close {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ The actual example repo you're probably looking for is [the kitchen sink app her
|
||||
|
||||
**THERE'S LIKELY NO REASON YOU NEED TO EDIT ANY OF THE CODE ON THIS REPO.**
|
||||
|
||||
- Want to edit the `example` tests? -> edit it [here](https://github.com/cypress-io/cypress-example-kitchensink/blob/master/cypress/integration/examples) instead.
|
||||
- Want to edit the `example` tests? -> edit it [here](https://github.com/cypress-io/cypress-example-kitchensink/blob/master/cypress/integration) instead.
|
||||
- Want to edit the actual [https://example.cypress.io](https://example.cypress.io) website? edit it [here](https://github.com/cypress-io/cypress-example-kitchensink/tree/master/app) instead.
|
||||
|
||||
## Updating the `example` app
|
||||
|
||||
@@ -8,11 +8,9 @@ shell.set('-v') // verbose
|
||||
shell.set('-e') // any error is fatal
|
||||
|
||||
shell.rm('-rf', 'app')
|
||||
shell.mkdir('app')
|
||||
|
||||
shell.cp('-r', join(resolvePkg('cypress-example-kitchensink'), 'app'), '.')
|
||||
shell.rm('-rf', 'cypress')
|
||||
|
||||
shell.rm('-rf', 'cypress')
|
||||
shell.cp('-r', join(resolvePkg('cypress-example-kitchensink'), 'cypress'), '.')
|
||||
|
||||
shell.exec('node ./bin/convert.js')
|
||||
|
||||
@@ -41,7 +41,7 @@ function replaceStringsIn (file) {
|
||||
glob('./app/**/*.html', { realpath: true }, (err, htmlFiles) => {
|
||||
if (err) throw err
|
||||
|
||||
glob('./cypress/integration/examples/**/*', { realpath: true }, (err, specFiles) => {
|
||||
glob('./cypress/integration/**/*.js', { realpath: true }, (err, specFiles) => {
|
||||
if (err) throw err
|
||||
|
||||
htmlFiles.concat(specFiles).forEach(function (file) {
|
||||
|
||||
1
packages/example/lib/example.d.ts
vendored
1
packages/example/lib/example.d.ts
vendored
@@ -1,6 +1,5 @@
|
||||
declare const example: {
|
||||
getPathToExamples(): Promise<string[]>;
|
||||
getFolderName(): string;
|
||||
getPathToPlugins(): string;
|
||||
getPathToSupportFiles(): Promise<string[]>;
|
||||
getPathToTsConfig(): string;
|
||||
|
||||
@@ -2,23 +2,22 @@ const path = require('path')
|
||||
const Promise = require('bluebird')
|
||||
const glob = Promise.promisify(require('glob'))
|
||||
|
||||
const pathToExamples = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'cypress',
|
||||
'integration',
|
||||
'**',
|
||||
'*'
|
||||
)
|
||||
|
||||
module.exports = {
|
||||
getPathToExamples () {
|
||||
return glob(
|
||||
path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'cypress',
|
||||
'integration',
|
||||
'examples',
|
||||
'**',
|
||||
'*'
|
||||
)
|
||||
)
|
||||
return glob(pathToExamples, { nodir: true })
|
||||
},
|
||||
|
||||
getFolderName () {
|
||||
return 'examples'
|
||||
|
||||
getPathToExampleFolders () {
|
||||
return glob(`${pathToExamples}${path.sep}`)
|
||||
},
|
||||
|
||||
getPathToPlugins() {
|
||||
|
||||
@@ -28,13 +28,13 @@
|
||||
"devDependencies": {
|
||||
"chai": "3.5.0",
|
||||
"cross-env": "6.0.3",
|
||||
"cypress-example-kitchensink": "1.14.0",
|
||||
"cypress-example-kitchensink": "1.15.2",
|
||||
"gulp": "4.0.2",
|
||||
"gulp-clean": "0.4.0",
|
||||
"gulp-gh-pages": "0.6.0-6",
|
||||
"gulp-rev-all": "2.0.2",
|
||||
"mocha": "2.5.3",
|
||||
"resolve-pkg": "2.0.0",
|
||||
"shelljs": "0.8.3"
|
||||
"shelljs": "0.8.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ const cwd = process.cwd()
|
||||
|
||||
/* global describe, it */
|
||||
describe('Cypress Example', function () {
|
||||
it('returns path to example_spec', function () {
|
||||
const expected = path.normalize(`${cwd}/cypress/integration/examples`)
|
||||
it('returns path to examples', function () {
|
||||
const expected = path.normalize(`${cwd}/cypress/integration`)
|
||||
|
||||
return example.getPathToExamples()
|
||||
.then(expectToAllEqual(expected))
|
||||
|
||||
@@ -3,7 +3,15 @@ exports['lib/scaffold .fileTree returns tree-like structure of scaffolded 1'] =
|
||||
"name": "tests",
|
||||
"children": [
|
||||
{
|
||||
"name": "examples",
|
||||
"name": "1-getting-started",
|
||||
"children": [
|
||||
{
|
||||
"name": "todo.spec.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "2-advanced-examples",
|
||||
"children": [
|
||||
{
|
||||
"name": "actions.spec.js"
|
||||
@@ -105,7 +113,15 @@ exports['lib/scaffold .fileTree leaves out integration tests if using component
|
||||
"name": "tests",
|
||||
"children": [
|
||||
{
|
||||
"name": "examples",
|
||||
"name": "1-getting-started",
|
||||
"children": [
|
||||
{
|
||||
"name": "todo.spec.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "2-advanced-examples",
|
||||
"children": [
|
||||
{
|
||||
"name": "actions.spec.js"
|
||||
@@ -207,7 +223,15 @@ exports['lib/scaffold .fileTree leaves out fixtures if configured to false 1'] =
|
||||
"name": "tests",
|
||||
"children": [
|
||||
{
|
||||
"name": "examples",
|
||||
"name": "1-getting-started",
|
||||
"children": [
|
||||
{
|
||||
"name": "todo.spec.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "2-advanced-examples",
|
||||
"children": [
|
||||
{
|
||||
"name": "actions.spec.js"
|
||||
@@ -301,7 +325,15 @@ exports['lib/scaffold .fileTree leaves out support if configured to false 1'] =
|
||||
"name": "tests",
|
||||
"children": [
|
||||
{
|
||||
"name": "examples",
|
||||
"name": "1-getting-started",
|
||||
"children": [
|
||||
{
|
||||
"name": "todo.spec.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "2-advanced-examples",
|
||||
"children": [
|
||||
{
|
||||
"name": "actions.spec.js"
|
||||
@@ -445,7 +477,15 @@ exports['lib/scaffold .fileTree leaves out plugins if configured to false 1'] =
|
||||
"name": "tests",
|
||||
"children": [
|
||||
{
|
||||
"name": "examples",
|
||||
"name": "1-getting-started",
|
||||
"children": [
|
||||
{
|
||||
"name": "todo.spec.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "2-advanced-examples",
|
||||
"children": [
|
||||
{
|
||||
"name": "actions.spec.js"
|
||||
|
||||
@@ -474,9 +474,6 @@ module.exports = {
|
||||
setScaffoldPaths (obj) {
|
||||
obj = _.clone(obj)
|
||||
|
||||
obj.integrationExampleName = scaffold.integrationExampleName()
|
||||
obj.integrationExamplePath = path.join(obj.integrationFolder, obj.integrationExampleName)
|
||||
|
||||
debug('set scaffold paths')
|
||||
|
||||
return scaffold.fileTree(obj)
|
||||
|
||||
@@ -25,6 +25,7 @@ const konfig = require('../konfig')
|
||||
const editors = require('../util/editors')
|
||||
const fileOpener = require('../util/file-opener')
|
||||
const api = require('../api')
|
||||
const savedState = require('../saved_state')
|
||||
|
||||
const nullifyUnserializableValues = (obj) => {
|
||||
// nullify values that cannot be cloned
|
||||
@@ -392,9 +393,31 @@ const handleEvent = function (options, bus, event, id, type, arg) {
|
||||
return sendErr(err)
|
||||
})
|
||||
|
||||
case 'onboarding:closed':
|
||||
case 'new:project:banner:closed':
|
||||
return openProject.getProject()
|
||||
.saveState({ showedOnBoardingModal: true })
|
||||
.saveState({ showedNewProjectBanner: true })
|
||||
.then(sendNull)
|
||||
|
||||
case 'has:opened:cypress':
|
||||
return savedState.create()
|
||||
.then(async (state) => {
|
||||
const currentState = await state.get()
|
||||
|
||||
// we check if there is any state at all so users existing before
|
||||
// we added firstOpenedCypress are not marked as new
|
||||
const hasOpenedCypress = !!Object.keys(currentState).length
|
||||
|
||||
if (!currentState.firstOpenedCypress) {
|
||||
await state.set('firstOpenedCypress', Date.now())
|
||||
}
|
||||
|
||||
return hasOpenedCypress
|
||||
})
|
||||
.then(send)
|
||||
|
||||
case 'remove:scaffolded:files':
|
||||
return openProject.getProject()
|
||||
.removeScaffoldedFiles()
|
||||
.then(sendNull)
|
||||
|
||||
case 'set:prompt:shown':
|
||||
|
||||
@@ -480,6 +480,14 @@ export class ProjectBase<TServer extends ServerE2E | ServerCt> extends EE {
|
||||
return this.automation
|
||||
}
|
||||
|
||||
removeScaffoldedFiles () {
|
||||
if (!this.cfg) {
|
||||
throw new Error('Missing project config')
|
||||
}
|
||||
|
||||
return scaffold.removeIntegration(this.cfg.integrationFolder, this.cfg)
|
||||
}
|
||||
|
||||
// do not check files again and again - keep previous promise
|
||||
// to refresh it - just close and open the project again.
|
||||
determineIsNewProject (folder) {
|
||||
@@ -511,12 +519,12 @@ export class ProjectBase<TServer extends ServerE2E | ServerCt> extends EE {
|
||||
throw new Error('Missing integration folder')
|
||||
}
|
||||
|
||||
return this.determineIsNewProject(cfg.integrationFolder)
|
||||
return this.determineIsNewProject(cfg)
|
||||
.then((untouchedScaffold) => {
|
||||
const userHasSeenOnBoarding = _.get(cfg, 'state.showedOnBoardingModal', false)
|
||||
const userHasSeenBanner = _.get(cfg, 'state.showedNewProjectBanner', false)
|
||||
|
||||
debugScaffold(`untouched scaffold ${untouchedScaffold} modal closed ${userHasSeenOnBoarding}`)
|
||||
cfg.isNewProject = untouchedScaffold && !userHasSeenOnBoarding
|
||||
debugScaffold(`untouched scaffold ${untouchedScaffold} banner closed ${userHasSeenBanner}`)
|
||||
cfg.isNewProject = untouchedScaffold && !userHasSeenBanner
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ browserY
|
||||
isAppDevToolsOpen
|
||||
isBrowserDevToolsOpen
|
||||
reporterWidth
|
||||
showedOnBoardingModal
|
||||
showedNewProjectBanner
|
||||
firstOpenedCypress
|
||||
showedStudioModal
|
||||
preferredOpener
|
||||
ctReporterWidth
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const _ = require('lodash')
|
||||
const Promise = require('bluebird')
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
const cypressEx = require('@packages/example')
|
||||
const { fs } = require('./util/fs')
|
||||
const glob = require('./util/glob')
|
||||
@@ -9,8 +10,8 @@ const debug = require('debug')('cypress:server:scaffold')
|
||||
const { isEmpty } = require('ramda')
|
||||
const { isDefault } = require('./util/config')
|
||||
|
||||
const exampleFolderName = cypressEx.getFolderName()
|
||||
const getExampleSpecsFullPaths = cypressEx.getPathToExamples()
|
||||
const getExampleFolderFullPaths = cypressEx.getPathToExampleFolders()
|
||||
|
||||
const getPathFromIntegrationFolder = (file) => {
|
||||
return file.substring(file.indexOf('integration/') + 'integration/'.length)
|
||||
@@ -20,8 +21,10 @@ const isDifferentNumberOfFiles = (files, exampleSpecs) => {
|
||||
return files.length !== exampleSpecs.length
|
||||
}
|
||||
|
||||
const getExampleSpecs = () => {
|
||||
return getExampleSpecsFullPaths
|
||||
const getExampleSpecs = (foldersOnly = false) => {
|
||||
const paths = foldersOnly ? getExampleFolderFullPaths : getExampleSpecsFullPaths
|
||||
|
||||
return paths
|
||||
.then((fullPaths) => {
|
||||
// short paths relative to integration folder (i.e. examples/actions.spec.js)
|
||||
const shortPaths = _.map(fullPaths, (file) => {
|
||||
@@ -38,6 +41,11 @@ const getExampleSpecs = () => {
|
||||
}
|
||||
|
||||
const getIndexedExample = (file, index) => {
|
||||
// convert to using posix sep if on win
|
||||
if (os.platform() === 'win32') {
|
||||
file = file.split(path.sep).join(path.posix.sep)
|
||||
}
|
||||
|
||||
return index[getPathFromIntegrationFolder(file)]
|
||||
}
|
||||
|
||||
@@ -51,6 +59,18 @@ const getFileSize = (file) => {
|
||||
return fs.statAsync(file).get('size')
|
||||
}
|
||||
|
||||
const fileSizeIsSame = (file, index) => {
|
||||
return Promise.join(
|
||||
getFileSize(file),
|
||||
getFileSize(getIndexedExample(file, index)),
|
||||
).spread((fileSize, originalFileSize) => {
|
||||
return fileSize === originalFileSize
|
||||
}).catch((e) => {
|
||||
// if the file does not exist, return false
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
const filesSizesAreSame = (files, index) => {
|
||||
return Promise.join(
|
||||
Promise.all(_.map(files, getFileSize)),
|
||||
@@ -71,16 +91,23 @@ const componentTestingEnabled = (config) => {
|
||||
return componentTestingEnabled && !isDefault(config, 'componentFolder')
|
||||
}
|
||||
|
||||
const isNewProject = (integrationFolder) => {
|
||||
const isNewProject = (config) => {
|
||||
// logic to determine if new project
|
||||
// 1. component testing is not enabled
|
||||
// 2. there are no files in 'integrationFolder'
|
||||
// 3. there is the same number of files in 'integrationFolder'
|
||||
// 4. the files are named the same as the example files
|
||||
// 5. the bytes of the files match the example files
|
||||
// 1. 'integrationFolder' is still the default
|
||||
// 2. component testing is not enabled
|
||||
// 3. there are no files in 'integrationFolder'
|
||||
// 4. there is the same number of files in 'integrationFolder'
|
||||
// 5. the files are named the same as the example files
|
||||
// 6. the bytes of the files match the example files
|
||||
|
||||
const { integrationFolder } = config
|
||||
|
||||
debug('determine if new project by globbing files in %o', { integrationFolder })
|
||||
|
||||
if (!isDefault(config, 'integrationFolder')) {
|
||||
return Promise.resolve(false)
|
||||
}
|
||||
|
||||
// checks for file up to 3 levels deep
|
||||
return glob('{*,*/*,*/*/*}', { cwd: integrationFolder, realpath: true, nodir: true })
|
||||
.then((files) => {
|
||||
@@ -91,7 +118,7 @@ const isNewProject = (integrationFolder) => {
|
||||
debug('- empty?', isEmpty(files))
|
||||
if (isEmpty(files)) {
|
||||
return true
|
||||
} // 1
|
||||
}
|
||||
|
||||
return getExampleSpecs()
|
||||
.then((exampleSpecs) => {
|
||||
@@ -100,14 +127,14 @@ const isNewProject = (integrationFolder) => {
|
||||
debug('- different number of files?', numFilesDifferent)
|
||||
if (numFilesDifferent) {
|
||||
return false
|
||||
} // 2
|
||||
}
|
||||
|
||||
const filesNamesDifferent = filesNamesAreDifferent(files, exampleSpecs.index)
|
||||
|
||||
debug('- different file names?', filesNamesDifferent)
|
||||
if (filesNamesDifferent) {
|
||||
return false
|
||||
} // 3
|
||||
}
|
||||
|
||||
return filesSizesAreSame(files, exampleSpecs.index)
|
||||
})
|
||||
@@ -121,10 +148,6 @@ const isNewProject = (integrationFolder) => {
|
||||
module.exports = {
|
||||
isNewProject,
|
||||
|
||||
integrationExampleName () {
|
||||
return exampleFolderName
|
||||
},
|
||||
|
||||
integration (folder, config) {
|
||||
debug(`integration folder ${folder}`)
|
||||
|
||||
@@ -140,7 +163,31 @@ module.exports = {
|
||||
return getExampleSpecs()
|
||||
.then(({ fullPaths }) => {
|
||||
return Promise.all(_.map(fullPaths, (file) => {
|
||||
return this._copy(file, path.join(folder, exampleFolderName), config)
|
||||
return this._copy(file, folder, config, true)
|
||||
}))
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
removeIntegration (folder, config) {
|
||||
debug(`integration folder ${folder}`)
|
||||
|
||||
// skip if user has explicitly set integrationFolder
|
||||
// since we wouldn't have scaffolded anything
|
||||
if (!isDefault(config, 'integrationFolder')) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
return getExampleSpecs()
|
||||
.then(({ shortPaths, index }) => {
|
||||
return Promise.all(_.map(shortPaths, (file) => {
|
||||
return this._removeFile(file, folder, index)
|
||||
}))
|
||||
}).then(() => {
|
||||
// remove folders after we've removed all files
|
||||
return getExampleSpecs(true).then(({ shortPaths }) => {
|
||||
return Promise.all(_.map(shortPaths, (folderPath) => {
|
||||
return this._removeFolder(folderPath, folder)
|
||||
}))
|
||||
})
|
||||
})
|
||||
@@ -198,10 +245,11 @@ module.exports = {
|
||||
})
|
||||
},
|
||||
|
||||
_copy (file, folder, config) {
|
||||
_copy (file, folder, config, integration = false) {
|
||||
// allow file to be relative or absolute
|
||||
const src = path.resolve(cwd('lib', 'scaffold'), file)
|
||||
const dest = path.join(folder, path.basename(file))
|
||||
const destFile = integration ? getPathFromIntegrationFolder(file) : path.basename(file)
|
||||
const dest = path.join(folder, destFile)
|
||||
|
||||
return this._assertInFileTree(dest, config)
|
||||
.then(() => {
|
||||
@@ -209,6 +257,27 @@ module.exports = {
|
||||
})
|
||||
},
|
||||
|
||||
_removeFile (file, folder, index) {
|
||||
const dest = path.join(folder, file)
|
||||
|
||||
return fileSizeIsSame(dest, index)
|
||||
.then((isSame) => {
|
||||
if (isSame) {
|
||||
// catch all errors since the user may have already removed
|
||||
// the file or changed permissions, etc.
|
||||
return fs.removeAsync(dest).catch(_.noop)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
_removeFolder (folderPath, folder) {
|
||||
const dest = path.join(folder, folderPath)
|
||||
|
||||
// catch all errors since the user may have already removed
|
||||
// the folder, changed permissions, added their own files to the folder, etc.
|
||||
return fs.rmdirAsync(dest).catch(_.noop)
|
||||
},
|
||||
|
||||
verifyScaffolding (folder, fn) {
|
||||
// we want to build out the folder + and example files
|
||||
// but only create the example files if the folder doesn't
|
||||
|
||||
@@ -606,9 +606,10 @@ describe('lib/cypress', () => {
|
||||
return fs.statAsync(cfg.integrationFolder)
|
||||
}).then(() => {
|
||||
return Promise.join(
|
||||
fs.statAsync(path.join(cfg.integrationFolder, 'examples', 'actions.spec.js')),
|
||||
fs.statAsync(path.join(cfg.integrationFolder, 'examples', 'files.spec.js')),
|
||||
fs.statAsync(path.join(cfg.integrationFolder, 'examples', 'viewport.spec.js')),
|
||||
fs.statAsync(path.join(cfg.integrationFolder, '1-getting-started', 'todo.spec.js')),
|
||||
fs.statAsync(path.join(cfg.integrationFolder, '2-advanced-examples', 'actions.spec.js')),
|
||||
fs.statAsync(path.join(cfg.integrationFolder, '2-advanced-examples', 'files.spec.js')),
|
||||
fs.statAsync(path.join(cfg.integrationFolder, '2-advanced-examples', 'viewport.spec.js')),
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1881,19 +1881,19 @@ describe('lib/config', () => {
|
||||
})
|
||||
|
||||
context('.setScaffoldPaths', () => {
|
||||
it('sets integrationExamplePath + integrationExampleName + scaffoldedFiles', () => {
|
||||
it('sets scaffoldedFiles', () => {
|
||||
const obj = {
|
||||
integrationFolder: '/_test-output/path/to/project/cypress/integration',
|
||||
}
|
||||
|
||||
sinon.stub(scaffold, 'fileTree').resolves([])
|
||||
const scaffoldedFiles = ['/_test-output/path/to/project/cypress/integration/example.spec.js']
|
||||
|
||||
sinon.stub(scaffold, 'fileTree').resolves(scaffoldedFiles)
|
||||
|
||||
return config.setScaffoldPaths(obj).then((result) => {
|
||||
expect(result).to.deep.eq({
|
||||
integrationFolder: '/_test-output/path/to/project/cypress/integration',
|
||||
integrationExamplePath: '/_test-output/path/to/project/cypress/integration/examples',
|
||||
integrationExampleName: 'examples',
|
||||
scaffoldedFiles: [],
|
||||
scaffoldedFiles,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -24,6 +24,7 @@ const files = require(`${root}../lib/gui/files`)
|
||||
const ensureUrl = require(`${root}../lib/util/ensure-url`)
|
||||
const konfig = require(`${root}../lib/konfig`)
|
||||
const api = require(`${root}../lib/api`)
|
||||
const savedState = require(`${root}../lib/saved_state`)
|
||||
|
||||
describe('lib/gui/events', () => {
|
||||
beforeEach(function () {
|
||||
@@ -496,6 +497,52 @@ describe('lib/gui/events', () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('has:opened:cypress', function () {
|
||||
beforeEach(function () {
|
||||
this.state = {
|
||||
set: sinon.stub().resolves(),
|
||||
get: sinon.stub().resolves({}),
|
||||
}
|
||||
|
||||
sinon.stub(savedState, 'create').resolves(this.state)
|
||||
})
|
||||
|
||||
it('returns false when there is no existing saved state', function () {
|
||||
return this.handleEvent('has:opened:cypress')
|
||||
.then((assert) => {
|
||||
assert.sendCalledWith(false)
|
||||
})
|
||||
})
|
||||
|
||||
it('returns true when there is any existing saved state', function () {
|
||||
this.state.get.resolves({ shownOnboardingModal: true })
|
||||
|
||||
return this.handleEvent('has:opened:cypress')
|
||||
.then((assert) => {
|
||||
assert.sendCalledWith(true)
|
||||
})
|
||||
})
|
||||
|
||||
it('sets firstOpenedCypress when the user first opened Cypress if not already set', function () {
|
||||
this.state.get.resolves({ shownOnboardingModal: true })
|
||||
sinon.stub(Date, 'now').returns(12345)
|
||||
|
||||
return this.handleEvent('has:opened:cypress')
|
||||
.then(() => {
|
||||
expect(this.state.set).to.be.calledWith('firstOpenedCypress', 12345)
|
||||
})
|
||||
})
|
||||
|
||||
it('does not set firstOpenedCypress if already set', function () {
|
||||
this.state.get.resolves({ firstOpenedCypress: 12345 })
|
||||
|
||||
return this.handleEvent('has:opened:cypress')
|
||||
.then(() => {
|
||||
expect(this.state.set).not.to.be.called
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('project events', () => {
|
||||
|
||||
@@ -669,6 +669,11 @@ describe('lib/modes/run', () => {
|
||||
video: true,
|
||||
videosFolder: 'videos',
|
||||
integrationFolder: '/path/to/integrationFolder',
|
||||
resolved: {
|
||||
integrationFolder: {
|
||||
integrationFolder: { value: '/path/to/integrationFolder', from: 'config' },
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
sinon.stub(specsUtil, 'find').resolves([
|
||||
|
||||
@@ -155,10 +155,10 @@ describe('lib/project-e2e', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('sets cfg.isNewProject to false when state.showedOnBoardingModal is true', function () {
|
||||
it('sets cfg.isNewProject to false when state.showedNewProjectBanner is true', function () {
|
||||
return savedState.create(this.todosPath)
|
||||
.then((state) => {
|
||||
sinon.stub(state, 'get').resolves({ showedOnBoardingModal: true })
|
||||
sinon.stub(state, 'get').resolves({ showedNewProjectBanner: true })
|
||||
|
||||
return this.project.getConfig({ foo: 'bar' })
|
||||
.then((cfg) => {
|
||||
@@ -167,7 +167,7 @@ describe('lib/project-e2e', () => {
|
||||
isNewProject: false,
|
||||
baz: 'quux',
|
||||
state: {
|
||||
showedOnBoardingModal: true,
|
||||
showedNewProjectBanner: true,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -62,6 +62,14 @@ describe('lib/saved_state', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('has an empty state by default', () => {
|
||||
return savedState.create()
|
||||
.then((state) => state.get())
|
||||
.then((state) => {
|
||||
expect(state).to.be.empty
|
||||
})
|
||||
})
|
||||
|
||||
it('only saves allowed keys', () => {
|
||||
return savedState.create()
|
||||
.then((state) => {
|
||||
|
||||
@@ -20,46 +20,59 @@ describe('lib/scaffold', () => {
|
||||
return Fixtures.remove()
|
||||
})
|
||||
|
||||
context('.integrationExampleName', () => {
|
||||
it('returns examples', () => {
|
||||
expect(scaffold.integrationExampleName()).to.eq('examples')
|
||||
})
|
||||
})
|
||||
|
||||
// TODO: fix it later
|
||||
context.skip('.isNewProject', () => {
|
||||
context('.isNewProject', () => {
|
||||
beforeEach(function () {
|
||||
const todosPath = Fixtures.projectPath('todos')
|
||||
this.pristinePath = Fixtures.projectPath('pristine')
|
||||
})
|
||||
|
||||
return config.get(todosPath)
|
||||
it('is true when integrationFolder is empty', function () {
|
||||
const pristine = new ProjectE2E(this.pristinePath)
|
||||
|
||||
return pristine.getConfig()
|
||||
.then((cfg) => {
|
||||
this.cfg = cfg;
|
||||
({ integrationFolder: this.integrationFolder } = this.cfg)
|
||||
return pristine.determineIsNewProject(cfg)
|
||||
}).then((ret) => {
|
||||
expect(ret).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
it('is false when integrationFolder has been changed', function () {
|
||||
const pristine = new ProjectE2E(this.pristinePath)
|
||||
|
||||
return pristine.getConfig({ integrationFolder: 'foo' })
|
||||
.then((cfg) => {
|
||||
return pristine.determineIsNewProject(cfg)
|
||||
}).then((ret) => {
|
||||
expect(ret).to.be.false
|
||||
})
|
||||
})
|
||||
|
||||
it('is false when files.length isnt 1', function () {
|
||||
const id = () => {
|
||||
this.ids = new ProjectE2E(this.idsPath)
|
||||
const idsPath = Fixtures.projectPath('ids')
|
||||
|
||||
this.ids = new ProjectE2E(idsPath)
|
||||
|
||||
return this.ids.getConfig()
|
||||
.then((cfg) => {
|
||||
return this.ids.scaffold(cfg).return(cfg)
|
||||
}).then((cfg) => {
|
||||
return this.ids.determineIsNewProject(cfg.integrationFolder)
|
||||
return this.ids.determineIsNewProject(cfg)
|
||||
}).then((ret) => {
|
||||
expect(ret).to.be.false
|
||||
})
|
||||
}
|
||||
|
||||
const todo = () => {
|
||||
this.todos = new ProjectE2E(this.todosPath)
|
||||
const todosPath = Fixtures.projectPath('todos')
|
||||
|
||||
this.todos = new ProjectE2E(todosPath)
|
||||
|
||||
return this.todos.getConfig()
|
||||
.then((cfg) => {
|
||||
return this.todos.scaffold(cfg).return(cfg)
|
||||
}).then((cfg) => {
|
||||
return this.todos.determineIsNewProject(cfg.integrationFolder)
|
||||
return this.todos.determineIsNewProject(cfg)
|
||||
}).then((ret) => {
|
||||
expect(ret).to.be.false
|
||||
})
|
||||
@@ -69,29 +82,26 @@ describe('lib/scaffold', () => {
|
||||
})
|
||||
|
||||
it('is true when files, name + bytes match to scaffold', function () {
|
||||
// TODO this test really can move to scaffold
|
||||
const pristine = new ProjectE2E(this.pristinePath)
|
||||
|
||||
return pristine.getConfig()
|
||||
.then((cfg) => {
|
||||
return pristine.scaffold(cfg).return(cfg)
|
||||
}).then((cfg) => {
|
||||
return pristine.determineIsNewProject(cfg.integrationFolder)
|
||||
return pristine.determineIsNewProject(cfg)
|
||||
}).then((ret) => {
|
||||
expect(ret).to.be.true
|
||||
})
|
||||
})
|
||||
|
||||
it('is false when bytes dont match scaffold', function () {
|
||||
// TODO this test really can move to scaffold
|
||||
const pristine = new ProjectE2E(this.pristinePath)
|
||||
|
||||
return pristine.getConfig()
|
||||
.then((cfg) => {
|
||||
return pristine.scaffold(cfg).return(cfg)
|
||||
}).then((cfg) => {
|
||||
const example = scaffold.integrationExampleName()
|
||||
const file = path.join(cfg.integrationFolder, example)
|
||||
const file = path.join(cfg.integrationFolder, '1-getting-started', 'todo.spec.js')
|
||||
|
||||
// write some data to the file so it is now
|
||||
// different in file size
|
||||
@@ -102,7 +112,7 @@ describe('lib/scaffold', () => {
|
||||
return fs.writeFileAsync(file, str).return(cfg)
|
||||
})
|
||||
}).then((cfg) => {
|
||||
return pristine.determineIsNewProject(cfg.integrationFolder)
|
||||
return pristine.determineIsNewProject(cfg)
|
||||
}).then((ret) => {
|
||||
expect(ret).to.be.false
|
||||
})
|
||||
@@ -126,10 +136,10 @@ describe('lib/scaffold', () => {
|
||||
)
|
||||
.spread((exampleSpecs) => {
|
||||
return Promise.join(
|
||||
fs.statAsync(`${this.integrationFolder}/examples/actions.spec.js`).get('size'),
|
||||
fs.statAsync(`${this.integrationFolder}/1-getting-started/todo.spec.js`).get('size'),
|
||||
fs.statAsync(exampleSpecs[0]).get('size'),
|
||||
fs.statAsync(`${this.integrationFolder}/examples/location.spec.js`).get('size'),
|
||||
fs.statAsync(exampleSpecs[8]).get('size'),
|
||||
fs.statAsync(`${this.integrationFolder}/2-advanced-examples/location.spec.js`).get('size'),
|
||||
fs.statAsync(exampleSpecs[9]).get('size'),
|
||||
).spread((size1, size2, size3, size4) => {
|
||||
expect(size1).to.eq(size2)
|
||||
|
||||
@@ -192,6 +202,117 @@ describe('lib/scaffold', () => {
|
||||
})
|
||||
})
|
||||
|
||||
context('.removeIntegration', () => {
|
||||
beforeEach(function () {
|
||||
const pristinePath = Fixtures.projectPath('pristine')
|
||||
|
||||
return config.get(pristinePath).then((cfg) => {
|
||||
this.cfg = cfg;
|
||||
({ integrationFolder: this.integrationFolder } = this.cfg)
|
||||
})
|
||||
})
|
||||
|
||||
it('removes all scaffolded files and folders', function () {
|
||||
return scaffold.integration(this.integrationFolder, this.cfg)
|
||||
.then(() => {
|
||||
return glob('**/*', { cwd: this.integrationFolder })
|
||||
})
|
||||
.then((files) => {
|
||||
expect(files.length).to.be.greaterThan(0)
|
||||
})
|
||||
.then(() => {
|
||||
return scaffold.removeIntegration(this.integrationFolder, this.cfg)
|
||||
})
|
||||
.then(() => {
|
||||
return glob('**/*', { cwd: this.integrationFolder })
|
||||
})
|
||||
.then((files) => {
|
||||
expect(files.length).to.equal(0)
|
||||
})
|
||||
})
|
||||
|
||||
it('removes all scaffolded files and folders after the user has deleted files', function () {
|
||||
return scaffold.integration(this.integrationFolder, this.cfg)
|
||||
.then(() => {
|
||||
return glob('**/*', { cwd: this.integrationFolder })
|
||||
})
|
||||
.then((files) => {
|
||||
expect(files.length).to.be.greaterThan(0)
|
||||
|
||||
return Promise.join(
|
||||
fs.unlinkAsync(`${this.integrationFolder}/2-advanced-examples/actions.spec.js`),
|
||||
fs.unlinkAsync(`${this.integrationFolder}/2-advanced-examples/assertions.spec.js`),
|
||||
fs.unlinkAsync(`${this.integrationFolder}/2-advanced-examples/location.spec.js`),
|
||||
)
|
||||
})
|
||||
.then(() => {
|
||||
return scaffold.removeIntegration(this.integrationFolder, this.cfg)
|
||||
})
|
||||
.then(() => {
|
||||
return glob('**/*', { cwd: this.integrationFolder })
|
||||
})
|
||||
.then((files) => {
|
||||
expect(files.length).to.equal(0)
|
||||
})
|
||||
})
|
||||
|
||||
it('does not remove files created by user', function () {
|
||||
return scaffold.integration(this.integrationFolder, this.cfg)
|
||||
.then(() => {
|
||||
return glob('**/*', { cwd: this.integrationFolder })
|
||||
})
|
||||
.then((files) => {
|
||||
expect(files.length).to.be.greaterThan(0)
|
||||
|
||||
return Promise.join(
|
||||
fs.writeFileAsync(`${this.integrationFolder}/2-advanced-examples/custom1.spec.js`, 'foo'),
|
||||
fs.writeFileAsync(`${this.integrationFolder}/2-advanced-examples/custom2.spec.js`, 'bar'),
|
||||
)
|
||||
})
|
||||
.then(() => {
|
||||
return scaffold.removeIntegration(this.integrationFolder, this.cfg)
|
||||
})
|
||||
.then(() => {
|
||||
return glob('**/*', { cwd: this.integrationFolder })
|
||||
})
|
||||
.then((files) => {
|
||||
expect(files).to.have.same.members([
|
||||
'2-advanced-examples',
|
||||
'2-advanced-examples/custom1.spec.js',
|
||||
'2-advanced-examples/custom2.spec.js',
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
it('does not remove files modified by user', function () {
|
||||
return scaffold.integration(this.integrationFolder, this.cfg)
|
||||
.then(() => {
|
||||
return glob('**/*', { cwd: this.integrationFolder })
|
||||
})
|
||||
.then((files) => {
|
||||
expect(files.length).to.be.greaterThan(0)
|
||||
|
||||
return Promise.join(
|
||||
fs.writeFileAsync(`${this.integrationFolder}/2-advanced-examples/actions.spec.js`, 'foo'),
|
||||
fs.writeFileAsync(`${this.integrationFolder}/2-advanced-examples/location.spec.js`, 'bar'),
|
||||
)
|
||||
})
|
||||
.then(() => {
|
||||
return scaffold.removeIntegration(this.integrationFolder, this.cfg)
|
||||
})
|
||||
.then(() => {
|
||||
return glob('**/*', { cwd: this.integrationFolder })
|
||||
})
|
||||
.then((files) => {
|
||||
expect(files).to.have.same.members([
|
||||
'2-advanced-examples',
|
||||
'2-advanced-examples/actions.spec.js',
|
||||
'2-advanced-examples/location.spec.js',
|
||||
])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
context('.support', () => {
|
||||
beforeEach(function () {
|
||||
const pristinePath = Fixtures.projectPath('pristine')
|
||||
|
||||
@@ -15444,10 +15444,10 @@ cyclist@^1.0.1:
|
||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
|
||||
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=
|
||||
|
||||
cypress-example-kitchensink@1.14.0:
|
||||
version "1.14.0"
|
||||
resolved "https://registry.yarnpkg.com/cypress-example-kitchensink/-/cypress-example-kitchensink-1.14.0.tgz#f7048172f5871e64e4e8bc4c065900289d80824a"
|
||||
integrity sha512-szaMnRjR9EqEDbQ4DE5Xdy8kHZx9F3qA/M55xNDuAsIm60Z9THbJxygKbz+C+sdpr0Rn6z2/cTxH/QzMJ8isNw==
|
||||
cypress-example-kitchensink@1.15.2:
|
||||
version "1.15.2"
|
||||
resolved "https://registry.yarnpkg.com/cypress-example-kitchensink/-/cypress-example-kitchensink-1.15.2.tgz#325015726291a5e1e0d0cf89177eb9dec1c13e19"
|
||||
integrity sha512-Ni/xbpMEllrNBrDVxh9juu7W4sbyBGpENuWvFdiojjBxzyvCCHaYCJIdF5kgGNzE5aP4AkoGW/jEk1KiKQzALA==
|
||||
dependencies:
|
||||
npm-run-all "^4.1.2"
|
||||
serve "11.3.0"
|
||||
|
||||
Reference in New Issue
Block a user