Skip to content

Commit

Permalink
feat: create new spec file from desktop-gui (#15335)
Browse files Browse the repository at this point in the history
Co-authored-by: Zach Bloomquist <github@chary.us>
Co-authored-by: Zach Bloomquist <git@chary.us>
  • Loading branch information
3 people authored Mar 23, 2021
1 parent a97318c commit 3700fe7
Show file tree
Hide file tree
Showing 20 changed files with 643 additions and 116 deletions.
3 changes: 2 additions & 1 deletion npm/webpack-preprocessor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"main": "dist",
"scripts": {
"ban": "ban",
"build": "rm -rf dist && tsc",
"build": "shx rm -rf dist && tsc",
"build-prod": "yarn build",
"deps": "deps-ok && dependency-check --no-dev .",
"license": "license-checker --production --onlyunknown --csv",
Expand Down Expand Up @@ -58,6 +58,7 @@
"react-dom": "16.13.1",
"react-scripts": "3.2",
"semantic-release": "17.0.4",
"shx": "0.3.3",
"sinon": "^9.0.0",
"sinon-chai": "^3.5.0",
"snap-shot-it": "7.9.2",
Expand Down
141 changes: 141 additions & 0 deletions packages/desktop-gui/cypress/integration/specs_list_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ describe('Specs List', function () {
cy.stub(this.ipc, 'onboardingClosed')
cy.stub(this.ipc, 'onSpecChanged')
cy.stub(this.ipc, 'setUserEditor')
cy.stub(this.ipc, 'showNewSpecDialog').resolves({ specs: null, path: null })

this.openProject = this.util.deferred()
cy.stub(this.ipc, 'openProject').returns(this.openProject.promise)
Expand Down Expand Up @@ -989,4 +990,144 @@ describe('Specs List', function () {
})
})
})

describe('new spec file', function () {
beforeEach(function () {
this.openProject.resolve(this.config)
})

it('launches system save dialog', function () {
cy.contains('New Spec File').click().then(function () {
expect(this.ipc.showNewSpecDialog).to.be.called
})
})

context('POSIX paths', function () {
context('when file is created within project path', function () {
beforeEach(function () {
this.newSpec = {
name: 'new_spec.js',
absolute: '/user/project/cypress/integration/new_spec.js',
relative: 'cypress/integration/new_spec.js',
}

this.ipc.showNewSpecDialog.resolves({
specs: { ...this.specs, integration: this.specs.integration.concat(this.newSpec) },
path: this.newSpec.absolute,
})
})

it('adds and highlights new spec item', function () {
cy.contains('New Spec File').click()
cy.contains('new_spec.js').closest('.file').should('have.class', 'new-spec')
})

it('scrolls the new spec item into view', function () {
cy.contains('New Spec File').click()
cy.contains('new_spec.js').closest('.file').then(function ($el) {
cy.stub($el[0], 'scrollIntoView')
cy.contains('New Spec File').click()
cy.wrap($el[0].scrollIntoView).should('be.called')
})
})

it('does not display warning message', function () {
cy.contains('New Spec File').click()
cy.contains('Your file has been successfully created').should('not.be.visible')
})
})

context('when file is created outside of project path', function () {
beforeEach(function () {
this.newSpec = {
name: 'new_spec.js',
absolute: '/user/desktop/my_folder/new_spec.js',
}

this.ipc.showNewSpecDialog.resolves({
specs: this.specs,
path: this.newSpec.absolute,
})
})

it('displays a dismissable warning message', function () {
cy.contains('New Spec File').click()

cy.contains('Your file has been successfully created')
.should('be.visible')
.closest('.notification-wrap')
.find('.notification-close')
.click()

cy.contains('Your file has been successfully created').should('not.be.visible')
})
})
})

context('Windows paths', function () {
beforeEach(function () {
this.ipc.getSpecs.yields(null, this.specsWindows)
})

context('when file is created within project path', function () {
beforeEach(function () {
this.newSpec = {
name: 'new_spec.js',
absolute: 'C:\\Users\\user\\project\\cypress\\integration\\new_spec.js',
relative: 'cypress\\integration\\new_spec.js',
}

this.ipc.showNewSpecDialog.resolves({
specs: { ...this.specsWindows, integration: this.specs.integration.concat(this.newSpec) },
path: this.newSpec.absolute,
})
})

it('adds and highlights new spec item', function () {
cy.contains('New Spec File').click()
cy.contains('new_spec.js').closest('.file').should('have.class', 'new-spec')
})

it('scrolls the new spec item into view', function () {
cy.contains('New Spec File').click()
cy.contains('new_spec.js').closest('.file').then(function ($el) {
cy.stub($el[0], 'scrollIntoView')
cy.contains('New Spec File').click()
cy.wrap($el[0].scrollIntoView).should('be.called')
})
})

it('does not display warning message', function () {
cy.contains('New Spec File').click()
cy.contains('Your file has been successfully created').should('not.be.visible')
})
})

context('when file is created outside of project path', function () {
beforeEach(function () {
this.newSpec = {
name: 'new_spec.js',
absolute: 'C:\\Users\\user\\Desktop\\my_folder\\new_spec.js',
}

this.ipc.showNewSpecDialog.resolves({
specs: this.specsWindows,
path: this.newSpec.absolute,
})
})

it('displays a dismissable warning message', function () {
cy.contains('New Spec File').click()

cy.contains('Your file has been successfully created')
.should('be.visible')
.closest('.notification-wrap')
.find('.notification-close')
.click()

cy.contains('Your file has been successfully created').should('not.be.visible')
})
})
})
})
})
1 change: 1 addition & 0 deletions packages/desktop-gui/src/lib/ipc.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ register('request:access')
register('setup:dashboard:project')
register('set:project:id')
register('show:directory:dialog')
register('show:new:spec:dialog')
register('updater:check', false)
register('updater:run', false)
register('window:open')
Expand Down
7 changes: 7 additions & 0 deletions packages/desktop-gui/src/notifications/notification.scss
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.notification {
bottom: 4.8rem;
padding-left: 1.2rem;
padding-right: 1.2rem;
position: fixed;
right: 0;
Expand Down Expand Up @@ -97,3 +98,9 @@
right: 1rem;
z-index: 9999;
}

