From a49a27c0e78517b4a2ad10ea8e554b7adced4110 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sat, 2 Nov 2019 19:46:32 -0400 Subject: [PATCH 01/26] Render an "uninitialized" view --- lib/controllers/github-tab-controller.js | 3 ++- lib/views/github-blank-uninitialized.js | 27 ++++++++++++++++++++++++ lib/views/github-tab-view.js | 8 +++++++ test/fixtures/props/github-tab-props.js | 2 ++ test/views/github-tab-view.test.js | 11 ++++++++++ 5 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 lib/views/github-blank-uninitialized.js diff --git a/lib/controllers/github-tab-controller.js b/lib/controllers/github-tab-controller.js index 37de392579..1d03552408 100644 --- a/lib/controllers/github-tab-controller.js +++ b/lib/controllers/github-tab-controller.js @@ -9,12 +9,12 @@ import GitHubTabView from '../views/github-tab-view'; export default class GitHubTabController extends React.Component { static propTypes = { workspace: PropTypes.object.isRequired, - repository: PropTypes.object.isRequired, remoteOperationObserver: OperationStateObserverPropType.isRequired, loginModel: GithubLoginModelPropType.isRequired, rootHolder: RefHolderPropType.isRequired, workingDirectory: PropTypes.string, + repository: PropTypes.object.isRequired, allRemotes: RemoteSetPropType.isRequired, branches: BranchSetPropType.isRequired, selectedRemoteName: PropTypes.string, @@ -48,6 +48,7 @@ export default class GitHubTabController extends React.Component { rootHolder={this.props.rootHolder} workingDirectory={this.props.workingDirectory || this.props.currentWorkDir} + repository={this.props.repository} branches={this.props.branches} currentBranch={currentBranch} remotes={gitHubRemotes} diff --git a/lib/views/github-blank-uninitialized.js b/lib/views/github-blank-uninitialized.js new file mode 100644 index 0000000000..d2c23b1d72 --- /dev/null +++ b/lib/views/github-blank-uninitialized.js @@ -0,0 +1,27 @@ +import React from 'react'; + +import Octicon from '../atom/octicon'; + +export default function GitHubBlankUninitialized() { + return ( +
+
+
+

This repository is not yet version controlled by git.

+

+ +

+

+ Create a new GitHub repository, then track the existing content within this directory as a git repository + configured to push there. +

+
+
+ To initialize this directory as a git repository without publishing it to GitHub, visit the + Git tab. +
+
+ ); +} diff --git a/lib/views/github-tab-view.js b/lib/views/github-tab-view.js index 67354b6d7a..f4facc1935 100644 --- a/lib/views/github-tab-view.js +++ b/lib/views/github-tab-view.js @@ -8,6 +8,7 @@ import { import LoadingView from './loading-view'; import RemoteSelectorView from './remote-selector-view'; import TabHeaderView from './tab-header-view'; +import GitHubBlankUninitialized from './github-blank-uninitialized'; import RemoteContainer from '../containers/remote-container'; export default class GitHubTabView extends React.Component { @@ -18,6 +19,7 @@ export default class GitHubTabView extends React.Component { rootHolder: RefHolderPropType.isRequired, workingDirectory: PropTypes.string, + repository: PropTypes.object.isRequired, branches: BranchSetPropType.isRequired, currentBranch: BranchPropType.isRequired, remotes: RemoteSetPropType.isRequired, @@ -50,6 +52,12 @@ export default class GitHubTabView extends React.Component { return ; } + if (this.props.repository.isEmpty()) { + return ( + + ); + } + if (this.props.currentRemote.isPresent()) { // Single, chosen or unambiguous remote return ( diff --git a/test/fixtures/props/github-tab-props.js b/test/fixtures/props/github-tab-props.js index 55c83fe75a..1a3888b86e 100644 --- a/test/fixtures/props/github-tab-props.js +++ b/test/fixtures/props/github-tab-props.js @@ -48,6 +48,7 @@ export function gitHubTabViewProps(atomEnv, repository, overrides = {}) { rootHolder: new RefHolder(), workingDirectory: repository.getWorkingDirectoryPath(), + repository, branches: new BranchSet(), currentBranch: nullBranch, remotes: new RemoteSet(), @@ -55,6 +56,7 @@ export function gitHubTabViewProps(atomEnv, repository, overrides = {}) { manyRemotesAvailable: false, aheadCount: 0, pushInProgress: false, + isLoading: false, handlePushBranch: () => {}, handleRemoteSelect: () => {}, diff --git a/test/views/github-tab-view.test.js b/test/views/github-tab-view.test.js index fac8582d9b..ac5f9cc16a 100644 --- a/test/views/github-tab-view.test.js +++ b/test/views/github-tab-view.test.js @@ -1,5 +1,6 @@ import React from 'react'; import {shallow} from 'enzyme'; +import temp from 'temp'; import {gitHubTabViewProps} from '../fixtures/props/github-tab-props'; import Repository from '../../lib/models/repository'; @@ -8,6 +9,8 @@ import RemoteSet from '../../lib/models/remote-set'; import Branch from '../../lib/models/branch'; import GitHubTabView from '../../lib/views/github-tab-view'; +import {buildRepository} from '../helpers'; + describe('GitHubTabView', function() { let atomEnv; @@ -29,6 +32,14 @@ describe('GitHubTabView', function() { assert.isTrue(wrapper.find('LoadingView').exists()); }); + it('renders a uninitialized view when a local repository is not initialized', async function() { + const workdir = temp.mkdirSync(); + const repository = await buildRepository(workdir); + + const wrapper = shallow(buildApp({repository})); + assert.isTrue(wrapper.exists('GitHubBlankUninitialized')); + }); + it('renders a RemoteContainer if a remote has been chosen', function() { const currentRemote = new Remote('aaa', 'git@github.com:aaa/bbb.git'); const currentBranch = new Branch('bbb'); From 6c4088e590a8fd78807141d625550726bce6c6d1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sat, 2 Nov 2019 19:57:44 -0400 Subject: [PATCH 02/26] Pull in the other prototype views --- lib/views/github-blank-nolocal.js | 23 +++++++++++++++++++++++ lib/views/github-blank-noremote.js | 19 +++++++++++++++++++ lib/views/github-tab-view.js | 18 +++++++++--------- test/views/github-tab-view.test.js | 30 +++++++++++++++++++++--------- 4 files changed, 72 insertions(+), 18 deletions(-) create mode 100644 lib/views/github-blank-nolocal.js create mode 100644 lib/views/github-blank-noremote.js diff --git a/lib/views/github-blank-nolocal.js b/lib/views/github-blank-nolocal.js new file mode 100644 index 0000000000..d3f3cb3d8d --- /dev/null +++ b/lib/views/github-blank-nolocal.js @@ -0,0 +1,23 @@ +import React from 'react'; + +export default function GitHubBlankNoLocal() { + return ( +
+
+

Welcome

+

How would you like to get started today?

+

+ +

+

+ +

+
+ ); +} diff --git a/lib/views/github-blank-noremote.js b/lib/views/github-blank-noremote.js new file mode 100644 index 0000000000..a7f13b6ddb --- /dev/null +++ b/lib/views/github-blank-noremote.js @@ -0,0 +1,19 @@ +import React from 'react'; + +export default function GitHubBlankNoRemote() { + return ( +
+
+

This repository has no remotes on GitHub.

+

+ +

+

+ Create a new GitHub repository and configure this git repository configured to push there. +

+
+ ); +} diff --git a/lib/views/github-tab-view.js b/lib/views/github-tab-view.js index f4facc1935..3000642298 100644 --- a/lib/views/github-tab-view.js +++ b/lib/views/github-tab-view.js @@ -8,7 +8,9 @@ import { import LoadingView from './loading-view'; import RemoteSelectorView from './remote-selector-view'; import TabHeaderView from './tab-header-view'; +import GitHubBlankNoLocal from './github-blank-nolocal'; import GitHubBlankUninitialized from './github-blank-uninitialized'; +import GitHubBlankNoRemote from './github-blank-noremote'; import RemoteContainer from '../containers/remote-container'; export default class GitHubTabView extends React.Component { @@ -52,6 +54,12 @@ export default class GitHubTabView extends React.Component { return ; } + if (this.props.repository.isAbsent()) { + return ( + + ); + } + if (this.props.repository.isEmpty()) { return ( @@ -90,16 +98,8 @@ export default class GitHubTabView extends React.Component { ); } - // No remotes available - // TODO: display a view that lets you create a repository on GitHub return ( -
-
-

No Remotes

-
- This repository does not have any remotes hosted at GitHub.com. -
-
+ ); } diff --git a/test/views/github-tab-view.test.js b/test/views/github-tab-view.test.js index ac5f9cc16a..cb5859cabb 100644 --- a/test/views/github-tab-view.test.js +++ b/test/views/github-tab-view.test.js @@ -9,7 +9,7 @@ import RemoteSet from '../../lib/models/remote-set'; import Branch from '../../lib/models/branch'; import GitHubTabView from '../../lib/views/github-tab-view'; -import {buildRepository} from '../helpers'; +import {buildRepository, cloneRepository} from '../helpers'; describe('GitHubTabView', function() { let atomEnv; @@ -32,6 +32,13 @@ describe('GitHubTabView', function() { assert.isTrue(wrapper.find('LoadingView').exists()); }); + it('renders a no-local view when no local repository is found', function() { + const wrapper = shallow(buildApp({ + repository: Repository.absent(), + })); + assert.isTrue(wrapper.exists('GitHubBlankNoLocal')); + }); + it('renders a uninitialized view when a local repository is not initialized', async function() { const workdir = temp.mkdirSync(); const repository = await buildRepository(workdir); @@ -40,11 +47,19 @@ describe('GitHubTabView', function() { assert.isTrue(wrapper.exists('GitHubBlankUninitialized')); }); - it('renders a RemoteContainer if a remote has been chosen', function() { + it('renders a no-remote view when the local repository has no remotes', async function() { + const repository = await buildRepository(await cloneRepository()); + + const wrapper = shallow(buildApp({repository, currentRemote: nullRemote, manyRemotesAvailable: false})); + assert.isTrue(wrapper.exists('GitHubBlankNoRemote')); + }); + + it('renders a RemoteContainer if a remote has been chosen', async function() { + const repository = await buildRepository(await cloneRepository()); const currentRemote = new Remote('aaa', 'git@github.com:aaa/bbb.git'); const currentBranch = new Branch('bbb'); const handlePushBranch = sinon.spy(); - const wrapper = shallow(buildApp({currentRemote, currentBranch, handlePushBranch})); + const wrapper = shallow(buildApp({repository, currentRemote, currentBranch, handlePushBranch})); const container = wrapper.find('RemoteContainer'); assert.isTrue(container.exists()); @@ -53,10 +68,12 @@ describe('GitHubTabView', function() { assert.isTrue(handlePushBranch.calledWith(currentBranch, currentRemote)); }); - it('renders a RemoteSelectorView when many remote choices are available', function() { + it('renders a RemoteSelectorView when many remote choices are available', async function() { + const repository = await buildRepository(await cloneRepository()); const remotes = new RemoteSet(); const handleRemoteSelect = sinon.spy(); const wrapper = shallow(buildApp({ + repository, remotes, currentRemote: nullRemote, manyRemotesAvailable: true, @@ -70,11 +87,6 @@ describe('GitHubTabView', function() { assert.isTrue(handleRemoteSelect.called); }); - it('renders a static message when no remotes are available', function() { - const wrapper = shallow(buildApp({currentRemote: nullRemote, manyRemotesAvailable: false})); - assert.isTrue(wrapper.find('.github-GitHub-noRemotes').exists()); - }); - it('calls changeWorkingDirectory when a project is selected', async function() { const changeWorkingDirectory = sinon.spy(); const wrapper = shallow(await buildApp({changeWorkingDirectory})); From dd0492536c3b13d57ceab0d5025409e0aeb7cbc1 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sat, 2 Nov 2019 20:23:44 -0400 Subject: [PATCH 03/26] Drill action props --- lib/containers/github-tab-container.js | 5 +++++ lib/controllers/github-tab-controller.js | 8 ++++++++ lib/controllers/root-controller.js | 4 ++++ lib/items/github-tab-item.js | 5 +++++ lib/views/github-blank-nolocal.js | 14 +++++++++----- lib/views/github-blank-noremote.js | 10 +++++++--- lib/views/github-blank-uninitialized.js | 14 +++++++++++--- lib/views/github-tab-view.js | 4 ++++ 8 files changed, 53 insertions(+), 11 deletions(-) diff --git a/lib/containers/github-tab-container.js b/lib/containers/github-tab-container.js index fb13964ab7..f28b93b581 100644 --- a/lib/containers/github-tab-container.js +++ b/lib/containers/github-tab-container.js @@ -15,6 +15,11 @@ export default class GitHubTabContainer extends React.Component { repository: PropTypes.object, loginModel: GithubLoginModelPropType.isRequired, rootHolder: RefHolderPropType.isRequired, + + openCreateDialog: PropTypes.func.isRequired, + openPublishDialog: PropTypes.func.isRequired, + openCloneDialog: PropTypes.func.isRequired, + openGitTab: PropTypes.func.isRequired, } state = {}; diff --git a/lib/controllers/github-tab-controller.js b/lib/controllers/github-tab-controller.js index 1d03552408..2aa4047f21 100644 --- a/lib/controllers/github-tab-controller.js +++ b/lib/controllers/github-tab-controller.js @@ -26,6 +26,10 @@ export default class GitHubTabController extends React.Component { changeWorkingDirectory: PropTypes.func.isRequired, onDidChangeWorkDirs: PropTypes.func.isRequired, getCurrentWorkDirs: PropTypes.func.isRequired, + openCreateDialog: PropTypes.func.isRequired, + openPublishDialog: PropTypes.func.isRequired, + openCloneDialog: PropTypes.func.isRequired, + openGitTab: PropTypes.func.isRequired, } render() { @@ -63,6 +67,10 @@ export default class GitHubTabController extends React.Component { changeWorkingDirectory={this.props.changeWorkingDirectory} getCurrentWorkDirs={this.props.getCurrentWorkDirs} onDidChangeWorkDirs={this.props.onDidChangeWorkDirs} + openCreateDialog={this.props.openCreateDialog} + openPublishDialog={this.props.openPublishDialog} + openCloneDialog={this.props.openCloneDialog} + openGitTab={this.props.openGitTab} /> ); } diff --git a/lib/controllers/root-controller.js b/lib/controllers/root-controller.js index bb512d626e..708fdc3561 100644 --- a/lib/controllers/root-controller.js +++ b/lib/controllers/root-controller.js @@ -312,6 +312,10 @@ export default class RootController extends React.Component { getCurrentWorkDirs={getCurrentWorkDirs} onDidChangeWorkDirs={onDidChangeWorkDirs} changeWorkingDirectory={this.props.changeWorkingDirectory} + openCreateDialog={this.openCreateDialog} + openPublishDialog={this.openPublishDialog} + openCloneDialog={this.openCloneDialog} + openGitTab={this.gitTabTracker.toggleFocus} /> )} diff --git a/lib/items/github-tab-item.js b/lib/items/github-tab-item.js index dc55141ef6..28f88aa8a2 100644 --- a/lib/items/github-tab-item.js +++ b/lib/items/github-tab-item.js @@ -12,6 +12,11 @@ export default class GitHubTabItem extends React.Component { loginModel: GithubLoginModelPropType.isRequired, documentActiveElement: PropTypes.func, + + openCreateDialog: PropTypes.func.isRequired, + openPublishDialog: PropTypes.func.isRequired, + openCloneDialog: PropTypes.func.isRequired, + openGitTab: PropTypes.func.isRequired, } static defaultProps = { diff --git a/lib/views/github-blank-nolocal.js b/lib/views/github-blank-nolocal.js index d3f3cb3d8d..3a8cc90100 100644 --- a/lib/views/github-blank-nolocal.js +++ b/lib/views/github-blank-nolocal.js @@ -1,23 +1,27 @@ import React from 'react'; +import PropTypes from 'prop-types'; -export default function GitHubBlankNoLocal() { +export default function GitHubBlankNoLocal(props) { return (

Welcome

How would you like to get started today?

-

-

); } + +GitHubBlankNoLocal.propTypes = { + openCreateDialog: PropTypes.func.isRequired, + openCloneDialog: PropTypes.func.isRequired, +}; diff --git a/lib/views/github-blank-noremote.js b/lib/views/github-blank-noremote.js index a7f13b6ddb..ecebbc1ff7 100644 --- a/lib/views/github-blank-noremote.js +++ b/lib/views/github-blank-noremote.js @@ -1,13 +1,13 @@ import React from 'react'; +import PropTypes from 'prop-types'; -export default function GitHubBlankNoRemote() { +export default function GitHubBlankNoRemote(props) { return (

This repository has no remotes on GitHub.

-

@@ -17,3 +17,7 @@ export default function GitHubBlankNoRemote() {
); } + +GitHubBlankNoRemote.propTypes = { + openPublishDialog: PropTypes.func.isRequired, +}; diff --git a/lib/views/github-blank-uninitialized.js b/lib/views/github-blank-uninitialized.js index d2c23b1d72..c964ab356f 100644 --- a/lib/views/github-blank-uninitialized.js +++ b/lib/views/github-blank-uninitialized.js @@ -1,15 +1,16 @@ import React from 'react'; +import PropTypes from 'prop-types'; import Octicon from '../atom/octicon'; -export default function GitHubBlankUninitialized() { +export default function GitHubBlankUninitialized(props) { return (

This repository is not yet version controlled by git.

-

@@ -20,8 +21,15 @@ export default function GitHubBlankUninitialized() {
To initialize this directory as a git repository without publishing it to GitHub, visit the - Git tab. +
); } + +GitHubBlankUninitialized.propTypes = { + openPublishDialog: PropTypes.func.isRequired, + openGitTab: PropTypes.func.isRequired, +}; diff --git a/lib/views/github-tab-view.js b/lib/views/github-tab-view.js index 3000642298..fc7d5e660a 100644 --- a/lib/views/github-tab-view.js +++ b/lib/views/github-tab-view.js @@ -36,6 +36,10 @@ export default class GitHubTabView extends React.Component { changeWorkingDirectory: PropTypes.func.isRequired, onDidChangeWorkDirs: PropTypes.func.isRequired, getCurrentWorkDirs: PropTypes.func.isRequired, + openCreateDialog: PropTypes.func.isRequired, + openPublishDialog: PropTypes.func.isRequired, + openCloneDialog: PropTypes.func.isRequired, + openGitTab: PropTypes.func.isRequired, } render() { From 8fb24d6bd3bc6ccd3e734650ddf1d6a5b6e63efa Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sat, 2 Nov 2019 21:09:20 -0400 Subject: [PATCH 04/26] Pass dialog func props to blank views --- lib/views/github-tab-view.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/views/github-tab-view.js b/lib/views/github-tab-view.js index fc7d5e660a..6edf624862 100644 --- a/lib/views/github-tab-view.js +++ b/lib/views/github-tab-view.js @@ -60,13 +60,19 @@ export default class GitHubTabView extends React.Component { if (this.props.repository.isAbsent()) { return ( - + ); } if (this.props.repository.isEmpty()) { return ( - + ); } @@ -103,7 +109,7 @@ export default class GitHubTabView extends React.Component { } return ( - + ); } From 7736430ffd40ace4084bec2642a0109060dffbb8 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sat, 2 Nov 2019 21:09:44 -0400 Subject: [PATCH 05/26] Flatten and update GitHub tab component props --- lib/containers/github-tab-container.js | 3 + lib/items/github-tab-item.js | 3 + test/containers/github-tab-container.test.js | 44 ++++++++---- .../controllers/github-tab-controller.test.js | 48 ++++++++++--- test/fixtures/props/github-tab-props.js | 69 ------------------- test/items/github-tab-item.test.js | 22 ++++-- test/views/github-tab-view.test.js | 44 ++++++++++-- 7 files changed, 132 insertions(+), 101 deletions(-) delete mode 100644 test/fixtures/props/github-tab-props.js diff --git a/lib/containers/github-tab-container.js b/lib/containers/github-tab-container.js index f28b93b581..7d47c9043f 100644 --- a/lib/containers/github-tab-container.js +++ b/lib/containers/github-tab-container.js @@ -16,6 +16,9 @@ export default class GitHubTabContainer extends React.Component { loginModel: GithubLoginModelPropType.isRequired, rootHolder: RefHolderPropType.isRequired, + changeWorkingDirectory: PropTypes.func.isRequired, + onDidChangeWorkDirs: PropTypes.func.isRequired, + getCurrentWorkDirs: PropTypes.func.isRequired, openCreateDialog: PropTypes.func.isRequired, openPublishDialog: PropTypes.func.isRequired, openCloneDialog: PropTypes.func.isRequired, diff --git a/lib/items/github-tab-item.js b/lib/items/github-tab-item.js index 28f88aa8a2..5a4452dc7c 100644 --- a/lib/items/github-tab-item.js +++ b/lib/items/github-tab-item.js @@ -13,6 +13,9 @@ export default class GitHubTabItem extends React.Component { documentActiveElement: PropTypes.func, + changeWorkingDirectory: PropTypes.func.isRequired, + onDidChangeWorkDirs: PropTypes.func.isRequired, + getCurrentWorkDirs: PropTypes.func.isRequired, openCreateDialog: PropTypes.func.isRequired, openPublishDialog: PropTypes.func.isRequired, openCloneDialog: PropTypes.func.isRequired, diff --git a/test/containers/github-tab-container.test.js b/test/containers/github-tab-container.test.js index f557eaee88..5ff5d9425d 100644 --- a/test/containers/github-tab-container.test.js +++ b/test/containers/github-tab-container.test.js @@ -4,28 +4,46 @@ import {mount} from 'enzyme'; import {cloneRepository} from '../helpers'; import GitHubTabContainer from '../../lib/containers/github-tab-container'; import Repository from '../../lib/models/repository'; -import {gitHubTabContainerProps} from '../fixtures/props/github-tab-props'; +import {InMemoryStrategy} from '../../lib/shared/keytar-strategy'; +import GithubLoginModel from '../../lib/models/github-login-model'; +import RefHolder from '../../lib/models/ref-holder'; describe('GitHubTabContainer', function() { - let atomEnv; + let atomEnv, repository; beforeEach(function() { atomEnv = global.buildAtomEnvironment(); + repository = Repository.absent(); }); afterEach(function() { atomEnv.destroy(); }); - function buildApp(overrideProps = {}) { - const repository = Repository.absent(); - return ; + function buildApp(props = {}) { + return ( + {}} + onDidChangeWorkDirs={() => {}} + getCurrentWorkDirs={() => []} + openCreateDialog={() => {}} + openPublishDialog={() => {}} + openCloneDialog={() => {}} + openGitTab={() => {}} + + {...props} + /> + ); } describe('operation state observer', function() { it('creates an observer on the current repository', function() { - const repository = Repository.absent(); - const wrapper = mount(buildApp({repository})); + const wrapper = mount(buildApp()); const observer = wrapper.state('remoteOperationObserver'); assert.strictEqual(observer.repository, repository); @@ -48,9 +66,9 @@ describe('GitHubTabContainer', function() { describe('while loading', function() { it('passes isLoading to its view', async function() { - const repository = new Repository(await cloneRepository()); - assert.isTrue(repository.isLoading()); - const wrapper = mount(buildApp({repository})); + const loadingRepo = new Repository(await cloneRepository()); + assert.isTrue(loadingRepo.isLoading()); + const wrapper = mount(buildApp({repository: loadingRepo})); assert.isTrue(wrapper.find('GitHubTabController').prop('isLoading')); }); @@ -59,9 +77,9 @@ describe('GitHubTabContainer', function() { describe('once loaded', function() { it('renders the controller', async function() { const workdir = await cloneRepository(); - const repository = new Repository(workdir); - await repository.getLoadPromise(); - const wrapper = mount(buildApp({repository})); + const presentRepo = new Repository(workdir); + await presentRepo.getLoadPromise(); + const wrapper = mount(buildApp({repository: presentRepo})); await assert.async.isFalse(wrapper.update().find('GitHubTabController').prop('isLoading')); assert.strictEqual(wrapper.find('GitHubTabController').prop('workingDirectory'), workdir); diff --git a/test/controllers/github-tab-controller.test.js b/test/controllers/github-tab-controller.test.js index 5843176be0..a1f284a343 100644 --- a/test/controllers/github-tab-controller.test.js +++ b/test/controllers/github-tab-controller.test.js @@ -1,32 +1,60 @@ import React from 'react'; import {shallow} from 'enzyme'; -import {gitHubTabControllerProps} from '../fixtures/props/github-tab-props'; import GitHubTabController from '../../lib/controllers/github-tab-controller'; import Repository from '../../lib/models/repository'; import BranchSet from '../../lib/models/branch-set'; import Branch, {nullBranch} from '../../lib/models/branch'; import RemoteSet from '../../lib/models/remote-set'; import Remote from '../../lib/models/remote'; +import {InMemoryStrategy} from '../../lib/shared/keytar-strategy'; +import GithubLoginModel from '../../lib/models/github-login-model'; +import RefHolder from '../../lib/models/ref-holder'; +import OperationStateObserver, {PUSH, PULL, FETCH} from '../../lib/models/operation-state-observer'; + +import {buildRepository, cloneRepository} from '../helpers'; describe('GitHubTabController', function() { - let atomEnv; + let atomEnv, repository; - beforeEach(function() { + beforeEach(async function() { atomEnv = global.buildAtomEnvironment(); + repository = await buildRepository(await cloneRepository()); }); afterEach(function() { atomEnv.destroy(); }); - function buildApp(overrideProps = {}) { - const props = { - repository: Repository.absent(), - ...overrideProps, - }; - - return ; + function buildApp(props = {}) { + const repo = props.repository || repository; + + return ( + {}} + onDidChangeWorkDirs={() => {}} + getCurrentWorkDirs={() => []} + openCreateDialog={() => {}} + openPublishDialog={() => {}} + openCloneDialog={() => {}} + openGitTab={() => {}} + + {...props} + /> + ); } describe('derived view props', function() { diff --git a/test/fixtures/props/github-tab-props.js b/test/fixtures/props/github-tab-props.js deleted file mode 100644 index 1a3888b86e..0000000000 --- a/test/fixtures/props/github-tab-props.js +++ /dev/null @@ -1,69 +0,0 @@ -import {InMemoryStrategy} from '../../../lib/shared/keytar-strategy'; -import GithubLoginModel from '../../../lib/models/github-login-model'; -import RefHolder from '../../../lib/models/ref-holder'; -import OperationStateObserver, {PUSH, PULL, FETCH} from '../../../lib/models/operation-state-observer'; -import RemoteSet from '../../../lib/models/remote-set'; -import {nullRemote} from '../../../lib/models/remote'; -import BranchSet from '../../../lib/models/branch-set'; -import {nullBranch} from '../../../lib/models/branch'; - -export function gitHubTabItemProps(atomEnv, repository, overrides = {}) { - return { - workspace: atomEnv.workspace, - repository, - loginModel: new GithubLoginModel(InMemoryStrategy), - changeWorkingDirectory: () => {}, - onDidChangeWorkDirs: () => {}, - getCurrentWorkDirs: () => [], - ...overrides, - }; -} - -export function gitHubTabContainerProps(atomEnv, repository, overrides = {}) { - return { - ...gitHubTabItemProps(atomEnv, repository), - rootHolder: new RefHolder(), - ...overrides, - }; -} - -export function gitHubTabControllerProps(atomEnv, repository, overrides = {}) { - return { - ...gitHubTabContainerProps(atomEnv, repository), - remoteOperationObserver: new OperationStateObserver(repository, PUSH, PULL, FETCH), - workingDirectory: repository.getWorkingDirectoryPath(), - allRemotes: new RemoteSet(), - branches: new BranchSet(), - aheadCount: 0, - pushInProgress: false, - ...overrides, - }; -} - -export function gitHubTabViewProps(atomEnv, repository, overrides = {}) { - return { - workspace: atomEnv.workspace, - remoteOperationObserver: new OperationStateObserver(repository, PUSH, PULL, FETCH), - loginModel: new GithubLoginModel(InMemoryStrategy), - rootHolder: new RefHolder(), - - workingDirectory: repository.getWorkingDirectoryPath(), - repository, - branches: new BranchSet(), - currentBranch: nullBranch, - remotes: new RemoteSet(), - currentRemote: nullRemote, - manyRemotesAvailable: false, - aheadCount: 0, - pushInProgress: false, - isLoading: false, - - handlePushBranch: () => {}, - handleRemoteSelect: () => {}, - changeWorkingDirectory: () => {}, - onDidChangeWorkDirs: () => {}, - getCurrentWorkDirs: () => [], - - ...overrides, - }; -} diff --git a/test/items/github-tab-item.test.js b/test/items/github-tab-item.test.js index ae127b9a51..cf52d1e45d 100644 --- a/test/items/github-tab-item.test.js +++ b/test/items/github-tab-item.test.js @@ -3,8 +3,10 @@ import {mount} from 'enzyme'; import PaneItem from '../../lib/atom/pane-item'; import GitHubTabItem from '../../lib/items/github-tab-item'; +import GithubLoginModel from '../../lib/models/github-login-model'; +import {InMemoryStrategy} from '../../lib/shared/keytar-strategy'; + import {cloneRepository, buildRepository} from '../helpers'; -import {gitHubTabItemProps} from '../fixtures/props/github-tab-props'; describe('GitHubTabItem', function() { let atomEnv, repository; @@ -20,14 +22,26 @@ describe('GitHubTabItem', function() { atomEnv.destroy(); }); - function buildApp(overrideProps = {}) { - const props = gitHubTabItemProps(atomEnv, repository, overrideProps); + function buildApp(props = {}) { + const workspace = props.workspace || atomEnv.workspace; return ( - + {({itemHolder}) => ( {}} + onDidChangeWorkDirs={() => {}} + getCurrentWorkDirs={() => []} + openCreateDialog={() => {}} + openPublishDialog={() => {}} + openCloneDialog={() => {}} + openGitTab={() => {}} {...props} /> )} diff --git a/test/views/github-tab-view.test.js b/test/views/github-tab-view.test.js index cb5859cabb..8699b60f0a 100644 --- a/test/views/github-tab-view.test.js +++ b/test/views/github-tab-view.test.js @@ -2,12 +2,16 @@ import React from 'react'; import {shallow} from 'enzyme'; import temp from 'temp'; -import {gitHubTabViewProps} from '../fixtures/props/github-tab-props'; import Repository from '../../lib/models/repository'; import Remote, {nullRemote} from '../../lib/models/remote'; import RemoteSet from '../../lib/models/remote-set'; -import Branch from '../../lib/models/branch'; +import Branch, {nullBranch} from '../../lib/models/branch'; +import BranchSet from '../../lib/models/branch-set'; import GitHubTabView from '../../lib/views/github-tab-view'; +import {InMemoryStrategy} from '../../lib/shared/keytar-strategy'; +import GithubLoginModel from '../../lib/models/github-login-model'; +import RefHolder from '../../lib/models/ref-holder'; +import OperationStateObserver, {PUSH, PULL, FETCH} from '../../lib/models/operation-state-observer'; import {buildRepository, cloneRepository} from '../helpers'; @@ -22,9 +26,39 @@ describe('GitHubTabView', function() { atomEnv.destroy(); }); - function buildApp(overrideProps = {}) { - const props = gitHubTabViewProps(atomEnv, overrideProps.repository || Repository.absent(), overrideProps); - return ; + function buildApp(props) { + const repo = props.repository || Repository.absent(); + + return ( + {}} + handleRemoteSelect={() => {}} + changeWorkingDirectory={() => {}} + onDidChangeWorkDirs={() => {}} + getCurrentWorkDirs={() => []} + openCreateDialog={() => {}} + openPublishDialog={() => {}} + openCloneDialog={() => {}} + openGitTab={() => {}} + + {...props} + /> + ); } it('renders a LoadingView if data is still loading', function() { From 2a50b9f4a8265167d5e93a921254b049adb8456d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Sat, 2 Nov 2019 21:11:29 -0400 Subject: [PATCH 06/26] Bring in blank view styles --- styles/github-blank.less | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 styles/github-blank.less diff --git a/styles/github-blank.less b/styles/github-blank.less new file mode 100644 index 0000000000..fa6fd072ef --- /dev/null +++ b/styles/github-blank.less @@ -0,0 +1,62 @@ +@import "variables"; + +.github-Blank { + display: flex; + flex-direction: column; + width: 100%; + height: 100%; + + &-body { + flex-grow: 1; + display: flex; + flex-direction: column; + } + + &-footer { + flex-shrink: 0; + padding: @component-padding*2 @component-padding; + } + + &-LargeIcon:before { + margin-right: 0; + margin-top: @component-padding * 5; + margin-bottom: @component-padding * 5; + width: auto; + height: auto; + font-size: 8em; + color: mix(@base-background-color, @text-color, 66%); // 2/3 of bg color + } + + &-banner, &-context , &-explanation , &-LargeIcon { + text-align: center; + } + + &-option { + display: flex; + flex-direction: row; + align-items: center; + + &--explained { + margin-bottom: @component-padding/2; + } + } + + &-actionBtn { + flex: 1; + margin: 0 10%; + } + + &-explanation { + margin-left: 5%; + margin-right: 5%; + color: @text-color-subtle; + } + + &-tabLink { + margin-left: 0.5em; + + .icon { + padding-right: 0; + } + } +} From cc41994d14809f2c3c5ba7eadbbefd90474754ee Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Nov 2019 15:35:39 -0500 Subject: [PATCH 07/26] Bind the active repository to the openPublishDialog call --- lib/controllers/github-tab-controller.js | 4 +++- test/controllers/github-tab-controller.test.js | 8 ++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/controllers/github-tab-controller.js b/lib/controllers/github-tab-controller.js index 2aa4047f21..1c0694e998 100644 --- a/lib/controllers/github-tab-controller.js +++ b/lib/controllers/github-tab-controller.js @@ -68,7 +68,7 @@ export default class GitHubTabController extends React.Component { getCurrentWorkDirs={this.props.getCurrentWorkDirs} onDidChangeWorkDirs={this.props.onDidChangeWorkDirs} openCreateDialog={this.props.openCreateDialog} - openPublishDialog={this.props.openPublishDialog} + openBoundPublishDialog={this.openBoundPublishDialog} openCloneDialog={this.props.openCloneDialog} openGitTab={this.props.openGitTab} /> @@ -86,4 +86,6 @@ export default class GitHubTabController extends React.Component { e.preventDefault(); return this.props.repository.setConfig('atomGithub.currentRemote', remote.getName()); } + + openBoundPublishDialog = () => this.props.openPublishDialog(this.props.repository); } diff --git a/test/controllers/github-tab-controller.test.js b/test/controllers/github-tab-controller.test.js index a1f284a343..431934461a 100644 --- a/test/controllers/github-tab-controller.test.js +++ b/test/controllers/github-tab-controller.test.js @@ -127,6 +127,14 @@ describe('GitHubTabController', function() { assert.isTrue(event.preventDefault.called); assert.isTrue(repository.setConfig.calledWith('atomGithub.currentRemote', 'aaa')); + + it('opens the publish dialog on the active repository', async function() { + const someRepo = await buildRepository(await cloneRepository()); + const openPublishDialog = sinon.spy(); + const wrapper = shallow(buildApp({repository: someRepo, openPublishDialog})); + + wrapper.find('GitHubTabView').prop('openBoundPublishDialog')(); + assert.isTrue(openPublishDialog.calledWith(someRepo)); }); }); }); From 200a9e6163b49fd091b00f4a8d62cc0e0ef677f6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Nov 2019 15:35:46 -0500 Subject: [PATCH 08/26] Rename shadowing variable --- test/controllers/github-tab-controller.test.js | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/controllers/github-tab-controller.test.js b/test/controllers/github-tab-controller.test.js index 431934461a..20443a8f97 100644 --- a/test/controllers/github-tab-controller.test.js +++ b/test/controllers/github-tab-controller.test.js @@ -105,28 +105,29 @@ describe('GitHubTabController', function() { describe('actions', function() { it('pushes a branch', async function() { - const repository = Repository.absent(); - sinon.stub(repository, 'push').resolves(true); - const wrapper = shallow(buildApp({repository})); + const absent = Repository.absent(); + sinon.stub(absent, 'push').resolves(true); + const wrapper = shallow(buildApp({repository: absent})); const branch = new Branch('abc'); const remote = new Remote('def', 'git@github.com:def/ghi.git'); assert.isTrue(await wrapper.find('GitHubTabView').prop('handlePushBranch')(branch, remote)); - assert.isTrue(repository.push.calledWith('abc', {remote, setUpstream: true})); + assert.isTrue(absent.push.calledWith('abc', {remote, setUpstream: true})); }); it('chooses a remote', async function() { - const repository = Repository.absent(); - sinon.stub(repository, 'setConfig').resolves(true); - const wrapper = shallow(buildApp({repository})); + const absent = Repository.absent(); + sinon.stub(absent, 'setConfig').resolves(true); + const wrapper = shallow(buildApp({repository: absent})); const remote = new Remote('aaa', 'git@github.com:aaa/aaa.git'); const event = {preventDefault: sinon.spy()}; assert.isTrue(await wrapper.find('GitHubTabView').prop('handleRemoteSelect')(event, remote)); assert.isTrue(event.preventDefault.called); - assert.isTrue(repository.setConfig.calledWith('atomGithub.currentRemote', 'aaa')); + assert.isTrue(absent.setConfig.calledWith('atomGithub.currentRemote', 'aaa')); + }); it('opens the publish dialog on the active repository', async function() { const someRepo = await buildRepository(await cloneRepository()); From 854dce9f096318f787d803cdaba5c64c1ab111ec Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Nov 2019 15:36:02 -0500 Subject: [PATCH 09/26] Render the no-local view for absent guess repositories --- lib/views/github-tab-view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/views/github-tab-view.js b/lib/views/github-tab-view.js index 6edf624862..d094f05eba 100644 --- a/lib/views/github-tab-view.js +++ b/lib/views/github-tab-view.js @@ -58,7 +58,7 @@ export default class GitHubTabView extends React.Component { return ; } - if (this.props.repository.isAbsent()) { + if (this.props.repository.isAbsent() || this.props.repository.isAbsentGuess()) { return ( Date: Wed, 6 Nov 2019 15:36:14 -0500 Subject: [PATCH 10/26] Vertically center blank GitHub tab views --- styles/github-blank.less | 1 + 1 file changed, 1 insertion(+) diff --git a/styles/github-blank.less b/styles/github-blank.less index fa6fd072ef..bf00794f00 100644 --- a/styles/github-blank.less +++ b/styles/github-blank.less @@ -3,6 +3,7 @@ .github-Blank { display: flex; flex-direction: column; + justify-content: center; width: 100%; height: 100%; From 2ddf6cba2e592b9a0ba3f6094b2b86c608b0f2ef Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Nov 2019 15:36:22 -0500 Subject: [PATCH 11/26] Apparently that can be undefined, who knew --- lib/containers/comment-decorations-container.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/containers/comment-decorations-container.js b/lib/containers/comment-decorations-container.js index 81fd521b90..6cab8d4ae0 100644 --- a/lib/containers/comment-decorations-container.js +++ b/lib/containers/comment-decorations-container.js @@ -146,6 +146,7 @@ export default class CommentDecorationsContainer extends React.Component { if ( !props || !props.repository || !props.repository.ref || + !props.repository.ref.associatedPullRequests || props.repository.ref.associatedPullRequests.totalCount === 0 ) { // no loading spinner for you From 1e0b28aa2339a3df3a99a094b469aa6bc91b256e Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Nov 2019 15:38:21 -0500 Subject: [PATCH 12/26] Rename openPublishDialog prop to openBoundPublishDialog when bound --- lib/views/github-blank-noremote.js | 4 ++-- lib/views/github-blank-uninitialized.js | 4 ++-- lib/views/github-tab-view.js | 6 +++--- test/views/github-tab-view.test.js | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/views/github-blank-noremote.js b/lib/views/github-blank-noremote.js index ecebbc1ff7..f59615bc93 100644 --- a/lib/views/github-blank-noremote.js +++ b/lib/views/github-blank-noremote.js @@ -7,7 +7,7 @@ export default function GitHubBlankNoRemote(props) {

This repository has no remotes on GitHub.

-

@@ -19,5 +19,5 @@ export default function GitHubBlankNoRemote(props) { } GitHubBlankNoRemote.propTypes = { - openPublishDialog: PropTypes.func.isRequired, + openBoundPublishDialog: PropTypes.func.isRequired, }; diff --git a/lib/views/github-blank-uninitialized.js b/lib/views/github-blank-uninitialized.js index c964ab356f..bb454bb8ae 100644 --- a/lib/views/github-blank-uninitialized.js +++ b/lib/views/github-blank-uninitialized.js @@ -10,7 +10,7 @@ export default function GitHubBlankUninitialized(props) {

This repository is not yet version controlled by git.

-

@@ -30,6 +30,6 @@ export default function GitHubBlankUninitialized(props) { } GitHubBlankUninitialized.propTypes = { - openPublishDialog: PropTypes.func.isRequired, + openBoundPublishDialog: PropTypes.func.isRequired, openGitTab: PropTypes.func.isRequired, }; diff --git a/lib/views/github-tab-view.js b/lib/views/github-tab-view.js index d094f05eba..8f03fde39d 100644 --- a/lib/views/github-tab-view.js +++ b/lib/views/github-tab-view.js @@ -37,7 +37,7 @@ export default class GitHubTabView extends React.Component { onDidChangeWorkDirs: PropTypes.func.isRequired, getCurrentWorkDirs: PropTypes.func.isRequired, openCreateDialog: PropTypes.func.isRequired, - openPublishDialog: PropTypes.func.isRequired, + openBoundPublishDialog: PropTypes.func.isRequired, openCloneDialog: PropTypes.func.isRequired, openGitTab: PropTypes.func.isRequired, } @@ -70,7 +70,7 @@ export default class GitHubTabView extends React.Component { if (this.props.repository.isEmpty()) { return ( ); @@ -109,7 +109,7 @@ export default class GitHubTabView extends React.Component { } return ( - + ); } diff --git a/test/views/github-tab-view.test.js b/test/views/github-tab-view.test.js index 8699b60f0a..d280c939ff 100644 --- a/test/views/github-tab-view.test.js +++ b/test/views/github-tab-view.test.js @@ -52,7 +52,7 @@ describe('GitHubTabView', function() { onDidChangeWorkDirs={() => {}} getCurrentWorkDirs={() => []} openCreateDialog={() => {}} - openPublishDialog={() => {}} + openBoundPublishDialog={() => {}} openCloneDialog={() => {}} openGitTab={() => {}} From 2e2aec333b6906ed7782f6be29331248baad22a0 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Nov 2019 15:41:43 -0500 Subject: [PATCH 13/26] Rename props as well --- lib/views/github-tab-view.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/github-tab-view.js b/lib/views/github-tab-view.js index 8f03fde39d..3d7d7652be 100644 --- a/lib/views/github-tab-view.js +++ b/lib/views/github-tab-view.js @@ -70,7 +70,7 @@ export default class GitHubTabView extends React.Component { if (this.props.repository.isEmpty()) { return ( ); @@ -109,7 +109,7 @@ export default class GitHubTabView extends React.Component { } return ( - + ); } From ed618527185fbc5cccc463a88e71b7beb5f471e7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Nov 2019 15:41:48 -0500 Subject: [PATCH 14/26] Un-style that button --- styles/github-blank.less | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/styles/github-blank.less b/styles/github-blank.less index bf00794f00..5dea736189 100644 --- a/styles/github-blank.less +++ b/styles/github-blank.less @@ -55,6 +55,12 @@ &-tabLink { margin-left: 0.5em; + border-color: inherit; + background-color: inherit; + border-style: none; + border-width: 0; + padding: 0; + text-decoration: underline; .icon { padding-right: 0; From b108eb569ecf843646c86e849255d228416e8c19 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Nov 2019 16:03:11 -0500 Subject: [PATCH 15/26] Move the git tab navigation paragraph into body --- lib/views/github-blank-uninitialized.js | 18 +++++++++--------- styles/github-blank.less | 1 + 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/views/github-blank-uninitialized.js b/lib/views/github-blank-uninitialized.js index bb454bb8ae..e68cc520ef 100644 --- a/lib/views/github-blank-uninitialized.js +++ b/lib/views/github-blank-uninitialized.js @@ -11,20 +11,20 @@ export default function GitHubBlankUninitialized(props) {

This repository is not yet version controlled by git.

- Create a new GitHub repository, then track the existing content within this directory as a git repository - configured to push there. + Create a new GitHub repository, then track the existing content within this directory as a git repository + configured to push there. +

+

+ To initialize this directory as a git repository without publishing it to GitHub, visit the +

-
- To initialize this directory as a git repository without publishing it to GitHub, visit the - -
); } diff --git a/styles/github-blank.less b/styles/github-blank.less index 5dea736189..ff68b0b339 100644 --- a/styles/github-blank.less +++ b/styles/github-blank.less @@ -11,6 +11,7 @@ flex-grow: 1; display: flex; flex-direction: column; + justify-content: center; } &-footer { From c427e2a4b2c49eebdaf1956e6148fcf87338ae3d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Wed, 6 Nov 2019 16:19:00 -0500 Subject: [PATCH 16/26] Initialize and publish empty repositories --- lib/views/create-dialog.js | 25 +++++++++++++++-------- test/views/create-dialog.test.js | 34 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/lib/views/create-dialog.js b/lib/views/create-dialog.js index 1b474bfc32..9b1d446729 100644 --- a/lib/views/create-dialog.js +++ b/lib/views/create-dialog.js @@ -40,15 +40,22 @@ export async function publishRepository( {ownerID, name, visibility, protocol, sourceRemoteName}, {repository, relayEnvironment}, ) { - let defaultBranchName; - const branchSet = await repository.getBranches(); - const branchNames = new Set(branchSet.getNames()); - if (branchNames.has('master')) { + let defaultBranchName, wasEmpty; + if (repository.isEmpty()) { + wasEmpty = true; + await repository.init(); defaultBranchName = 'master'; } else { - const head = branchSet.getHeadBranch(); - if (head.isPresent()) { - defaultBranchName = head.getName(); + wasEmpty = false; + const branchSet = await repository.getBranches(); + const branchNames = new Set(branchSet.getNames()); + if (branchNames.has('master')) { + defaultBranchName = 'master'; + } else { + const head = branchSet.getHeadBranch(); + if (head.isPresent()) { + defaultBranchName = head.getName(); + } } } if (!defaultBranchName) { @@ -58,5 +65,7 @@ export async function publishRepository( const result = await createRepositoryMutation(relayEnvironment, {name, ownerID, visibility}); const sourceURL = result.createRepository.repository[protocol === 'ssh' ? 'sshUrl' : 'url']; const remote = await repository.addRemote(sourceRemoteName, sourceURL); - await repository.push(defaultBranchName, {remote, setUpstream: true}); + if (!wasEmpty) { + await repository.push(defaultBranchName, {remote, setUpstream: true}); + } } diff --git a/test/views/create-dialog.test.js b/test/views/create-dialog.test.js index ac877841bd..f48b72037e 100644 --- a/test/views/create-dialog.test.js +++ b/test/views/create-dialog.test.js @@ -292,6 +292,40 @@ describe('CreateDialog', function() { assert.isTrue(repository.push.calledWith('other-branch', {remote: CREATED_REMOTE, setUpstream: true})); }); + it('initializes an empty repository', async function() { + expectRelayQuery({ + name: createRepositoryQuery.operation.name, + variables: {input: {name: 'repo-name', ownerId: 'user0', visibility: 'PUBLIC'}}, + }, op => { + return relayResponseBuilder(op) + .createRepository(m => { + m.repository(r => { + r.sshUrl('ssh@github.com:user0/repo-name.git'); + r.url('https://github.com/user0/repo-name'); + }); + }) + .build(); + }).resolve(); + + const empty = await buildRepository(temp.mkdirSync()); + assert.isTrue(empty.isEmpty()); + + sinon.stub(empty, 'addRemote').resolves(CREATED_REMOTE); + sinon.stub(empty, 'push'); + + await publishRepository({ + ownerID: 'user0', + name: 'repo-name', + visibility: 'PUBLIC', + protocol: 'https', + sourceRemoteName: 'origin', + }, {repository: empty, relayEnvironment}); + + assert.isTrue(empty.isPresent()); + assert.isTrue(empty.addRemote.calledWith('origin', 'https://github.com/user0/repo-name')); + assert.isFalse(empty.push.called); + }); + it('fails if the source repository has no "master" or current branches', async function() { await repository.checkout('other-branch', {createNew: true}); await repository.checkout('HEAD^'); From 63cb0ea5326131981d815532c5b59ea25e400ca7 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 7 Nov 2019 14:48:14 -0500 Subject: [PATCH 17/26] Model to unify the refresh operation in the GitHub tab --- lib/models/refresher.js | 26 ++++++++++++++ test/models/refresher.test.js | 67 +++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 lib/models/refresher.js create mode 100644 test/models/refresher.test.js diff --git a/lib/models/refresher.js b/lib/models/refresher.js new file mode 100644 index 0000000000..864cbd798a --- /dev/null +++ b/lib/models/refresher.js @@ -0,0 +1,26 @@ +/** + * Uniformly trigger a refetch of all GraphQL query containers within a scoped hierarchy. + */ +export default class Refresher { + constructor() { + this.dispose(); + } + + setRetryCallback(key, retryCallback) { + this.retryByKey.set(key, retryCallback); + } + + trigger() { + for (const [, retryCallback] of this.retryByKey) { + retryCallback(); + } + } + + deregister(key) { + this.retryByKey.delete(key); + } + + dispose() { + this.retryByKey = new Map(); + } +} diff --git a/test/models/refresher.test.js b/test/models/refresher.test.js new file mode 100644 index 0000000000..5bec43d169 --- /dev/null +++ b/test/models/refresher.test.js @@ -0,0 +1,67 @@ +import Refresher from '../../lib/models/refresher'; + +describe('Refresher', function() { + let refresher; + + beforeEach(function() { + refresher = new Refresher(); + }); + + afterEach(function() { + refresher.dispose(); + }); + + it('calls the latest retry method registered per key instance when triggered', function() { + const keyOne = Symbol('one'); + const keyTwo = Symbol('two'); + + const one0 = sinon.spy(); + const one1 = sinon.spy(); + const two0 = sinon.spy(); + + refresher.setRetryCallback(keyOne, one0); + refresher.setRetryCallback(keyOne, one1); + refresher.setRetryCallback(keyTwo, two0); + + refresher.trigger(); + + assert.isFalse(one0.called); + assert.isTrue(one1.called); + assert.isTrue(two0.called); + }); + + it('deregisters a retry callback for a key', function() { + const keyOne = Symbol('one'); + const keyTwo = Symbol('two'); + + const one = sinon.spy(); + const two = sinon.spy(); + + refresher.setRetryCallback(keyOne, one); + refresher.setRetryCallback(keyTwo, two); + + refresher.deregister(keyOne); + + refresher.trigger(); + + assert.isFalse(one.called); + assert.isTrue(two.called); + }); + + it('deregisters all retry callbacks on dispose', function() { + const keyOne = Symbol('one'); + const keyTwo = Symbol('two'); + + const one = sinon.spy(); + const two = sinon.spy(); + + refresher.setRetryCallback(keyOne, one); + refresher.setRetryCallback(keyTwo, two); + + refresher.dispose(); + refresher.trigger(); + + assert.isFalse(one.called); + assert.isFalse(two.called); + }); +}); From ac0db9c8e17f33ab117fdb80518bab383ae16282 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Thu, 7 Nov 2019 14:48:59 -0500 Subject: [PATCH 18/26] Drive the Refresher in GitHubTabContainer --- lib/containers/github-tab-container.js | 31 ++++++- test/containers/github-tab-container.test.js | 88 +++++++++++++++----- 2 files changed, 98 insertions(+), 21 deletions(-) diff --git a/lib/containers/github-tab-container.js b/lib/containers/github-tab-container.js index 7d47c9043f..2ad9a07768 100644 --- a/lib/containers/github-tab-container.js +++ b/lib/containers/github-tab-container.js @@ -1,9 +1,11 @@ import React from 'react'; import PropTypes from 'prop-types'; import yubikiri from 'yubikiri'; +import {CompositeDisposable, Disposable} from 'event-kit'; import {GithubLoginModelPropType, RefHolderPropType} from '../prop-types'; import OperationStateObserver, {PUSH, PULL, FETCH} from '../models/operation-state-observer'; +import Refresher from '../models/refresher'; import GitHubTabController from '../controllers/github-tab-controller'; import ObserveModel from '../views/observe-model'; import RemoteSet from '../models/remote-set'; @@ -25,19 +27,41 @@ export default class GitHubTabContainer extends React.Component { openGitTab: PropTypes.func.isRequired, } - state = {}; + constructor(props) { + super(props); + + this.state = { + lastRepository: null, + remoteOperationObserver: new Disposable(), + refresher: new Refresher(), + observerSub: new Disposable(), + }; + } static getDerivedStateFromProps(props, state) { if (props.repository !== state.lastRepository) { + state.remoteOperationObserver.dispose(); + state.observerSub.dispose(); + + const remoteOperationObserver = new OperationStateObserver(props.repository, PUSH, PULL, FETCH); + const observerSub = remoteOperationObserver.onDidComplete(() => state.refresher.trigger()); + return { lastRepository: props.repository, - remoteOperationObserver: new OperationStateObserver(props.repository, PUSH, PULL, FETCH), + remoteOperationObserver, + observerSub, }; } return null; } + componentWillUnmount() { + this.state.observerSub.dispose(); + this.state.remoteOperationObserver.dispose(); + this.state.refresher.dispose(); + } + fetchRepositoryData = repository => { return yubikiri({ workingDirectory: repository.getWorkingDirectoryPath(), @@ -66,6 +90,7 @@ export default class GitHubTabContainer extends React.Component { return ( diff --git a/test/containers/github-tab-container.test.js b/test/containers/github-tab-container.test.js index 5ff5d9425d..4ccd4c11b2 100644 --- a/test/containers/github-tab-container.test.js +++ b/test/containers/github-tab-container.test.js @@ -1,19 +1,29 @@ import React from 'react'; -import {mount} from 'enzyme'; +import {mount, shallow} from 'enzyme'; -import {cloneRepository} from '../helpers'; +import {buildRepository, cloneRepository} from '../helpers'; import GitHubTabContainer from '../../lib/containers/github-tab-container'; +import GitHubTabController from '../../lib/controllers/github-tab-controller'; import Repository from '../../lib/models/repository'; import {InMemoryStrategy} from '../../lib/shared/keytar-strategy'; import GithubLoginModel from '../../lib/models/github-login-model'; import RefHolder from '../../lib/models/ref-holder'; describe('GitHubTabContainer', function() { - let atomEnv, repository; + let atomEnv, repository, defaultRepositoryData; - beforeEach(function() { + beforeEach(async function() { atomEnv = global.buildAtomEnvironment(); - repository = Repository.absent(); + repository = await buildRepository(await cloneRepository()); + + defaultRepositoryData = { + workingDirectory: repository.getWorkingDirectoryPath(), + allRemotes: await repository.getRemotes(), + branches: await repository.getBranches(), + selectedRemoteName: 'origin', + aheadCount: 0, + pushInProgress: false, + }; }); afterEach(function() { @@ -41,26 +51,66 @@ describe('GitHubTabContainer', function() { ); } - describe('operation state observer', function() { - it('creates an observer on the current repository', function() { - const wrapper = mount(buildApp()); + describe('refresher', function() { + let wrapper, retry; + + function stubRepository(repo) { + sinon.stub(repo.getOperationStates(), 'isFetchInProgress').returns(false); + sinon.stub(repo.getOperationStates(), 'isPushInProgress').returns(false); + sinon.stub(repo.getOperationStates(), 'isPullInProgress').returns(false); + } + + function simulateOperation(repo, name, middle = () => {}) { + const accessor = `is${name[0].toUpperCase()}${name.slice(1)}InProgress`; + const methodStub = repo.getOperationStates()[accessor]; + methodStub.returns(true); + repo.state.didUpdate(); + middle(); + methodStub.returns(false); + repo.state.didUpdate(); + } + + beforeEach(function() { + wrapper = shallow(buildApp()); + const childWrapper = wrapper.find('ObserveModel').renderProp('children')(defaultRepositoryData); + + retry = sinon.spy(); + const refresher = childWrapper.find(GitHubTabController).prop('refresher'); + refresher.setRetryCallback(Symbol('key'), retry); + + stubRepository(repository); + }); + + it('triggers a refresh when the current repository completes a fetch, push, or pull', function() { + assert.isFalse(retry.called); + + simulateOperation(repository, 'fetch', () => assert.isFalse(retry.called)); + assert.strictEqual(retry.callCount, 1); - const observer = wrapper.state('remoteOperationObserver'); - assert.strictEqual(observer.repository, repository); + simulateOperation(repository, 'push', () => assert.strictEqual(retry.callCount, 1)); + assert.strictEqual(retry.callCount, 2); - wrapper.setProps({}); - assert.strictEqual(wrapper.state('remoteOperationObserver'), observer); + simulateOperation(repository, 'pull', () => assert.strictEqual(retry.callCount, 2)); + assert.strictEqual(retry.callCount, 3); }); - it('creates a new observer when the repository changes', function() { - const repository0 = Repository.absent(); - const wrapper = mount(buildApp({repository: repository0})); + it('un-observes an old repository and observes a new one', async function() { + const other = await buildRepository(await cloneRepository()); + stubRepository(other); + wrapper.setProps({repository: other}); + + simulateOperation(repository, 'fetch'); + assert.isFalse(retry.called); + + simulateOperation(other, 'fetch'); + assert.isTrue(retry.called); + }); - const observer0 = wrapper.state('remoteOperationObserver'); + it('un-observes the repository when unmounting', function() { + wrapper.unmount(); - const repository1 = Repository.absent(); - wrapper.setProps({repository: repository1}); - assert.notStrictEqual(wrapper.state('remoteOperationObserver'), observer0); + simulateOperation(repository, 'fetch'); + assert.isFalse(retry.called); }); }); From be9284cb49c80696c5a61390a7bc6add2d9384d6 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 8 Nov 2019 10:14:57 -0500 Subject: [PATCH 19/26] Replace remoteOperationObserver with a Refresher --- .../current-pull-request-container.js | 15 +--- lib/containers/github-tab-container.js | 5 +- lib/containers/issueish-search-container.js | 10 +-- lib/containers/remote-container.js | 9 +-- lib/controllers/github-tab-controller.js | 6 +- .../issueish-searches-controller.js | 7 +- lib/controllers/remote-controller.js | 6 +- lib/prop-types.js | 6 ++ lib/views/github-tab-view.js | 6 +- .../current-pull-request-container.test.js | 19 ----- .../issueish-search-container.test.js | 80 +------------------ test/containers/remote-container.test.js | 4 +- .../controllers/github-tab-controller.test.js | 4 +- .../issueish-searches-controller.test.js | 2 - test/controllers/remote-controller.test.js | 17 ++-- test/views/github-tab-view.test.js | 8 +- 16 files changed, 41 insertions(+), 163 deletions(-) diff --git a/lib/containers/current-pull-request-container.js b/lib/containers/current-pull-request-container.js index 705f7ca965..13541bca76 100644 --- a/lib/containers/current-pull-request-container.js +++ b/lib/containers/current-pull-request-container.js @@ -4,9 +4,7 @@ import {QueryRenderer, graphql} from 'react-relay'; import {Disposable} from 'event-kit'; import {autobind, CHECK_SUITE_PAGE_SIZE, CHECK_RUN_PAGE_SIZE} from '../helpers'; -import { - RemotePropType, RemoteSetPropType, BranchSetPropType, OperationStateObserverPropType, EndpointPropType, -} from '../prop-types'; +import {RemotePropType, RemoteSetPropType, BranchSetPropType, EndpointPropType} from '../prop-types'; import IssueishListController, {BareIssueishListController} from '../controllers/issueish-list-controller'; import CreatePullRequestTile from '../views/create-pull-request-tile'; import RelayNetworkLayerManager from '../relay-network-layer-manager'; @@ -30,7 +28,6 @@ export default class CurrentPullRequestContainer extends React.Component { limit: PropTypes.number, // Repository model attributes - remoteOperationObserver: OperationStateObserverPropType.isRequired, remote: RemotePropType.isRequired, remotes: RemoteSetPropType.isRequired, branches: BranchSetPropType.isRequired, @@ -120,18 +117,10 @@ export default class CurrentPullRequestContainer extends React.Component { } renderEmptyResult() { - this.sub.dispose(); - this.sub = this.props.remoteOperationObserver.onDidComplete(() => this.forceUpdate()); - return ; } - renderQueryResult({error, props, retry}) { - if (retry) { - this.sub.dispose(); - this.sub = this.props.remoteOperationObserver.onDidComplete(retry); - } - + renderQueryResult({error, props}) { if (error) { return ( ); diff --git a/lib/containers/issueish-search-container.js b/lib/containers/issueish-search-container.js index 024eeb51d0..b8f45b5287 100644 --- a/lib/containers/issueish-search-container.js +++ b/lib/containers/issueish-search-container.js @@ -4,7 +4,7 @@ import {QueryRenderer, graphql} from 'react-relay'; import {Disposable} from 'event-kit'; import {autobind, CHECK_SUITE_PAGE_SIZE, CHECK_RUN_PAGE_SIZE} from '../helpers'; -import {SearchPropType, OperationStateObserverPropType, EndpointPropType} from '../prop-types'; +import {SearchPropType, EndpointPropType} from '../prop-types'; import IssueishListController, {BareIssueishListController} from '../controllers/issueish-list-controller'; import RelayNetworkLayerManager from '../relay-network-layer-manager'; @@ -17,7 +17,6 @@ export default class IssueishSearchContainer extends React.Component { // Search model limit: PropTypes.number, search: SearchPropType.isRequired, - remoteOperationObserver: OperationStateObserverPropType.isRequired, // Action methods onOpenIssueish: PropTypes.func.isRequired, @@ -89,12 +88,7 @@ export default class IssueishSearchContainer extends React.Component { ); } - renderQueryResult({error, props, retry}) { - if (retry) { - this.sub.dispose(); - this.sub = this.props.remoteOperationObserver.onDidComplete(retry); - } - + renderQueryResult({error, props}) { if (error) { return ( '100'})); assert.isFalse(filterFn({getHeadRepositoryID: () => '12'})); }); - - it('performs the query again when a remote operation completes', function() { - const wrapper = shallow(buildApp()); - - const props = queryBuilder(currentQuery).build(); - const retry = sinon.spy(); - wrapper.find(QueryRenderer).renderProp('render')({error: null, props, retry}); - - observer.trigger(); - assert.isTrue(retry.called); - }); }); diff --git a/test/containers/issueish-search-container.test.js b/test/containers/issueish-search-container.test.js index 5de719ba9d..c08d771643 100644 --- a/test/containers/issueish-search-container.test.js +++ b/test/containers/issueish-search-container.test.js @@ -88,18 +88,13 @@ describe('IssueishSearchContainer', function() { }); describe('when the query errors', function() { - // eslint-disable-next-line no-unused-vars - let stub; + // Consumes the failing Relay Query console error beforeEach(function() { - stub = sinon.stub(console, 'error'); - // eslint-disable-next-line no-console - console.error.withArgs( + sinon.stub(console, 'error').withArgs( 'Error encountered in subquery', sinon.match.defined.and(sinon.match.hasNested('errors[0].message', sinon.match('uh oh'))), - ).callsFake(() => {}); - // eslint-disable-next-line no-console - console.error.callThrough(); + ).callsFake(() => {}).callThrough(); }); it('passes an empty result list and an error prop to the controller', async function() { @@ -170,73 +165,4 @@ describe('IssueishSearchContainer', function() { assert.isTrue(controller.prop('results').some(node => node.number === 1)); assert.isTrue(controller.prop('results').some(node => node.number === 2)); }); - - it('performs the query again when a remote operation completes', async function() { - const {promise: promise0, resolve: resolve0, disable: disable0} = expectRelayQuery({ - name: 'issueishSearchContainerQuery', - variables: { - query: 'type:pr author:me', - first: 20, - checkSuiteCount: CHECK_SUITE_PAGE_SIZE, - checkSuiteCursor: null, - checkRunCount: CHECK_RUN_PAGE_SIZE, - checkRunCursor: null, - }, - }, op => { - return relayResponseBuilder(op) - .search(s => { - s.issueCount(1); - s.addNode(n => n.bePullRequest(pr => { - pr.number(1); - pr.commits(conn => conn.addNode()); - })); - }) - .build(); - }); - - const search = new Search('pull requests', 'type:pr author:me'); - const wrapper = mount(buildApp({search})); - resolve0(); - await promise0; - - assert.isTrue( - wrapper.update().find('BareIssueishListController').prop('results').some(node => node.number === 1), - ); - - disable0(); - const {promise: promise1, resolve: resolve1} = expectRelayQuery({ - name: 'issueishSearchContainerQuery', - variables: { - query: 'type:pr author:me', - first: 20, - checkSuiteCount: CHECK_SUITE_PAGE_SIZE, - checkSuiteCursor: null, - checkRunCount: CHECK_RUN_PAGE_SIZE, - checkRunCursor: null, - }, - }, op => { - return relayResponseBuilder(op) - .search(s => { - s.issueCount(1); - s.addNode(n => n.bePullRequest(pr => { - pr.number(2); - pr.commits(conn => conn.addNode()); - })); - }) - .build(); - }); - - resolve1(); - await promise1; - - assert.isTrue( - wrapper.update().find('BareIssueishListController').prop('results').some(node => node.number === 1), - ); - - observer.trigger(); - - await assert.async.isTrue( - wrapper.update().find('BareIssueishListController').prop('results').some(node => node.number === 2), - ); - }); }); diff --git a/test/containers/remote-container.test.js b/test/containers/remote-container.test.js index beda1e34f2..b6a1cb09eb 100644 --- a/test/containers/remote-container.test.js +++ b/test/containers/remote-container.test.js @@ -10,8 +10,8 @@ import RemoteSet from '../../lib/models/remote-set'; import Branch, {nullBranch} from '../../lib/models/branch'; import BranchSet from '../../lib/models/branch-set'; import GithubLoginModel from '../../lib/models/github-login-model'; -import {nullOperationStateObserver} from '../../lib/models/operation-state-observer'; import {getEndpoint} from '../../lib/models/endpoint'; +import Refresher from '../../lib/models/refresher'; import {InMemoryStrategy, INSUFFICIENT, UNAUTHENTICATED} from '../../lib/shared/keytar-strategy'; import remoteQuery from '../../lib/containers/__generated__/remoteContainerQuery.graphql'; @@ -39,7 +39,7 @@ describe('RemoteContainer', function() { loginModel={model} endpoint={getEndpoint('github.com')} - remoteOperationObserver={nullOperationStateObserver} + refresher={new Refresher()} workingDirectory={__dirname} notifications={atomEnv.notifications} workspace={atomEnv.workspace} diff --git a/test/controllers/github-tab-controller.test.js b/test/controllers/github-tab-controller.test.js index 20443a8f97..ad5e3c2fd2 100644 --- a/test/controllers/github-tab-controller.test.js +++ b/test/controllers/github-tab-controller.test.js @@ -10,7 +10,7 @@ import Remote from '../../lib/models/remote'; import {InMemoryStrategy} from '../../lib/shared/keytar-strategy'; import GithubLoginModel from '../../lib/models/github-login-model'; import RefHolder from '../../lib/models/ref-holder'; -import OperationStateObserver, {PUSH, PULL, FETCH} from '../../lib/models/operation-state-observer'; +import Refresher from '../../lib/models/refresher'; import {buildRepository, cloneRepository} from '../helpers'; @@ -32,7 +32,7 @@ describe('GitHubTabController', function() { return ( {}; - return ( {}} {...props} /> diff --git a/test/views/github-tab-view.test.js b/test/views/github-tab-view.test.js index d280c939ff..928cd4134d 100644 --- a/test/views/github-tab-view.test.js +++ b/test/views/github-tab-view.test.js @@ -11,7 +11,7 @@ import GitHubTabView from '../../lib/views/github-tab-view'; import {InMemoryStrategy} from '../../lib/shared/keytar-strategy'; import GithubLoginModel from '../../lib/models/github-login-model'; import RefHolder from '../../lib/models/ref-holder'; -import OperationStateObserver, {PUSH, PULL, FETCH} from '../../lib/models/operation-state-observer'; +import Refresher from '../../lib/models/refresher'; import {buildRepository, cloneRepository} from '../helpers'; @@ -32,7 +32,7 @@ describe('GitHubTabView', function() { return ( Date: Fri, 8 Nov 2019 10:16:00 -0500 Subject: [PATCH 20/26] Ignore coverage of blank views --- lib/views/github-blank-nolocal.js | 2 ++ lib/views/github-blank-noremote.js | 2 ++ lib/views/github-blank-uninitialized.js | 2 ++ 3 files changed, 6 insertions(+) diff --git a/lib/views/github-blank-nolocal.js b/lib/views/github-blank-nolocal.js index 3a8cc90100..e7dca4a2d3 100644 --- a/lib/views/github-blank-nolocal.js +++ b/lib/views/github-blank-nolocal.js @@ -1,3 +1,5 @@ +/* istanbul ignore file */ + import React from 'react'; import PropTypes from 'prop-types'; diff --git a/lib/views/github-blank-noremote.js b/lib/views/github-blank-noremote.js index f59615bc93..74a4ea6e68 100644 --- a/lib/views/github-blank-noremote.js +++ b/lib/views/github-blank-noremote.js @@ -1,3 +1,5 @@ +/* istanbul ignore file */ + import React from 'react'; import PropTypes from 'prop-types'; diff --git a/lib/views/github-blank-uninitialized.js b/lib/views/github-blank-uninitialized.js index e68cc520ef..cba9a5642a 100644 --- a/lib/views/github-blank-uninitialized.js +++ b/lib/views/github-blank-uninitialized.js @@ -1,3 +1,5 @@ +/* istanbul ignore file */ + import React from 'react'; import PropTypes from 'prop-types'; From 0781b23c501de48c28b6fe825975656697ed377c Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 8 Nov 2019 10:30:52 -0500 Subject: [PATCH 21/26] Report some metrics to gauge usefulness --- lib/github-package.js | 4 ++-- lib/views/create-dialog.js | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/github-package.js b/lib/github-package.js index 3d187a43a3..f2fb23f72d 100644 --- a/lib/github-package.js +++ b/lib/github-package.js @@ -466,10 +466,10 @@ export default class GithubPackage { } this.workdirCache.invalidate(); - this.project.addPath(projectPath); - await this.scheduleActiveContextUpdate(); + + reporterProxy.addEvent('clone-repository', {project: 'github'}); } getRepositoryForWorkdir(projectPath) { diff --git a/lib/views/create-dialog.js b/lib/views/create-dialog.js index 9b1d446729..59c005710b 100644 --- a/lib/views/create-dialog.js +++ b/lib/views/create-dialog.js @@ -5,6 +5,7 @@ import fs from 'fs-extra'; import CreateDialogContainer from '../containers/create-dialog-container'; import createRepositoryMutation from '../mutations/create-repository'; import {GithubLoginModelPropType} from '../prop-types'; +import {addEvent} from '../reporter-proxy'; export default class CreateDialog extends React.Component { static propTypes = { @@ -34,6 +35,7 @@ export async function createRepository( const result = await createRepositoryMutation(relayEnvironment, {name, ownerID, visibility}); const sourceURL = result.createRepository.repository[protocol === 'ssh' ? 'sshUrl' : 'url']; await clone(sourceURL, localPath, sourceRemoteName); + addEvent('create-github-repository', {package: 'github'}); } export async function publishRepository( @@ -65,7 +67,10 @@ export async function publishRepository( const result = await createRepositoryMutation(relayEnvironment, {name, ownerID, visibility}); const sourceURL = result.createRepository.repository[protocol === 'ssh' ? 'sshUrl' : 'url']; const remote = await repository.addRemote(sourceRemoteName, sourceURL); - if (!wasEmpty) { + if (wasEmpty) { + addEvent('publish-github-repository', {package: 'github'}); + } else { await repository.push(defaultBranchName, {remote, setUpstream: true}); + addEvent('init-publish-github-repository', {package: 'github'}); } } From 46453be117fe6117dbf2b26cc3f8fab56709c4fc Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 8 Nov 2019 11:13:06 -0500 Subject: [PATCH 22/26] Diagnose tests failing only in CI --- lib/controllers/reviews-controller.js | 12 ++++++++++++ test/controllers/reviews-controller.test.js | 6 +++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/controllers/reviews-controller.js b/lib/controllers/reviews-controller.js index a6692db2ca..9b1c8098da 100644 --- a/lib/controllers/reviews-controller.js +++ b/lib/controllers/reviews-controller.js @@ -292,15 +292,19 @@ export class BareReviewsController extends React.Component { addSingleComment = async (commentBody, threadID, replyToID, commentPath, position, callbacks = {}) => { let pendingReviewID = null; try { + console.log(`setting state: postingToThreadID=${threadID}`); this.setState({postingToThreadID: threadID}); + console.log('calling addReviewMutation'); const reviewResult = await addReviewMutation(this.props.relay.environment, { pullRequestID: this.props.pullRequest.id, viewerID: this.props.viewer.id, }); const reviewID = reviewResult.addPullRequestReview.reviewEdge.node.id; pendingReviewID = reviewID; + console.log(`addReviewMutation returned: reviewID=${reviewID} pendingReviewID=${pendingReviewID}`); + console.log('calling addReviewCommentMutation'); const commentPromise = addReviewCommentMutation(this.props.relay.environment, { body: commentBody, inReplyTo: replyToID, @@ -310,28 +314,36 @@ export class BareReviewsController extends React.Component { path: commentPath, position, }); + console.log('firing didSubmitComment callback'); if (callbacks.didSubmitComment) { callbacks.didSubmitComment(); } + console.log('awaiting comment promise'); await commentPromise; + console.log('comment promise resolved successfully'); pendingReviewID = null; + console.log('calling submitReviewMutation'); await submitReviewMutation(this.props.relay.environment, { event: 'COMMENT', reviewID, }); + console.log('submitReviewMutation resolved successfully'); addEvent('add-single-comment', {package: 'github'}); } catch (error) { + console.log(`Caught an error: ${error.stack}`); if (callbacks.didFailComment) { callbacks.didFailComment(); } if (pendingReviewID !== null) { try { + console.log('Calling deleteReviewMutation'); await deleteReviewMutation(this.props.relay.environment, { reviewID: pendingReviewID, pullRequestID: this.props.pullRequest.id, }); + console.log('deleteReviewMutation completed'); } catch (e) { /* istanbul ignore else */ if (error.errors && e.errors) { diff --git a/test/controllers/reviews-controller.test.js b/test/controllers/reviews-controller.test.js index 37b62eb510..817f51a805 100644 --- a/test/controllers/reviews-controller.test.js +++ b/test/controllers/reviews-controller.test.js @@ -291,7 +291,7 @@ describe('ReviewsController', function() { }); describe('adding a single comment', function() { - it('creates a review, attaches the comment, and submits it', async function() { + it.only('creates a review, attaches the comment, and submits it', async function() { expectRelayQuery({ name: addPrReviewMutation.operation.name, variables: { @@ -347,7 +347,7 @@ describe('ReviewsController', function() { assert.isFalse(didFailComment.called); }); - it('creates a notification when the review cannot be created', async function() { + it.only('creates a notification when the review cannot be created', async function() { const reportRelayError = sinon.spy(); expectRelayQuery({ @@ -378,7 +378,7 @@ describe('ReviewsController', function() { assert.isTrue(didFailComment.called); }); - it('creates a notification and deletes the review when the comment cannot be added', async function() { + it.only('creates a notification and deletes the review when the comment cannot be added', async function() { const reportRelayError = sinon.spy(); expectRelayQuery({ From 8d1d4a1a413a7ea88703fad936193b8b79a2699d Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 8 Nov 2019 11:23:10 -0500 Subject: [PATCH 23/26] Run all ReviewsController tests --- test/controllers/reviews-controller.test.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/controllers/reviews-controller.test.js b/test/controllers/reviews-controller.test.js index 817f51a805..b649b2e8aa 100644 --- a/test/controllers/reviews-controller.test.js +++ b/test/controllers/reviews-controller.test.js @@ -31,7 +31,7 @@ import unresolveThreadMutation from '../../lib/mutations/__generated__/unresolve import updateReviewCommentMutation from '../../lib/mutations/__generated__/updatePrReviewCommentMutation.graphql'; import updatePrReviewMutation from '../../lib/mutations/__generated__/updatePrReviewSummaryMutation.graphql'; -describe('ReviewsController', function() { +describe.only('ReviewsController', function() { let atomEnv, relayEnv, localRepository, noop, clock; beforeEach(async function() { @@ -291,7 +291,7 @@ describe('ReviewsController', function() { }); describe('adding a single comment', function() { - it.only('creates a review, attaches the comment, and submits it', async function() { + it('creates a review, attaches the comment, and submits it', async function() { expectRelayQuery({ name: addPrReviewMutation.operation.name, variables: { @@ -347,7 +347,7 @@ describe('ReviewsController', function() { assert.isFalse(didFailComment.called); }); - it.only('creates a notification when the review cannot be created', async function() { + it('creates a notification when the review cannot be created', async function() { const reportRelayError = sinon.spy(); expectRelayQuery({ @@ -378,7 +378,7 @@ describe('ReviewsController', function() { assert.isTrue(didFailComment.called); }); - it.only('creates a notification and deletes the review when the comment cannot be added', async function() { + it('creates a notification and deletes the review when the comment cannot be added', async function() { const reportRelayError = sinon.spy(); expectRelayQuery({ From 9d7188f1c273d76f360e60846e46aef391f83c31 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 8 Nov 2019 11:28:25 -0500 Subject: [PATCH 24/26] Grrr it only fails when the full suite runs? --- test/controllers/reviews-controller.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/controllers/reviews-controller.test.js b/test/controllers/reviews-controller.test.js index b649b2e8aa..37b62eb510 100644 --- a/test/controllers/reviews-controller.test.js +++ b/test/controllers/reviews-controller.test.js @@ -31,7 +31,7 @@ import unresolveThreadMutation from '../../lib/mutations/__generated__/unresolve import updateReviewCommentMutation from '../../lib/mutations/__generated__/updatePrReviewCommentMutation.graphql'; import updatePrReviewMutation from '../../lib/mutations/__generated__/updatePrReviewSummaryMutation.graphql'; -describe.only('ReviewsController', function() { +describe('ReviewsController', function() { let atomEnv, relayEnv, localRepository, noop, clock; beforeEach(async function() { From 4693a13a03936d92be16652500c82f50833775d5 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 8 Nov 2019 13:32:27 -0500 Subject: [PATCH 25/26] Clear all global Relay state along with expectations --- lib/relay-network-layer-manager.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/relay-network-layer-manager.js b/lib/relay-network-layer-manager.js index f97ddd859b..0d359936b1 100644 --- a/lib/relay-network-layer-manager.js +++ b/lib/relay-network-layer-manager.js @@ -46,6 +46,10 @@ export function expectRelayQuery(operationPattern, response) { export function clearRelayExpectations() { responsesByQuery.clear(); + relayEnvironmentPerURL.clear(); + tokenPerURL.clear(); + fetchPerURL.clear(); + responsesByQuery.clear(); } function createFetchQuery(url) { From 86a8dd29bea11055b65b72cbe318debc1869f486 Mon Sep 17 00:00:00 2001 From: Ash Wilson Date: Fri, 8 Nov 2019 13:51:14 -0500 Subject: [PATCH 26/26] Tidy up console statements --- lib/controllers/reviews-controller.js | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/lib/controllers/reviews-controller.js b/lib/controllers/reviews-controller.js index 9b1c8098da..a6692db2ca 100644 --- a/lib/controllers/reviews-controller.js +++ b/lib/controllers/reviews-controller.js @@ -292,19 +292,15 @@ export class BareReviewsController extends React.Component { addSingleComment = async (commentBody, threadID, replyToID, commentPath, position, callbacks = {}) => { let pendingReviewID = null; try { - console.log(`setting state: postingToThreadID=${threadID}`); this.setState({postingToThreadID: threadID}); - console.log('calling addReviewMutation'); const reviewResult = await addReviewMutation(this.props.relay.environment, { pullRequestID: this.props.pullRequest.id, viewerID: this.props.viewer.id, }); const reviewID = reviewResult.addPullRequestReview.reviewEdge.node.id; pendingReviewID = reviewID; - console.log(`addReviewMutation returned: reviewID=${reviewID} pendingReviewID=${pendingReviewID}`); - console.log('calling addReviewCommentMutation'); const commentPromise = addReviewCommentMutation(this.props.relay.environment, { body: commentBody, inReplyTo: replyToID, @@ -314,36 +310,28 @@ export class BareReviewsController extends React.Component { path: commentPath, position, }); - console.log('firing didSubmitComment callback'); if (callbacks.didSubmitComment) { callbacks.didSubmitComment(); } - console.log('awaiting comment promise'); await commentPromise; - console.log('comment promise resolved successfully'); pendingReviewID = null; - console.log('calling submitReviewMutation'); await submitReviewMutation(this.props.relay.environment, { event: 'COMMENT', reviewID, }); - console.log('submitReviewMutation resolved successfully'); addEvent('add-single-comment', {package: 'github'}); } catch (error) { - console.log(`Caught an error: ${error.stack}`); if (callbacks.didFailComment) { callbacks.didFailComment(); } if (pendingReviewID !== null) { try { - console.log('Calling deleteReviewMutation'); await deleteReviewMutation(this.props.relay.environment, { reviewID: pendingReviewID, pullRequestID: this.props.pullRequest.id, }); - console.log('deleteReviewMutation completed'); } catch (e) { /* istanbul ignore else */ if (error.errors && e.errors) {