diff --git a/lib/items/changed-file-item.js b/lib/items/changed-file-item.js index da54340e9d..f3d1b6aa0a 100644 --- a/lib/items/changed-file-item.js +++ b/lib/items/changed-file-item.js @@ -1,13 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {Emitter} from 'event-kit'; +import ItemComponent from './item-component'; import {WorkdirContextPoolPropType} from '../prop-types'; -import {autobind} from '../helpers'; import ChangedFileContainer from '../containers/changed-file-container'; -import RefHolder from '../models/ref-holder'; -export default class ChangedFileItem extends React.Component { +export default class ChangedFileItem extends ItemComponent { static propTypes = { workdirContextPool: WorkdirContextPoolPropType.isRequired, @@ -36,15 +34,9 @@ export default class ChangedFileItem extends React.Component { } constructor(props) { - super(props); - autobind(this, 'destroy'); + super(props, {}); - this.emitter = new Emitter(); - this.isDestroyed = false; - this.hasTerminatedPendingState = false; - - this.refEditor = new RefHolder(); - this.refEditor.observe(editor => { + this.refHolder.observe(editor => { this.emitter.emit('did-change-embedded-text-editor', editor); }); } @@ -56,29 +48,6 @@ export default class ChangedFileItem extends React.Component { return title; } - terminatePendingState() { - if (!this.hasTerminatedPendingState) { - this.emitter.emit('did-terminate-pending-state'); - this.hasTerminatedPendingState = true; - } - } - - onDidTerminatePendingState(callback) { - return this.emitter.on('did-terminate-pending-state', callback); - } - - destroy() { - /* istanbul ignore else */ - if (!this.isDestroyed) { - this.emitter.emit('did-destroy'); - this.isDestroyed = true; - } - } - - onDidDestroy(callback) { - return this.emitter.on('did-destroy', callback); - } - render() { const repository = this.props.workdirContextPool.getContext(this.props.workingDirectory).getRepository(); @@ -87,14 +56,14 @@ export default class ChangedFileItem extends React.Component { itemType={this.constructor} repository={repository} destroy={this.destroy} - refEditor={this.refEditor} + refEditor={this.refHolder} {...this.props} /> ); } observeEmbeddedTextEditor(cb) { - this.refEditor.map(editor => cb(editor)); + this.refHolder.map(editor => cb(editor)); return this.emitter.on('did-change-embedded-text-editor', cb); } diff --git a/lib/items/commit-detail-item.js b/lib/items/commit-detail-item.js index 4d019a2a16..3a1561005f 100644 --- a/lib/items/commit-detail-item.js +++ b/lib/items/commit-detail-item.js @@ -1,12 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {Emitter} from 'event-kit'; +import ItemComponent from './item-component'; import {WorkdirContextPoolPropType} from '../prop-types'; import CommitDetailContainer from '../containers/commit-detail-container'; import RefHolder from '../models/ref-holder'; -export default class CommitDetailItem extends React.Component { +export default class CommitDetailItem extends ItemComponent { static propTypes = { workdirContextPool: WorkdirContextPoolPropType.isRequired, workingDirectory: PropTypes.string.isRequired, @@ -20,43 +20,16 @@ export default class CommitDetailItem extends React.Component { } constructor(props) { - super(props); + super(props, {title: 'Commit', icon: 'git-commit'}); - this.emitter = new Emitter(); - this.isDestroyed = false; - this.hasTerminatedPendingState = false; this.shouldFocus = true; this.refInitialFocus = new RefHolder(); - this.refEditor = new RefHolder(); - this.refEditor.observe(editor => { + this.refHolder.observe(editor => { this.emitter.emit('did-change-embedded-text-editor', editor); }); } - terminatePendingState() { - if (!this.hasTerminatedPendingState) { - this.emitter.emit('did-terminate-pending-state'); - this.hasTerminatedPendingState = true; - } - } - - onDidTerminatePendingState(callback) { - return this.emitter.on('did-terminate-pending-state', callback); - } - - destroy = () => { - /* istanbul ignore else */ - if (!this.isDestroyed) { - this.emitter.emit('did-destroy'); - this.isDestroyed = true; - } - } - - onDidDestroy(callback) { - return this.emitter.on('did-destroy', callback); - } - render() { const repository = this.props.workdirContextPool.getContext(this.props.workingDirectory).getRepository(); @@ -66,7 +39,7 @@ export default class CommitDetailItem extends React.Component { repository={repository} {...this.props} destroy={this.destroy} - refEditor={this.refEditor} + refEditor={this.refHolder} refInitialFocus={this.refInitialFocus} /> ); @@ -76,12 +49,8 @@ export default class CommitDetailItem extends React.Component { return `Commit: ${this.props.sha}`; } - getIconName() { - return 'git-commit'; - } - observeEmbeddedTextEditor(cb) { - this.refEditor.map(editor => cb(editor)); + this.refHolder.map(editor => cb(editor)); return this.emitter.on('did-change-embedded-text-editor', cb); } diff --git a/lib/items/commit-preview-item.js b/lib/items/commit-preview-item.js index 9b44b3f4ad..fe687a6eb1 100644 --- a/lib/items/commit-preview-item.js +++ b/lib/items/commit-preview-item.js @@ -1,12 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {Emitter} from 'event-kit'; +import ItemComponent from './item-component'; import {WorkdirContextPoolPropType} from '../prop-types'; import CommitPreviewContainer from '../containers/commit-preview-container'; import RefHolder from '../models/ref-holder'; -export default class CommitPreviewItem extends React.Component { +export default class CommitPreviewItem extends ItemComponent { static propTypes = { workdirContextPool: WorkdirContextPoolPropType.isRequired, workingDirectory: PropTypes.string.isRequired, @@ -23,42 +23,15 @@ export default class CommitPreviewItem extends React.Component { } constructor(props) { - super(props); + super(props, {title: 'Staged Changes', icon: 'tasklist'}); - this.emitter = new Emitter(); - this.isDestroyed = false; - this.hasTerminatedPendingState = false; this.refInitialFocus = new RefHolder(); - this.refEditor = new RefHolder(); - this.refEditor.observe(editor => { + this.refHolder.observe(editor => { this.emitter.emit('did-change-embedded-text-editor', editor); }); } - terminatePendingState() { - if (!this.hasTerminatedPendingState) { - this.emitter.emit('did-terminate-pending-state'); - this.hasTerminatedPendingState = true; - } - } - - onDidTerminatePendingState(callback) { - return this.emitter.on('did-terminate-pending-state', callback); - } - - destroy = () => { - /* istanbul ignore else */ - if (!this.isDestroyed) { - this.emitter.emit('did-destroy'); - this.isDestroyed = true; - } - } - - onDidDestroy(callback) { - return this.emitter.on('did-destroy', callback); - } - render() { const repository = this.props.workdirContextPool.getContext(this.props.workingDirectory).getRepository(); @@ -68,22 +41,14 @@ export default class CommitPreviewItem extends React.Component { repository={repository} {...this.props} destroy={this.destroy} - refEditor={this.refEditor} + refEditor={this.refHolder} refInitialFocus={this.refInitialFocus} /> ); } - getTitle() { - return 'Staged Changes'; - } - - getIconName() { - return 'tasklist'; - } - observeEmbeddedTextEditor(cb) { - this.refEditor.map(editor => cb(editor)); + this.refHolder.map(editor => cb(editor)); return this.emitter.on('did-change-embedded-text-editor', cb); } diff --git a/lib/items/git-tab-item.js b/lib/items/git-tab-item.js index e8de6d7cca..ff164d3da3 100644 --- a/lib/items/git-tab-item.js +++ b/lib/items/git-tab-item.js @@ -1,10 +1,10 @@ import React from 'react'; import PropTypes from 'prop-types'; -import RefHolder from '../models/ref-holder'; +import ItemComponent from './item-component'; import GitTabContainer from '../containers/git-tab-container'; -export default class GitTabItem extends React.Component { +export default class GitTabItem extends ItemComponent { static propTypes = { repository: PropTypes.object.isRequired, } @@ -16,15 +16,13 @@ export default class GitTabItem extends React.Component { } constructor(props) { - super(props); - - this.refController = new RefHolder(); + super(props, {title: 'Git', icon: 'git-commit'}); } render() { return ( ); @@ -37,14 +35,6 @@ export default class GitTabItem extends React.Component { }; } - getTitle() { - return 'Git'; - } - - getIconName() { - return 'git-commit'; - } - getDefaultLocation() { return 'right'; } @@ -61,37 +51,47 @@ export default class GitTabItem extends React.Component { return this.props.repository.getWorkingDirectoryPath(); } + // no-op + destroy() { + return false; + } + + // no-op + terminatePendingState() { + return false; + } + // Forwarded to the controller instance when one is present rememberLastFocus(...args) { - return this.refController.map(c => c.rememberLastFocus(...args)); + return this.refHolder.map(c => c.rememberLastFocus(...args)); } restoreFocus(...args) { - return this.refController.map(c => c.restoreFocus(...args)); + return this.refHolder.map(c => c.restoreFocus(...args)); } hasFocus(...args) { - return this.refController.map(c => c.hasFocus(...args)); + return this.refHolder.map(c => c.hasFocus(...args)); } focus() { - return this.refController.map(c => c.restoreFocus()); + return this.refHolder.map(c => c.restoreFocus()); } focusAndSelectStagingItem(...args) { - return this.refController.map(c => c.focusAndSelectStagingItem(...args)); + return this.refHolder.map(c => c.focusAndSelectStagingItem(...args)); } focusAndSelectCommitPreviewButton() { - return this.refController.map(c => c.focusAndSelectCommitPreviewButton()); + return this.refHolder.map(c => c.focusAndSelectCommitPreviewButton()); } quietlySelectItem(...args) { - return this.refController.map(c => c.quietlySelectItem(...args)); + return this.refHolder.map(c => c.quietlySelectItem(...args)); } focusAndSelectRecentCommit() { - return this.refController.map(c => c.focusAndSelectRecentCommit()); + return this.refHolder.map(c => c.focusAndSelectRecentCommit()); } } diff --git a/lib/items/github-tab-item.js b/lib/items/github-tab-item.js index dc55141ef6..c4ba5089d8 100644 --- a/lib/items/github-tab-item.js +++ b/lib/items/github-tab-item.js @@ -2,10 +2,10 @@ import React from 'react'; import PropTypes from 'prop-types'; import {GithubLoginModelPropType} from '../prop-types'; -import RefHolder from '../models/ref-holder'; +import ItemComponent from './item-component'; import GitHubTabContainer from '../containers/github-tab-container'; -export default class GitHubTabItem extends React.Component { +export default class GitHubTabItem extends ItemComponent { static propTypes = { workspace: PropTypes.object.isRequired, repository: PropTypes.object, @@ -25,17 +25,7 @@ export default class GitHubTabItem extends React.Component { } constructor(props) { - super(props); - - this.rootHolder = new RefHolder(); - } - - getTitle() { - return 'GitHub'; - } - - getIconName() { - return 'octoface'; + super(props, {title: 'GitHub', icon: 'octoface'}); } getDefaultLocation() { @@ -59,15 +49,25 @@ export default class GitHubTabItem extends React.Component { render() { return ( - + ); } hasFocus() { - return this.rootHolder.map(root => root.contains(this.props.documentActiveElement())).getOr(false); + return this.refHolder.map(root => root.contains(this.props.documentActiveElement())).getOr(false); } restoreFocus() { // No-op } + + // no-op + destroy() { + return false; + } + + // no-op + terminatePendingState() { + return false; + } } diff --git a/lib/items/issueish-detail-item.js b/lib/items/issueish-detail-item.js index a0c5c3a1f6..74b21f3dc2 100644 --- a/lib/items/issueish-detail-item.js +++ b/lib/items/issueish-detail-item.js @@ -1,6 +1,6 @@ -import React, {Component} from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; -import {Emitter} from 'event-kit'; +import ItemComponent from './item-component'; import {autobind} from '../helpers'; import {GithubLoginModelPropType, WorkdirContextPoolPropType} from '../prop-types'; @@ -8,9 +8,8 @@ import {addEvent} from '../reporter-proxy'; import Repository from '../models/repository'; import {getEndpoint} from '../models/endpoint'; import IssueishDetailContainer from '../containers/issueish-detail-container'; -import RefHolder from '../models/ref-holder'; -export default class IssueishDetailItem extends Component { +export default class IssueishDetailItem extends ItemComponent { static propTypes = { // Issueish selection criteria // Parsed from item URI @@ -45,12 +44,10 @@ export default class IssueishDetailItem extends Component { } constructor(props) { - super(props); - autobind(this, 'switchToIssueish', 'handleTitleChanged'); + super(props, {}); - this.emitter = new Emitter(); this.title = `${this.props.owner}/${this.props.repo}#${this.props.issueishNumber}`; - this.hasTerminatedPendingState = false; + autobind(this, 'switchToIssueish', 'handleTitleChanged'); const repository = this.props.workingDirectory === '' ? Repository.absent() @@ -68,8 +65,7 @@ export default class IssueishDetailItem extends Component { this.switchToIssueish(this.props.owner, this.props.repo, this.props.issueishNumber); } - this.refEditor = new RefHolder(); - this.refEditor.observe(editor => { + this.refHolder.observe(editor => { this.emitter.emit('did-change-embedded-text-editor', editor); }); } @@ -95,7 +91,7 @@ export default class IssueishDetailItem extends Component { destroy={this.destroy} itemType={this.constructor} - refEditor={this.refEditor} + refEditor={this.refHolder} /> ); } @@ -150,29 +146,6 @@ export default class IssueishDetailItem extends Component { return this.emitter.on('did-change-title', cb); } - terminatePendingState() { - if (!this.hasTerminatedPendingState) { - this.emitter.emit('did-terminate-pending-state'); - this.hasTerminatedPendingState = true; - } - } - - onDidTerminatePendingState(callback) { - return this.emitter.on('did-terminate-pending-state', callback); - } - - destroy = () => { - /* istanbul ignore else */ - if (!this.isDestroyed) { - this.emitter.emit('did-destroy'); - this.isDestroyed = true; - } - } - - onDidDestroy(callback) { - return this.emitter.on('did-destroy', callback); - } - serialize() { return { uri: this.getURI(), @@ -180,12 +153,8 @@ export default class IssueishDetailItem extends Component { }; } - getTitle() { - return this.title; - } - observeEmbeddedTextEditor(cb) { - this.refEditor.map(editor => cb(editor)); + this.refHolder.map(editor => cb(editor)); return this.emitter.on('did-change-embedded-text-editor', cb); } } diff --git a/lib/items/item-component.js b/lib/items/item-component.js new file mode 100644 index 0000000000..f13fad1a34 --- /dev/null +++ b/lib/items/item-component.js @@ -0,0 +1,51 @@ +import React from 'react'; +import RefHolder from '../models/ref-holder'; +import {Emitter} from 'event-kit'; +import {autobind} from '../helpers'; + +export default class ItemComponent extends React.Component { + + constructor(props, {title, icon}) { + super(props); + autobind(this, 'destroy'); + + this.title = title; + this.icon = icon; + + this.refHolder = new RefHolder(); + this.emitter = new Emitter(); + this.isDestroyed = false; + this.hasTerminatedPendingState = false; + } + + getTitle() { + return this.title; + } + + getIconName() { + return this.icon; + } + + destroy() { + /* istanbul ignore else */ + if (!this.isDestroyed) { + this.emitter.emit('did-destroy'); + this.isDestroyed = true; + } + } + + onDidDestroy(callback) { + return this.emitter.on('did-destroy', callback); + } + + terminatePendingState() { + if (!this.hasTerminatedPendingState) { + this.emitter.emit('did-terminate-pending-state'); + this.hasTerminatedPendingState = true; + } + } + + onDidTerminatePendingState(callback) { + return this.emitter.on('did-terminate-pending-state', callback); + } +} diff --git a/test/items/git-tab-item.test.js b/test/items/git-tab-item.test.js index 4bd740b459..8f4ceec06e 100644 --- a/test/items/git-tab-item.test.js +++ b/test/items/git-tab-item.test.js @@ -74,4 +74,34 @@ describe('GitTabItem', function() { assert.isTrue(spies[method].called); } }); + + describe('destroy and terminatePendingState should be overridden as no-ops', function() { + it('does not destroy', async function() { + mount(buildApp()); + const item = await atomEnv.workspace.open(GitTabItem.buildURI()); + const callback = sinon.spy(); + const sub = item.onDidDestroy(callback); + + assert.strictEqual(callback.callCount, 0); + item.destroy(); + assert.strictEqual(callback.callCount, 0); + + sub.dispose(); + }); + + it('does not terminate pending state', async function() { + mount(buildApp()); + + const item = await atomEnv.workspace.open(GitTabItem.buildURI()); + const callback = sinon.spy(); + const sub = item.onDidTerminatePendingState(callback); + + assert.strictEqual(callback.callCount, 0); + item.terminatePendingState(); + assert.strictEqual(callback.callCount, 0); + + sub.dispose(); + }); + }); + }); diff --git a/test/items/github-tab-item.test.js b/test/items/github-tab-item.test.js index ae127b9a51..c2f7e81c60 100644 --- a/test/items/github-tab-item.test.js +++ b/test/items/github-tab-item.test.js @@ -76,4 +76,34 @@ describe('GitHubTabItem', function() { activeElement = wrapper.find('.github-GitHub').getDOMNode(); assert.isTrue(item.hasFocus()); }); + + describe('destroy and terminatePendingState should be overridden as no-ops', function() { + it('does not destroy', async function() { + mount(buildApp()); + const item = await atomEnv.workspace.open(GitHubTabItem.buildURI()); + const callback = sinon.spy(); + const sub = item.onDidDestroy(callback); + + assert.strictEqual(callback.callCount, 0); + item.destroy(); + assert.strictEqual(callback.callCount, 0); + + sub.dispose(); + }); + + it('does not terminate pending state', async function() { + mount(buildApp()); + + const item = await atomEnv.workspace.open(GitHubTabItem.buildURI()); + const callback = sinon.spy(); + const sub = item.onDidTerminatePendingState(callback); + + assert.strictEqual(callback.callCount, 0); + item.terminatePendingState(); + assert.strictEqual(callback.callCount, 0); + + sub.dispose(); + }); + }); + });