.new-spec-warning {
.content i {
color: #F5A327;
}
}
49 changes: 47 additions & 2 deletions packages/desktop-gui/src/specs/specs-list.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Loader from 'react-loader'
import Tooltip from '@cypress/react-tooltip'

import FileOpener from './file-opener'
import Notification from '../notifications/notification'
import ipc from '../lib/ipc'
import projectsApi from '../projects/projects-api'
import specsStore, { allIntegrationSpecsSpec, allComponentSpecsSpec } from './specs-store'
Expand Down Expand Up @@ -62,6 +63,7 @@ class SpecsList extends Component {
}

this.filterRef = React.createRef()
this.newSpecRef = React.createRef()
// when the specs are running and the user changes the search filter
// we still want to show the previous button label to reflect what
// is currently running
Expand All @@ -79,6 +81,22 @@ class SpecsList extends Component {
}
}

componentDidUpdate () {
if (this.newSpecRef.current) {
this.newSpecRef.current.scrollIntoView({ behavior: 'smooth', block: 'center' })
// unset new spec after animation to prevent further scrolling
this.removeNewSpecTimeout = setTimeout(() => specsStore.setNewSpecPath(null), 3000)
}
}

componentWillUnmount () {
if (this.removeNewSpecTimeout) {
clearTimeout(this.removeNewSpecTimeout)
}

specsStore.setNewSpecPath(null)
}

render () {
if (specsStore.isLoading) return <Loader color='#888' scale={0.5}/>

Expand Down Expand Up @@ -132,8 +150,12 @@ class SpecsList extends Component {
<a className='clear-filter fas fa-times' onClick={this._clearFilter} />
</Tooltip>
</div>
<div className='new-file-button'>
<button className='btn btn-primary' onClick={this._createNewFile.bind(this)}>New Spec File</button>
</div>
</header>
{this._specsList()}
{this._newSpecNotification()}
</div>
)
}
Expand Down Expand Up @@ -267,6 +289,18 @@ class SpecsList extends Component {
specsStore.toggleExpandSpecFolder(specFolderPath)
}

_createNewFile (e) {
e.preventDefault()
e.stopPropagation()

ipc.showNewSpecDialog().then(({ specs, path }) => {
if (path) {
specsStore.setNewSpecPath(path)
specsStore.setSpecs(specs)
}
})
}

