From 8b0368f70569913532e13f1730dfcd3a8afcb161 Mon Sep 17 00:00:00 2001 From: Chris Breiding <chrisbreiding@gmail.com> Date: Thu, 5 Jul 2018 14:59:09 -0400 Subject: [PATCH 1/2] refactor desktop-gui spec path handling - differentiate folders vs spec files - simplify specs store, remove mutations - fix spec changing via browser url - normalize paths for windows on server --- .../cypress/fixtures/specs_windows.json | 34 ---- .../integration/specs_list_spec.coffee | 156 +++++++++--------- .../desktop-gui/src/projects/projects-api.js | 6 +- .../desktop-gui/src/specs/folder-model.js | 21 +++ packages/desktop-gui/src/specs/spec-model.js | 37 ++--- packages/desktop-gui/src/specs/specs-list.jsx | 34 ++-- packages/desktop-gui/src/specs/specs-store.js | 142 ++++++---------- packages/server/lib/util/specs.coffee | 7 + 8 files changed, 178 insertions(+), 259 deletions(-) delete mode 100644 packages/desktop-gui/cypress/fixtures/specs_windows.json create mode 100644 packages/desktop-gui/src/specs/folder-model.js diff --git a/packages/desktop-gui/cypress/fixtures/specs_windows.json b/packages/desktop-gui/cypress/fixtures/specs_windows.json deleted file mode 100644 index 093c9a06d879..000000000000 --- a/packages/desktop-gui/cypress/fixtures/specs_windows.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "integration": [ - { - "name": "app_spec.coffee", - "relative": "cypress\\integration\\app_spec.coffee" - }, - { - "name": "accounts\\account_new_spec.coffee", - "relative": "cypress\\integration\\accounts\\account_new_spec.coffee" - }, - { - "name": "accounts\\accounts_list_spec.coffee", - "relative": "cypress\\integration\\accounts\\accounts_list_spec.coffee" - }, - { - "name": "admin_users\\admin_users_list_spec.coffee", - "relative": "cypress\\integration\\admin_users\\admin_users_list_spec.coffee" - }, - { - "name": "admin_users\\admin\\foo_list_spec.coffee", - "relative": "cypress\\integration\\admin_users\\admin\\foo_list_spec.coffee" - } - ], - "unit": [ - { - "name": "admin_users\\admin\\users\\bar_list_spec.coffee", - "relative": "cypress\\unit\\admin_users\\admin\\users\\bar_list_spec.coffee" - }, - { - "name": "admin_users\\admin\\users\\all\\admin\\baz_list_spec.coffee", - "relative": "cypress\\unit\\admin_users\\admin\\users\\all\\admin\\baz_list_spec.coffee" - } - ] -} diff --git a/packages/desktop-gui/cypress/integration/specs_list_spec.coffee b/packages/desktop-gui/cypress/integration/specs_list_spec.coffee index 35788a83270c..9b5b9b165c40 100644 --- a/packages/desktop-gui/cypress/integration/specs_list_spec.coffee +++ b/packages/desktop-gui/cypress/integration/specs_list_spec.coffee @@ -3,7 +3,6 @@ describe "Specs List", -> cy.fixture("user").as("user") cy.fixture("config").as("config") cy.fixture("specs").as("specs") - cy.fixture("specs_windows").as("specsWindows") cy.visitIndex().then (win) -> { start, @ipc } = win.App @@ -92,115 +91,109 @@ describe "Specs List", -> expect(@ipc.openFinder).to.be.calledWith(@config.integrationFolder) describe "lists specs", -> - context "Windows paths", -> + context "run all specs", -> beforeEach -> - @ipc.getSpecs.yields(null, @specsWindows) + @ipc.getSpecs.yields(null, @specs) @openProject.resolve(@config) - context "displays list of specs", -> - it "lists nested folders", -> - cy.get(".folder .folder").contains("accounts") - - it "lists test specs", -> - cy.get(".file a").last().should("contain", "baz_list_spec.coffee") - cy.get(".file a").last().should("not.contain", "admin_users") + it "displays run all specs button", -> + cy.contains(".btn", "Run all specs") - context "Linux paths", -> - beforeEach -> - @ipc.getSpecs.yields(null, @specs) - @openProject.resolve(@config) + it "has play icon", -> + cy + .contains(".btn", "Run all specs") + .find("i").should("have.class", "fa-play") - context "run all specs", -> - it "displays run all specs button", -> - cy.contains(".btn", "Run all specs") + it "triggers browser launch on click of button", -> + cy + .contains(".btn", "Run all specs").click() + .then -> + launchArgs = @ipc.launchBrowser.lastCall.args - it "has play icon", -> - cy - .contains(".btn", "Run all specs") - .find("i").should("have.class", "fa-play") + expect(launchArgs[0].browser.name).to.eq "chrome" + expect(launchArgs[0].spec.name).to.eq "All Specs" - it "triggers browser launch on click of button", -> - cy - .contains(".btn", "Run all specs").click() - .then -> - launchArgs = @ipc.launchBrowser.lastCall.args + describe "all specs running in browser", -> + beforeEach -> + cy.contains(".btn", "Run all specs").as("allSpecs").click() - expect(launchArgs[0].browser.name).to.eq "chrome" - expect(launchArgs[0].spec.name).to.eq "All Specs" + it "updates spec icon", -> + cy.get("@allSpecs").find("i").should("have.class", "fa-dot-circle-o") + cy.get("@allSpecs").find("i").should("not.have.class", "fa-play") - describe "all specs running in browser", -> - beforeEach -> - cy.contains(".btn", "Run all specs").as("allSpecs").click() + it "sets spec as active", -> + cy.get("@allSpecs").should("have.class", "active") - it "updates spec icon", -> - cy.get("@allSpecs").find("i").should("have.class", "fa-dot-circle-o") - cy.get("@allSpecs").find("i").should("not.have.class", "fa-play") + context "displays list of specs", -> + beforeEach -> + @ipc.getSpecs.yields(null, @specs) + @openProject.resolve(@config) - it "sets spec as active", -> - cy.get("@allSpecs").should("have.class", "active") + it "lists main folders of specs", -> + cy.contains(".folder", "integration") + cy.contains(".folder", "unit") - context "displays list of specs", -> - it "lists main folders of specs", -> - cy.contains(".folder", "integration") - cy.contains(".folder", "unit") + it "lists nested folders", -> + cy.get(".folder .folder").contains("accounts") - it "lists nested folders", -> - cy.get(".folder .folder").contains("accounts") + it "lists test specs", -> + cy.get(".file a").contains("app_spec.coffee") - it "lists test specs", -> - cy.get(".file a").contains("app_spec.coffee") + context "collapsing specs", -> + beforeEach -> + @ipc.getSpecs.yields(null, @specs) + @openProject.resolve(@config) - context "collapsing specs", -> - it "sets folder collapsed when clicked", -> - cy.get(".folder:first").should("have.class", "folder-expanded") - cy.get(".folder .folder-display-name:first").click() - cy.get(".folder:first").should("have.class", "folder-collapsed") + it "sets folder collapsed when clicked", -> + cy.get(".folder:first").should("have.class", "folder-expanded") + cy.get(".folder .folder-display-name:first").click() + cy.get(".folder:first").should("have.class", "folder-collapsed") - it "hides children when folder clicked", -> - cy.get(".file").should("have.length", 7) - cy.get(".folder .folder-display-name:first").click() - cy.get(".file").should("have.length", 2) + it "hides children when folder clicked", -> + cy.get(".file").should("have.length", 7) + cy.get(".folder .folder-display-name:first").click() + cy.get(".file").should("have.length", 2) - it "sets folder expanded when clicked twice", -> - cy.get(".folder .folder-display-name:first").click() - cy.get(".folder:first").should("have.class", "folder-collapsed") - cy.get(".folder .folder-display-name:first").click() - cy.get(".folder:first").should("have.class", "folder-expanded") + it "sets folder expanded when clicked twice", -> + cy.get(".folder .folder-display-name:first").click() + cy.get(".folder:first").should("have.class", "folder-collapsed") + cy.get(".folder .folder-display-name:first").click() + cy.get(".folder:first").should("have.class", "folder-expanded") - it "hides children for every folder collapsed", -> - lastExpandedFolderSelector = ".folder-expanded:last > div > div > .folder-display-name:last" + it "hides children for every folder collapsed", -> + lastExpandedFolderSelector = ".folder-expanded:last > div > div > .folder-display-name:last" - cy.get(".file").should("have.length", 7) + cy.get(".file").should("have.length", 7) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 6) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 6) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 6) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 6) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 5) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 5) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 5) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 5) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 5) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 5) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 5) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 5) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 4) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 4) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 3) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 3) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 1) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 1) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 0) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 0) context "filtering specs", -> describe "typing the filter", -> @@ -256,7 +249,6 @@ describe "Specs List", -> @openProject.resolve(@config) cy.get(".filter").should("have.value", "") - context "click on spec", -> beforeEach -> @ipc.getSpecs.yields(null, @specs) diff --git a/packages/desktop-gui/src/projects/projects-api.js b/packages/desktop-gui/src/projects/projects-api.js index a264b0a583cf..37f37931aae1 100644 --- a/packages/desktop-gui/src/projects/projects-api.js +++ b/packages/desktop-gui/src/projects/projects-api.js @@ -68,7 +68,7 @@ const runSpec = (project, spec, browser) => { const launchBrowser = () => { project.browserOpening() - ipc.launchBrowser({ browser, spec: spec.obj }, (err, data = {}) => { + ipc.launchBrowser({ browser, spec: spec.file }, (err, data = {}) => { if (data.browserOpened) { project.browserOpened() } @@ -149,8 +149,8 @@ const openProject = (project) => { viewStore.showProjectSpecs(project) }) - ipc.onSpecChanged((__, spec) => { - specsStore.setChosenSpecByRelativePath(spec) + ipc.onSpecChanged((__, relativeSpecPath) => { + specsStore.setChosenSpecByRelativePath(relativeSpecPath) }) ipc.onConfigChanged(() => { diff --git a/packages/desktop-gui/src/specs/folder-model.js b/packages/desktop-gui/src/specs/folder-model.js new file mode 100644 index 000000000000..ddeb48d6486c --- /dev/null +++ b/packages/desktop-gui/src/specs/folder-model.js @@ -0,0 +1,21 @@ +import { action, computed, observable } from 'mobx' + +export default class Directory { + @observable path + @observable displayName + @observable isExpanded = true + @observable children = [] + + constructor ({ path, displayName }) { + this.path = path + this.displayName = displayName + } + + @computed get hasChildren () { + return this.children.length + } + + @action setExpanded (isExpanded) { + this.isExpanded = isExpanded + } +} diff --git a/packages/desktop-gui/src/specs/spec-model.js b/packages/desktop-gui/src/specs/spec-model.js index 95531bd80ba5..f87d307bdd92 100644 --- a/packages/desktop-gui/src/specs/spec-model.js +++ b/packages/desktop-gui/src/specs/spec-model.js @@ -1,39 +1,28 @@ import _ from 'lodash' -import { action, observable } from 'mobx' +import { computed, observable } from 'mobx' export default class Spec { - @observable name @observable path + @observable name + @observable absolute @observable displayName + @observable type @observable isChosen = false - @observable isExpanded = false - @observable children = [] - constructor ({ obj, name, displayName, path }) { - this.obj = obj - this.name = name + constructor ({ path, name, absolute, relative, displayName, type }) { this.path = path - this.isExpanded = true + this.name = name + this.absolute = absolute + this.relative = relative this.displayName = displayName + this.type = type } - getStateProps () { - return _.pick(this, 'isChosen', 'isExpanded') - } - - hasChildren () { - return this.children && this.children.length - } - - @action merge (other) { - _.extend(this, other.getStateProps()) - } - - @action setChosen (isChosen) { - this.isChosen = isChosen + @computed get hasChildren () { + return false } - @action setExpanded (isExpanded) { - this.isExpanded = isExpanded + @computed get file () { + return _.pick(this, 'name', 'absolute', 'relative') } } diff --git a/packages/desktop-gui/src/specs/specs-list.jsx b/packages/desktop-gui/src/specs/specs-list.jsx index b9aa6b5bbb44..e763a5747d62 100644 --- a/packages/desktop-gui/src/specs/specs-list.jsx +++ b/packages/desktop-gui/src/specs/specs-list.jsx @@ -6,17 +6,15 @@ import Loader from 'react-loader' import ipc from '../lib/ipc' import projectsApi from '../projects/projects-api' -import specsStore from './specs-store' +import specsStore, { allSpecsSpec } from './specs-store' @observer -class Specs extends Component { +class SpecsList extends Component { render () { if (specsStore.isLoading) return <Loader color='#888' scale={0.5}/> if (!specsStore.filter && !specsStore.specs.length) return this._empty() - const allSpecsSpec = specsStore.getAllSpecsSpec() - return ( <div id='tests-list-page'> <header> @@ -36,8 +34,8 @@ class Specs extends Component { /> <a className='clear-filter fa fa-times' onClick={this._clearFilter} /> </div> - <a onClick={this._selectSpec.bind(this, allSpecsSpec)} className={cs('all-tests btn btn-default', { active: allSpecsSpec.isChosen })}> - <i className={`fa fa-fw ${this._allSpecsIcon(allSpecsSpec.isChosen)}`}></i>{' '} + <a onClick={this._selectSpec.bind(this, allSpecsSpec)} className={cs('all-tests btn btn-default', { active: specsStore.isChosen(allSpecsSpec) })}> + <i className={`fa fa-fw ${this._allSpecsIcon(specsStore.isChosen(allSpecsSpec))}`}></i>{' '} {allSpecsSpec.displayName} </a> </header> @@ -63,27 +61,15 @@ class Specs extends Component { } _specItem (spec) { - if (spec.hasChildren()) { - return this._folderContent(spec) - } else { - return this._specContent(spec) - } + return spec.hasChildren ? this._folderContent(spec) : this._specContent(spec) } _allSpecsIcon (allSpecsChosen) { - if (allSpecsChosen) { - return 'fa-dot-circle-o green' - } else { - return 'fa-play' - } + return allSpecsChosen ? 'fa-dot-circle-o green' : 'fa-play' } _specIcon (isChosen) { - if (isChosen) { - return 'fa-dot-circle-o green' - } else { - return 'fa-file-code-o' - } + return isChosen ? 'fa-dot-circle-o green' : 'fa-file-code-o' } _clearFilter = () => { @@ -142,10 +128,10 @@ class Specs extends Component { _specContent (spec) { return ( <li key={spec.path} className='file'> - <a href='#' onClick={this._selectSpec.bind(this, spec)} className={cs({ active: spec.isChosen })}> + <a href='#' onClick={this._selectSpec.bind(this, spec)} className={cs({ active: specsStore.isChosen(spec) })}> <div> <div> - <i className={`fa fa-fw ${this._specIcon(spec.isChosen)}`}></i> + <i className={`fa fa-fw ${this._specIcon(specsStore.isChosen(spec))}`}></i> {spec.displayName} </div> </div> @@ -186,4 +172,4 @@ class Specs extends Component { } } -export default Specs +export default SpecsList diff --git a/packages/desktop-gui/src/specs/specs-store.js b/packages/desktop-gui/src/specs/specs-store.js index 3bdc0b3e8c1a..23ad1518a7db 100644 --- a/packages/desktop-gui/src/specs/specs-store.js +++ b/packages/desktop-gui/src/specs/specs-store.js @@ -3,55 +3,53 @@ import { action, computed, observable } from 'mobx' import localData from '../lib/local-data' import Spec from './spec-model' +import Folder from './folder-model' -const ALL_SPECS = '__all' +const extRegex = /.*\.\w+$/ +const isFile = (maybeFile) => extRegex.test(maybeFile) + +export const allSpecsSpec = new Spec({ + name: 'All Specs', + absolute: '__all', + relative: '__all', + displayName: 'Run all specs', +}) + +const formRelativePath = (spec) => { + return spec === allSpecsSpec ? spec.relative : `${spec.type}/${spec.name}` +} export class SpecsStore { - @observable _specs = [] - @observable error = null + @observable _files = [] + @observable chosenSpecPath + @observable error @observable isLoading = false - @observable filter = null - - constructor () { - this.models = [] - - this.allSpecsSpec = new Spec({ - name: null, - path: ALL_SPECS, - displayName: 'Run all specs', - obj: { - name: 'All Specs', - relative: null, - absolute: null, - }, - }) - } + @observable filter @computed get specs () { - return this._tree(this._specs) + return this._tree(this._files) } @action loading (bool) { this.isLoading = bool } - @action setSpecs (specs) { - this._specs = specs + @action setSpecs (specsByType) { + this._files = _.flatten(_.map(specsByType, (specs, type) => { + return _.map(specs, (spec) => { + return _.extend({}, spec, { type }) + }) + })) this.isLoading = false } @action setChosenSpec (spec) { - // set all the models to false - _ - .chain(this.models) - .concat(this.allSpecsSpec) - .invokeMap('setChosen', false) - .value() - - if (spec) { - spec.setChosen(true) - } + this.chosenSpecPath = spec ? formRelativePath(spec) : null + } + + @action setChosenSpecByRelativePath (relativePath) { + this.chosenSpecPath = relativePath } @action setExpandSpecFolder (spec) { @@ -70,85 +68,45 @@ export class SpecsStore { this.filter = null } - setChosenSpecByRelativePath (relativePath) { - // TODO: currently this will always find nothing - // because this data is sent from the driver when - // a spec first opens. it passes the normalized url - // which will no longer match any spec. we need to - // change the logic to do this. it's barely worth it though. - const found = this.findSpecModelByPath(relativePath) - - if (found) { - this.setChosenSpec(found) - } + isChosen (spec) { + return this.chosenSpecPath === formRelativePath(spec) } - findOrCreateSpec (file, segment) { - const spec = new Spec({ - obj: file, // store the original obj - name: file.name, - path: file.relative, - displayName: segment, - }) - - const found = this.findSpecModelByPath(file.relative) - - if (found) { - spec.merge(found) - } - - return spec - } - - findSpecModelByPath (path) { - return _.find(this.models, { path }) - } - - getAllSpecsSpec () { - return this.allSpecsSpec - } - - _tree (specsByType) { - let specs = _.flatten(_.map(specsByType, (specs, type) => { - return _.map(specs, (spec) => { - // add type (unit, integration, etc) to beginning - // and change \\ to / for Windows - return _.extend({}, spec, { - name: `${type}/${spec.name.replace(/\\/g, '/')}`, - }) - }) - })) - + _tree (files) { if (this.filter) { - specs = _.filter(specs, (spec) => { + files = _.filter(files, (spec) => { return spec.name.toLowerCase().includes(this.filter.toLowerCase()) }) } - const specModels = [] + const tree = _.reduce(files, (root, file) => { + const segments = [file.type].concat(file.name.split('/')) + const segmentsPassed = [] - const tree = _.reduce(specs, (root, file) => { let placeholder = root - const segments = file.name.split('/') - _.each(segments, (segment) => { - let spec = _.find(placeholder, { displayName: segment }) - if (!spec) { - spec = this.findOrCreateSpec(file, segment) + segmentsPassed.push(segment) + const path = segmentsPassed.join('/') + const isCurrentAFile = isFile(path) + const props = { path, displayName: segment } + + let existing = _.find(placeholder, { path }) - specModels.push(spec) - placeholder.push(spec) + if (!existing) { + existing = isCurrentAFile ? new Spec(_.extend(file, props)) : new Folder(props) + + placeholder.push(existing) } - placeholder = spec.children + if (!isCurrentAFile) { + placeholder = existing.children + } }) return root }, []) - this.models = specModels - return tree } } diff --git a/packages/server/lib/util/specs.coffee b/packages/server/lib/util/specs.coffee index fe0dc1613265..2f1a10e84fbf 100644 --- a/packages/server/lib/util/specs.coffee +++ b/packages/server/lib/util/specs.coffee @@ -7,6 +7,7 @@ minimatch = require("minimatch") glob = require("./glob") MINIMATCH_OPTIONS = { dot: true, matchBase: true } +backslashRe = /\\/g getPatternRelativeToProjectRoot = (specPattern, projectRoot) -> _.map specPattern, (p) -> @@ -60,6 +61,10 @@ find = (config, specPattern) -> ## relativePathFromIntegrationFolder = foo.coffee ## relativePathFromProjectRoot = cypress/integration/foo.coffee + ## normalize windows \ into / + normalizePath = (file) -> + file.replace(backslashRe, '/') + relativePathFromIntegrationFolder = (file) -> path.relative(integrationFolderPath, file) @@ -72,6 +77,8 @@ find = (config, specPattern) -> if not path.isAbsolute(file) throw new Error("Cannot set parts of file from non-absolute path #{file}") + file = normalizePath(file) + { name: relativePathFromIntegrationFolder(file) relative: relativePathFromProjectRoot(file) From 9ac63fb2edfd23cfd47dccc88b153897b9276572 Mon Sep 17 00:00:00 2001 From: Chris Breiding <chrisbreiding@gmail.com> Date: Fri, 6 Jul 2018 10:15:47 -0400 Subject: [PATCH 2/2] move windows paths logic back to desktop-gui MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit using any method from node’s path util converts / back to \ on windows, so trying to normalize the paths to use / is futile. instead, properly split and compare paths in the desktop gui as needed --- .../cypress/fixtures/specs_windows.json | 34 ++++ .../integration/specs_list_spec.coffee | 160 +++++++++--------- packages/desktop-gui/src/specs/specs-store.js | 22 ++- packages/server/lib/util/specs.coffee | 7 - 4 files changed, 132 insertions(+), 91 deletions(-) create mode 100644 packages/desktop-gui/cypress/fixtures/specs_windows.json diff --git a/packages/desktop-gui/cypress/fixtures/specs_windows.json b/packages/desktop-gui/cypress/fixtures/specs_windows.json new file mode 100644 index 000000000000..093c9a06d879 --- /dev/null +++ b/packages/desktop-gui/cypress/fixtures/specs_windows.json @@ -0,0 +1,34 @@ +{ + "integration": [ + { + "name": "app_spec.coffee", + "relative": "cypress\\integration\\app_spec.coffee" + }, + { + "name": "accounts\\account_new_spec.coffee", + "relative": "cypress\\integration\\accounts\\account_new_spec.coffee" + }, + { + "name": "accounts\\accounts_list_spec.coffee", + "relative": "cypress\\integration\\accounts\\accounts_list_spec.coffee" + }, + { + "name": "admin_users\\admin_users_list_spec.coffee", + "relative": "cypress\\integration\\admin_users\\admin_users_list_spec.coffee" + }, + { + "name": "admin_users\\admin\\foo_list_spec.coffee", + "relative": "cypress\\integration\\admin_users\\admin\\foo_list_spec.coffee" + } + ], + "unit": [ + { + "name": "admin_users\\admin\\users\\bar_list_spec.coffee", + "relative": "cypress\\unit\\admin_users\\admin\\users\\bar_list_spec.coffee" + }, + { + "name": "admin_users\\admin\\users\\all\\admin\\baz_list_spec.coffee", + "relative": "cypress\\unit\\admin_users\\admin\\users\\all\\admin\\baz_list_spec.coffee" + } + ] +} diff --git a/packages/desktop-gui/cypress/integration/specs_list_spec.coffee b/packages/desktop-gui/cypress/integration/specs_list_spec.coffee index 9b5b9b165c40..340688daa679 100644 --- a/packages/desktop-gui/cypress/integration/specs_list_spec.coffee +++ b/packages/desktop-gui/cypress/integration/specs_list_spec.coffee @@ -3,6 +3,7 @@ describe "Specs List", -> cy.fixture("user").as("user") cy.fixture("config").as("config") cy.fixture("specs").as("specs") + cy.fixture("specs_windows").as("specsWindows") cy.visitIndex().then (win) -> { start, @ipc } = win.App @@ -91,109 +92,115 @@ describe "Specs List", -> expect(@ipc.openFinder).to.be.calledWith(@config.integrationFolder) describe "lists specs", -> - context "run all specs", -> + context "Windows paths", -> beforeEach -> - @ipc.getSpecs.yields(null, @specs) + @ipc.getSpecs.yields(null, @specsWindows) @openProject.resolve(@config) - it "displays run all specs button", -> - cy.contains(".btn", "Run all specs") + context "displays list of specs", -> + it "lists nested folders", -> + cy.get(".folder .folder").contains("accounts") - it "has play icon", -> - cy - .contains(".btn", "Run all specs") - .find("i").should("have.class", "fa-play") + it "lists test specs", -> + cy.get(".file a").last().should("contain", "baz_list_spec.coffee") + cy.get(".file a").last().should("not.contain", "admin_users") - it "triggers browser launch on click of button", -> - cy - .contains(".btn", "Run all specs").click() - .then -> - launchArgs = @ipc.launchBrowser.lastCall.args + context "Linux paths", -> + beforeEach -> + @ipc.getSpecs.yields(null, @specs) + @openProject.resolve(@config) - expect(launchArgs[0].browser.name).to.eq "chrome" - expect(launchArgs[0].spec.name).to.eq "All Specs" + context "run all specs", -> + it "displays run all specs button", -> + cy.contains(".btn", "Run all specs") - describe "all specs running in browser", -> - beforeEach -> - cy.contains(".btn", "Run all specs").as("allSpecs").click() + it "has play icon", -> + cy + .contains(".btn", "Run all specs") + .find("i").should("have.class", "fa-play") - it "updates spec icon", -> - cy.get("@allSpecs").find("i").should("have.class", "fa-dot-circle-o") - cy.get("@allSpecs").find("i").should("not.have.class", "fa-play") + it "triggers browser launch on click of button", -> + cy + .contains(".btn", "Run all specs").click() + .then -> + launchArgs = @ipc.launchBrowser.lastCall.args - it "sets spec as active", -> - cy.get("@allSpecs").should("have.class", "active") + expect(launchArgs[0].browser.name).to.eq "chrome" + expect(launchArgs[0].spec.name).to.eq "All Specs" - context "displays list of specs", -> - beforeEach -> - @ipc.getSpecs.yields(null, @specs) - @openProject.resolve(@config) + describe "all specs running in browser", -> + beforeEach -> + cy.contains(".btn", "Run all specs").as("allSpecs").click() - it "lists main folders of specs", -> - cy.contains(".folder", "integration") - cy.contains(".folder", "unit") + it "updates spec icon", -> + cy.get("@allSpecs").find("i").should("have.class", "fa-dot-circle-o") + cy.get("@allSpecs").find("i").should("not.have.class", "fa-play") - it "lists nested folders", -> - cy.get(".folder .folder").contains("accounts") + it "sets spec as active", -> + cy.get("@allSpecs").should("have.class", "active") - it "lists test specs", -> - cy.get(".file a").contains("app_spec.coffee") + context "displays list of specs", -> + it "lists main folders of specs", -> + cy.contains(".folder", "integration") + cy.contains(".folder", "unit") - context "collapsing specs", -> - beforeEach -> - @ipc.getSpecs.yields(null, @specs) - @openProject.resolve(@config) + it "lists nested folders", -> + cy.get(".folder .folder").contains("accounts") - it "sets folder collapsed when clicked", -> - cy.get(".folder:first").should("have.class", "folder-expanded") - cy.get(".folder .folder-display-name:first").click() - cy.get(".folder:first").should("have.class", "folder-collapsed") + it "lists test specs", -> + cy.get(".file a").contains("app_spec.coffee") - it "hides children when folder clicked", -> - cy.get(".file").should("have.length", 7) - cy.get(".folder .folder-display-name:first").click() - cy.get(".file").should("have.length", 2) + context "collapsing specs", -> + it "sets folder collapsed when clicked", -> + cy.get(".folder:first").should("have.class", "folder-expanded") + cy.get(".folder .folder-display-name:first").click() + cy.get(".folder:first").should("have.class", "folder-collapsed") - it "sets folder expanded when clicked twice", -> - cy.get(".folder .folder-display-name:first").click() - cy.get(".folder:first").should("have.class", "folder-collapsed") - cy.get(".folder .folder-display-name:first").click() - cy.get(".folder:first").should("have.class", "folder-expanded") + it "hides children when folder clicked", -> + cy.get(".file").should("have.length", 7) + cy.get(".folder .folder-display-name:first").click() + cy.get(".file").should("have.length", 2) - it "hides children for every folder collapsed", -> - lastExpandedFolderSelector = ".folder-expanded:last > div > div > .folder-display-name:last" + it "sets folder expanded when clicked twice", -> + cy.get(".folder .folder-display-name:first").click() + cy.get(".folder:first").should("have.class", "folder-collapsed") + cy.get(".folder .folder-display-name:first").click() + cy.get(".folder:first").should("have.class", "folder-expanded") - cy.get(".file").should("have.length", 7) + it "hides children for every folder collapsed", -> + lastExpandedFolderSelector = ".folder-expanded:last > div > div > .folder-display-name:last" - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 6) + cy.get(".file").should("have.length", 7) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 6) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 6) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 5) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 6) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 5) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 5) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 5) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 5) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 5) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 5) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 4) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 5) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 3) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 4) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 1) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 3) - cy.get(lastExpandedFolderSelector).click() - cy.get(".file").should("have.length", 0) + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 1) + + cy.get(lastExpandedFolderSelector).click() + cy.get(".file").should("have.length", 0) context "filtering specs", -> describe "typing the filter", -> @@ -249,6 +256,7 @@ describe "Specs List", -> @openProject.resolve(@config) cy.get(".filter").should("have.value", "") + context "click on spec", -> beforeEach -> @ipc.getSpecs.yields(null, @specs) @@ -322,9 +330,7 @@ describe "Specs List", -> cy.get("@firstSpec").should("not.have.class", "active") cy.get("@secondSpec").should("have.class", "active") - ## We aren't properly handling this event so skipping - ## this test for now until its implemented - describe.skip "spec list updates", -> + describe "spec list updates", -> beforeEach -> @ipc.getSpecs.yields(null, @specs) @openProject.resolve(@config) diff --git a/packages/desktop-gui/src/specs/specs-store.js b/packages/desktop-gui/src/specs/specs-store.js index 23ad1518a7db..b096a4717516 100644 --- a/packages/desktop-gui/src/specs/specs-store.js +++ b/packages/desktop-gui/src/specs/specs-store.js @@ -1,10 +1,12 @@ import _ from 'lodash' import { action, computed, observable } from 'mobx' +import path from 'path' import localData from '../lib/local-data' import Spec from './spec-model' import Folder from './folder-model' +const pathSeparatorRe = /[\\\/]/g const extRegex = /.*\.\w+$/ const isFile = (maybeFile) => extRegex.test(maybeFile) @@ -16,7 +18,13 @@ export const allSpecsSpec = new Spec({ }) const formRelativePath = (spec) => { - return spec === allSpecsSpec ? spec.relative : `${spec.type}/${spec.name}` + return spec === allSpecsSpec ? spec.relative : path.join(spec.type, spec.name) +} + +const pathsEqual = (path1, path2) => { + if (!path1 || !path2) return false + + return path1.replace(pathSeparatorRe, '') === path2.replace(pathSeparatorRe, '') } export class SpecsStore { @@ -69,7 +77,7 @@ export class SpecsStore { } isChosen (spec) { - return this.chosenSpecPath === formRelativePath(spec) + return pathsEqual(this.chosenSpecPath, formRelativePath(spec)) } _tree (files) { @@ -80,18 +88,18 @@ export class SpecsStore { } const tree = _.reduce(files, (root, file) => { - const segments = [file.type].concat(file.name.split('/')) + const segments = [file.type].concat(file.name.split(pathSeparatorRe)) const segmentsPassed = [] let placeholder = root _.each(segments, (segment) => { segmentsPassed.push(segment) - const path = segmentsPassed.join('/') - const isCurrentAFile = isFile(path) - const props = { path, displayName: segment } + const currentPath = path.join(...segmentsPassed) + const isCurrentAFile = isFile(currentPath) + const props = { path: currentPath, displayName: segment } - let existing = _.find(placeholder, { path }) + let existing = _.find(placeholder, (file) => pathsEqual(file.path, currentPath)) if (!existing) { existing = isCurrentAFile ? new Spec(_.extend(file, props)) : new Folder(props) diff --git a/packages/server/lib/util/specs.coffee b/packages/server/lib/util/specs.coffee index 2f1a10e84fbf..fe0dc1613265 100644 --- a/packages/server/lib/util/specs.coffee +++ b/packages/server/lib/util/specs.coffee @@ -7,7 +7,6 @@ minimatch = require("minimatch") glob = require("./glob") MINIMATCH_OPTIONS = { dot: true, matchBase: true } -backslashRe = /\\/g getPatternRelativeToProjectRoot = (specPattern, projectRoot) -> _.map specPattern, (p) -> @@ -61,10 +60,6 @@ find = (config, specPattern) -> ## relativePathFromIntegrationFolder = foo.coffee ## relativePathFromProjectRoot = cypress/integration/foo.coffee - ## normalize windows \ into / - normalizePath = (file) -> - file.replace(backslashRe, '/') - relativePathFromIntegrationFolder = (file) -> path.relative(integrationFolderPath, file) @@ -77,8 +72,6 @@ find = (config, specPattern) -> if not path.isAbsolute(file) throw new Error("Cannot set parts of file from non-absolute path #{file}") - file = normalizePath(file) - { name: relativePathFromIntegrationFolder(file) relative: relativePathFromProjectRoot(file)