Skip to content

Commit

Permalink
Enable push/pull buttons for empty repos (#1365)
Browse files Browse the repository at this point in the history
* Enable push/pull buttons for empty repos

* Update Toolbar tests

* Update tests again
  • Loading branch information
gjmooney authored Oct 25, 2024
1 parent 771e739 commit da4710f
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 59 deletions.
156 changes: 102 additions & 54 deletions src/__tests__/test-components/Toolbar.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
import { nullTranslator } from '@jupyterlab/translation';
import '@testing-library/jest-dom';
import { render, screen } from '@testing-library/react';
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import 'jest';
import * as React from 'react';
import { IToolbarProps, Toolbar } from '../../components/Toolbar';
import * as git from '../../git';
import { GitExtension } from '../../model';
import { badgeClass } from '../../style/Toolbar';
import { DEFAULT_REPOSITORY_PATH, mockedRequestAPI } from '../utils';
import { CommandIDs } from '../../tokens';
import {
DEFAULT_REPOSITORY_PATH,
defaultMockedResponses,
mockedRequestAPI
} from '../utils';

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

const REMOTES = [
{
name: 'test',
url: 'https://test.com'
},
{
name: 'origin',
url: 'https://origin.com'
}
];

async function createModel() {
const model = new GitExtension();
model.pathRepository = DEFAULT_REPOSITORY_PATH;
Expand Down Expand Up @@ -65,7 +80,18 @@ describe('Toolbar', () => {
jest.restoreAllMocks();

const mock = git as jest.Mocked<typeof git>;
mock.requestAPI.mockImplementation(mockedRequestAPI() as any);
mock.requestAPI.mockImplementation(
mockedRequestAPI({
responses: {
...defaultMockedResponses,
'remote/show': {
body: () => {
return { code: 0, remotes: REMOTES };
}
}
}
}) as any
);

model = await createModel();
});
Expand All @@ -79,12 +105,14 @@ describe('Toolbar', () => {
});

describe('render', () => {
it('should display a button to pull the latest changes', () => {
it('should display a button to pull the latest changes', async () => {
render(<Toolbar {...createProps()} />);

expect(
screen.getAllByRole('button', { name: 'Pull latest changes' })
).toBeDefined();
await waitFor(() => {
expect(
screen.getAllByRole('button', { name: 'Pull latest changes' })
).toBeDefined();
});

expect(
screen
Expand All @@ -93,22 +121,26 @@ describe('Toolbar', () => {
).toHaveClass('MuiBadge-invisible');
});

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

expect(
screen
.getByRole('button', { name: /^Pull latest changes/ })
.parentElement?.querySelector(`.${badgeClass} > .MuiBadge-badge`)
).not.toHaveClass('MuiBadge-invisible');
await waitFor(() => {
expect(
screen
.getByRole('button', { name: /^Pull latest changes/ })
.parentElement?.querySelector(`.${badgeClass} > .MuiBadge-badge`)
).not.toHaveClass('MuiBadge-invisible');
});
});

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

expect(
screen.getAllByRole('button', { name: 'Push committed changes' })
).toBeDefined();
await waitFor(() => {
expect(
screen.getAllByRole('button', { name: 'Push committed changes' })
).toBeDefined();
});

expect(
screen
Expand All @@ -117,14 +149,16 @@ describe('Toolbar', () => {
).toHaveClass('MuiBadge-invisible');
});

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

expect(
screen
.getByRole('button', { name: /^Push committed changes/ })
.parentElement?.querySelector(`.${badgeClass} > .MuiBadge-badge`)
).not.toHaveClass('MuiBadge-invisible');
await waitFor(() => {
expect(
screen
.getByRole('button', { name: /^Push committed changes/ })
.parentElement?.querySelector(`.${badgeClass} > .MuiBadge-badge`)
).not.toHaveClass('MuiBadge-invisible');
});
});

it('should display a button to refresh the current repository', () => {
Expand Down Expand Up @@ -177,7 +211,7 @@ describe('Toolbar', () => {
});
});

describe('pull changes', () => {
describe('push/pull changes with remote', () => {
it('should pull changes when the button to pull the latest changes is clicked', async () => {
const mockedExecute = jest.fn();
render(
Expand All @@ -190,6 +224,12 @@ describe('Toolbar', () => {
/>
);

await waitFor(() => {
expect(
screen.getByRole('button', { name: 'Pull latest changes' })
).toBeDefined();
});

await userEvent.click(
screen.getByRole('button', { name: 'Pull latest changes' })
);
Expand All @@ -198,40 +238,55 @@ describe('Toolbar', () => {
expect(mockedExecute).toHaveBeenCalledWith(CommandIDs.gitPull);
});

it('should not pull changes when the pull button is clicked but there is no remote branch', async () => {
it('should push changes when the button to push the latest changes is clicked', async () => {
const mockedExecute = jest.fn();
render(
<Toolbar
{...createProps({
branches: [
{
is_current_branch: true,
is_remote_branch: false,
name: 'main',
upstream: '',
top_commit: '',
tag: ''
}
],
commands: {
execute: mockedExecute
} as any
})}
/>
);

await waitFor(() => {
expect(
screen.getByRole('button', { name: 'Push committed changes' })
).toBeDefined();
});

await userEvent.click(
screen.getAllByRole('button', {
name: 'No remote repository defined'
})[0]
screen.getByRole('button', { name: 'Push committed changes' })
);

expect(mockedExecute).toHaveBeenCalledTimes(0);
expect(mockedExecute).toHaveBeenCalledTimes(1);
expect(mockedExecute).toHaveBeenCalledWith(CommandIDs.gitPush);
});
});

describe('push changes', () => {
it('should push changes when the button to push the latest changes is clicked', async () => {
describe('push/pull changes without remote', () => {
beforeEach(async () => {
jest.restoreAllMocks();

const mock = git as jest.Mocked<typeof git>;
mock.requestAPI.mockImplementation(
mockedRequestAPI({
responses: {
...defaultMockedResponses,
'remote/show': {
body: () => {
return { code: -1, remotes: [] };
}
}
}
}) as any
);

model = await createModel();
});

it('should not pull changes when the pull button is clicked but there is no remote branch', async () => {
const mockedExecute = jest.fn();
render(
<Toolbar
Expand All @@ -242,28 +297,21 @@ describe('Toolbar', () => {
})}
/>
);

await userEvent.click(
screen.getByRole('button', { name: 'Push committed changes' })
screen.getAllByRole('button', {
name: 'No remote repository defined'
})[0]
);
expect(mockedExecute).toHaveBeenCalledTimes(1);
expect(mockedExecute).toHaveBeenCalledWith(CommandIDs.gitPush);

expect(mockedExecute).toHaveBeenCalledTimes(0);
});

it('should not push changes when the push button is clicked but there is no remote branch', async () => {
const mockedExecute = jest.fn();
render(
<Toolbar
{...createProps({
branches: [
{
is_current_branch: true,
is_remote_branch: false,
name: 'main',
upstream: '',
top_commit: '',
tag: ''
}
],
commands: {
execute: mockedExecute
} as any
Expand Down
26 changes: 21 additions & 5 deletions src/components/Toolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,11 @@ export interface IToolbarState {
* Boolean indicating whether a refresh is currently in progress.
*/
refreshInProgress: boolean;

/**
* Boolean indicating whether a remote exists.
*/
hasRemote: boolean;
}

/**
Expand All @@ -132,10 +137,24 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> {
this.state = {
branchMenu: false,
tab: 0,
refreshInProgress: false
refreshInProgress: false,
hasRemote: false
};
}

/**
* Check whether or not the repo has any remotes
*/
async componentDidMount(): Promise<void> {
try {
const remotes = await this.props.model.getRemotes();
const hasRemote = remotes.length > 0 ? true : false;
this.setState({ hasRemote });
} catch (err) {
console.error(err);
}
}

/**
* Renders the component.
*
Expand All @@ -160,10 +179,7 @@ export class Toolbar extends React.Component<IToolbarProps, IToolbarState> {
const activeBranch = this.props.branches.filter(
branch => branch.is_current_branch
);
// FIXME whether the repository as a remote or not should be done through a call to `git remote`
const hasRemote = this.props.branches.some(
branch => branch.is_remote_branch
);
const hasRemote = this.state.hasRemote;
const hasUpstream = activeBranch[0]?.upstream !== null;

return (
Expand Down

0 comments on commit da4710f

Please sign in to comment.