_folderContent (spec, nestingLevel) {
const isExpanded = spec.isExpanded
const specType = spec.specType || 'integration'
Expand Down Expand Up @@ -349,10 +383,11 @@ class SpecsList extends Component {
}

const isActive = specsStore.isChosen(spec)
const className = cs(`file level-${nestingLevel}`, { active: isActive })
const isNew = specsStore.isNew(spec)
const className = cs(`file level-${nestingLevel}`, { active: isActive, 'new-spec': isNew })

return (
<li key={spec.path} className={className}>
<li key={spec.path} className={className} ref={isNew ? this.newSpecRef : null}>
<a href='#' onClick={this._selectSpec.bind(this, spec)} className="file-name-wrapper">
<div className="file-name">
<i className={`fa-fw ${this._specIcon(isActive)}`} />
Expand Down Expand Up @@ -395,6 +430,16 @@ class SpecsList extends Component {
)
}

_newSpecNotification () {
return (
<Notification className='new-spec-warning' show={specsStore.showNewSpecWarning} onClose={specsStore.dismissNewSpecWarning}>
<i className='fas fa-exclamation-triangle' />
Your file has been successfully created.
However, since it was created outside of your integration folder or is not recognized as a spec file, it won't be visible in this list.
</Notification>
)
}

_openIntegrationFolder () {
ipc.openFinder(this.props.project.integrationFolder)
}
Expand Down
19 changes: 19 additions & 0 deletions packages/desktop-gui/src/specs/specs-store.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ export class SpecsStore {
@observable isLoading = false
@observable filter
@observable selectedSpec
@observable newSpecAbsolutePath
@observable showNewSpecWarning = false

@computed get specs () {
return this._tree(this._files)
Expand All @@ -77,6 +79,10 @@ export class SpecsStore {
})
}))

if (this.newSpecAbsolutePath && !_.find(this._files, this.isNew)) {
this.showNewSpecWarning = true
}

this.isLoading = false
}

Expand Down Expand Up @@ -104,6 +110,15 @@ export class SpecsStore {
}
}

@action setNewSpecPath (absolutePath) {
this.newSpecAbsolutePath = absolutePath
this.dismissNewSpecWarning()
}

@action dismissNewSpecWarning = () => {
this.showNewSpecWarning = false
}

@action setExpandSpecFolder (spec, isExpanded) {
spec.setExpanded(isExpanded)
}
Expand Down Expand Up @@ -144,6 +159,10 @@ export class SpecsStore {
return pathsEqual(this.chosenSpecPath, formRelativePath(spec))
}

isNew = (spec) => {
return pathsEqual(this.newSpecAbsolutePath, spec.absolute)
}

getSpecsFilterId ({ id, path = '' }) {
const shortenedPath = path.replace(/.*cypress/, 'cypress')

Expand Down
25 changes: 25 additions & 0 deletions packages/desktop-gui/src/specs/specs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ $max-nesting-level: 14;
border-bottom: 1px solid #ddd;
display: flex;
justify-content: space-between;
align-items: center;
}

.search {
Expand Down Expand Up @@ -72,6 +73,15 @@ $max-nesting-level: 14;
}
}

.new-file-button {
padding-right: 15px;

button {
font-size: 13px;
padding: 6px 10px;
}
}

.all-tests {
margin-left: auto;
font-size: 13px;
Expand Down Expand Up @@ -197,6 +207,21 @@ $max-nesting-level: 14;
}
}

&.new-spec {
animation: 3s ease-in-out 1 new-spec-highlight;

@keyframes new-spec-highlight {
0%, 100% {
background-color: inherit;
color: inherit;
}

40%, 60% {
background-color: #cdedff;
}
}
}

&:hover, &:focus {
background-color: #f8f8f8;
cursor: pointer;
Expand Down
Loading

4 comments on commit 3700fe7

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 3700fe7 Mar 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the linux x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/6.9.0/circle-develop-3700fe7271b016f8a89c5a7a4c40d0af62155b45/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 3700fe7 Mar 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppVeyor has built the win32 ia32 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/6.7.2/appveyor-develop-3700fe7271b016f8a89c5a7a4c40d0af62155b45/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 3700fe7 Mar 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AppVeyor has built the win32 x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/6.7.2/appveyor-develop-3700fe7271b016f8a89c5a7a4c40d0af62155b45/cypress.tgz

@cypress-bot
Copy link
Contributor

@cypress-bot cypress-bot bot commented on 3700fe7 Mar 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Circle has built the darwin x64 version of the Test Runner.

Learn more about this pre-release platform-specific build at https://on.cypress.io/installing-cypress#Install-pre-release-version.

Run this command to install the pre-release locally:

npm install https://cdn.cypress.io/beta/npm/6.9.0/circle-develop-3700fe7271b016f8a89c5a7a4c40d0af62155b45/cypress.tgz

Please sign in to comment.