From da4710f1518895e885cd71ac74caa12f3a76b530 Mon Sep 17 00:00:00 2001 From: Greg Mooney Date: Fri, 25 Oct 2024 17:16:23 +0200 Subject: [PATCH] Enable push/pull buttons for empty repos (#1365) * Enable push/pull buttons for empty repos * Update Toolbar tests * Update tests again --- .../test-components/Toolbar.spec.tsx | 156 ++++++++++++------ src/components/Toolbar.tsx | 26 ++- 2 files changed, 123 insertions(+), 59 deletions(-) diff --git a/src/__tests__/test-components/Toolbar.spec.tsx b/src/__tests__/test-components/Toolbar.spec.tsx index 9bd0b85c7..7ab92af3c 100644 --- a/src/__tests__/test-components/Toolbar.spec.tsx +++ b/src/__tests__/test-components/Toolbar.spec.tsx @@ -1,6 +1,6 @@ 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'; @@ -8,11 +8,26 @@ 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; @@ -65,7 +80,18 @@ describe('Toolbar', () => { jest.restoreAllMocks(); const mock = git as jest.Mocked; - 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(); }); @@ -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(); - expect( - screen.getAllByRole('button', { name: 'Pull latest changes' }) - ).toBeDefined(); + await waitFor(() => { + expect( + screen.getAllByRole('button', { name: 'Pull latest changes' }) + ).toBeDefined(); + }); expect( screen @@ -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(); - 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(); - expect( - screen.getAllByRole('button', { name: 'Push committed changes' }) - ).toBeDefined(); + await waitFor(() => { + expect( + screen.getAllByRole('button', { name: 'Push committed changes' }) + ).toBeDefined(); + }); expect( screen @@ -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(); - 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', () => { @@ -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( @@ -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' }) ); @@ -198,21 +238,11 @@ 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( { /> ); + 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; + 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( { })} /> ); + 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 () => { @@ -254,16 +312,6 @@ describe('Toolbar', () => { render( { this.state = { branchMenu: false, tab: 0, - refreshInProgress: false + refreshInProgress: false, + hasRemote: false }; } + /** + * Check whether or not the repo has any remotes + */ + async componentDidMount(): Promise { + 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. * @@ -160,10 +179,7 @@ export class Toolbar extends React.Component { 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 (