Skip to content

Commit da4710f

Browse files
authored
Enable push/pull buttons for empty repos (#1365)
* Enable push/pull buttons for empty repos * Update Toolbar tests * Update tests again
1 parent 771e739 commit da4710f

File tree

2 files changed

+123
-59
lines changed

2 files changed

+123
-59
lines changed

src/__tests__/test-components/Toolbar.spec.tsx

Lines changed: 102 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,33 @@
11
import { nullTranslator } from '@jupyterlab/translation';
22
import '@testing-library/jest-dom';
3-
import { render, screen } from '@testing-library/react';
3+
import { render, screen, waitFor } from '@testing-library/react';
44
import userEvent from '@testing-library/user-event';
55
import 'jest';
66
import * as React from 'react';
77
import { IToolbarProps, Toolbar } from '../../components/Toolbar';
88
import * as git from '../../git';
99
import { GitExtension } from '../../model';
1010
import { badgeClass } from '../../style/Toolbar';
11-
import { DEFAULT_REPOSITORY_PATH, mockedRequestAPI } from '../utils';
1211
import { CommandIDs } from '../../tokens';
12+
import {
13+
DEFAULT_REPOSITORY_PATH,
14+
defaultMockedResponses,
15+
mockedRequestAPI
16+
} from '../utils';
1317

1418
jest.mock('../../git');
1519

20+
const REMOTES = [
21+
{
22+
name: 'test',
23+
url: 'https://test.com'
24+
},
25+
{
26+
name: 'origin',
27+
url: 'https://origin.com'
28+
}
29+
];
30+
1631
async function createModel() {
1732
const model = new GitExtension();
1833
model.pathRepository = DEFAULT_REPOSITORY_PATH;
@@ -65,7 +80,18 @@ describe('Toolbar', () => {
6580
jest.restoreAllMocks();
6681

6782
const mock = git as jest.Mocked<typeof git>;
68-
mock.requestAPI.mockImplementation(mockedRequestAPI() as any);
83+
mock.requestAPI.mockImplementation(
84+
mockedRequestAPI({
85+
responses: {
86+
...defaultMockedResponses,
87+
'remote/show': {
88+
body: () => {
89+
return { code: 0, remotes: REMOTES };
90+
}
91+
}
92+
}
93+
}) as any
94+
);
6995

7096
model = await createModel();
7197
});
@@ -79,12 +105,14 @@ describe('Toolbar', () => {
79105
});
80106

81107
describe('render', () => {
82-
it('should display a button to pull the latest changes', () => {
108+
it('should display a button to pull the latest changes', async () => {
83109
render(<Toolbar {...createProps()} />);
84110

85-
expect(
86-
screen.getAllByRole('button', { name: 'Pull latest changes' })
87-
).toBeDefined();
111+
await waitFor(() => {
112+
expect(
113+
screen.getAllByRole('button', { name: 'Pull latest changes' })
114+
).toBeDefined();
115+
});
88116

89117
expect(
90118
screen
@@ -93,22 +121,26 @@ describe('Toolbar', () => {
93121
).toHaveClass('MuiBadge-invisible');
94122
});
95123

96-
it('should display a badge on pull icon if behind', () => {
124+
it('should display a badge on pull icon if behind', async () => {
97125
render(<Toolbar {...createProps({ nCommitsBehind: 1 })} />);
98126

99-
expect(
100-
screen
101-
.getByRole('button', { name: /^Pull latest changes/ })
102-
.parentElement?.querySelector(`.${badgeClass} > .MuiBadge-badge`)
103-
).not.toHaveClass('MuiBadge-invisible');
127+
await waitFor(() => {
128+
expect(
129+
screen
130+
.getByRole('button', { name: /^Pull latest changes/ })
131+
.parentElement?.querySelector(`.${badgeClass} > .MuiBadge-badge`)
132+
).not.toHaveClass('MuiBadge-invisible');
133+
});
104134
});
105135

106-
it('should display a button to push the latest changes', () => {
136+
it('should display a button to push the latest changes', async () => {
107137
render(<Toolbar {...createProps()} />);
108138

109-
expect(
110-
screen.getAllByRole('button', { name: 'Push committed changes' })
111-
).toBeDefined();
139+
await waitFor(() => {
140+
expect(
141+
screen.getAllByRole('button', { name: 'Push committed changes' })
142+
).toBeDefined();
143+
});
112144

113145
expect(
114146
screen
@@ -117,14 +149,16 @@ describe('Toolbar', () => {
117149
).toHaveClass('MuiBadge-invisible');
118150
});
119151

120-
it('should display a badge on push icon if behind', () => {
152+
it('should display a badge on push icon if behind', async () => {
121153
render(<Toolbar {...createProps({ nCommitsAhead: 1 })} />);
122154

123-
expect(
124-
screen
125-
.getByRole('button', { name: /^Push committed changes/ })
126-
.parentElement?.querySelector(`.${badgeClass} > .MuiBadge-badge`)
127-
).not.toHaveClass('MuiBadge-invisible');
155+
await waitFor(() => {
156+
expect(
157+
screen
158+
.getByRole('button', { name: /^Push committed changes/ })
159+
.parentElement?.querySelector(`.${badgeClass} > .MuiBadge-badge`)
160+
).not.toHaveClass('MuiBadge-invisible');
161+
});
128162
});
129163

130164
it('should display a button to refresh the current repository', () => {
@@ -177,7 +211,7 @@ describe('Toolbar', () => {
177211
});
178212
});
179213

180-
describe('pull changes', () => {
214+
describe('push/pull changes with remote', () => {
181215
it('should pull changes when the button to pull the latest changes is clicked', async () => {
182216
const mockedExecute = jest.fn();
183217
render(
@@ -190,6 +224,12 @@ describe('Toolbar', () => {
190224
/>
191225
);
192226

227+
await waitFor(() => {
228+
expect(
229+
screen.getByRole('button', { name: 'Pull latest changes' })
230+
).toBeDefined();
231+
});
232+
193233
await userEvent.click(
194234
screen.getByRole('button', { name: 'Pull latest changes' })
195235
);
@@ -198,40 +238,55 @@ describe('Toolbar', () => {
198238
expect(mockedExecute).toHaveBeenCalledWith(CommandIDs.gitPull);
199239
});
200240

201-
it('should not pull changes when the pull button is clicked but there is no remote branch', async () => {
241+
it('should push changes when the button to push the latest changes is clicked', async () => {
202242
const mockedExecute = jest.fn();
203243
render(
204244
<Toolbar
205245
{...createProps({
206-
branches: [
207-
{
208-
is_current_branch: true,
209-
is_remote_branch: false,
210-
name: 'main',
211-
upstream: '',
212-
top_commit: '',
213-
tag: ''
214-
}
215-
],
216246
commands: {
217247
execute: mockedExecute
218248
} as any
219249
})}
220250
/>
221251
);
222252

253+
await waitFor(() => {
254+
expect(
255+
screen.getByRole('button', { name: 'Push committed changes' })
256+
).toBeDefined();
257+
});
258+
223259
await userEvent.click(
224-
screen.getAllByRole('button', {
225-
name: 'No remote repository defined'
226-
})[0]
260+
screen.getByRole('button', { name: 'Push committed changes' })
227261
);
228262

229-
expect(mockedExecute).toHaveBeenCalledTimes(0);
263+
expect(mockedExecute).toHaveBeenCalledTimes(1);
264+
expect(mockedExecute).toHaveBeenCalledWith(CommandIDs.gitPush);
230265
});
231266
});
232267

233-
describe('push changes', () => {
234-
it('should push changes when the button to push the latest changes is clicked', async () => {
268+
describe('push/pull changes without remote', () => {
269+
beforeEach(async () => {
270+
jest.restoreAllMocks();
271+
272+
const mock = git as jest.Mocked<typeof git>;
273+
mock.requestAPI.mockImplementation(
274+
mockedRequestAPI({
275+
responses: {
276+
...defaultMockedResponses,
277+
'remote/show': {
278+
body: () => {
279+
return { code: -1, remotes: [] };
280+
}
281+
}
282+
}
283+
}) as any
284+
);
285+
286+
model = await createModel();
287+
});
288+
289+
it('should not pull changes when the pull button is clicked but there is no remote branch', async () => {
235290
const mockedExecute = jest.fn();
236291
render(
237292
<Toolbar
@@ -242,28 +297,21 @@ describe('Toolbar', () => {
242297
})}
243298
/>
244299
);
300+
245301
await userEvent.click(
246-
screen.getByRole('button', { name: 'Push committed changes' })
302+
screen.getAllByRole('button', {
303+
name: 'No remote repository defined'
304+
})[0]
247305
);
248-
expect(mockedExecute).toHaveBeenCalledTimes(1);
249-
expect(mockedExecute).toHaveBeenCalledWith(CommandIDs.gitPush);
306+
307+
expect(mockedExecute).toHaveBeenCalledTimes(0);
250308
});
251309

252310
it('should not push changes when the push button is clicked but there is no remote branch', async () => {
253311
const mockedExecute = jest.fn();
254312
render(
255313
<Toolbar
256314
{...createProps({
257-
branches: [
258-
{
259-
is_current_branch: true,
260-
is_remote_branch: false,
261-
name: 'main',
262-
upstream: '',
263-
top_commit: '',
264-
tag: ''
265-
}
266-
],
267315
commands: {
268316
execute: mockedExecute
269317
} as any

src/components/Toolbar.tsx

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,11 @@ export interface IToolbarState {
115115
* Boolean indicating whether a refresh is currently in progress.
116116
*/
117117
refreshInProgress: boolean;
118+
119+
/**
120+
* Boolean indicating whether a remote exists.
121+
*/
122+
hasRemote: boolean;
118123
}
119124

120125
/**
@@ -132,10 +137,24 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> {
132137
this.state = {
133138
branchMenu: false,
134139
tab: 0,
135-
refreshInProgress: false
140+
refreshInProgress: false,
141+
hasRemote: false
136142
};
137143
}
138144

145+
/**
146+
* Check whether or not the repo has any remotes
147+
*/
148+
async componentDidMount(): Promise<void> {
149+
try {
150+
const remotes = await this.props.model.getRemotes();
151+
const hasRemote = remotes.length > 0 ? true : false;
152+
this.setState({ hasRemote });
153+
} catch (err) {
154+
console.error(err);
155+
}
156+
}
157+
139158
/**
140159
* Renders the component.
141160
*
@@ -160,10 +179,7 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> {
160179
const activeBranch = this.props.branches.filter(
161180
branch => branch.is_current_branch
162181
);
163-
// FIXME whether the repository as a remote or not should be done through a call to `git remote`
164-
const hasRemote = this.props.branches.some(
165-
branch => branch.is_remote_branch
166-
);
182+
const hasRemote = this.state.hasRemote;
167183
const hasUpstream = activeBranch[0]?.upstream !== null;
168184

169185
return (

0 commit comments

Comments
 (0)