diff --git a/lib/controllers/git-controller.js b/lib/controllers/git-controller.js index ef5d53631a..7a9c4ed1bb 100644 --- a/lib/controllers/git-controller.js +++ b/lib/controllers/git-controller.js @@ -36,6 +36,7 @@ export default class GitController extends React.Component { workspace: React.PropTypes.object.isRequired, commandRegistry: React.PropTypes.object.isRequired, notificationManager: React.PropTypes.object.isRequired, + tooltips: React.PropTypes.object.isRequired, confirm: React.PropTypes.func.isRequired, repository: React.PropTypes.object, resolutionProgress: React.PropTypes.object, @@ -116,15 +117,14 @@ export default class GitController extends React.Component { renderStatusBarTile() { return ( this.onConsumeStatusBar(sb)}> - - - + ); } diff --git a/lib/controllers/status-bar-tile-controller.js b/lib/controllers/status-bar-tile-controller.js index e62ca5a8e4..7502dd5f64 100644 --- a/lib/controllers/status-bar-tile-controller.js +++ b/lib/controllers/status-bar-tile-controller.js @@ -1,94 +1,162 @@ -/** @jsx etch.dom */ -/* eslint react/no-unknown-property: "off" */ +import React from 'react'; -import etch from 'etch'; -import {CompositeDisposable} from 'atom'; -import ModelObserver from '../models/model-observer'; +import ObserveModel from '../decorators/observe-model'; import BranchView from '../views/branch-view'; import BranchMenuView from '../views/branch-menu-view'; import PushPullView from '../views/push-pull-view'; import PushPullMenuView from '../views/push-pull-menu-view'; import ChangedFilesCountView from '../views/changed-files-count-view'; +import Tooltip from '../views/tooltip'; +import Commands, {Command} from '../views/commands'; import {autobind} from 'core-decorators'; -export default class StatusBarTileController { - constructor(props) { - this.props = props; - this.inProgress = false; - this.branchMenuView = null; - this.pushPullMenuView = null; - this.branchTooltipDisposable = null; - this.pushPullTooltipDisposable = null; - this.repositoryObserver = new ModelObserver({ - fetchData: this.fetchRepositoryData, - didUpdate: () => { - // Since changing repositories causes the destruction of the DOM - // elements that the tooltips are bound to, we get rid of the old - // disposables & create new tooltips when the disposables are nulled out - this.branchTooltipDisposable && this.branchTooltipDisposable.dispose(); - this.pushPullTooltipDisposable && this.pushPullTooltipDisposable.dispose(); - this.branchTooltipDisposable = null; - this.pushPullTooltipDisposable = null; - return etch.update(this); - }, - }); - this.repositoryObserver.setActiveModel(props.repository); - - this.subs = new CompositeDisposable( - this.props.commandRegistry.add('atom-workspace', { - 'github:fetch': () => this.fetch(), - 'github:pull': () => this.pull(), - 'github:push': () => { - const {remoteName} = this.repositoryObserver.getActiveModelData(); - this.push({force: false, setUpstream: !remoteName}); - }, - 'github:force-push': () => { - const {remoteName} = this.repositoryObserver.getActiveModelData(); - this.push({force: true, setUpstream: !remoteName}); - }, +@ObserveModel({ + getModel: props => props.repository, + fetchData: async repository => { + const promises = { + branchName: repository.getCurrentBranch(), + branches: repository.getBranches(), + changedFilesCount: repository.getStatusesForChangedFiles().then(statuses => { + const {stagedFiles, unstagedFiles, mergeConflictFiles} = statuses; + const changedFiles = new Set(); + + for (const filePath in unstagedFiles) { + changedFiles.add(filePath); + } + for (const filePath in stagedFiles) { + changedFiles.add(filePath); + } + for (const filePath in mergeConflictFiles) { + changedFiles.add(filePath); + } + + return changedFiles.size; }), - ); + }; - etch.initialize(this); - } + const branchName = await promises.branchName; + const branchPromises = { + remoteName: repository.getRemoteForBranch(branchName), + aheadCount: repository.getAheadCount(branchName), + behindCount: repository.getBehindCount(branchName), + }; - async update(props) { - this.props = {...this.props, ...props}; - await this.repositoryObserver.setActiveModel(props.repository); - return etch.update(this); + return { + branchName, + changedFilesCount: await promises.changedFilesCount, + branches: await promises.branches, + remoteName: await branchPromises.remoteName, + aheadCount: await branchPromises.aheadCount, + behindCount: await branchPromises.behindCount, + pullDisabled: (await promises.changedFilesCount) > 0, + }; + }, +}) +export default class StatusBarTileController extends React.Component { + static propTypes = { + workspace: React.PropTypes.object.isRequired, + notificationManager: React.PropTypes.object.isRequired, + commandRegistry: React.PropTypes.object.isRequired, + tooltips: React.PropTypes.object.isRequired, + repository: React.PropTypes.object, + branchName: React.PropTypes.string, + branches: React.PropTypes.arrayOf(React.PropTypes.string), + remoteName: React.PropTypes.string, + aheadCount: React.PropTypes.number, + behindCount: React.PropTypes.number, + changedFilesCount: React.PropTypes.number, + pullDisabled: React.PropTypes.bool, + toggleGitPanel: React.PropTypes.func, + } + + static defaultProps = { + toggleGitPanel: () => {}, + } + + constructor(props, context) { + super(props, context); + + this.state = { + inProgress: false, + pushInProgress: false, + fetchInProgress: false, + }; } render() { - const modelData = this.repositoryObserver.getActiveModelData(); - - if (modelData) { - return ( -
- + + + + this.push({force: false, setUpstream: !this.props.remoteName})} + /> + this.push({force: true, setUpstream: !this.props.remoteName})} + /> + + { this.branchView = e; }} + workspace={this.props.workspace} + checkout={this.checkout} + {...repoProps} + /> + this.branchView} + trigger="click" + className="github-StatusBarTileController-tooltipMenu"> + - - + { this.pushPullView = e; }} + pushInProgress={this.state.pushInProgress} + fetchInProgress={this.state.fetchInProgress} + {...repoProps} + /> + this.pushPullView} + trigger="click" + className="github-StatusBarTileController-tooltipMenu"> + - - ⏰ - -
- ); - } else { - return
; - } + + + + ⏰ + +
+ ); } @autobind @@ -97,150 +165,46 @@ export default class StatusBarTileController { this.props.workspace.open('atom-github://debug/timings'); } - writeAfterUpdate() { - const modelData = this.repositoryObserver.getActiveModelData(); - if (modelData) { - if (this.refs.pushPullView) { this.createOrUpdatePushPullMenu(modelData); } - this.createOrUpdateBranchMenu(modelData); - } - } - - createOrUpdatePushPullMenu(modelData) { - if (this.pushPullMenuView) { - this.pushPullMenuView.update({...modelData, inProgress: this.inProgress}); - } else { - this.pushPullMenuView = new PushPullMenuView({ - ...modelData, - workspace: this.props.workspace, - notificationManager: this.props.notificationManager, - inProgress: this.inProgress, - push: this.push, - pull: this.pull, - fetch: this.fetch, + setInProgressWhile(block, {push, pull, fetch} = {push: false, pull: false, fetch: false}) { + return new Promise((resolve, reject) => { + if (this.state.inProgress) { + resolve(); + return; + } + + this.setState({inProgress: true, pushInProgress: push, fetchInProgress: pull || fetch}, async () => { + try { + await block(); + } catch (e) { + reject(e); + } finally { + this.setState({inProgress: false, pushInProgress: false, fetchInProgress: false}, resolve); + } }); - } - - if (!this.pushPullTooltipDisposable) { - this.pushPullTooltipDisposable = atom.tooltips.add(this.refs.pushPullView.element, { - item: this.pushPullMenuView, - class: 'github-StatusBarTileController-tooltipMenu', - trigger: 'click', - }); - } - } - - createOrUpdateBranchMenu(modelData) { - if (this.branchMenuView) { - this.branchMenuView.update(modelData); - } else { - this.branchMenuView = new BranchMenuView({ - ...modelData, - workspace: this.props.workspace, - notificationManager: this.props.notificationManager, - checkout: this.checkout, - }); - } - - if (!this.branchTooltipDisposable) { - this.branchTooltipDisposable = atom.tooltips.add(this.refs.branchView.element, { - item: this.branchMenuView, - class: 'github-StatusBarTileController-tooltipMenu', - trigger: 'click', - }); - } - } - - getActiveRepository() { - return this.repositoryObserver.getActiveModel(); - } - - @autobind - async fetchRepositoryData(repository) { - const branchName = await repository.getCurrentBranch(); - const remoteName = await repository.getRemoteForBranch(branchName); - const changedFilesCount = await this.fetchChangedFilesCount(repository); - const data = { - branchName, - changedFilesCount, - branches: await repository.getBranches(), - remoteName, - aheadCount: await repository.getAheadCount(branchName), - behindCount: await repository.getBehindCount(branchName), - }; - return data; - } - - async fetchChangedFilesCount(repository) { - const changedFiles = new Set(); - const {stagedFiles, unstagedFiles, mergeConflictFiles} = await repository.getStatusesForChangedFiles(); - - for (const filePath in unstagedFiles) { - changedFiles.add(filePath); - } - for (const filePath in stagedFiles) { - changedFiles.add(filePath); - } - for (const filePath in mergeConflictFiles) { - changedFiles.add(filePath); - } - return changedFiles.size; - } - - getLastModelDataRefreshPromise() { - return this.repositoryObserver.getLastModelDataRefreshPromise(); - } - - async setInProgressWhile(block, {push, pull, fetch} = {}) { - if (this.inProgress) { - return; - } - - this.inProgress = true; - if (push) { - this.pushInProgress = true; - } else if (pull || fetch) { - this.fetchInProgress = true; - } - etch.update(this); - try { - await block(); - } finally { - this.inProgress = false; - this.pushInProgress = false; - this.fetchInProgress = false; - await etch.update(this); - } + }); } @autobind checkout(branchName, options) { - return this.setInProgressWhile(() => this.getActiveRepository().checkout(branchName, options)); + return this.setInProgressWhile(() => this.props.repository.checkout(branchName, options)); } @autobind async push(options) { - const {branchName} = this.repositoryObserver.getActiveModelData(); - - await this.setInProgressWhile(() => this.getActiveRepository().push(branchName, options), {push: true}); + await this.setInProgressWhile(() => this.props.repository.push(this.props.branchName, options), {push: true}); } @autobind async pull() { - const {branchName} = this.repositoryObserver.getActiveModelData(); - await this.setInProgressWhile(() => this.getActiveRepository().pull(branchName), {pull: true}); + if (this.props.pullDisabled) { + return; + } + + await this.setInProgressWhile(() => this.props.repository.pull(this.props.branchName), {pull: true}); } @autobind async fetch() { - const {branchName} = this.repositoryObserver.getActiveModelData(); - await this.setInProgressWhile(() => this.getActiveRepository().fetch(branchName), {fetch: true}); - } - - destroy(removeDomNode) { - this.subs.dispose(); - this.branchTooltipDisposable && this.branchTooltipDisposable.dispose(); - this.pushPullTooltipDisposable && this.pushPullTooltipDisposable.dispose(); - this.repositoryObserver.destroy(); - etch.destroy(this, removeDomNode); + await this.setInProgressWhile(() => this.props.repository.fetch(this.props.branchName), {fetch: true}); } } diff --git a/lib/decorators/observe-model.js b/lib/decorators/observe-model.js index 8ac66e4eaa..3f8b6287a3 100644 --- a/lib/decorators/observe-model.js +++ b/lib/decorators/observe-model.js @@ -30,6 +30,8 @@ export default function ObserveModel(spec) { constructor(props, context) { super(props, context); this.mounted = true; + this.resolve = () => {}; + this.state = { modelData: {}, }; @@ -38,7 +40,7 @@ export default function ObserveModel(spec) { fetchData: model => fetchData(model, this.props), didUpdate: () => { if (this.mounted) { - this.setState({modelData: this.modelObserver.getActiveModelData()}); + this.setState({modelData: this.modelObserver.getActiveModelData()}, this.resolve); } }, }); @@ -61,6 +63,19 @@ export default function ObserveModel(spec) { this.mounted = false; this.modelObserver.destroy(); } + + refreshModelData() { + return new Promise(resolve => { + this.resolve = resolve; + + const model = getModel(this.props); + if (model !== this.modelObserver.getActiveModel()) { + this.modelObserver.setActiveModel(model); + } else { + this.modelObserver.refreshModelData(); + } + }); + } }; }; } diff --git a/lib/github-package.js b/lib/github-package.js index 29aa5179b2..6e1e97ab30 100644 --- a/lib/github-package.js +++ b/lib/github-package.js @@ -20,11 +20,12 @@ const defaultState = { }; export default class GithubPackage { - constructor(workspace, project, commandRegistry, notificationManager, config, confirm) { + constructor(workspace, project, commandRegistry, notificationManager, tooltips, config, confirm) { this.workspace = workspace; this.project = project; this.commandRegistry = commandRegistry; this.notificationManager = notificationManager; + this.tooltips = tooltips; this.config = config; this.modelPromisesByProjectPath = new Map(); @@ -119,6 +120,7 @@ export default class GithubPackage { workspace={this.workspace} commandRegistry={this.commandRegistry} notificationManager={this.notificationManager} + tooltips={this.tooltips} confirm={this.confirm} repository={this.getActiveRepository()} resolutionProgress={this.getActiveResolutionProgress()} diff --git a/lib/index.js b/lib/index.js index 9617de5560..bef51e8783 100644 --- a/lib/index.js +++ b/lib/index.js @@ -39,6 +39,7 @@ function startPackage() { } return new GithubPackage( - atom.workspace, atom.project, atom.commands, atom.notifications, atom.config, atom.confirm.bind(atom), + atom.workspace, atom.project, atom.commands, atom.notifications, atom.tooltips, + atom.config, atom.confirm.bind(atom), ); } diff --git a/lib/views/branch-menu-view.js b/lib/views/branch-menu-view.js index 823f2fdcf3..84e25df7da 100644 --- a/lib/views/branch-menu-view.js +++ b/lib/views/branch-menu-view.js @@ -1,50 +1,91 @@ -/** @jsx etch.dom */ -/* eslint react/no-unknown-property: "off" */ -import {CompositeDisposable} from 'atom'; - -import etch from 'etch'; +import React from 'react'; import {autobind} from 'core-decorators'; +import Commands, {Command} from './commands'; import {GitError} from '../git-shell-out-strategy'; -export default class BranchMenuView { - constructor(props) { - this.props = props; - const TextEditor = props.workspace.buildTextEditor; - this.textEditorWidget = ( - +export default class BranchMenuView extends React.Component { + static propTypes = { + workspace: React.PropTypes.object.isRequired, + commandRegistry: React.PropTypes.object.isRequired, + notificationManager: React.PropTypes.object.isRequired, + branches: React.PropTypes.arrayOf(React.PropTypes.string), + branchName: React.PropTypes.string, + checkout: React.PropTypes.func, + } + + static defaultProps = { + branches: [], + checkout: () => Promise.resolve(), + } + + constructor(props, context) { + super(props, context); + + this.state = { + createNew: false, + checkingOutBranch: null, + }; + } + + render() { + const currentBranch = this.state.checkingOutBranch || this.props.branchName; + const branches = this.props.branches; + if (this.state.checkingOutBranch && this.props.branches.indexOf(this.state.checkingOutBranch) === -1) { + branches.push(this.state.checkingOutBranch); + } + + const newBranchEditor = ( +
+ { this.editorElement = e; }} + mini={true} + softWrapped={true} + placeholderText="enter new branch name" + lineNumberGutterVisible={false} + showInvisibles={false} + scrollPastEnd={false} + /> +
); - this.createNew = false; - etch.initialize(this); - this.subscriptions = new CompositeDisposable( - atom.commands.add('.github-BranchMenuView-editor atom-text-editor[mini]', { - 'tool-panel:unfocus': this.cancelCreateNewBranch, - 'core:cancel': this.cancelCreateNewBranch, - 'core:confirm': this.createBranch, - }), + + const selectBranchView = ( + ); - } - update(props) { - this.props = {...this.props, ...props}; - this.createNew = false; - return etch.update(this); + return ( +
+ + + + + +
+ + { this.state.createNew ? newBranchEditor : selectBranchView } + +
+
+ ); } @autobind - async didSelectItem() { - const branchName = this.refs.list.selectedOptions[0].text; + async didSelectItem(event) { + const branchName = event.target.value; + this.setState({checkingOutBranch: branchName}); + try { await this.props.checkout(branchName); } catch (e) { + this.setState({checkingOutBranch: null}); if (!(e instanceof GitError)) { throw e; } if (e.stdErr.match(/local changes.*would be overwritten/)) { const files = e.stdErr.split(/\r?\n/).filter(l => l.startsWith('\t')).map(l => `\`${l.trim()}\``).join('
'); @@ -56,22 +97,20 @@ export default class BranchMenuView { dismissable: true, }, ); - this.refs.list.selectedIndex = this.props.branches.indexOf(this.props.branchName); } else { this.props.notificationManager.addError('Checkout aborted', {description: e.stdErr, dismissable: true}); } - return etch.update(this); } return null; } @autobind async createBranch() { - if (this.createNew) { - const branchName = this.refs.editor.getText().trim(); + if (this.state.createNew) { + const branchName = this.editorElement.getModel().getText().trim(); try { await this.props.checkout(branchName, {createNew: true}); - return null; + this.setState({createNew: false, checkingOutBranch: branchName}); } catch (e) { if (!(e instanceof GitError)) { throw e; } if (e.stdErr.match(/branch.*already exists/)) { @@ -92,55 +131,15 @@ export default class BranchMenuView { } else { this.props.notificationManager.addError('Cannot create branch', {description: e.stdErr, dismissable: true}); } - this.createNew = false; - return etch.update(this); + this.setState({createNew: false}); } } else { - this.createNew = true; - return etch.update(this).then(() => { - this.refs.editor.element.focus(); - }); + this.setState({createNew: true}); } } @autobind cancelCreateNewBranch() { - this.createNew = false; - etch.update(this); - } - - render() { - const newBranchEditor = ( -
- {this.textEditorWidget} -
- ); - - const selectBranchView = ( - - ); - - return ( -
-
- - { this.createNew ? newBranchEditor : selectBranchView } - -
-
- ); - } - - destroy() { - this.subscriptions.dispose(); - etch.destroy(this); + this.setState({createNew: false}); } } diff --git a/lib/views/branch-view.js b/lib/views/branch-view.js index dd4a7b21e1..93e8887474 100644 --- a/lib/views/branch-view.js +++ b/lib/views/branch-view.js @@ -1,22 +1,13 @@ -/** @jsx etch.dom */ -/* eslint react/no-unknown-property: "off" */ +import React from 'react'; -import etch from 'etch'; - -export default class BranchView { - constructor(props) { - this.props = props; - etch.initialize(this); - } - - update(props) { - this.props = {...this.props, ...props}; - return etch.update(this); +export default class BranchView extends React.Component { + static propTypes = { + branchName: React.PropTypes.string, } render() { return ( -
+
{ this.element = e; }}> {this.props.branchName}
diff --git a/lib/views/changed-files-count-view.js b/lib/views/changed-files-count-view.js index 7cfdf864dc..bc7e42f7f7 100644 --- a/lib/views/changed-files-count-view.js +++ b/lib/views/changed-files-count-view.js @@ -1,17 +1,14 @@ -/** @jsx etch.dom */ -/* eslint react/no-unknown-property: "off" */ +import React from 'react'; -import etch from 'etch'; - -export default class ChangedFilesCountView { - constructor(props) { - this.props = props; - etch.initialize(this); +export default class ChangedFilesCountView extends React.Component { + static propTypes = { + changedFilesCount: React.PropTypes.number, + didClick: React.PropTypes.func, } - update(props) { - this.props = {...this.props, ...props}; - return etch.update(this); + static defaultProps = { + changedFilesCount: 0, + didClick: () => {}, } render() { @@ -23,7 +20,7 @@ export default class ChangedFilesCountView { {label} + onClick={this.props.didClick}>{label} ); } } diff --git a/lib/views/push-pull-menu-view.js b/lib/views/push-pull-menu-view.js index 22fdfa89fd..4d3fc11d61 100644 --- a/lib/views/push-pull-menu-view.js +++ b/lib/views/push-pull-menu-view.js @@ -1,71 +1,90 @@ -/** @jsx etch.dom */ -/* eslint react/no-unknown-property: "off" */ - -import etch from 'etch'; +import React from 'react'; import {autobind} from 'core-decorators'; import {GitError} from '../git-shell-out-strategy'; -export default class PushPullMenuView { - constructor(props) { - this.props = props; - this.checkRemote(); - etch.initialize(this); +export default class PushPullMenuView extends React.Component { + static propTypes = { + inProgress: React.PropTypes.bool, + pullDisabled: React.PropTypes.bool, + branchName: React.PropTypes.string, + remoteName: React.PropTypes.string, + aheadCount: React.PropTypes.number, + behindCount: React.PropTypes.number, } - update(props) { - this.props = {...this.props, ...props}; - this.checkRemote(); - return etch.update(this); + static defaultProps = { + inProgress: false, + pullDisabled: false, } - checkRemote() { - if (!this.props.remoteName) { - this.errorMessage = `Note: No remote detected for branch ${this.props.branchName}. ` + - 'Pushing will set up a remote tracking branch on remote repo "origin"'; - } else { - this.errorMessage = ''; - } + constructor(props, context) { + super(props, context); + + this.state = { + errorMessage: '', + }; } render() { + const errorMessage = this.getErrorMessage(); + return (
-
-
+
+ {errorMessage} +
); } + getErrorMessage() { + if (this.state.errorMessage !== '') { + return this.state.errorMessage; + } + + if (!this.props.remoteName) { + return `Note: No remote detected for branch ${this.props.branchName}. ` + + 'Pushing will set up a remote tracking branch on remote repo "origin"'; + } + + return ''; + } + async attemptGitOperation(operation, errorTransform = message => message) { const operationPromise = operation(); try { - etch.update(this); - await operationPromise; + return await operationPromise; } catch (error) { if (!(error instanceof GitError)) { throw error; } const {message, description} = errorTransform(error.stdErr); @@ -73,8 +92,8 @@ export default class PushPullMenuView { message || 'Cannot complete remote interaction', {description, dismissable: true}, ); + return null; } - return etch.update(this); } @autobind diff --git a/lib/views/push-pull-view.js b/lib/views/push-pull-view.js index 10fead429c..41f2bf6b8e 100644 --- a/lib/views/push-pull-view.js +++ b/lib/views/push-pull-view.js @@ -1,18 +1,19 @@ -/** @jsx etch.dom */ -/* eslint react/no-unknown-property: "off" */ - -import etch from 'etch'; +import React from 'react'; import cx from 'classnames'; -export default class PushPullView { - constructor(props) { - this.props = props; - etch.initialize(this); +export default class PushPullView extends React.Component { + static propTypes = { + pushInProgress: React.PropTypes.bool, + fetchInProgress: React.PropTypes.bool, + behindCount: React.PropTypes.number, + aheadCount: React.PropTypes.number, } - update(props) { - this.props = {...this.props, ...props}; - return etch.update(this); + static defaultProps = { + pushInProgress: false, + fetchInProgress: false, + behindCount: 0, + aheadCount: 0, } render() { @@ -21,13 +22,13 @@ export default class PushPullView { const pushClasses = cx('github-PushPull-icon', 'icon', {'icon-arrow-up': !pushing, 'icon-sync': pushing}); const pullClasses = cx('github-PushPull-icon', 'icon', {'icon-arrow-down': !pulling, 'icon-sync': pulling}); return ( -
+
{ this.element = e; }}> - + {this.props.behindCount ? `${this.props.behindCount}` : ''} - + {this.props.aheadCount ? `${this.props.aheadCount}` : ''}
diff --git a/lib/views/tooltip.js b/lib/views/tooltip.js index 3d91dbc0d3..7445a3e820 100644 --- a/lib/views/tooltip.js +++ b/lib/views/tooltip.js @@ -1,53 +1,105 @@ -/** @jsx etch.dom */ - -import etch from 'etch'; -import {autobind} from 'core-decorators'; - -export default class Tooltip { - constructor({active, text, ...otherProps}, children) { - this.active = active; - this.text = text; - this.children = children; - this.otherProps = otherProps; - this.handleMouseOut = this.handleMouseOut; - this.handleMouseOver = this.handleMouseOver; - etch.initialize(this); - } - - update({active, text, ...otherProps}, children) { - this.active = active; - this.text = text; - this.children = children; - this.otherProps = otherProps; - return etch.update(this); - } - - @autobind - handleMouseOut() { - if (this.tooltipDisposable) { - this.tooltipDisposable.dispose(); - this.tooltipDisposable = null; - } +import React from 'react'; + +import Portal from './portal'; + +export default class Tooltip extends React.Component { + static propTypes = { + manager: React.PropTypes.object.isRequired, + target: React.PropTypes.func.isRequired, + title: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.func, + ]), + html: React.PropTypes.bool, + className: React.PropTypes.string, + placement: React.PropTypes.oneOfType([ + React.PropTypes.string, + React.PropTypes.func, + ]), + trigger: React.PropTypes.oneOf(['hover', 'click', 'focus', 'manual']), + showDelay: React.PropTypes.number, + hideDelay: React.PropTypes.number, + keyBindingCommand: React.PropTypes.string, + keyBindingTarget: React.PropTypes.element, + children: React.PropTypes.element, + } + + constructor(props, context) { + super(props, context); + + this.disposable = null; } - @autobind - handleMouseOver() { - if (this.active && !this.tooltipDisposable) { - const element = this.element; - this.tooltipDisposable = atom.tooltips.add(element, {title: this.text, trigger: 'manual'}); + componentWillReceiveProps(nextProps) { + const propKeys = [ + 'tooltips', 'title', 'html', 'className', 'placement', 'trigger', 'showDelay', 'hideDelay', + 'keyBindingCommand', 'keyBindingTarget', + ]; + + if (propKeys.some(key => this.props[key] !== nextProps[key])) { + this.disposable && this.disposable.dispose(); + this.disposable = null; + + this.setupTooltip(nextProps); } } + componentDidMount() { + this.setupTooltip(this.props); + } + render() { - return ( -
- {this.children} -
- ); + if (this.props.children !== undefined) { + return ( + { this.portal = c; }}>{this.props.children} + ); + } else { + return null; + } } - destroy() { - this.tooltipDisposable && this.tooltipDisposable.dispose(); - etch.destroy(this); + componentWillUnmount() { + this.disposable && this.disposable.dispose(); + } + + setupTooltip(props) { + if (this.disposable) { + return; + } + + const options = {}; + ['title', 'html', 'placement', 'trigger', 'keyBindingCommand', 'keyBindingTarget'].forEach(key => { + if (props[key] !== undefined) { + options[key] = props[key]; + } + }); + if (props.className !== undefined) { + options.class = props.className; + } + if (props.showDelay !== undefined || props.hideDelay !== undefined) { + const delayDefaults = (props.trigger === 'hover' || props.trigger === undefined) + && {show: 1000, hide: 100} + || {show: 0, hide: 0}; + + options.delay = { + show: props.showDelay !== undefined ? props.showDelay : delayDefaults.show, + hide: props.hideDelay !== undefined ? props.hideDelay : delayDefaults.hide, + }; + } + if (props.children !== undefined) { + options.item = this.portal; + } + + const target = this.getCurrentTarget(props); + this.disposable = props.manager.add(target, options); + } + + getCurrentTarget(props) { + const target = props.target(); + if (target !== null && target.element !== undefined) { + return target.element; + } else { + return target; + } } } diff --git a/test/controllers/git-controller.test.js b/test/controllers/git-controller.test.js index 57519ed078..bbd2725db8 100644 --- a/test/controllers/git-controller.test.js +++ b/test/controllers/git-controller.test.js @@ -262,9 +262,9 @@ describe('GitController', function() { const wrapper = shallow(app); assert.isFalse(wrapper.find('Panel').prop('visible')); - wrapper.find('StatusBarTileController').prop('toggleGitPanel')(); + wrapper.find('ObserveModel(StatusBarTileController)').prop('toggleGitPanel')(); assert.isTrue(wrapper.find('Panel').prop('visible')); - wrapper.find('StatusBarTileController').prop('toggleGitPanel')(); + wrapper.find('ObserveModel(StatusBarTileController)').prop('toggleGitPanel')(); assert.isFalse(wrapper.find('Panel').prop('visible')); }); }); diff --git a/test/controllers/status-bar-tile-controller.test.js b/test/controllers/status-bar-tile-controller.test.js index b49b585bd4..fb42481389 100644 --- a/test/controllers/status-bar-tile-controller.test.js +++ b/test/controllers/status-bar-tile-controller.test.js @@ -1,51 +1,73 @@ import fs from 'fs'; import path from 'path'; -import etch from 'etch'; +import React from 'react'; import until from 'test-until'; +import {mount} from 'enzyme'; import {cloneRepository, buildRepository, setUpLocalAndRemoteRepositories} from '../helpers'; import StatusBarTileController from '../../lib/controllers/status-bar-tile-controller'; +import BranchView from '../../lib/views/branch-view'; +import PushPullView from '../../lib/views/push-pull-view'; +import ChangedFilesCountView from '../../lib/views/changed-files-count-view'; describe('StatusBarTileController', function() { - let atomEnvironment, workspace, workspaceElement, commandRegistry, notificationManager; + let atomEnvironment; + let workspace, workspaceElement, commandRegistry, notificationManager, tooltips; + let component; beforeEach(function() { atomEnvironment = global.buildAtomEnvironment(); workspace = atomEnvironment.workspace; commandRegistry = atomEnvironment.commands; notificationManager = atomEnvironment.notifications; + tooltips = atomEnvironment.tooltips; workspaceElement = atomEnvironment.views.getView(workspace); + + component = ( + + ); }); afterEach(function() { atomEnvironment.destroy(); }); + function getTooltipNode(wrapper, selector) { + const ts = tooltips.findTooltips(wrapper.find(selector).node.element); + assert.lengthOf(ts, 1); + ts[0].show(); + return ts[0].getTooltipElement(); + } + describe('branches', function() { - it('indicates the current branch and toggles visibility of the branch menu when clicked', async function() { + it('indicates the current branch', async function() { const workdirPath = await cloneRepository('three-files'); const repository = await buildRepository(workdirPath); - const controller = new StatusBarTileController({workspace, repository, commandRegistry}); - await controller.getLastModelDataRefreshPromise(); - await etch.getScheduler().getNextUpdatePromise(); - - const branchView = controller.refs.branchView; - assert.equal(branchView.element.textContent, 'master'); + const wrapper = mount(React.cloneElement(component, {repository})); + await wrapper.instance().refreshModelData(); - // FIXME: Remove this guard when 1.13 is on stable. - if (parseFloat(atom.getVersion() >= 1.13)) { - assert.isUndefined(document.querySelectorAll('.github-BranchMenuView')[0]); - branchView.element.click(); - assert.isDefined(document.querySelectorAll('.github-BranchMenuView')[0]); - branchView.element.click(); - assert.isUndefined(document.querySelectorAll('.github-BranchMenuView')[0]); - } + assert.equal(wrapper.find(BranchView).prop('branchName'), 'master'); }); describe('the branch menu', function() { + function selectOption(tip, value) { + const selects = Array.from(tip.getElementsByTagName('select')); + assert.lengthOf(selects, 1); + const select = selects[0]; + select.value = value; + + const event = new Event('change', {bubbles: true, cancelable: true}); + select.dispatchEvent(event); + } + describe('checking out an existing branch', function() { it('can check out existing branches with no conflicts', async function() { const workdirPath = await cloneRepository('three-files'); @@ -54,27 +76,28 @@ describe('StatusBarTileController', function() { // create branch called 'branch' await repository.git.exec(['branch', 'branch']); - const controller = new StatusBarTileController({workspace, repository, commandRegistry}); - await controller.getLastModelDataRefreshPromise(); - await etch.getScheduler().getNextUpdatePromise(); + const wrapper = mount(React.cloneElement(component, {repository})); + await wrapper.instance().refreshModelData(); - const branchMenuView = controller.branchMenuView; - const {list} = branchMenuView.refs; + const tip = getTooltipNode(wrapper, BranchView); - const branches = Array.from(list.options).map(option => option.value); + const branches = Array.from(tip.getElementsByTagName('option'), e => e.innerHTML); assert.deepEqual(branches, ['branch', 'master']); + assert.equal(await repository.getCurrentBranch(), 'master'); - assert.equal(list.selectedOptions[0].value, 'master'); + assert.equal(tip.querySelector('select').value, 'master'); - list.selectedIndex = branches.indexOf('branch'); - list.onchange(); + selectOption(tip, 'branch'); assert.equal(await repository.getCurrentBranch(), 'branch'); - assert.equal(list.selectedOptions[0].value, 'branch'); + assert.equal(tip.querySelector('select').value, 'branch'); + await wrapper.instance().refreshModelData(); + assert.equal(tip.querySelector('select').value, 'branch'); - list.selectedIndex = branches.indexOf('master'); - list.onchange(); + selectOption(tip, 'master'); assert.equal(await repository.getCurrentBranch(), 'master'); - assert.equal(list.selectedOptions[0].value, 'master'); + assert.equal(tip.querySelector('select').value, 'master'); + await wrapper.instance().refreshModelData(); + assert.equal(tip.querySelector('select').value, 'master'); }); it('displays an error message if checkout fails', async function() { @@ -89,25 +112,25 @@ describe('StatusBarTileController', function() { await repository.checkout('branch'); fs.writeFileSync(path.join(localRepoPath, 'a.txt'), 'a change that conflicts'); - const controller = new StatusBarTileController({workspace, repository, commandRegistry, notificationManager}); - await controller.getLastModelDataRefreshPromise(); - await etch.getScheduler().getNextUpdatePromise(); + const wrapper = mount(React.cloneElement(component, {repository})); + await wrapper.instance().refreshModelData(); - const branchMenuView = controller.branchMenuView; - const {list} = branchMenuView.refs; + const tip = getTooltipNode(wrapper, BranchView); - const branches = Array.from(list.options).map(option => option.value); assert.equal(await repository.getCurrentBranch(), 'branch'); - assert.equal(list.selectedOptions[0].value, 'branch'); + assert.equal(tip.querySelector('select').value, 'branch'); sinon.stub(notificationManager, 'addError'); - list.selectedIndex = branches.indexOf('master'); - list.onchange(); - await etch.getScheduler().getNextUpdatePromise(); - assert.equal(await repository.getCurrentBranch(), 'branch'); - await assert.async.equal(list.selectedOptions[0].value, 'branch'); - await assert.async.isTrue(notificationManager.addError.called); + selectOption(tip, 'master'); + // Optimistic render + assert.equal(tip.querySelector('select').value, 'master'); + await until(async () => { + await wrapper.instance().refreshModelData(); + return tip.querySelector('select').value === 'branch'; + }); + + assert.isTrue(notificationManager.addError.called); const notificationArgs = notificationManager.addError.args[0]; assert.equal(notificationArgs[0], 'Checkout aborted'); assert.match(notificationArgs[1].description, /Local changes to the following would be overwritten/); @@ -119,110 +142,108 @@ describe('StatusBarTileController', function() { const workdirPath = await cloneRepository('three-files'); const repository = await buildRepository(workdirPath); - const controller = new StatusBarTileController({workspace, repository, commandRegistry}); - await controller.getLastModelDataRefreshPromise(); - await etch.getScheduler().getNextUpdatePromise(); + const wrapper = mount(React.cloneElement(component, {repository})); + await wrapper.instance().refreshModelData(); - const branchMenuView = controller.branchMenuView; - const {list, newBranchButton} = branchMenuView.refs; + const tip = getTooltipNode(wrapper, BranchView); - const branches = Array.from(list.options).map(option => option.value); + const branches = Array.from(tip.querySelectorAll('option'), option => option.value); assert.deepEqual(branches, ['master']); assert.equal(await repository.getCurrentBranch(), 'master'); - assert.equal(list.selectedOptions[0].value, 'master'); + assert.equal(tip.querySelector('select').value, 'master'); + + tip.querySelector('button').click(); - assert.isDefined(branchMenuView.refs.list); - assert.isUndefined(branchMenuView.refs.editor); - newBranchButton.click(); - await etch.getScheduler().getNextUpdatePromise(); - assert.isUndefined(branchMenuView.refs.list); - assert.isDefined(branchMenuView.refs.editor); + assert.lengthOf(tip.querySelectorAll('select'), 0); + assert.lengthOf(tip.querySelectorAll('.github-BranchMenuView-editor'), 1); - branchMenuView.refs.editor.setText('new-branch'); - await newBranchButton.onclick(); - repository.refresh(); - await controller.getLastModelDataRefreshPromise(); - await etch.getScheduler().getNextUpdatePromise(); + tip.querySelector('atom-text-editor').getModel().setText('new-branch'); + tip.querySelector('button').click(); - assert.isUndefined(branchMenuView.refs.editor); - assert.isDefined(branchMenuView.refs.list); + await until(async () => { + await wrapper.instance().refreshModelData(); + return tip.querySelectorAll('select').length === 1; + }); + assert.equal(tip.querySelector('select').value, 'new-branch'); assert.equal(await repository.getCurrentBranch(), 'new-branch'); - assert.equal(branchMenuView.refs.list.selectedOptions[0].value, 'new-branch'); + + assert.lengthOf(tip.querySelectorAll('.github-BranchMenuView-editor'), 0); + assert.lengthOf(tip.querySelectorAll('select'), 1); }); it('displays an error message if branch already exists', async function() { const workdirPath = await cloneRepository('three-files'); const repository = await buildRepository(workdirPath); - await repository.git.exec(['checkout', '-b', 'branch']); - const controller = new StatusBarTileController({workspace, repository, commandRegistry, notificationManager}); - await controller.getLastModelDataRefreshPromise(); - await etch.getScheduler().getNextUpdatePromise(); - - const branchMenuView = controller.branchMenuView; - const {list, newBranchButton} = branchMenuView.refs; + const wrapper = mount(React.cloneElement(component, {repository})); + await wrapper.instance().refreshModelData(); + const tip = getTooltipNode(wrapper, BranchView); sinon.stub(notificationManager, 'addError'); - const branches = Array.from(branchMenuView.refs.list.options).map(option => option.value); + const branches = Array.from(tip.getElementsByTagName('option'), option => option.value); assert.deepEqual(branches, ['branch', 'master']); assert.equal(await repository.getCurrentBranch(), 'branch'); - assert.equal(list.selectedOptions[0].value, 'branch'); + assert.equal(tip.querySelector('select').value, 'branch'); - await newBranchButton.onclick(); + tip.querySelector('button').click(); + tip.querySelector('atom-text-editor').getModel().setText('master'); + tip.querySelector('button').click(); - branchMenuView.refs.editor.setText('master'); - await newBranchButton.onclick(); await assert.async.isTrue(notificationManager.addError.called); const notificationArgs = notificationManager.addError.args[0]; assert.equal(notificationArgs[0], 'Cannot create branch'); assert.match(notificationArgs[1].description, /already exists/); assert.equal(await repository.getCurrentBranch(), 'branch'); - assert.equal(branchMenuView.refs.list.selectedOptions[0].value, 'branch'); + assert.equal(tip.querySelector('select').value, 'branch'); }); }); }); }); describe('pushing and pulling', function() { - it('indicates the ahead and behind counts and toggles visibility of the push pull menu when clicked', async function() { + it('shows and hides the PushPullView', async function() { const {localRepoPath} = await setUpLocalAndRemoteRepositories(); const repository = await buildRepository(localRepoPath); - const controller = new StatusBarTileController({workspace, repository, commandRegistry}); - await controller.getLastModelDataRefreshPromise(); - await etch.getScheduler().getNextUpdatePromise(); + const wrapper = mount(React.cloneElement(component, {repository})); + await wrapper.instance().refreshModelData(); - const pushPullView = controller.refs.pushPullView; - const {aheadCount, behindCount} = pushPullView.refs; - assert.equal(aheadCount.textContent, ''); - assert.equal(behindCount.textContent, ''); + assert.lengthOf(document.querySelectorAll('.github-PushPullMenuView'), 0); + wrapper.find(PushPullView).node.element.click(); + assert.lengthOf(document.querySelectorAll('.github-PushPullMenuView'), 1); + wrapper.find(PushPullView).node.element.click(); + assert.lengthOf(document.querySelectorAll('.github-PushPullMenuView'), 0); + }); + + it('indicates the ahead and behind counts', async function() { + const {localRepoPath} = await setUpLocalAndRemoteRepositories(); + const repository = await buildRepository(localRepoPath); + + const wrapper = mount(React.cloneElement(component, {repository})); + await wrapper.instance().refreshModelData(); + + const tip = getTooltipNode(wrapper, PushPullView); + + assert.equal(tip.querySelector('.github-PushPullMenuView-pull').textContent.trim(), 'Pull'); + assert.equal(tip.querySelector('.github-PushPullMenuView-push').textContent.trim(), 'Push'); await repository.git.exec(['reset', '--hard', 'head~2']); repository.refresh(); - await controller.getLastModelDataRefreshPromise(); - await etch.getScheduler().getNextUpdatePromise(); - assert.equal(aheadCount.textContent, ''); - assert.equal(behindCount.textContent, '2'); + await wrapper.instance().refreshModelData(); + + assert.equal(tip.querySelector('.github-PushPullMenuView-pull').textContent.trim(), 'Pull (2)'); + assert.equal(tip.querySelector('.github-PushPullMenuView-push').textContent.trim(), 'Push'); await repository.git.commit('new local commit', {allowEmpty: true}); repository.refresh(); - await controller.getLastModelDataRefreshPromise(); - await etch.getScheduler().getNextUpdatePromise(); - assert.equal(aheadCount.textContent, '1'); - assert.equal(behindCount.textContent, '2'); - - // FIXME: Remove this guard when 1.13 is on stable. - if (parseFloat(atom.getVersion() >= 1.13)) { - assert.isUndefined(document.querySelectorAll('.github-PushPullMenuView')[0]); - pushPullView.element.click(); - assert.isDefined(document.querySelectorAll('.github-PushPullMenuView')[0]); - pushPullView.element.click(); - assert.isUndefined(document.querySelectorAll('.github-PushPullMenuView')[0]); - } + await wrapper.instance().refreshModelData(); + + assert.equal(tip.querySelector('.github-PushPullMenuView-pull').textContent.trim(), 'Pull (2)'); + assert.equal(tip.querySelector('.github-PushPullMenuView-push').textContent.trim(), 'Push (1)'); }); describe('the push/pull menu', function() { @@ -231,26 +252,27 @@ describe('StatusBarTileController', function() { const repository = await buildRepository(localRepoPath); await repository.git.exec(['checkout', '-b', 'new-branch']); - const controller = new StatusBarTileController({workspace, repository, commandRegistry}); - await controller.getLastModelDataRefreshPromise(); - await etch.getScheduler().getNextUpdatePromise(); + const wrapper = mount(React.cloneElement(component, {repository})); + await wrapper.instance().refreshModelData(); + + const tip = getTooltipNode(wrapper, PushPullView); - const pushPullMenuView = controller.pushPullMenuView; - const {pushButton, pullButton, fetchButton, message} = pushPullMenuView.refs; + const pullButton = tip.querySelector('button.github-PushPullMenuView-pull'); + const pushButton = tip.querySelector('button.github-PushPullMenuView-push'); + const message = tip.querySelector('.github-PushPullMenuView-message'); assert.isTrue(pullButton.disabled); - assert.isTrue(fetchButton.disabled); + assert.isFalse(pushButton.disabled); assert.match(message.innerHTML, /No remote detected.*Pushing will set up a remote tracking branch/); - pushButton.dispatchEvent(new MouseEvent('click')); + pushButton.click(); await until(async fail => { try { repository.refresh(); - await controller.getLastModelDataRefreshPromise(); - await etch.getScheduler().getNextUpdatePromise(); + await wrapper.instance().refreshModelData(); assert.isFalse(pullButton.disabled); - assert.isFalse(fetchButton.disabled); + assert.isFalse(pushButton.disabled); assert.equal(message.textContent, ''); return true; } catch (err) { @@ -265,30 +287,29 @@ describe('StatusBarTileController', function() { await repository.git.exec(['reset', '--hard', 'head~2']); await repository.git.commit('another commit', {allowEmpty: true}); - const controller = new StatusBarTileController({workspace, repository, commandRegistry, notificationManager}); - await controller.getLastModelDataRefreshPromise(); - await etch.getScheduler().getNextUpdatePromise(); + const wrapper = mount(React.cloneElement(component, {repository})); + await wrapper.instance().refreshModelData(); - const pushPullMenuView = controller.pushPullMenuView; - const {pushButton, pullButton} = pushPullMenuView.refs; + const tip = getTooltipNode(wrapper, PushPullView); + + const pullButton = tip.querySelector('button.github-PushPullMenuView-pull'); + const pushButton = tip.querySelector('button.github-PushPullMenuView-push'); sinon.stub(notificationManager, 'addError'); assert.equal(pushButton.textContent, 'Push (1)'); assert.equal(pullButton.textContent, 'Pull (2)'); - pushButton.dispatchEvent(new MouseEvent('click')); - await controller.getLastModelDataRefreshPromise(); - await etch.getScheduler().getNextUpdatePromise(); + pushButton.click(); + await wrapper.instance().refreshModelData(); await assert.async.isTrue(notificationManager.addError.called); const notificationArgs = notificationManager.addError.args[0]; assert.equal(notificationArgs[0], 'Push rejected'); assert.match(notificationArgs[1].description, /Try pulling before pushing again/); - pushButton.dispatchEvent(new MouseEvent('click', {metaKey: true})); - repository.refresh(); - await controller.getLastModelDataRefreshPromise(); + pushButton.dispatchEvent(new MouseEvent('click', {metaKey: true, bubbles: true})); + await wrapper.instance().refreshModelData(); await assert.async.equal(pushButton.textContent, 'Push '); await assert.async.equal(pullButton.textContent, 'Pull '); @@ -300,8 +321,8 @@ describe('StatusBarTileController', function() { const {localRepoPath} = await setUpLocalAndRemoteRepositories('multiple-commits', {remoteAhead: true}); const repository = await buildRepository(localRepoPath); - const controller = new StatusBarTileController({workspace, repository, commandRegistry}); - await controller.getLastModelDataRefreshPromise(); + const wrapper = mount(React.cloneElement(component, {repository})); + await wrapper.instance().refreshModelData(); sinon.spy(repository, 'fetch'); @@ -314,8 +335,8 @@ describe('StatusBarTileController', function() { const {localRepoPath} = await setUpLocalAndRemoteRepositories('multiple-commits', {remoteAhead: true}); const repository = await buildRepository(localRepoPath); - const controller = new StatusBarTileController({workspace, repository, commandRegistry}); - await controller.getLastModelDataRefreshPromise(); + const wrapper = mount(React.cloneElement(component, {repository})); + await wrapper.instance().refreshModelData(); sinon.spy(repository, 'pull'); @@ -328,8 +349,8 @@ describe('StatusBarTileController', function() { const {localRepoPath} = await setUpLocalAndRemoteRepositories(); const repository = await buildRepository(localRepoPath); - const controller = new StatusBarTileController({workspace, repository, commandRegistry}); - await controller.getLastModelDataRefreshPromise(); + const wrapper = mount(React.cloneElement(component, {repository})); + await wrapper.instance().refreshModelData(); sinon.spy(repository, 'push'); @@ -342,8 +363,8 @@ describe('StatusBarTileController', function() { const {localRepoPath} = await setUpLocalAndRemoteRepositories(); const repository = await buildRepository(localRepoPath); - const controller = new StatusBarTileController({workspace, repository, commandRegistry}); - await controller.getLastModelDataRefreshPromise(); + const wrapper = mount(React.cloneElement(component, {repository})); + await wrapper.instance().refreshModelData(); sinon.spy(repository, 'push'); @@ -360,25 +381,31 @@ describe('StatusBarTileController', function() { const repository = await buildRepository(workdirPath); const toggleGitPanel = sinon.spy(); - const controller = new StatusBarTileController({workspace, repository, toggleGitPanel, commandRegistry}); - await controller.getLastModelDataRefreshPromise(); - await etch.getScheduler().getNextUpdatePromise(); - const changedFilesCountView = controller.refs.changedFilesCountView; + const wrapper = mount(React.cloneElement(component, {repository, toggleGitPanel})); + await wrapper.instance().refreshModelData(); - assert.equal(changedFilesCountView.element.textContent, '0 files'); + assert.equal(wrapper.find('.github-ChangedFilesCount').render().text(), '0 files'); fs.writeFileSync(path.join(workdirPath, 'a.txt'), 'a change\n'); fs.unlinkSync(path.join(workdirPath, 'b.txt')); - repository.refresh(); + await repository.stageFiles(['a.txt']); repository.refresh(); - await controller.getLastModelDataRefreshPromise(); - await etch.getScheduler().getNextUpdatePromise(); - assert.equal(changedFilesCountView.element.textContent, '2 files'); + await assert.async.equal(wrapper.find('.github-ChangedFilesCount').render().text(), '2 files'); + }); + + it('toggles the git panel when clicked', async function() { + const workdirPath = await cloneRepository('three-files'); + const repository = await buildRepository(workdirPath); + + const toggleGitPanel = sinon.spy(); + + const wrapper = mount(React.cloneElement(component, {repository, toggleGitPanel})); + await wrapper.instance().refreshModelData(); - changedFilesCountView.element.click(); + wrapper.find(ChangedFilesCountView).simulate('click'); assert(toggleGitPanel.calledOnce); }); }); diff --git a/test/github-package.test.js b/test/github-package.test.js index ba18531ba8..ff20f4c19f 100644 --- a/test/github-package.test.js +++ b/test/github-package.test.js @@ -9,7 +9,8 @@ import {cloneRepository} from './helpers'; import GithubPackage from '../lib/github-package'; describe('GithubPackage', function() { - let atomEnv, workspace, project, commandRegistry, notificationManager, config, confirm, githubPackage; + let atomEnv, workspace, project, commandRegistry, notificationManager, config, confirm, tooltips; + let githubPackage; beforeEach(function() { atomEnv = global.buildAtomEnvironment(); @@ -17,9 +18,12 @@ describe('GithubPackage', function() { project = atomEnv.project; commandRegistry = atomEnv.commands; notificationManager = atomEnv.notifications; + tooltips = atomEnv.tooltips; config = atomEnv.config; confirm = atomEnv.confirm.bind(atomEnv); - githubPackage = new GithubPackage(workspace, project, commandRegistry, notificationManager, config, confirm); + githubPackage = new GithubPackage( + workspace, project, commandRegistry, notificationManager, tooltips, config, confirm, + ); }); afterEach(async function() { @@ -327,7 +331,9 @@ describe('GithubPackage', function() { assert.isDefined(payload.resolutionProgressByPath[workdirMergeConflict]); assert.isUndefined(payload.resolutionProgressByPath[workdirNoConflict]); - const githubPackage1 = new GithubPackage(workspace, project, commandRegistry, notificationManager, config, confirm); + const githubPackage1 = new GithubPackage( + workspace, project, commandRegistry, notificationManager, tooltips, config, confirm, + ); await githubPackage1.activate(payload); await githubPackage1.getInitialModelsPromise();