From 5abde1f2893812b67b5fe0123964b2a519dbd2c1 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 12:43:00 -0800 Subject: [PATCH 01/48] Add GSOS#getDiffsForCommit and test --- lib/git-shell-out-strategy.js | 8 ++++++++ test/git-strategies.test.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/lib/git-shell-out-strategy.js b/lib/git-shell-out-strategy.js index 0e8f303d4c3..1addc791b31 100644 --- a/lib/git-shell-out-strategy.js +++ b/lib/git-shell-out-strategy.js @@ -687,6 +687,14 @@ export default class GitShellOutStrategy { return headCommit; } + async getDiffsForCommit(sha) { + const output = await this.exec([ + 'diff', '--no-prefix', '--no-ext-diff', '--no-renames', `${sha}~`, sha, + ]); + + return parseDiff(output); + } + async getCommits(options = {}) { const {max, ref, includeUnborn} = {max: 1, ref: 'HEAD', includeUnborn: false, ...options}; diff --git a/test/git-strategies.test.js b/test/git-strategies.test.js index 34a24fb290c..574dceadd46 100644 --- a/test/git-strategies.test.js +++ b/test/git-strategies.test.js @@ -158,6 +158,36 @@ import * as reporterProxy from '../lib/reporter-proxy'; }); }); + describe('getDiffsForCommit(sha)', function() { + it('returns the diff for the specified commit sha', async function() { + const workingDirPath = await cloneRepository('multiple-commits'); + const git = createTestStrategy(workingDirPath); + + const diffs = await git.getDiffForCommit('18920c90'); + + assertDeepPropertyVals(diffs, [{ + oldPath: 'file.txt', + newPath: 'file.txt', + oldMode: '100644', + newMode: '100644', + hunks: [ + { + oldStartLine: 1, + oldLineCount: 1, + newStartLine: 1, + newLineCount: 1, + heading: '', + lines: [ + '-one', + '+two', + ], + }, + ], + status: 'modified', + }]); + }); + }); + describe('getCommits()', function() { describe('when no commits exist in the repository', function() { it('returns an array with an unborn ref commit when the include unborn option is passed', async function() { From 45d1a45fe4b6549744be2b74a0dcdba3acdf9d8b Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 13:07:50 -0800 Subject: [PATCH 02/48] Wire up `getCommit` --- lib/models/repository-states/present.js | 11 ++++++++++- lib/models/repository-states/state.js | 4 ++++ lib/models/repository.js | 1 + test/models/repository.test.js | 11 +++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index a9f9691a2ab..3741647e9a0 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -723,6 +723,15 @@ export default class Present extends State { }); } + getCommit(sha) { + return this.cache.getOrSet(Keys.blob.oneWith(sha), async () => { + const [commitMetaData] = await this.git().getCommits({max: 1, ref: sha}); + // todo: check need for error handling in the case of 0 commit and 1 commit + const fileDiffs = await this.git().getDiffsForCommit(sha); + return {...commitMetaData, fileDiffs}; + }); + } + getRecentCommits(options) { return this.cache.getOrSet(Keys.recentCommits, async () => { const commits = await this.git().getCommits({ref: 'HEAD', ...options}); @@ -1097,7 +1106,7 @@ const Keys = { }, blob: { - oneWith: sha => `blob:${sha}`, + oneWith: sha => new CacheKey(`blob:${sha}`, ['blob']), }, // Common collections of keys and patterns for use with invalidate(). diff --git a/lib/models/repository-states/state.js b/lib/models/repository-states/state.js index 85bb53e3a8f..92961a777ad 100644 --- a/lib/models/repository-states/state.js +++ b/lib/models/repository-states/state.js @@ -298,6 +298,10 @@ export default class State { return Promise.resolve(nullCommit); } + getCommit() { + return Promise.resolve(nullCommit); + } + getRecentCommits() { return Promise.resolve([]); } diff --git a/lib/models/repository.js b/lib/models/repository.js index 9c39d827eb1..68202d136e2 100644 --- a/lib/models/repository.js +++ b/lib/models/repository.js @@ -331,6 +331,7 @@ const delegates = [ 'readFileFromIndex', 'getLastCommit', + 'getCommit', 'getRecentCommits', 'getAuthors', diff --git a/test/models/repository.test.js b/test/models/repository.test.js index 834567e9ca8..2eaac205e5f 100644 --- a/test/models/repository.test.js +++ b/test/models/repository.test.js @@ -731,6 +731,17 @@ describe('Repository', function() { }); }); + describe('getCommit(sha)', function() { + it('returns the commit information for the provided sha', async function() { + const workingDirPath = await cloneRepository('multiple-commits'); + const repo = new Repository(workingDirPath); + await repo.getLoadPromise(); + + console.log(await repo.getCommit('18920c90')); + // TODO ... + }); + }); + describe('undoLastCommit()', function() { it('performs a soft reset', async function() { const workingDirPath = await cloneRepository('multiple-commits'); From dacd2f0a3f3ba1acc2171c5ef2eb860542e9789c Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 14:07:12 -0800 Subject: [PATCH 03/48] Add the framework for displaying commit details --- lib/containers/commit-detail-container.js | 42 ++++++ lib/controllers/commit-detail-controller.js | 26 ++++ lib/controllers/root-controller.js | 54 ++++++++ lib/github-package.js | 10 ++ lib/items/commit-detail-item.js | 93 +++++++++++++ lib/views/open-commit-dialog.js | 139 ++++++++++++++++++++ package.json | 3 +- 7 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 lib/containers/commit-detail-container.js create mode 100644 lib/controllers/commit-detail-controller.js create mode 100644 lib/items/commit-detail-item.js create mode 100644 lib/views/open-commit-dialog.js diff --git a/lib/containers/commit-detail-container.js b/lib/containers/commit-detail-container.js new file mode 100644 index 00000000000..b4d91765f69 --- /dev/null +++ b/lib/containers/commit-detail-container.js @@ -0,0 +1,42 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import yubikiri from 'yubikiri'; + +import ObserveModel from '../views/observe-model'; +import LoadingView from '../views/loading-view'; +import CommitDetailController from '../controllers/commit-detail-controller'; + +export default class CommitDetailContainer extends React.Component { + static propTypes = { + repository: PropTypes.object.isRequired, + sha: PropTypes.string.isRequired, + } + + fetchData = repository => { + return yubikiri({ + commit: repository.getCommit(this.props.sha), + }); + } + + render() { + console.log('container'); + return ( + + {this.renderResult} + + ); + } + + renderResult = data => { + if (this.props.repository.isLoading() || data === null) { + return ; + } + + return ( + + ); + } +} diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js new file mode 100644 index 00000000000..f1245cb904a --- /dev/null +++ b/lib/controllers/commit-detail-controller.js @@ -0,0 +1,26 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import MultiFilePatchController from './multi-file-patch-controller'; + +export default class CommitDetailController extends React.Component { + static propTypes = { + repository: PropTypes.object.isRequired, + + workspace: PropTypes.object.isRequired, + commands: PropTypes.object.isRequired, + keymaps: PropTypes.object.isRequired, + tooltips: PropTypes.object.isRequired, + config: PropTypes.object.isRequired, + + destroy: PropTypes.func.isRequired, + } + + render() { + return ( + + ); + } +} diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index ec5d22f536f..a720cba46c1 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -11,12 +11,14 @@ import Panel from '../atom/panel'; import PaneItem from '../atom/pane-item'; import CloneDialog from '../views/clone-dialog'; import OpenIssueishDialog from '../views/open-issueish-dialog'; +import OpenCommitDialog from '../views/open-commit-dialog'; import InitDialog from '../views/init-dialog'; import CredentialDialog from '../views/credential-dialog'; import Commands, {Command} from '../atom/commands'; import GitTimingsView from '../views/git-timings-view'; import ChangedFileItem from '../items/changed-file-item'; import IssueishDetailItem from '../items/issueish-detail-item'; +import CommitDetailItem from '../items/commit-detail-item'; import CommitPreviewItem from '../items/commit-preview-item'; import GitTabItem from '../items/git-tab-item'; import GitHubTabItem from '../items/github-tab-item'; @@ -139,6 +141,7 @@ export default class RootController extends React.Component { + ); } @@ -244,6 +248,22 @@ export default class RootController extends React.Component { ); } + renderOpenCommitDialog() { + if (!this.state.openCommitDialogActive) { + return null; + } + + return ( + + + + ); + } + renderCredentialDialog() { if (this.state.credentialDialogQuery === null) { return null; @@ -362,6 +382,24 @@ export default class RootController extends React.Component { /> )} + + {({itemHolder, params}) => ( + + )} + {({itemHolder, params}) => ( { + this.setState({openCommitDialogActive: true}); + } + showWaterfallDiagnostics() { this.props.workspace.open(GitTimingsView.buildURI()); } @@ -548,6 +590,18 @@ export default class RootController extends React.Component { this.setState({openIssueishDialogActive: false}); } + acceptOpenCommit = ({sha}) => { + const uri = CommitDetailItem.buildURI(sha); + this.setState({openCommitDialogActive: false}); + this.props.workspace.open(uri).then(() => { + addEvent('open-commit-in-pane', {package: 'github', from: 'dialog'}); + }); + } + + cancelOpenCommit = () => { + this.setState({openCommitDialogActive: false}); + } + surfaceFromFileAtPath = (filePath, stagingStatus) => { const gitTab = this.gitTabTracker.getComponent(); return gitTab && gitTab.focusAndSelectStagingItem(filePath, stagingStatus); diff --git a/lib/github-package.js b/lib/github-package.js index 0da56cd648a..a0ed4797100 100644 --- a/lib/github-package.js +++ b/lib/github-package.js @@ -389,6 +389,16 @@ export default class GithubPackage { return item; } + createCommitDetailStub({uri}) { + const item = StubItem.create('git-commit-detail', { + title: 'Commit', + }, uri); + if (this.controller) { + this.rerender(); + } + return item; + } + destroyGitTabItem() { if (this.gitTabStubItem) { this.gitTabStubItem.destroy(); diff --git a/lib/items/commit-detail-item.js b/lib/items/commit-detail-item.js new file mode 100644 index 00000000000..564b637144b --- /dev/null +++ b/lib/items/commit-detail-item.js @@ -0,0 +1,93 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {Emitter} from 'event-kit'; + +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 { + static propTypes = { + workdirContextPool: WorkdirContextPoolPropType.isRequired, + workingDirectory: PropTypes.string.isRequired, + sha: PropTypes.string.isRequired, + + surfaceToCommitDetailButton: PropTypes.func.isRequired, + } + + static uriPattern = 'atom-github://commit-detail?sha={sha}' + + static buildURI(sha) { + return `atom-github://commit-detail?sha=${encodeURIComponent(sha)}`; + } + + constructor(props) { + super(props); + + console.log('item'); + this.emitter = new Emitter(); + this.isDestroyed = false; + this.hasTerminatedPendingState = false; + this.refInitialFocus = new RefHolder(); + } + + 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(); + + return ( + + ); + } + + getTitle() { + return `Commit: ${this.props.sha}`; + } + + getIconName() { + return 'git-commit'; + } + + getWorkingDirectory() { + return this.props.workingDirectory; + } + + serialize() { + return { + deserializer: 'CommitDetailStub', + uri: CommitDetailItem.buildURI(this.props.sha), + }; + } + + focus() { + this.refInitialFocus.map(focusable => focusable.focus()); + } +} diff --git a/lib/views/open-commit-dialog.js b/lib/views/open-commit-dialog.js new file mode 100644 index 00000000000..6cd2f04d09e --- /dev/null +++ b/lib/views/open-commit-dialog.js @@ -0,0 +1,139 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {CompositeDisposable} from 'event-kit'; + +import Commands, {Command} from '../atom/commands'; +import {autobind} from '../helpers'; + +// const COMMIT_SHA_REGEX = /^(?:https?:\/\/)?github.com\/([^/]+)\/([^/]+)\/(?:issues|pull)\/(\d+)/; + +export default class OpenCommitDialog extends React.Component { + static propTypes = { + commandRegistry: PropTypes.object.isRequired, + didAccept: PropTypes.func, + didCancel: PropTypes.func, + } + + static defaultProps = { + didAccept: () => {}, + didCancel: () => {}, + } + + constructor(props, context) { + super(props, context); + autobind(this, 'accept', 'cancel', 'editorRefs', 'didChangeCommitSha'); + console.log('sdfasdf'); + + this.state = { + cloneDisabled: false, + }; + + this.subs = new CompositeDisposable(); + } + + componentDidMount() { + if (this.commitShaElement) { + setTimeout(() => this.commitShaElement.focus()); + } + } + + render() { + return this.renderDialog(); + } + + renderDialog() { + return ( +
+ + + + +
+ + {this.state.error && {this.state.error}} +
+
+ + +
+
+ ); + } + + accept() { + if (this.getCommitSha().length === 0) { + return; + } + + const parsed = this.parseSha(); + if (!parsed) { + this.setState({ + error: 'That is not a valid commit sha.', + }); + return; + } + const {sha} = parsed; + + this.props.didAccept({sha}); + } + + cancel() { + this.props.didCancel(); + } + + editorRefs(baseName) { + const elementName = `${baseName}Element`; + const modelName = `${baseName}Editor`; + const subName = `${baseName}Subs`; + const changeMethodName = `didChange${baseName[0].toUpperCase()}${baseName.substring(1)}`; + + return element => { + if (!element) { + return; + } + + this[elementName] = element; + const editor = element.getModel(); + if (this[modelName] !== editor) { + this[modelName] = editor; + + if (this[subName]) { + this[subName].dispose(); + this.subs.remove(this[subName]); + } + + this[subName] = editor.onDidChange(this[changeMethodName]); + this.subs.add(this[subName]); + } + }; + } + + didChangeCommitSha() { + this.setState({error: null}); + } + + parseSha() { + const sha = this.getCommitSha(); + // const matches = url.match(ISSUEISH_URL_REGEX); + // if (!matches) { + // return false; + // } + // const [_full, repoOwner, repoName, issueishNumber] = matches; // eslint-disable-line no-unused-vars + return {sha}; + } + + getCommitSha() { + return this.commitShaEditor ? this.commitShaEditor.getText() : ''; + } +} diff --git a/package.json b/package.json index 628a56bc5dd..af60df2ba9d 100644 --- a/package.json +++ b/package.json @@ -198,6 +198,7 @@ "GitDockItem": "createDockItemStub", "GithubDockItem": "createDockItemStub", "FilePatchControllerStub": "createFilePatchControllerStub", - "CommitPreviewStub": "createCommitPreviewStub" + "CommitPreviewStub": "createCommitPreviewStub", + "CommitDetailStub": "createCommitDetailStub" } } From 3f692793e3de8396ffaab4c1d41d983d64bd4e42 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 15:08:30 -0800 Subject: [PATCH 04/48] Get CommitDetailItem to actually render! --- lib/containers/commit-detail-container.js | 2 +- lib/controllers/commit-detail-controller.js | 1 + .../multi-file-patch-controller.js | 6 ++-- lib/controllers/root-controller.js | 5 ++- lib/items/commit-detail-item.js | 7 ++-- lib/models/commit.js | 9 +++++ lib/models/repository-states/present.js | 8 +++-- lib/views/hunk-header-view.js | 10 +++--- lib/views/multi-file-patch-view.js | 36 ++++++++++++------- 9 files changed, 54 insertions(+), 30 deletions(-) diff --git a/lib/containers/commit-detail-container.js b/lib/containers/commit-detail-container.js index b4d91765f69..1c5d3022d87 100644 --- a/lib/containers/commit-detail-container.js +++ b/lib/containers/commit-detail-container.js @@ -28,7 +28,7 @@ export default class CommitDetailContainer extends React.Component { } renderResult = data => { - if (this.props.repository.isLoading() || data === null) { + if (this.props.repository.isLoading() || data === null || !data.commit.isPresent()) { return ; } diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index f1245cb904a..1eac077294b 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -19,6 +19,7 @@ export default class CommitDetailController extends React.Component { render() { return ( ); diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index 09944505c58..cdba88def62 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -22,9 +22,9 @@ export default class MultiFilePatchController extends React.Component { config: PropTypes.object.isRequired, destroy: PropTypes.func.isRequired, - discardLines: PropTypes.func.isRequired, - undoLastDiscard: PropTypes.func.isRequired, - surface: PropTypes.func.isRequired, + discardLines: PropTypes.func, + undoLastDiscard: PropTypes.func, + surface: PropTypes.func, } constructor(props) { diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index a720cba46c1..c57c7a9d4f7 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -397,6 +397,8 @@ export default class RootController extends React.Component { keymaps={this.props.keymaps} tooltips={this.props.tooltips} config={this.props.config} + + sha={params.sha} /> )}
@@ -591,7 +593,8 @@ export default class RootController extends React.Component { } acceptOpenCommit = ({sha}) => { - const uri = CommitDetailItem.buildURI(sha); + const workdir = this.props.repository.getWorkingDirectoryPath(); + const uri = CommitDetailItem.buildURI(workdir, sha); this.setState({openCommitDialogActive: false}); this.props.workspace.open(uri).then(() => { addEvent('open-commit-in-pane', {package: 'github', from: 'dialog'}); diff --git a/lib/items/commit-detail-item.js b/lib/items/commit-detail-item.js index 564b637144b..90c023129f6 100644 --- a/lib/items/commit-detail-item.js +++ b/lib/items/commit-detail-item.js @@ -15,16 +15,15 @@ export default class CommitDetailItem extends React.Component { surfaceToCommitDetailButton: PropTypes.func.isRequired, } - static uriPattern = 'atom-github://commit-detail?sha={sha}' + static uriPattern = 'atom-github://commit-detail?workdir={workingDirectory}&sha={sha}' - static buildURI(sha) { - return `atom-github://commit-detail?sha=${encodeURIComponent(sha)}`; + static buildURI(workingDirectory, sha) { + return `atom-github://commit-detail?workdir=${encodeURIComponent(workingDirectory)}&sha=${encodeURIComponent(sha)}`; } constructor(props) { super(props); - console.log('item'); this.emitter = new Emitter(); this.isDestroyed = false; this.hasTerminatedPendingState = false; diff --git a/lib/models/commit.js b/lib/models/commit.js index 6bfa738b8d3..f1fbd72a1c2 100644 --- a/lib/models/commit.js +++ b/lib/models/commit.js @@ -13,6 +13,7 @@ export default class Commit { this.messageSubject = messageSubject; this.messageBody = messageBody; this.unbornRef = unbornRef === UNBORN; + this.multiFileDiff = null; } getSha() { @@ -43,6 +44,14 @@ export default class Commit { return `${this.getMessageSubject()}\n\n${this.getMessageBody()}`.trim(); } + setMultiFileDiff(multiFileDiff) { + this.multiFileDiff = multiFileDiff; + } + + getMultiFileDiff() { + return this.multiFileDiff; + } + isUnbornRef() { return this.unbornRef; } diff --git a/lib/models/repository-states/present.js b/lib/models/repository-states/present.js index 3741647e9a0..bc285890810 100644 --- a/lib/models/repository-states/present.js +++ b/lib/models/repository-states/present.js @@ -725,10 +725,12 @@ export default class Present extends State { getCommit(sha) { return this.cache.getOrSet(Keys.blob.oneWith(sha), async () => { - const [commitMetaData] = await this.git().getCommits({max: 1, ref: sha}); + const [rawCommitMetadata] = await this.git().getCommits({max: 1, ref: sha}); + const commit = new Commit(rawCommitMetadata); // todo: check need for error handling in the case of 0 commit and 1 commit - const fileDiffs = await this.git().getDiffsForCommit(sha); - return {...commitMetaData, fileDiffs}; + const multiFileDiff = await this.git().getDiffsForCommit(sha).then(buildMultiFilePatch); + commit.setMultiFileDiff(multiFileDiff); + return commit; }); } diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js index cb72feb36ac..8ee5e96ddad 100644 --- a/lib/views/hunk-header-view.js +++ b/lib/views/hunk-header-view.js @@ -17,16 +17,16 @@ export default class HunkHeaderView extends React.Component { refTarget: RefHolderPropType.isRequired, hunk: PropTypes.object.isRequired, isSelected: PropTypes.bool.isRequired, - stagingStatus: PropTypes.oneOf(['unstaged', 'staged']).isRequired, + stagingStatus: PropTypes.oneOf(['unstaged', 'staged']), selectionMode: PropTypes.oneOf(['hunk', 'line']).isRequired, - toggleSelectionLabel: PropTypes.string.isRequired, - discardSelectionLabel: PropTypes.string.isRequired, + toggleSelectionLabel: PropTypes.string, + discardSelectionLabel: PropTypes.string, tooltips: PropTypes.object.isRequired, keymaps: PropTypes.object.isRequired, - toggleSelection: PropTypes.func.isRequired, - discardSelection: PropTypes.func.isRequired, + toggleSelection: PropTypes.func, + discardSelection: PropTypes.func, mouseDown: PropTypes.func.isRequired, }; diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 0ff63f97b39..dc922a39d62 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -18,6 +18,7 @@ import HunkHeaderView from './hunk-header-view'; import RefHolder from '../models/ref-holder'; import ChangedFileItem from '../items/changed-file-item'; import CommitPreviewItem from '../items/commit-preview-item'; +import CommitDetailItem from '../items/commit-detail-item'; import File from '../models/patch/file'; const executableText = { @@ -31,7 +32,7 @@ const BLANK_LABEL = () => NBSP_CHARACTER; export default class MultiFilePatchView extends React.Component { static propTypes = { - stagingStatus: PropTypes.oneOf(['staged', 'unstaged']).isRequired, + stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), isPartiallyStaged: PropTypes.bool, multiFilePatch: MultiFilePatchPropType.isRequired, selectionMode: PropTypes.oneOf(['hunk', 'line']).isRequired, @@ -46,17 +47,17 @@ export default class MultiFilePatchView extends React.Component { tooltips: PropTypes.object.isRequired, config: PropTypes.object.isRequired, - selectedRowsChanged: PropTypes.func.isRequired, + selectedRowsChanged: PropTypes.func, - diveIntoMirrorPatch: PropTypes.func.isRequired, - surface: PropTypes.func.isRequired, - openFile: PropTypes.func.isRequired, - toggleFile: PropTypes.func.isRequired, - toggleRows: PropTypes.func.isRequired, - toggleModeChange: PropTypes.func.isRequired, - toggleSymlinkChange: PropTypes.func.isRequired, - undoLastDiscard: PropTypes.func.isRequired, - discardRows: PropTypes.func.isRequired, + diveIntoMirrorPatch: PropTypes.func, + surface: PropTypes.func, + openFile: PropTypes.func, + toggleFile: PropTypes.func, + toggleRows: PropTypes.func, + toggleModeChange: PropTypes.func, + toggleSymlinkChange: PropTypes.func, + undoLastDiscard: PropTypes.func, + discardRows: PropTypes.func, refInitialFocus: RefHolderPropType, itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem]).isRequired, @@ -186,6 +187,15 @@ export default class MultiFilePatchView extends React.Component { } renderCommands() { + if (this.props.itemType === CommitDetailItem) { + return ( + + + + + ); + } + let stageModeCommand = null; let stageSymlinkCommand = null; @@ -205,11 +215,11 @@ export default class MultiFilePatchView extends React.Component { return ( + + - - From 2bdf25a73ce227e96110d57116d81667ad32a581 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 15:13:43 -0800 Subject: [PATCH 05/48] Clean up prop errors in the console --- lib/controllers/commit-detail-controller.js | 11 +++++++---- lib/items/commit-detail-item.js | 2 -- lib/views/file-patch-header-view.js | 5 +++-- lib/views/multi-file-patch-view.js | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index 1eac077294b..035dea47e25 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -14,14 +14,17 @@ export default class CommitDetailController extends React.Component { config: PropTypes.object.isRequired, destroy: PropTypes.func.isRequired, + commit: PropTypes.object.isRequired, } render() { return ( - +
+ +
); } } diff --git a/lib/items/commit-detail-item.js b/lib/items/commit-detail-item.js index 90c023129f6..ed8f5f19368 100644 --- a/lib/items/commit-detail-item.js +++ b/lib/items/commit-detail-item.js @@ -11,8 +11,6 @@ export default class CommitDetailItem extends React.Component { workdirContextPool: WorkdirContextPoolPropType.isRequired, workingDirectory: PropTypes.string.isRequired, sha: PropTypes.string.isRequired, - - surfaceToCommitDetailButton: PropTypes.func.isRequired, } static uriPattern = 'atom-github://commit-detail?workdir={workingDirectory}&sha={sha}' diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index 8f7db7d8e2b..97a54c81d9a 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -7,11 +7,12 @@ import cx from 'classnames'; import RefHolder from '../models/ref-holder'; import ChangedFileItem from '../items/changed-file-item'; import CommitPreviewItem from '../items/commit-preview-item'; +import CommitDetailItem from '../items/commit-detail-item'; export default class FilePatchHeaderView extends React.Component { static propTypes = { relPath: PropTypes.string.isRequired, - stagingStatus: PropTypes.oneOf(['staged', 'unstaged']).isRequired, + stagingStatus: PropTypes.oneOf(['staged', 'unstaged']), isPartiallyStaged: PropTypes.bool, hasHunks: PropTypes.bool.isRequired, hasUndoHistory: PropTypes.bool, @@ -24,7 +25,7 @@ export default class FilePatchHeaderView extends React.Component { openFile: PropTypes.func.isRequired, toggleFile: PropTypes.func.isRequired, - itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem]).isRequired, + itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, }; constructor(props) { diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index dc922a39d62..0c81bd6cfd3 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -60,7 +60,7 @@ export default class MultiFilePatchView extends React.Component { discardRows: PropTypes.func, refInitialFocus: RefHolderPropType, - itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem]).isRequired, + itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, } constructor(props) { From 31ed5f48c3e94b4c4eabdb7f59b0ede60e74f20e Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 15:45:22 -0800 Subject: [PATCH 06/48] Display some commit metadata (needs prettifying!) --- lib/controllers/commit-detail-controller.js | 38 ++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index 035dea47e25..40f6ae4ad79 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -1,8 +1,12 @@ import React from 'react'; import PropTypes from 'prop-types'; +import {emojify} from 'node-emoji'; +import moment from 'moment'; import MultiFilePatchController from './multi-file-patch-controller'; +const avatarAltText = 'committer avatar'; + export default class CommitDetailController extends React.Component { static propTypes = { repository: PropTypes.object.isRequired, @@ -18,13 +22,45 @@ export default class CommitDetailController extends React.Component { } render() { + const commit = this.props.commit; + // const {messageHeadline, messageBody, abbreviatedOid, url} = this.props.item; + // const {avatarUrl, name, date} = this.props.item.committer; + return (
+
+

+ {emojify(commit.getMessageSubject())} +
+              {emojify(commit.getMessageBody())}
+

+
+ {/* TODO fix image src */} + {avatarAltText} + + {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} + +
+
+
+ {/* TODO fix href */} + + {commit.getSha()} + +
); } + + humanizeTimeSince(date) { + return moment(date).fromNow(); + } } From c5571490dc1f90ea7d2ae3597c7161ae795010b1 Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 15:51:53 -0800 Subject: [PATCH 07/48] Render co-authors --- lib/controllers/commit-detail-controller.js | 36 ++++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index 40f6ae4ad79..b61cdf2177e 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -36,10 +36,7 @@ export default class CommitDetailController extends React.Component {
{/* TODO fix image src */} - {avatarAltText} + {this.renderAuthors()} {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} @@ -63,4 +60,35 @@ export default class CommitDetailController extends React.Component { humanizeTimeSince(date) { return moment(date).fromNow(); } + + renderAuthor(email) { + const match = email.match(/^(\d+)\+[^@]+@users.noreply.github.com$/); + + let avatarUrl; + if (match) { + avatarUrl = 'https://avatars.githubusercontent.com/u/' + match[1] + '?s=32'; + } else { + avatarUrl = 'https://avatars.githubusercontent.com/u/e?email=' + encodeURIComponent(email) + '&s=32'; + } + + return ( + {`${email}'s + ); + } + + renderAuthors() { + const coAuthorEmails = this.props.commit.getCoAuthors().map(author => author.email); + const authorEmails = [this.props.commit.getAuthorEmail(), ...coAuthorEmails]; + + return ( + + {authorEmails.map(this.renderAuthor)} + + ); + } } From fffd2cba2a3187b198199df9698cffad70bbc69c Mon Sep 17 00:00:00 2001 From: Katrina Uychaco Date: Wed, 21 Nov 2018 15:57:37 -0800 Subject: [PATCH 08/48] Render author number count if co-authors present --- lib/controllers/commit-detail-controller.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index b61cdf2177e..ef3a3f1e6c2 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -61,6 +61,11 @@ export default class CommitDetailController extends React.Component { return moment(date).fromNow(); } + getAuthorInfo() { + const coAuthorCount = this.props.commit.getCoAuthors().length; + return coAuthorCount ? this.props.commit.getAuthorEmail() : `${coAuthorCount + 1} people`; + } + renderAuthor(email) { const match = email.match(/^(\d+)\+[^@]+@users.noreply.github.com$/); From 200f0e790658dd55916861ba83ed2abc8f880aee Mon Sep 17 00:00:00 2001 From: Tilde Ann Thurium Date: Wed, 21 Nov 2018 16:42:01 -0800 Subject: [PATCH 09/48] add height so commit shows up --- lib/containers/commit-detail-container.js | 1 - lib/controllers/commit-detail-controller.js | 1 + lib/controllers/commit-preview-controller.js | 1 + lib/controllers/multi-file-patch-controller.js | 1 + lib/views/multi-file-patch-view.js | 3 ++- lib/views/pr-commit-view.js | 1 + 6 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/containers/commit-detail-container.js b/lib/containers/commit-detail-container.js index 1c5d3022d87..911c73b5f8f 100644 --- a/lib/containers/commit-detail-container.js +++ b/lib/containers/commit-detail-container.js @@ -19,7 +19,6 @@ export default class CommitDetailContainer extends React.Component { } render() { - console.log('container'); return ( {this.renderResult} diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index ef3a3f1e6c2..f9926d9ef49 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -51,6 +51,7 @@ export default class CommitDetailController extends React.Component {
diff --git a/lib/controllers/commit-preview-controller.js b/lib/controllers/commit-preview-controller.js index f1ce3c988c7..ff5f3cf72ba 100644 --- a/lib/controllers/commit-preview-controller.js +++ b/lib/controllers/commit-preview-controller.js @@ -23,6 +23,7 @@ export default class CommitPreviewController extends React.Component { return ( ); diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index cdba88def62..667184aae44 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -25,6 +25,7 @@ export default class MultiFilePatchController extends React.Component { discardLines: PropTypes.func, undoLastDiscard: PropTypes.func, surface: PropTypes.func, + autoHeight: PropTypes.bool, } constructor(props) { diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 0c81bd6cfd3..7e2d7757998 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -58,6 +58,7 @@ export default class MultiFilePatchView extends React.Component { toggleSymlinkChange: PropTypes.func, undoLastDiscard: PropTypes.func, discardRows: PropTypes.func, + autoHeight: PropTypes.bool, refInitialFocus: RefHolderPropType, itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, @@ -241,7 +242,7 @@ export default class MultiFilePatchView extends React.Component { buffer={this.props.multiFilePatch.getBuffer()} lineNumberGutterVisible={false} autoWidth={false} - autoHeight={false} + autoHeight={this.props.autoHeight} readOnly={true} softWrapped={true} diff --git a/lib/views/pr-commit-view.js b/lib/views/pr-commit-view.js index 88d879a3cbf..1d31e5947da 100644 --- a/lib/views/pr-commit-view.js +++ b/lib/views/pr-commit-view.js @@ -38,6 +38,7 @@ export class PrCommitView extends React.Component { } render() { + console.log('zzz'); const {messageHeadline, messageBody, abbreviatedOid, url} = this.props.item; const {avatarUrl, name, date} = this.props.item.committer; return ( From 70bf46544bc37af6f19442bcf948aa6f374e8272 Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 22 Nov 2018 10:13:16 +0900 Subject: [PATCH 10/48] Add scrolling --- styles/commit-detail.less | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 styles/commit-detail.less diff --git a/styles/commit-detail.less b/styles/commit-detail.less new file mode 100644 index 00000000000..03fc322e950 --- /dev/null +++ b/styles/commit-detail.less @@ -0,0 +1,10 @@ +@import "variables"; + +.github-CommitDetail { + + &-root { + // TODO: Remove if CommitDetailView gets moved to a editor decoration + overflow-y: auto; + } + +} From 8fbb4fc0651f9f87446f9a3b30312d30ea01451b Mon Sep 17 00:00:00 2001 From: simurai Date: Thu, 22 Nov 2018 11:03:46 +0900 Subject: [PATCH 11/48] Style CommitDetail header --- lib/controllers/commit-detail-controller.js | 44 ++++++----- styles/commit-detail.less | 87 +++++++++++++++++++++ 2 files changed, 111 insertions(+), 20 deletions(-) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index f9926d9ef49..7d229933340 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -27,28 +27,32 @@ export default class CommitDetailController extends React.Component { // const {avatarUrl, name, date} = this.props.item.committer; return ( -
-
-

- {emojify(commit.getMessageSubject())} -
-              {emojify(commit.getMessageBody())}
-

-
- {/* TODO fix image src */} - {this.renderAuthors()} - - {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} - +
+
+
+
+

+ {emojify(commit.getMessageSubject())} +

+
+                {emojify(commit.getMessageBody())}
+
+ {/* TODO fix image src */} + {this.renderAuthors()} + + {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} + +
+
+
+ {/* TODO fix href */} + + {commit.getSha()} + +
-
- {/* TODO fix href */} - - {commit.getSha()} - -
Date: Thu, 22 Nov 2018 22:01:51 +0100 Subject: [PATCH 12/48] Click on a recent commit to reveal `CommitDetailItem` --- lib/controllers/recent-commits-controller.js | 19 +++++++++++++++++++ lib/views/git-tab-view.js | 2 ++ lib/views/open-commit-dialog.js | 1 - lib/views/recent-commits-view.js | 7 ++++++- 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/lib/controllers/recent-commits-controller.js b/lib/controllers/recent-commits-controller.js index 60ba927619b..7e3a8243096 100644 --- a/lib/controllers/recent-commits-controller.js +++ b/lib/controllers/recent-commits-controller.js @@ -1,6 +1,9 @@ import React from 'react'; import PropTypes from 'prop-types'; +import {autobind} from '../helpers'; +import {addEvent} from '../reporter-proxy'; +import CommitDetailItem from '../items/commit-detail-item'; import RecentCommitsView from '../views/recent-commits-view'; export default class RecentCommitsController extends React.Component { @@ -8,6 +11,13 @@ export default class RecentCommitsController extends React.Component { commits: PropTypes.arrayOf(PropTypes.object).isRequired, isLoading: PropTypes.bool.isRequired, undoLastCommit: PropTypes.func.isRequired, + workspace: PropTypes.object.isRequired, + repository: PropTypes.object.isRequired, + } + + constructor(props, context) { + super(props, context); + autobind(this, 'openCommit'); } render() { @@ -16,7 +26,16 @@ export default class RecentCommitsController extends React.Component { commits={this.props.commits} isLoading={this.props.isLoading} undoLastCommit={this.props.undoLastCommit} + openCommit={this.openCommit} /> ); } + + openCommit({sha}) { + const workdir = this.props.repository.getWorkingDirectoryPath(); + const uri = CommitDetailItem.buildURI(workdir, sha); + this.props.workspace.open(uri).then(() => { + addEvent('open-commit-in-pane', {package: 'github', from: 'dialog'}); + }); + } } diff --git a/lib/views/git-tab-view.js b/lib/views/git-tab-view.js index 9899daca097..fb7339869db 100644 --- a/lib/views/git-tab-view.js +++ b/lib/views/git-tab-view.js @@ -196,6 +196,8 @@ export default class GitTabView extends React.Component { commits={this.props.recentCommits} isLoading={this.props.isLoading} undoLastCommit={this.props.undoLastCommit} + workspace={this.props.workspace} + repository={this.props.repository} />
); diff --git a/lib/views/open-commit-dialog.js b/lib/views/open-commit-dialog.js index 6cd2f04d09e..47ab2d3ec21 100644 --- a/lib/views/open-commit-dialog.js +++ b/lib/views/open-commit-dialog.js @@ -22,7 +22,6 @@ export default class OpenCommitDialog extends React.Component { constructor(props, context) { super(props, context); autobind(this, 'accept', 'cancel', 'editorRefs', 'didChangeCommitSha'); - console.log('sdfasdf'); this.state = { cloneDisabled: false, diff --git a/lib/views/recent-commits-view.js b/lib/views/recent-commits-view.js index 855fad6515a..5ca08ccffeb 100644 --- a/lib/views/recent-commits-view.js +++ b/lib/views/recent-commits-view.js @@ -11,6 +11,7 @@ class RecentCommitView extends React.Component { commit: PropTypes.object.isRequired, undoLastCommit: PropTypes.func.isRequired, isMostRecent: PropTypes.bool.isRequired, + openCommit: PropTypes.func.isRequired, }; render() { @@ -18,7 +19,9 @@ class RecentCommitView extends React.Component { const fullMessage = this.props.commit.getFullMessage(); return ( -
  • +
  • {this.renderAuthors()} this.props.openCommit({sha: commit.getSha()})} /> ); })} From 6914f2e21550e2a6ec0efb3c8041584b8145ab00 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 22 Nov 2018 22:36:45 +0100 Subject: [PATCH 13/48] telemetry event --- lib/controllers/recent-commits-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/controllers/recent-commits-controller.js b/lib/controllers/recent-commits-controller.js index 7e3a8243096..e29e9a2dac4 100644 --- a/lib/controllers/recent-commits-controller.js +++ b/lib/controllers/recent-commits-controller.js @@ -35,7 +35,7 @@ export default class RecentCommitsController extends React.Component { const workdir = this.props.repository.getWorkingDirectoryPath(); const uri = CommitDetailItem.buildURI(workdir, sha); this.props.workspace.open(uri).then(() => { - addEvent('open-commit-in-pane', {package: 'github', from: 'dialog'}); + addEvent('open-commit-in-pane', {package: 'github', from: 'recent commit'}); }); } } From 5bb615828e3e7b3b81a612d1f4caa4c46f8bbca5 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Thu, 22 Nov 2018 22:51:32 +0100 Subject: [PATCH 14/48] fix date --- lib/controllers/commit-detail-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index 7d229933340..4d9ecebcc44 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -63,7 +63,7 @@ export default class CommitDetailController extends React.Component { } humanizeTimeSince(date) { - return moment(date).fromNow(); + return moment(date * 1000).fromNow(); } getAuthorInfo() { From 4735badba64a201a6ee46692b5eef97b6293351c Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 23 Nov 2018 17:04:51 +0100 Subject: [PATCH 15/48] selected state for each recent commit --- lib/controllers/recent-commits-controller.js | 29 +++++++++++++++++++- lib/views/recent-commits-view.js | 8 +++++- 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/lib/controllers/recent-commits-controller.js b/lib/controllers/recent-commits-controller.js index e29e9a2dac4..e88ec50959b 100644 --- a/lib/controllers/recent-commits-controller.js +++ b/lib/controllers/recent-commits-controller.js @@ -4,7 +4,9 @@ import {autobind} from '../helpers'; import {addEvent} from '../reporter-proxy'; import CommitDetailItem from '../items/commit-detail-item'; +import URIPattern from '../atom/uri-pattern'; import RecentCommitsView from '../views/recent-commits-view'; +import {CompositeDisposable} from 'event-kit'; export default class RecentCommitsController extends React.Component { static propTypes = { @@ -17,7 +19,31 @@ export default class RecentCommitsController extends React.Component { constructor(props, context) { super(props, context); - autobind(this, 'openCommit'); + autobind(this, 'openCommit', 'updateSelectedCommit'); + + this.subscriptions = new CompositeDisposable( + this.props.workspace.onDidChangeActivePaneItem(this.updateSelectedCommit), + ); + this.state = {selectedCommitSha: ''}; + } + + updateSelectedCommit() { + const activeItem = this.props.workspace.getActivePaneItem(); + + const pattern = new URIPattern(decodeURIComponent( + CommitDetailItem.buildURI( + this.props.repository.getWorkingDirectoryPath(), + '{sha}'), + )); + + if (activeItem && activeItem.getURI) { + const match = pattern.matches(activeItem.getURI()); + if (match.ok()) { + const {sha} = match.getParams(); + return new Promise(resolve => this.setState({selectedCommitSha: sha}, resolve)); + } + } + return Promise.resolve(); } render() { @@ -27,6 +53,7 @@ export default class RecentCommitsController extends React.Component { isLoading={this.props.isLoading} undoLastCommit={this.props.undoLastCommit} openCommit={this.openCommit} + selectedCommitSha={this.state.selectedCommitSha} /> ); } diff --git a/lib/views/recent-commits-view.js b/lib/views/recent-commits-view.js index 5ca08ccffeb..0f01a6f3f80 100644 --- a/lib/views/recent-commits-view.js +++ b/lib/views/recent-commits-view.js @@ -12,6 +12,7 @@ class RecentCommitView extends React.Component { undoLastCommit: PropTypes.func.isRequired, isMostRecent: PropTypes.bool.isRequired, openCommit: PropTypes.func.isRequired, + isSelected: PropTypes.bool.isRequired, }; render() { @@ -20,7 +21,10 @@ class RecentCommitView extends React.Component { return (
  • {this.renderAuthors()} this.props.openCommit({sha: commit.getSha()})} + isSelected={this.props.selectedCommitSha === commit.getSha()} /> ); })} From 140dc2577a9879282b4a39f358d21364d6467ec7 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 23 Nov 2018 17:05:04 +0100 Subject: [PATCH 16/48] visually show selected state --- styles/recent-commits.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/styles/recent-commits.less b/styles/recent-commits.less index 06a3bda3b56..31e0d2034c4 100644 --- a/styles/recent-commits.less +++ b/styles/recent-commits.less @@ -87,6 +87,12 @@ color: @text-color-subtle; } + &.is-selected { + // is selected + color: @text-color-selected; + background: @background-color-selected; + } + } From 8a76a617647aef7a6fbd1d0c1f01cefe23b9beb5 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 23 Nov 2018 18:11:21 +0100 Subject: [PATCH 17/48] match sha better --- lib/controllers/recent-commits-controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/controllers/recent-commits-controller.js b/lib/controllers/recent-commits-controller.js index e88ec50959b..2e72da64f97 100644 --- a/lib/controllers/recent-commits-controller.js +++ b/lib/controllers/recent-commits-controller.js @@ -38,8 +38,8 @@ export default class RecentCommitsController extends React.Component { if (activeItem && activeItem.getURI) { const match = pattern.matches(activeItem.getURI()); - if (match.ok()) { - const {sha} = match.getParams(); + const {sha} = match.getParams(); + if (match.ok() && sha && sha !== this.state.selectedCommitSha) { return new Promise(resolve => this.setState({selectedCommitSha: sha}, resolve)); } } From 41eb1c39fa895733e5d38be22fa2c4375f9648cc Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 23 Nov 2018 18:11:52 +0100 Subject: [PATCH 18/48] stop propagation after undoing last commit --- lib/views/recent-commits-view.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/views/recent-commits-view.js b/lib/views/recent-commits-view.js index 0f01a6f3f80..fd0bea625c7 100644 --- a/lib/views/recent-commits-view.js +++ b/lib/views/recent-commits-view.js @@ -35,7 +35,7 @@ class RecentCommitView extends React.Component { {this.props.isMostRecent && ( )} @@ -80,6 +80,11 @@ class RecentCommitView extends React.Component { ); } + + undoLastCommit = event => { + event.stopPropagation(); + this.props.undoLastCommit(); + } } export default class RecentCommitsView extends React.Component { From c9c1c07309b837e071a9766e5f819c2809528567 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 23 Nov 2018 18:45:31 +0100 Subject: [PATCH 19/48] disable most of the file patch header functionality when it's in a commit detail item --- lib/controllers/commit-detail-controller.js | 1 + lib/controllers/multi-file-patch-controller.js | 1 + lib/views/file-patch-header-view.js | 7 ++++--- lib/views/multi-file-patch-view.js | 5 ++++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index 4d9ecebcc44..ab5ca279533 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -57,6 +57,7 @@ export default class CommitDetailController extends React.Component { multiFilePatch={commit.getMultiFileDiff()} autoHeight={true} {...this.props} + disableStageUnstage={true} />
  • ); diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index 667184aae44..139d15a0a34 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -26,6 +26,7 @@ export default class MultiFilePatchController extends React.Component { undoLastDiscard: PropTypes.func, surface: PropTypes.func, autoHeight: PropTypes.bool, + disableStageUnstage: PropTypes.bool, } constructor(props) { diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index 97a54c81d9a..c76311f3530 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -26,6 +26,7 @@ export default class FilePatchHeaderView extends React.Component { toggleFile: PropTypes.func.isRequired, itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, + disableStageUnstage: PropTypes.bool, }; constructor(props) { @@ -75,10 +76,10 @@ export default class FilePatchHeaderView extends React.Component { renderButtonGroup() { return ( - {this.renderUndoDiscardButton()} - {this.renderMirrorPatchButton()} + {!this.props.disableStageUnstage && this.renderUndoDiscardButton()} + {!this.props.disableStageUnstage && this.renderMirrorPatchButton()} {this.renderOpenFileButton()} - {this.renderToggleFileButton()} + {!this.props.disableStageUnstage && this.renderToggleFileButton()} ); } diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 7e2d7757998..a066c29bac4 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -59,7 +59,7 @@ export default class MultiFilePatchView extends React.Component { undoLastDiscard: PropTypes.func, discardRows: PropTypes.func, autoHeight: PropTypes.bool, - + disableStageUnstage: PropTypes.bool, refInitialFocus: RefHolderPropType, itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, } @@ -329,6 +329,7 @@ export default class MultiFilePatchView extends React.Component { diveIntoMirrorPatch={() => this.props.diveIntoMirrorPatch(filePatch)} openFile={() => this.didOpenFile({selectedFilePatch: filePatch})} toggleFile={() => this.props.toggleFile(filePatch)} + disableStageUnstage={this.props.disableStageUnstage} /> {this.renderSymlinkChangeMeta(filePatch)} {this.renderExecutableModeChangeMeta(filePatch)} @@ -504,6 +505,8 @@ export default class MultiFilePatchView extends React.Component { toggleSelection={() => this.toggleHunkSelection(hunk, containsSelection)} discardSelection={() => this.discardHunkSelection(hunk, containsSelection)} mouseDown={this.didMouseDownOnHeader} + + disableStageUnstage={this.props.disableStageUnstage} /> From b80ee27e0f145a2be86c41641a6673836732aeb2 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Fri, 23 Nov 2018 18:45:44 +0100 Subject: [PATCH 20/48] same for hunk header --- lib/views/hunk-header-view.js | 59 +++++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js index 8ee5e96ddad..0c4dd8516d9 100644 --- a/lib/views/hunk-header-view.js +++ b/lib/views/hunk-header-view.js @@ -28,11 +28,12 @@ export default class HunkHeaderView extends React.Component { toggleSelection: PropTypes.func, discardSelection: PropTypes.func, mouseDown: PropTypes.func.isRequired, + disableStageUnstage: PropTypes.bool, }; constructor(props) { super(props); - autobind(this, 'didMouseDown'); + autobind(this, 'didMouseDown', 'renderButtons'); this.refDiscardButton = new RefHolder(); } @@ -48,32 +49,44 @@ export default class HunkHeaderView extends React.Component { {this.props.hunk.getHeader().trim()} {this.props.hunk.getSectionHeading().trim()} - - {this.props.stagingStatus === 'unstaged' && ( - -
    ); } + renderButtons() { + if (this.props.disableStageUnstage) { + return null; + } else { + return ( + + + {this.props.stagingStatus === 'unstaged' && ( + +
    diff --git a/styles/commit-detail.less b/styles/commit-detail.less index fc8db7a9404..6f8c24f975a 100644 --- a/styles/commit-detail.less +++ b/styles/commit-detail.less @@ -3,19 +3,13 @@ @default-padding: @component-padding; @avatar-dimensions: 16px; -.github-CommitDetail { - - &-root { - // TODO: Remove if CommitDetailView gets moved to a editor decoration - overflow-y: auto; - } - -} - - .github-CommitDetailView { + display: flex; + flex-direction: column; + height: 100%; &-header { + flex: 0; padding: @default-padding; padding-bottom: 0; background-color: @syntax-background-color; From 5c7aa08f03effb162b2408255cd0d2e08c9fda5c Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 28 Nov 2018 14:51:58 +0100 Subject: [PATCH 36/48] remove jump to file --- lib/views/file-patch-header-view.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index c76311f3530..5eb5476d08a 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -74,14 +74,18 @@ export default class FilePatchHeaderView extends React.Component { } renderButtonGroup() { - return ( - - {!this.props.disableStageUnstage && this.renderUndoDiscardButton()} - {!this.props.disableStageUnstage && this.renderMirrorPatchButton()} - {this.renderOpenFileButton()} - {!this.props.disableStageUnstage && this.renderToggleFileButton()} - - ); + if (this.props.disableStageUnstage) { + return null; + } else { + return ( + + {this.renderUndoDiscardButton()} + {this.renderMirrorPatchButton()} + {this.renderOpenFileButton()} + {this.renderToggleFileButton()} + + ); + } } renderUndoDiscardButton() { From fc6ca8ac95dc2d25627383a4347cbe788bfdea2e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 10:38:09 -0500 Subject: [PATCH 37/48] Cover that last line in CommitDetailItem --- test/items/commit-detail-item.test.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/items/commit-detail-item.test.js b/test/items/commit-detail-item.test.js index 7199b6b20d5..6a6d4e89d3e 100644 --- a/test/items/commit-detail-item.test.js +++ b/test/items/commit-detail-item.test.js @@ -152,4 +152,17 @@ describe.only('CommitDetailItem', function() { assert.strictEqual(item.getSha(), '420'); }); + it('passes a focus() call to the component designated as its initial focus', async function() { + const wrapper = mount(buildPaneApp()); + const item = await open(wrapper); + wrapper.update(); + + const refHolder = wrapper.find('CommitDetailContainer').prop('refInitialFocus'); + const initialFocus = await refHolder.getPromise(); + sinon.spy(initialFocus, 'focus'); + + item.focus(); + + assert.isTrue(initialFocus.focus.called); + }); }); From b61b9ae96f5c4ee89941f2f86730fece51d88ea2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 10:38:16 -0500 Subject: [PATCH 38/48] :fire: dot only --- test/items/commit-detail-item.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/items/commit-detail-item.test.js b/test/items/commit-detail-item.test.js index 6a6d4e89d3e..eecbc66cf6a 100644 --- a/test/items/commit-detail-item.test.js +++ b/test/items/commit-detail-item.test.js @@ -6,7 +6,7 @@ import PaneItem from '../../lib/atom/pane-item'; import WorkdirContextPool from '../../lib/models/workdir-context-pool'; import {cloneRepository} from '../helpers'; -describe.only('CommitDetailItem', function() { +describe('CommitDetailItem', function() { let atomEnv, repository, pool; beforeEach(async function() { From 867397f5b48266a486fb3a509762377a7ed8c7f3 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 10:46:13 -0500 Subject: [PATCH 39/48] Ensure we return a valid Commit to suppress console errors --- test/containers/commit-detail-container.test.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/containers/commit-detail-container.test.js b/test/containers/commit-detail-container.test.js index 2fccdc3614f..e4dff8718fc 100644 --- a/test/containers/commit-detail-container.test.js +++ b/test/containers/commit-detail-container.test.js @@ -5,6 +5,8 @@ import CommitDetailContainer from '../../lib/containers/commit-detail-container' import CommitDetailItem from '../../lib/items/commit-detail-item'; import {cloneRepository, buildRepository} from '../helpers'; +const VALID_SHA = '18920c900bfa6e4844853e7e246607a31c3e2e8c'; + describe('CommitDetailContainer', function() { let atomEnv, repository; @@ -23,7 +25,7 @@ describe('CommitDetailContainer', function() { const props = { repository, - sha: '18920c900bfa6e4844853e7e246607a31c3e2e8c', + sha: VALID_SHA, itemType: CommitDetailItem, workspace: atomEnv.workspace, @@ -47,7 +49,7 @@ describe('CommitDetailContainer', function() { it('renders a loading spinner while the file patch is being loaded', async function() { await repository.getLoadPromise(); - const patchPromise = repository.getStagedChangesPatch(); + const commitPromise = repository.getCommit(VALID_SHA); let resolveDelayedPromise = () => {}; const delayedPromise = new Promise(resolve => { resolveDelayedPromise = resolve; @@ -57,13 +59,13 @@ describe('CommitDetailContainer', function() { const wrapper = mount(buildApp()); assert.isTrue(wrapper.find('LoadingView').exists()); - resolveDelayedPromise(patchPromise); + resolveDelayedPromise(commitPromise); await assert.async.isFalse(wrapper.update().find('LoadingView').exists()); }); it('renders a CommitDetailController once the commit is loaded', async function() { await repository.getLoadPromise(); - const commit = await repository.getCommit('18920c900bfa6e4844853e7e246607a31c3e2e8c'); + const commit = await repository.getCommit(VALID_SHA); const wrapper = mount(buildApp()); await assert.async.isTrue(wrapper.update().find('CommitDetailController').exists()); From b27744610eab776f72814bbc14fe7bc480fcbe1e Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 28 Nov 2018 18:50:04 +0100 Subject: [PATCH 40/48] remove `disableStageUnstage` flag and use `itemType` as checker instead --- lib/controllers/commit-detail-controller.js | 1 - lib/controllers/multi-file-patch-controller.js | 1 - lib/views/file-patch-header-view.js | 3 +-- lib/views/hunk-header-view.js | 1 - lib/views/multi-file-patch-view.js | 4 ---- test/controllers/commit-detail-controller.test.js | 3 +-- 6 files changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index d73bf852f23..b5b3c3f5bbe 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -57,7 +57,6 @@ export default class CommitDetailController extends React.Component { multiFilePatch={commit.getMultiFileDiff()} autoHeight={false} {...this.props} - disableStageUnstage={true} /> ); diff --git a/lib/controllers/multi-file-patch-controller.js b/lib/controllers/multi-file-patch-controller.js index 139d15a0a34..667184aae44 100644 --- a/lib/controllers/multi-file-patch-controller.js +++ b/lib/controllers/multi-file-patch-controller.js @@ -26,7 +26,6 @@ export default class MultiFilePatchController extends React.Component { undoLastDiscard: PropTypes.func, surface: PropTypes.func, autoHeight: PropTypes.bool, - disableStageUnstage: PropTypes.bool, } constructor(props) { diff --git a/lib/views/file-patch-header-view.js b/lib/views/file-patch-header-view.js index 5eb5476d08a..51131689f0f 100644 --- a/lib/views/file-patch-header-view.js +++ b/lib/views/file-patch-header-view.js @@ -26,7 +26,6 @@ export default class FilePatchHeaderView extends React.Component { toggleFile: PropTypes.func.isRequired, itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, - disableStageUnstage: PropTypes.bool, }; constructor(props) { @@ -74,7 +73,7 @@ export default class FilePatchHeaderView extends React.Component { } renderButtonGroup() { - if (this.props.disableStageUnstage) { + if (this.props.itemType === CommitDetailItem) { return null; } else { return ( diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js index 0c4dd8516d9..57e71a28a31 100644 --- a/lib/views/hunk-header-view.js +++ b/lib/views/hunk-header-view.js @@ -28,7 +28,6 @@ export default class HunkHeaderView extends React.Component { toggleSelection: PropTypes.func, discardSelection: PropTypes.func, mouseDown: PropTypes.func.isRequired, - disableStageUnstage: PropTypes.bool, }; constructor(props) { diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index a066c29bac4..3cb5a617314 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -59,7 +59,6 @@ export default class MultiFilePatchView extends React.Component { undoLastDiscard: PropTypes.func, discardRows: PropTypes.func, autoHeight: PropTypes.bool, - disableStageUnstage: PropTypes.bool, refInitialFocus: RefHolderPropType, itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, } @@ -329,7 +328,6 @@ export default class MultiFilePatchView extends React.Component { diveIntoMirrorPatch={() => this.props.diveIntoMirrorPatch(filePatch)} openFile={() => this.didOpenFile({selectedFilePatch: filePatch})} toggleFile={() => this.props.toggleFile(filePatch)} - disableStageUnstage={this.props.disableStageUnstage} /> {this.renderSymlinkChangeMeta(filePatch)} {this.renderExecutableModeChangeMeta(filePatch)} @@ -505,8 +503,6 @@ export default class MultiFilePatchView extends React.Component { toggleSelection={() => this.toggleHunkSelection(hunk, containsSelection)} discardSelection={() => this.discardHunkSelection(hunk, containsSelection)} mouseDown={this.didMouseDownOnHeader} - - disableStageUnstage={this.props.disableStageUnstage} /> diff --git a/test/controllers/commit-detail-controller.test.js b/test/controllers/commit-detail-controller.test.js index d045d8b6d5f..92a5659a22a 100644 --- a/test/controllers/commit-detail-controller.test.js +++ b/test/controllers/commit-detail-controller.test.js @@ -41,10 +41,9 @@ describe('CommitDetailController', function() { return ; } - it('has a MultiFilePatchController that has `disableStageUnstage` flag set to true', function() { + it('has a MultiFilePatchController', function() { const wrapper = mount(buildApp()); assert.isTrue(wrapper.find('MultiFilePatchController').exists()); - assert.isTrue(wrapper.find('MultiFilePatchController').prop('disableStageUnstage')); }); it('passes unrecognized props to a MultiFilePatchController', function() { From e8620da653cefc3b17e988e11175d927ee667aa1 Mon Sep 17 00:00:00 2001 From: Vanessa Yuen Date: Wed, 28 Nov 2018 18:50:35 +0100 Subject: [PATCH 41/48] use `itemType` in hunk headers too! --- lib/views/hunk-header-view.js | 6 +++++- lib/views/multi-file-patch-view.js | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/views/hunk-header-view.js b/lib/views/hunk-header-view.js index 57e71a28a31..6fd986ee542 100644 --- a/lib/views/hunk-header-view.js +++ b/lib/views/hunk-header-view.js @@ -7,6 +7,9 @@ import {RefHolderPropType} from '../prop-types'; import RefHolder from '../models/ref-holder'; import Tooltip from '../atom/tooltip'; import Keystroke from '../atom/keystroke'; +import ChangedFileItem from '../items/changed-file-item'; +import CommitPreviewItem from '../items/commit-preview-item'; +import CommitDetailItem from '../items/commit-detail-item'; function theBuckStopsHere(event) { event.stopPropagation(); @@ -28,6 +31,7 @@ export default class HunkHeaderView extends React.Component { toggleSelection: PropTypes.func, discardSelection: PropTypes.func, mouseDown: PropTypes.func.isRequired, + itemType: PropTypes.oneOf([ChangedFileItem, CommitPreviewItem, CommitDetailItem]).isRequired, }; constructor(props) { @@ -54,7 +58,7 @@ export default class HunkHeaderView extends React.Component { } renderButtons() { - if (this.props.disableStageUnstage) { + if (this.props.itemType === CommitPreviewItem) { return null; } else { return ( diff --git a/lib/views/multi-file-patch-view.js b/lib/views/multi-file-patch-view.js index 3cb5a617314..5e3d3d73bfc 100644 --- a/lib/views/multi-file-patch-view.js +++ b/lib/views/multi-file-patch-view.js @@ -503,6 +503,7 @@ export default class MultiFilePatchView extends React.Component { toggleSelection={() => this.toggleHunkSelection(hunk, containsSelection)} discardSelection={() => this.discardHunkSelection(hunk, containsSelection)} mouseDown={this.didMouseDownOnHeader} + itemType={this.props.itemType} /> From 22718c24c38b98b0b4cfbe7e9e9020373a0a0eac Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 13:48:21 -0500 Subject: [PATCH 42/48] Totally arbitrary "long commit message threshold" --- lib/models/commit.js | 10 ++++++++ test/models/commit.test.js | 51 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 test/models/commit.test.js diff --git a/lib/models/commit.js b/lib/models/commit.js index f1fbd72a1c2..c568de80665 100644 --- a/lib/models/commit.js +++ b/lib/models/commit.js @@ -1,6 +1,8 @@ const UNBORN = Symbol('unborn'); export default class Commit { + static LONG_MESSAGE_THRESHOLD = 1000; + static createUnborn() { return new Commit({unbornRef: UNBORN}); } @@ -40,6 +42,10 @@ export default class Commit { return this.messageBody; } + isBodyLong() { + return this.getMessageBody().length > this.constructor.LONG_MESSAGE_THRESHOLD; + } + getFullMessage() { return `${this.getMessageSubject()}\n\n${this.getMessageBody()}`.trim(); } @@ -77,4 +83,8 @@ export const nullCommit = { isPresent() { return false; }, + + isBodyLong() { + return false; + }, }; diff --git a/test/models/commit.test.js b/test/models/commit.test.js new file mode 100644 index 00000000000..a8cd468a4c3 --- /dev/null +++ b/test/models/commit.test.js @@ -0,0 +1,51 @@ +import moment from 'moment'; +import dedent from 'dedent-js'; + +import Commit, {nullCommit} from '../../lib/models/commit'; + +describe('Commit', function() { + function buildCommit(override = {}) { + return new Commit({ + sha: '0123456789abcdefghij0123456789abcdefghij', + authorEmail: 'me@email.com', + coAuthors: [], + authorDate: moment('2018-11-28T12:00:00', moment.ISO_8601).unix(), + messageSubject: 'subject', + messageBody: 'body', + ...override, + }); + } + + describe('isBodyLong()', function() { + it('returns false if the commit message body is short', function() { + assert.isFalse(buildCommit({messageBody: 'short'}).isBodyLong()); + }); + + it('returns true if the commit message body is long', function() { + const messageBody = dedent` + Lorem ipsum dolor sit amet, et his justo deleniti, omnium fastidii adversarium at has. Mazim alterum sea ea, + essent malorum persius ne mei. Nam ea tempor qualisque, modus doming te has. Affert dolore albucius te vis, eam + tantas nullam corrumpit ad, in oratio luptatum eleifend vim. + + Ea salutatus contentiones eos. Eam in veniam facete volutpat, solum appetere adversarium ut quo. Vel cu appetere + urbanitas, usu ut aperiri mediocritatem, alia molestie urbanitas cu qui. Velit antiopam erroribus no eum, + scripta iudicabit ne nam, in duis clita commodo sit. + + Assum sensibus oportere te vel, vis semper evertitur definiebas in. Tamquam feugiat comprehensam ut his, et eum + voluptua ullamcorper, ex mei debitis inciderint. Sit discere pertinax te, an mei liber putant. Ad doctus + tractatos ius, duo ad civibus alienum, nominati voluptaria sed an. Libris essent philosophia et vix. Nusquam + reprehendunt et mea. Ea eius omnes voluptua sit. + + No cum illud verear efficiantur. Id altera imperdiet nec. Noster audiam accusamus mei at, no zril libris nemore + duo, ius ne rebum doctus fuisset. Legimus epicurei in sit, esse purto suscipit eu qui, oporteat deserunt + delicatissimi sea in. Est id putent accusata convenire, no tibique molestie accommodare quo, cu est fuisset + offendit evertitur. + `; + assert.isTrue(buildCommit({messageBody}).isBodyLong()); + }); + + it('returns false for a null commit', function() { + assert.isFalse(nullCommit.isBodyLong()); + }); + }); +}); From 8434c08aec30d92726d4ac2599297d1e132ddf19 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 13:59:03 -0500 Subject: [PATCH 43/48] CommitDetailController is only responsible for message collapse state All markup generation has been moved (verbatim) to CommitDetailView. --- lib/controllers/commit-detail-controller.js | 102 +++------------- lib/views/commit-detail-view.js | 102 ++++++++++++++++ .../commit-detail-controller.test.js | 110 ++++++++++-------- test/views/commit-detail-view.test.js | 69 +++++++++++ 4 files changed, 248 insertions(+), 135 deletions(-) create mode 100644 lib/views/commit-detail-view.js create mode 100644 test/views/commit-detail-view.test.js diff --git a/lib/controllers/commit-detail-controller.js b/lib/controllers/commit-detail-controller.js index b5b3c3f5bbe..6e0df0c146c 100644 --- a/lib/controllers/commit-detail-controller.js +++ b/lib/controllers/commit-detail-controller.js @@ -1,104 +1,38 @@ import React from 'react'; import PropTypes from 'prop-types'; -import {emojify} from 'node-emoji'; -import moment from 'moment'; -import MultiFilePatchController from './multi-file-patch-controller'; - -const avatarAltText = 'committer avatar'; +import CommitDetailView from '../views/commit-detail-view'; export default class CommitDetailController extends React.Component { static propTypes = { - repository: PropTypes.object.isRequired, - - workspace: PropTypes.object.isRequired, - commands: PropTypes.object.isRequired, - keymaps: PropTypes.object.isRequired, - tooltips: PropTypes.object.isRequired, - config: PropTypes.object.isRequired, + ...CommitDetailView.propTypes, - destroy: PropTypes.func.isRequired, commit: PropTypes.object.isRequired, } - render() { - const commit = this.props.commit; - // const {messageHeadline, messageBody, abbreviatedOid, url} = this.props.item; - // const {avatarUrl, name, date} = this.props.item.committer; - - return ( -
    -
    -
    -
    -

    - {emojify(commit.getMessageSubject())} -

    -
    -                {emojify(commit.getMessageBody())}
    -
    - {/* TODO fix image src */} - {this.renderAuthors()} - - {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} - -
    -
    -
    - {/* TODO fix href */} - - {commit.getSha()} - -
    -
    -
    - -
    - ); - } + constructor(props) { + super(props); - humanizeTimeSince(date) { - return moment(date * 1000).fromNow(); + this.state = { + messageCollapsible: this.props.commit.isBodyLong(), + messageOpen: !this.props.commit.isBodyLong(), + }; } - getAuthorInfo() { - const coAuthorCount = this.props.commit.getCoAuthors().length; - return coAuthorCount ? this.props.commit.getAuthorEmail() : `${coAuthorCount + 1} people`; - } - - renderAuthor(email) { - const match = email.match(/^(\d+)\+[^@]+@users.noreply.github.com$/); - - let avatarUrl; - if (match) { - avatarUrl = 'https://avatars.githubusercontent.com/u/' + match[1] + '?s=32'; - } else { - avatarUrl = 'https://avatars.githubusercontent.com/u/e?email=' + encodeURIComponent(email) + '&s=32'; - } - + render() { return ( - {`${email}'s ); } - renderAuthors() { - const coAuthorEmails = this.props.commit.getCoAuthors().map(author => author.email); - const authorEmails = [this.props.commit.getAuthorEmail(), ...coAuthorEmails]; - - return ( - - {authorEmails.map(this.renderAuthor)} - - ); + toggleMessage = () => { + return new Promise(resolve => { + this.setState(prevState => ({messageOpen: !prevState.messageOpen}), resolve); + }); } } diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js new file mode 100644 index 00000000000..96b38f1ec61 --- /dev/null +++ b/lib/views/commit-detail-view.js @@ -0,0 +1,102 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import {emojify} from 'node-emoji'; +import moment from 'moment'; + +import MultiFilePatchController from '../controllers/multi-file-patch-controller'; + +export default class CommitDetailView extends React.Component { + static propTypes = { + repository: PropTypes.object.isRequired, + + workspace: PropTypes.object.isRequired, + commands: PropTypes.object.isRequired, + keymaps: PropTypes.object.isRequired, + tooltips: PropTypes.object.isRequired, + config: PropTypes.object.isRequired, + + destroy: PropTypes.func.isRequired, + commit: PropTypes.object.isRequired, + } + + render() { + const commit = this.props.commit; + // const {messageHeadline, messageBody, abbreviatedOid, url} = this.props.item; + // const {avatarUrl, name, date} = this.props.item.committer; + + return ( +
    +
    +
    +
    +

    + {emojify(commit.getMessageSubject())} +

    +
    +                {emojify(commit.getMessageBody())}
    +
    + {/* TODO fix image src */} + {this.renderAuthors()} + + {commit.getAuthorEmail()} committed {this.humanizeTimeSince(commit.getAuthorDate())} + +
    +
    +
    + {/* TODO fix href */} + + {commit.getSha()} + +
    +
    +
    + +
    + ); + } + + humanizeTimeSince(date) { + return moment(date * 1000).fromNow(); + } + + getAuthorInfo() { + const coAuthorCount = this.props.commit.getCoAuthors().length; + return coAuthorCount ? this.props.commit.getAuthorEmail() : `${coAuthorCount + 1} people`; + } + + renderAuthor(email) { + const match = email.match(/^(\d+)\+[^@]+@users.noreply.github.com$/); + + let avatarUrl; + if (match) { + avatarUrl = 'https://avatars.githubusercontent.com/u/' + match[1] + '?s=32'; + } else { + avatarUrl = 'https://avatars.githubusercontent.com/u/e?email=' + encodeURIComponent(email) + '&s=32'; + } + + return ( + {`${email}'s + ); + } + + renderAuthors() { + const coAuthorEmails = this.props.commit.getCoAuthors().map(author => author.email); + const authorEmails = [this.props.commit.getAuthorEmail(), ...coAuthorEmails]; + + return ( + + {authorEmails.map(this.renderAuthor)} + + ); + } +} diff --git a/test/controllers/commit-detail-controller.test.js b/test/controllers/commit-detail-controller.test.js index 92a5659a22a..2231af47da2 100644 --- a/test/controllers/commit-detail-controller.test.js +++ b/test/controllers/commit-detail-controller.test.js @@ -1,21 +1,20 @@ import React from 'react'; -import moment from 'moment'; -import {shallow, mount} from 'enzyme'; +import {shallow} from 'enzyme'; +import dedent from 'dedent-js'; import {cloneRepository, buildRepository} from '../helpers'; import CommitDetailItem from '../../lib/items/commit-detail-item'; import CommitDetailController from '../../lib/controllers/commit-detail-controller'; -import Commit from '../../lib/models/commit'; -import {multiFilePatchBuilder} from '../builder/patch'; -describe('CommitDetailController', function() { +const VALID_SHA = '18920c900bfa6e4844853e7e246607a31c3e2e8c'; +describe('CommitDetailController', function() { let atomEnv, repository, commit; beforeEach(async function() { atomEnv = global.buildAtomEnvironment(); repository = await buildRepository(await cloneRepository('multiple-commits')); - commit = await repository.getCommit('18920c900bfa6e4844853e7e246607a31c3e2e8c'); + commit = await repository.getCommit(VALID_SHA); }); afterEach(function() { @@ -41,59 +40,68 @@ describe('CommitDetailController', function() { return ; } - it('has a MultiFilePatchController', function() { - const wrapper = mount(buildApp()); - assert.isTrue(wrapper.find('MultiFilePatchController').exists()); + it('forwards props to its CommitDetailView', function() { + const wrapper = shallow(buildApp()); + const view = wrapper.find('CommitDetailView'); + + assert.strictEqual(view.prop('repository'), repository); + assert.strictEqual(view.prop('commit'), commit); + assert.strictEqual(view.prop('itemType'), CommitDetailItem); }); - it('passes unrecognized props to a MultiFilePatchController', function() { + it('passes unrecognized props to its CommitDetailView', function() { const extra = Symbol('extra'); const wrapper = shallow(buildApp({extra})); - - assert.strictEqual(wrapper.find('MultiFilePatchController').prop('extra'), extra); + assert.strictEqual(wrapper.find('CommitDetailView').prop('extra'), extra); }); - it('renders commit details properly', function() { - const newCommit = new Commit({ - sha: '420', - authorEmail: 'very@nice.com', - authorDate: moment().subtract(2, 'days').unix(), - messageSubject: 'subject', - messageBody: 'messageBody', + describe('commit body collapsing', function() { + const LONG_MESSAGE = dedent` + Lorem ipsum dolor sit amet, et his justo deleniti, omnium fastidii adversarium at has. Mazim alterum sea ea, + essent malorum persius ne mei. Nam ea tempor qualisque, modus doming te has. Affert dolore albucius te vis, eam + tantas nullam corrumpit ad, in oratio luptatum eleifend vim. + + Ea salutatus contentiones eos. Eam in veniam facete volutpat, solum appetere adversarium ut quo. Vel cu appetere + urbanitas, usu ut aperiri mediocritatem, alia molestie urbanitas cu qui. Velit antiopam erroribus no eum, scripta + iudicabit ne nam, in duis clita commodo sit. + + Assum sensibus oportere te vel, vis semper evertitur definiebas in. Tamquam feugiat comprehensam ut his, et eum + voluptua ullamcorper, ex mei debitis inciderint. Sit discere pertinax te, an mei liber putant. Ad doctus tractatos + ius, duo ad civibus alienum, nominati voluptaria sed an. Libris essent philosophia et vix. Nusquam reprehendunt et + mea. Ea eius omnes voluptua sit. + + No cum illud verear efficiantur. Id altera imperdiet nec. Noster audiam accusamus mei at, no zril libris nemore + duo, ius ne rebum doctus fuisset. Legimus epicurei in sit, esse purto suscipit eu qui, oporteat deserunt + delicatissimi sea in. Est id putent accusata convenire, no tibique molestie accommodare quo, cu est fuisset + offendit evertitur. + `; + + it('is uncollapsible if the commit message is short', function() { + sinon.stub(commit, 'getMessageBody').returns('short'); + const wrapper = shallow(buildApp()); + const view = wrapper.find('CommitDetailView'); + assert.isFalse(view.prop('messageCollapsible')); + assert.isTrue(view.prop('messageOpen')); }); - const {multiFilePatch: mfp} = multiFilePatchBuilder().addFilePatch().build(); - sinon.stub(newCommit, 'getMultiFileDiff').returns(mfp); - const wrapper = mount(buildApp({commit: newCommit})); - - assert.strictEqual(wrapper.find('.github-CommitDetailView-title').text(), 'subject'); - assert.strictEqual(wrapper.find('.github-CommitDetailView-moreText').text(), 'messageBody'); - assert.strictEqual(wrapper.find('.github-CommitDetailView-metaText').text(), 'very@nice.com committed 2 days ago'); - assert.strictEqual(wrapper.find('.github-CommitDetailView-sha').text(), '420'); - /* TODO fix href test */ - // assert.strictEqual(wrapper.find('.github-CommitDetailView-sha a').prop('href'), '420'); - assert.strictEqual(wrapper.find('img.github-RecentCommit-avatar').prop('src'), 'https://avatars.githubusercontent.com/u/e?email=very%40nice.com&s=32'); - }); - it('renders multiple avatars for co-authored commit', function() { - const newCommit = new Commit({ - sha: '420', - authorEmail: 'very@nice.com', - authorDate: moment().subtract(2, 'days').unix(), - messageSubject: 'subject', - messageBody: 'messageBody', - coAuthors: [{name: 'two', email: 'two@coauthor.com'}, {name: 'three', email: 'three@coauthor.com'}], + it('is collapsible and begins collapsed if the commit message is long', function() { + sinon.stub(commit, 'getMessageBody').returns(LONG_MESSAGE); + + const wrapper = shallow(buildApp()); + const view = wrapper.find('CommitDetailView'); + assert.isTrue(view.prop('messageCollapsible')); + assert.isFalse(view.prop('messageOpen')); }); - const {multiFilePatch: mfp} = multiFilePatchBuilder().addFilePatch().build(); - sinon.stub(newCommit, 'getMultiFileDiff').returns(mfp); - const wrapper = mount(buildApp({commit: newCommit})); - assert.deepEqual( - wrapper.find('img.github-RecentCommit-avatar').map(w => w.prop('src')), - [ - 'https://avatars.githubusercontent.com/u/e?email=very%40nice.com&s=32', - 'https://avatars.githubusercontent.com/u/e?email=two%40coauthor.com&s=32', - 'https://avatars.githubusercontent.com/u/e?email=three%40coauthor.com&s=32', - ], - ); - }); + it('toggles collapsed state', async function() { + sinon.stub(commit, 'getMessageBody').returns(LONG_MESSAGE); + + const wrapper = shallow(buildApp()); + assert.isFalse(wrapper.find('CommitDetailView').prop('messageOpen')); + + await wrapper.find('CommitDetailView').prop('toggleMessage')(); + + assert.isTrue(wrapper.find('CommitDetailView').prop('messageOpen')); + }); + }); }); diff --git a/test/views/commit-detail-view.test.js b/test/views/commit-detail-view.test.js new file mode 100644 index 00000000000..731a5b71187 --- /dev/null +++ b/test/views/commit-detail-view.test.js @@ -0,0 +1,69 @@ +import React from 'react'; +import {shallow} from 'enzyme'; + +describe('CommitDetailView', function() { + it('has a MultiFilePatchController that its itemType set'); + + it('passes unrecognized props to a MultiFilePatchController'); + + it('renders commit details properly'); + + it('renders multiple avatars for co-authored commit'); +}); + +/* +it('has a MultiFilePatchController that has `disableStageUnstage` flag set to true', function() { + const wrapper = mount(buildApp()); + assert.isTrue(wrapper.find('MultiFilePatchController').exists()); + assert.isTrue(wrapper.find('MultiFilePatchController').prop('disableStageUnstage')); +}); + +it('passes unrecognized props to a MultiFilePatchController', function() { + const extra = Symbol('extra'); + const wrapper = shallow(buildApp({extra})); + + assert.strictEqual(wrapper.find('MultiFilePatchController').prop('extra'), extra); +}); + +it('renders commit details properly', function() { + const newCommit = new Commit({ + sha: '420', + authorEmail: 'very@nice.com', + authorDate: moment().subtract(2, 'days').unix(), + messageSubject: 'subject', + messageBody: 'messageBody', + }); + const {multiFilePatch: mfp} = multiFilePatchBuilder().addFilePatch().build(); + sinon.stub(newCommit, 'getMultiFileDiff').returns(mfp); + const wrapper = mount(buildApp({commit: newCommit})); + + assert.strictEqual(wrapper.find('.github-CommitDetailView-title').text(), 'subject'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-moreText').text(), 'messageBody'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-metaText').text(), 'very@nice.com committed 2 days ago'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-sha').text(), '420'); + // assert.strictEqual(wrapper.find('.github-CommitDetailView-sha a').prop('href'), '420'); + assert.strictEqual(wrapper.find('img.github-RecentCommit-avatar').prop('src'), 'https://avatars.githubusercontent.com/u/e?email=very%40nice.com&s=32'); +}); + +it('renders multiple avatars for co-authored commit', function() { + const newCommit = new Commit({ + sha: '420', + authorEmail: 'very@nice.com', + authorDate: moment().subtract(2, 'days').unix(), + messageSubject: 'subject', + messageBody: 'messageBody', + coAuthors: [{name: 'two', email: 'two@coauthor.com'}, {name: 'three', email: 'three@coauthor.com'}], + }); + const {multiFilePatch: mfp} = multiFilePatchBuilder().addFilePatch().build(); + sinon.stub(newCommit, 'getMultiFileDiff').returns(mfp); + const wrapper = mount(buildApp({commit: newCommit})); + assert.deepEqual( + wrapper.find('img.github-RecentCommit-avatar').map(w => w.prop('src')), + [ + 'https://avatars.githubusercontent.com/u/e?email=very%40nice.com&s=32', + 'https://avatars.githubusercontent.com/u/e?email=two%40coauthor.com&s=32', + 'https://avatars.githubusercontent.com/u/e?email=three%40coauthor.com&s=32', + ], + ); +}); +*/ From 23ed35b724d7b460acc2cde7d6d3e40d2fd13ec2 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 14:36:30 -0500 Subject: [PATCH 44/48] Builder for creating test Commits --- test/builder/commit.js | 54 ++++++++++++++++++++++++++++++++++++++ test/models/commit.test.js | 22 +++++----------- 2 files changed, 60 insertions(+), 16 deletions(-) create mode 100644 test/builder/commit.js diff --git a/test/builder/commit.js b/test/builder/commit.js new file mode 100644 index 00000000000..3d4dfef2061 --- /dev/null +++ b/test/builder/commit.js @@ -0,0 +1,54 @@ +import moment from 'moment'; + +import Commit from '../../lib/models/commit'; + +class CommitBuilder { + constructor() { + this._sha = '0123456789abcdefghij0123456789abcdefghij'; + this._authorEmail = 'default@email.com'; + this._authorDate = moment('2018-11-28T12:00:00', moment.ISO_8601).unix(); + this._coAuthors = []; + this._messageSubject = 'subject'; + this._messageBody = 'body'; + } + + sha(newSha) { + this._sha = newSha; + return this; + } + + authorEmail(newEmail) { + this._authorEmail = newEmail; + return this; + } + + authorDate(timestamp) { + this._authorDate = timestamp; + return this; + } + + messageSubject(subject) { + this._messageSubject = subject; + return this; + } + + messageBody(body) { + this._messageBody = body; + return this; + } + + build() { + return new Commit({ + sha: this._sha, + authorEmail: this._authorEmail, + authorDate: this._authorDate, + coAuthors: this._coAuthors, + messageSubject: this._messageSubject, + messageBody: this._messageBody, + }); + } +} + +export function commitBuilder() { + return new CommitBuilder(); +} diff --git a/test/models/commit.test.js b/test/models/commit.test.js index a8cd468a4c3..1adb497f100 100644 --- a/test/models/commit.test.js +++ b/test/models/commit.test.js @@ -1,24 +1,13 @@ -import moment from 'moment'; import dedent from 'dedent-js'; -import Commit, {nullCommit} from '../../lib/models/commit'; +import {nullCommit} from '../../lib/models/commit'; +import {commitBuilder} from '../builder/commit'; describe('Commit', function() { - function buildCommit(override = {}) { - return new Commit({ - sha: '0123456789abcdefghij0123456789abcdefghij', - authorEmail: 'me@email.com', - coAuthors: [], - authorDate: moment('2018-11-28T12:00:00', moment.ISO_8601).unix(), - messageSubject: 'subject', - messageBody: 'body', - ...override, - }); - } - describe('isBodyLong()', function() { it('returns false if the commit message body is short', function() { - assert.isFalse(buildCommit({messageBody: 'short'}).isBodyLong()); + const commit = commitBuilder().messageBody('short').build(); + assert.isFalse(commit.isBodyLong()); }); it('returns true if the commit message body is long', function() { @@ -41,7 +30,8 @@ describe('Commit', function() { delicatissimi sea in. Est id putent accusata convenire, no tibique molestie accommodare quo, cu est fuisset offendit evertitur. `; - assert.isTrue(buildCommit({messageBody}).isBodyLong()); + const commit = commitBuilder().messageBody(messageBody).build(); + assert.isTrue(commit.isBodyLong()); }); it('returns false for a null commit', function() { From d1237527de6eb258cb2bb0710d9807fb176b340b Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 14:56:15 -0500 Subject: [PATCH 45/48] setMultiFileDiff() to construct a Commit's diff --- test/builder/commit.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/test/builder/commit.js b/test/builder/commit.js index 3d4dfef2061..a68ada437b6 100644 --- a/test/builder/commit.js +++ b/test/builder/commit.js @@ -1,6 +1,7 @@ import moment from 'moment'; import Commit from '../../lib/models/commit'; +import {multiFilePatchBuilder} from './patch'; class CommitBuilder { constructor() { @@ -10,6 +11,8 @@ class CommitBuilder { this._coAuthors = []; this._messageSubject = 'subject'; this._messageBody = 'body'; + + this._multiFileDiff = null; } sha(newSha) { @@ -37,8 +40,14 @@ class CommitBuilder { return this; } + setMultiFileDiff(block = () => {}) { + const builder = multiFilePatchBuilder(); + block(builder); + this._multiFileDiff = builder.build().multiFilePatch; + return this; + } build() { - return new Commit({ + const commit = new Commit({ sha: this._sha, authorEmail: this._authorEmail, authorDate: this._authorDate, @@ -46,6 +55,12 @@ class CommitBuilder { messageSubject: this._messageSubject, messageBody: this._messageBody, }); + + if (this._multiFileDiff !== null) { + commit.setMultiFileDiff(this._multiFileDiff); + } + + return commit; } } From a22b69233e5e10e6fc8724af2ee52cdeed8379ed Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 14:56:28 -0500 Subject: [PATCH 46/48] CoAuthor construction --- test/builder/commit.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/builder/commit.js b/test/builder/commit.js index a68ada437b6..d9dd960bb77 100644 --- a/test/builder/commit.js +++ b/test/builder/commit.js @@ -46,6 +46,12 @@ class CommitBuilder { this._multiFileDiff = builder.build().multiFilePatch; return this; } + + addCoAuthor(name, email) { + this._coAuthors.push({name, email}); + return this; + } + build() { const commit = new Commit({ sha: this._sha, From 942e7dccda77c95aed4e1a08713e292eaf8c5ec1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 14:56:41 -0500 Subject: [PATCH 47/48] PropTypes shuffle :dancer: --- lib/views/commit-detail-view.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/views/commit-detail-view.js b/lib/views/commit-detail-view.js index 96b38f1ec61..ec0416a57be 100644 --- a/lib/views/commit-detail-view.js +++ b/lib/views/commit-detail-view.js @@ -4,10 +4,13 @@ import {emojify} from 'node-emoji'; import moment from 'moment'; import MultiFilePatchController from '../controllers/multi-file-patch-controller'; +import CommitDetailItem from '../items/commit-detail-item'; export default class CommitDetailView extends React.Component { static propTypes = { repository: PropTypes.object.isRequired, + commit: PropTypes.object.isRequired, + itemType: PropTypes.oneOf([CommitDetailItem]).isRequired, workspace: PropTypes.object.isRequired, commands: PropTypes.object.isRequired, @@ -16,7 +19,6 @@ export default class CommitDetailView extends React.Component { config: PropTypes.object.isRequired, destroy: PropTypes.func.isRequired, - commit: PropTypes.object.isRequired, } render() { From 1a28b7d3a7b3a62615690b605dc8ad54367e1a0d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 28 Nov 2018 14:56:59 -0500 Subject: [PATCH 48/48] Ported CommitDetailView tests, all passing --- test/views/commit-detail-view.test.js | 127 +++++++++++++++----------- 1 file changed, 74 insertions(+), 53 deletions(-) diff --git a/test/views/commit-detail-view.test.js b/test/views/commit-detail-view.test.js index 731a5b71187..0dfc18ef132 100644 --- a/test/views/commit-detail-view.test.js +++ b/test/views/commit-detail-view.test.js @@ -1,69 +1,90 @@ import React from 'react'; import {shallow} from 'enzyme'; +import moment from 'moment'; + +import CommitDetailView from '../../lib/views/commit-detail-view'; +import CommitDetailItem from '../../lib/items/commit-detail-item'; +import {cloneRepository, buildRepository} from '../helpers'; +import {commitBuilder} from '../builder/commit'; describe('CommitDetailView', function() { - it('has a MultiFilePatchController that its itemType set'); + let repository, atomEnv; - it('passes unrecognized props to a MultiFilePatchController'); + beforeEach(async function() { + atomEnv = global.buildAtomEnvironment(); + repository = await buildRepository(await cloneRepository('multiple-commits')); + }); - it('renders commit details properly'); + afterEach(function() { + atomEnv.destroy(); + }); - it('renders multiple avatars for co-authored commit'); -}); + function buildApp(override = {}) { + const props = { + repository, + commit: commitBuilder().build(), + itemType: CommitDetailItem, -/* -it('has a MultiFilePatchController that has `disableStageUnstage` flag set to true', function() { - const wrapper = mount(buildApp()); - assert.isTrue(wrapper.find('MultiFilePatchController').exists()); - assert.isTrue(wrapper.find('MultiFilePatchController').prop('disableStageUnstage')); -}); + workspace: atomEnv.workspace, + commands: atomEnv.commands, + keymaps: atomEnv.keymaps, + tooltips: atomEnv.tooltips, + config: atomEnv.config, -it('passes unrecognized props to a MultiFilePatchController', function() { - const extra = Symbol('extra'); - const wrapper = shallow(buildApp({extra})); + destroy: () => {}, + ...override, + }; - assert.strictEqual(wrapper.find('MultiFilePatchController').prop('extra'), extra); -}); + return ; + } -it('renders commit details properly', function() { - const newCommit = new Commit({ - sha: '420', - authorEmail: 'very@nice.com', - authorDate: moment().subtract(2, 'days').unix(), - messageSubject: 'subject', - messageBody: 'messageBody', + it('has a MultiFilePatchController that its itemType set', function() { + const wrapper = shallow(buildApp({itemType: CommitDetailItem})); + assert.strictEqual(wrapper.find('MultiFilePatchController').prop('itemType'), CommitDetailItem); }); - const {multiFilePatch: mfp} = multiFilePatchBuilder().addFilePatch().build(); - sinon.stub(newCommit, 'getMultiFileDiff').returns(mfp); - const wrapper = mount(buildApp({commit: newCommit})); - assert.strictEqual(wrapper.find('.github-CommitDetailView-title').text(), 'subject'); - assert.strictEqual(wrapper.find('.github-CommitDetailView-moreText').text(), 'messageBody'); - assert.strictEqual(wrapper.find('.github-CommitDetailView-metaText').text(), 'very@nice.com committed 2 days ago'); - assert.strictEqual(wrapper.find('.github-CommitDetailView-sha').text(), '420'); - // assert.strictEqual(wrapper.find('.github-CommitDetailView-sha a').prop('href'), '420'); - assert.strictEqual(wrapper.find('img.github-RecentCommit-avatar').prop('src'), 'https://avatars.githubusercontent.com/u/e?email=very%40nice.com&s=32'); -}); - -it('renders multiple avatars for co-authored commit', function() { - const newCommit = new Commit({ - sha: '420', - authorEmail: 'very@nice.com', - authorDate: moment().subtract(2, 'days').unix(), - messageSubject: 'subject', - messageBody: 'messageBody', - coAuthors: [{name: 'two', email: 'two@coauthor.com'}, {name: 'three', email: 'three@coauthor.com'}], + it('passes unrecognized props to a MultiFilePatchController', function() { + const extra = Symbol('extra'); + const wrapper = shallow(buildApp({extra})); + assert.strictEqual(wrapper.find('MultiFilePatchController').prop('extra'), extra); }); - const {multiFilePatch: mfp} = multiFilePatchBuilder().addFilePatch().build(); - sinon.stub(newCommit, 'getMultiFileDiff').returns(mfp); - const wrapper = mount(buildApp({commit: newCommit})); - assert.deepEqual( - wrapper.find('img.github-RecentCommit-avatar').map(w => w.prop('src')), - [ + + it('renders commit details properly', function() { + const commit = commitBuilder() + .sha('420') + .authorEmail('very@nice.com') + .authorDate(moment().subtract(2, 'days').unix()) + .messageSubject('subject') + .messageBody('body') + .setMultiFileDiff() + .build(); + const wrapper = shallow(buildApp({commit})); + + assert.strictEqual(wrapper.find('.github-CommitDetailView-title').text(), 'subject'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-moreText').text(), 'body'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-metaText').text(), 'very@nice.com committed 2 days ago'); + assert.strictEqual(wrapper.find('.github-CommitDetailView-sha').text(), '420'); + // assert.strictEqual(wrapper.find('.github-CommitDetailView-sha a').prop('href'), '420'); + assert.strictEqual( + wrapper.find('img.github-RecentCommit-avatar').prop('src'), 'https://avatars.githubusercontent.com/u/e?email=very%40nice.com&s=32', - 'https://avatars.githubusercontent.com/u/e?email=two%40coauthor.com&s=32', - 'https://avatars.githubusercontent.com/u/e?email=three%40coauthor.com&s=32', - ], - ); + ); + }); + + it('renders multiple avatars for co-authored commit', function() { + const commit = commitBuilder() + .authorEmail('blaze@it.com') + .addCoAuthor('two', 'two@coauthor.com') + .addCoAuthor('three', 'three@coauthor.com') + .build(); + const wrapper = shallow(buildApp({commit})); + assert.deepEqual( + wrapper.find('img.github-RecentCommit-avatar').map(w => w.prop('src')), + [ + 'https://avatars.githubusercontent.com/u/e?email=blaze%40it.com&s=32', + 'https://avatars.githubusercontent.com/u/e?email=two%40coauthor.com&s=32', + 'https://avatars.githubusercontent.com/u/e?email=three%40coauthor.com&s=32', + ], + ); + }); }); -*/