@@ -18,8 +18,8 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import * as React from 'react'; | |||
import { Button } from '../../../components/controls/buttons'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import { Button } from '../../../components/controls/buttons'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import CreateWebhookForm from './CreateWebhookForm'; | |||
@@ -33,7 +33,7 @@ interface State { | |||
openCreate: boolean; | |||
} | |||
const WEBHOOKS_LIMIT = 10; | |||
export const WEBHOOKS_LIMIT = 10; | |||
export default class PageActions extends React.PureComponent<Props, State> { | |||
mounted = false; | |||
@@ -61,14 +61,16 @@ export default class PageActions extends React.PureComponent<Props, State> { | |||
if (this.props.webhooksCount >= WEBHOOKS_LIMIT) { | |||
return ( | |||
<Tooltip overlay={translateWithParameters('webhooks.maximum_reached', WEBHOOKS_LIMIT)}> | |||
<Button className="js-webhook-create disabled">{translate('create')}</Button> | |||
<Button className="it__webhook-create" disabled> | |||
{translate('create')} | |||
</Button> | |||
</Tooltip> | |||
); | |||
} | |||
return ( | |||
<> | |||
<Button className="js-webhook-create" onClick={this.handleCreateOpen}> | |||
<Button className="it__webhook-create" onClick={this.handleCreateOpen}> | |||
{translate('create')} | |||
</Button> | |||
{this.state.openCreate && ( |
@@ -23,7 +23,7 @@ import AlertErrorIcon from '../../../components/icons/AlertErrorIcon'; | |||
import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon'; | |||
import BulletListIcon from '../../../components/icons/BulletListIcon'; | |||
import DateTimeFormatter from '../../../components/intl/DateTimeFormatter'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { WebhookResponse } from '../../../types/webhook'; | |||
import LatestDeliveryForm from './LatestDeliveryForm'; | |||
@@ -73,7 +73,11 @@ export default class WebhookItemLatestDelivery extends React.PureComponent<Props | |||
)} | |||
<span className="spacer-left display-inline-flex-center"> | |||
<DateTimeFormatter date={webhook.latestDelivery.at} /> | |||
<ButtonIcon className="button-small little-spacer-left" onClick={this.handleClick}> | |||
<ButtonIcon | |||
aria-label={translateWithParameters('webhooks.last_execution.open_for_x', webhook.name)} | |||
className="button-small little-spacer-left" | |||
onClick={this.handleClick} | |||
> | |||
<BulletListIcon /> | |||
</ButtonIcon> | |||
</span> |
@@ -0,0 +1,385 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { act, screen, waitFor, within } from '@testing-library/react'; | |||
import userEvent from '@testing-library/user-event'; | |||
import React from 'react'; | |||
import WebhooksMock from '../../../../api/mocks/WebhooksMock'; | |||
import { mockComponent } from '../../../../helpers/mocks/component'; | |||
import { mockWebhook } from '../../../../helpers/mocks/webhook'; | |||
import { renderComponent } from '../../../../helpers/testReactTestingUtils'; | |||
import { byLabelText, byRole, byText } from '../../../../helpers/testSelector'; | |||
import { App } from '../App'; | |||
import { WEBHOOKS_LIMIT } from '../PageActions'; | |||
const webhookService = new WebhooksMock(); | |||
beforeEach(() => { | |||
webhookService.reset(); | |||
}); | |||
describe('app should render correctly', () => { | |||
it('global webhooks', async () => { | |||
const { ui } = getPageObject(); | |||
renderWebhooksApp(); | |||
await ui.waitForWebhooksLoaded(); | |||
expect(ui.webhookTable.get()).toBeInTheDocument(); | |||
ui.checkWebhookRow(0, { | |||
name: 'Global webhook 1', | |||
url: 'https://example.com/1', | |||
secret: false, | |||
lastDeliveryDate: 'June 24, 2019', | |||
}); | |||
ui.checkWebhookRow(1, { | |||
name: 'Global webhook 2', | |||
url: 'https://example.com/2', | |||
secret: true, | |||
}); | |||
}); | |||
it('project webhooks', async () => { | |||
const { ui } = getPageObject(); | |||
webhookService.reset('project1'); | |||
renderWebhooksApp({ | |||
component: mockComponent({ key: 'project1' }), | |||
}); | |||
await ui.waitForWebhooksLoaded(); | |||
expect(ui.webhookTable.get()).toBeInTheDocument(); | |||
ui.checkWebhookRow(0, { | |||
name: 'Project 1 webhook 1', | |||
url: 'https://example.com/1', | |||
secret: false, | |||
lastDeliveryDate: 'June 24, 2019', | |||
}); | |||
ui.checkWebhookRow(1, { | |||
name: 'Project 1 webhook 2', | |||
url: 'https://example.com/2', | |||
secret: false, | |||
}); | |||
}); | |||
it('project with no webhook', async () => { | |||
const { ui } = getPageObject(); | |||
webhookService.reset('project2'); | |||
renderWebhooksApp({ | |||
component: mockComponent({ key: 'project2' }), | |||
}); | |||
await ui.waitForNoResults(); | |||
expect(ui.webhookTable.query()).not.toBeInTheDocument(); | |||
}); | |||
}); | |||
describe('webhook CRUD', () => { | |||
it('should not allow webhook creation when too many', async () => { | |||
const { ui } = getPageObject(); | |||
webhookService.addWebhook( | |||
...Array.from({ length: 8 }).map((_, i) => mockWebhook({ key: `newwebhook${i}` })) | |||
); | |||
renderWebhooksApp(); | |||
await ui.waitForWebhooksLoaded(); | |||
expect(ui.webhookCreateButton.get()).toBeDisabled(); | |||
await expect(ui.webhookCreateButton.get()).toHaveATooltipWithContent( | |||
`webhooks.maximum_reached.${WEBHOOKS_LIMIT}` | |||
); | |||
}); | |||
// eslint-disable-next-line jest/expect-expect | |||
it('should allow to create a webhook', async () => { | |||
const { ui, user } = getPageObject(); | |||
renderWebhooksApp(); | |||
await ui.waitForWebhooksLoaded(); | |||
await act(async () => { | |||
await user.click(ui.webhookCreateButton.get()); | |||
}); | |||
await ui.fillUpdateForm('new-webhook'); | |||
ui.formShouldNotBeValid(); | |||
await ui.fillUpdateForm(undefined, 'https://webhook.example.sonarqube.com'); | |||
await ui.submitForm(); | |||
ui.checkWebhookRow(2, { | |||
name: 'new-webhook', | |||
url: 'https://webhook.example.sonarqube.com', | |||
secret: false, | |||
}); | |||
}); | |||
// eslint-disable-next-line jest/expect-expect | |||
it('should allow to update a webhook', async () => { | |||
const { ui } = getPageObject(); | |||
renderWebhooksApp(); | |||
await ui.waitForWebhooksLoaded(); | |||
await ui.clickWebhookRowAction(1, 'Global webhook 2', 'update_verb'); | |||
await ui.fillUpdateForm('modified-webhook', 'https://webhook.example.sonarqube.com', 'secret'); | |||
await ui.submitForm(); | |||
ui.checkWebhookRow(1, { | |||
name: 'modified-webhook', | |||
url: 'https://webhook.example.sonarqube.com', | |||
secret: true, | |||
lastDeliveryDate: 'webhooks.last_execution.none', | |||
}); | |||
// Edit again, removing the secret | |||
await ui.clickWebhookRowAction(1, 'modified-webhook', 'update_verb'); | |||
await ui.fillUpdateForm(undefined, undefined, ''); | |||
await ui.submitForm(); | |||
ui.checkWebhookRow(1, { | |||
name: 'modified-webhook', | |||
url: 'https://webhook.example.sonarqube.com', | |||
secret: false, | |||
lastDeliveryDate: 'webhooks.last_execution.none', | |||
}); | |||
// Edit once again, not touching the secret | |||
await ui.clickWebhookRowAction(1, 'modified-webhook', 'update_verb'); | |||
await ui.fillUpdateForm('modified-webhook2'); | |||
await ui.submitForm(); | |||
ui.checkWebhookRow(1, { | |||
name: 'modified-webhook', | |||
url: 'https://webhook.example.sonarqube.com', | |||
secret: false, | |||
lastDeliveryDate: 'webhooks.last_execution.none', | |||
}); | |||
}); | |||
it('should allow to delete a webhook', async () => { | |||
const { ui } = getPageObject(); | |||
renderWebhooksApp(); | |||
await ui.waitForWebhooksLoaded(); | |||
expect(ui.webhookRow.getAll()).toHaveLength(3); // We count the header | |||
await ui.clickWebhookRowAction(0, 'Global webhook 1', 'delete'); | |||
await ui.submitForm(); | |||
expect(ui.webhookRow.getAll()).toHaveLength(2); | |||
}); | |||
}); | |||
describe('should properly show deliveries', () => { | |||
it('should render delivery list', async () => { | |||
const { ui } = getPageObject(); | |||
renderWebhooksApp(); | |||
await ui.waitForWebhooksLoaded(); | |||
await act(async () => { | |||
await ui.clickWebhookRowAction(0, 'Global webhook 1', 'webhooks.deliveries.show'); | |||
}); | |||
ui.checkDeliveryRow(0, { | |||
date: 'June 24, 2019', | |||
status: 'success', | |||
}); | |||
ui.checkDeliveryRow(1, { | |||
date: 'June 23, 2019', | |||
status: 'success', | |||
}); | |||
ui.checkDeliveryRow(2, { | |||
date: 'June 22, 2019', | |||
status: 'error', | |||
}); | |||
ui.checkDeliveryRow(3, { | |||
date: 'June 21, 2019', | |||
status: 'success', | |||
}); | |||
await act(async () => { | |||
await ui.toggleDeliveryRow(1); | |||
}); | |||
expect(screen.getByText('webhooks.delivery.response_x.200')).toBeInTheDocument(); | |||
expect(screen.getByText('webhooks.delivery.duration_x.1s')).toBeInTheDocument(); | |||
expect(screen.getByText('{ "id": "global-webhook-1-delivery-0" }')).toBeInTheDocument(); | |||
await act(async () => { | |||
await ui.toggleDeliveryRow(1); | |||
await ui.toggleDeliveryRow(2); | |||
}); | |||
expect( | |||
screen.getByText('webhooks.delivery.response_x.webhooks.delivery.server_unreachable') | |||
).toBeInTheDocument(); | |||
expect(screen.getByText('webhooks.delivery.duration_x.1s')).toBeInTheDocument(); | |||
expect(screen.getByText('{ "id": "global-webhook-1-delivery-1" }')).toBeInTheDocument(); | |||
}); | |||
it('should render delivery modal', async () => { | |||
const { ui } = getPageObject(); | |||
renderWebhooksApp(); | |||
await ui.waitForWebhooksLoaded(); | |||
await ui.clickWebhookLatestDelivery(0, 'Global webhook 1'); | |||
expect(screen.getByText('webhooks.delivery.response_x.200')).toBeInTheDocument(); | |||
expect(screen.getByText('webhooks.delivery.duration_x.20ms')).toBeInTheDocument(); | |||
expect(screen.getByText('{ "id": "global-delivery1" }')).toBeInTheDocument(); | |||
}); | |||
it('should handle pagination', async () => { | |||
const { ui, user } = getPageObject(); | |||
renderWebhooksApp(); | |||
await ui.waitForWebhooksLoaded(); | |||
await ui.clickWebhookRowAction(0, 'Global webhook 1', 'webhooks.deliveries.show'); | |||
expect(screen.getByText('x_of_y_shown.10.16')).toBeInTheDocument(); | |||
await act(async () => { | |||
await user.click(screen.getByRole('button', { name: 'show_more' })); | |||
}); | |||
expect(screen.getByText('x_of_y_shown.16.16')).toBeInTheDocument(); | |||
}); | |||
}); | |||
function renderWebhooksApp(overrides: Partial<App['props']> = {}) { | |||
return renderComponent(<App {...overrides} />); | |||
} | |||
function getPageObject() { | |||
const user = userEvent.setup(); | |||
const selectors = { | |||
webhookCreateButton: byRole('button', { name: 'create' }), | |||
webhookTable: byRole('table'), | |||
webhookRow: byRole('row'), | |||
noWebhook: byText('webhooks.no_result'), | |||
formDialog: byRole('dialog'), | |||
formNameInput: byRole('textbox', { name: 'webhooks.name field_required' }), | |||
formUrlInput: byRole('textbox', { name: 'webhooks.url field_required' }), | |||
formSecretInput: byLabelText('webhooks.secret'), | |||
formSecretInputMaskButton: byRole('button', { name: 'webhooks.secret.field_mask.link' }), | |||
formUpdateButton: byRole('button', { name: 'update_verb' }), | |||
}; | |||
const ui = { | |||
...selectors, | |||
// General | |||
waitForWebhooksLoaded: async () => { | |||
expect(await selectors.webhookTable.find()).toBeInTheDocument(); | |||
}, | |||
waitForNoResults: async () => { | |||
expect(await selectors.noWebhook.find()).toBeInTheDocument(); | |||
}, | |||
// Row-related | |||
getWebhookRow: (index: number) => { | |||
return selectors.webhookRow.getAll()[index + 1]; | |||
}, | |||
clickWebhookRowAction: async (rowIndex: number, webhookName: string, actionName: string) => { | |||
const row = ui.getWebhookRow(rowIndex); | |||
await act(async () => { | |||
await user.click( | |||
within(row).getByRole('button', { name: `webhooks.show_actions.${webhookName}` }) | |||
); | |||
await user.click(within(row).getByRole('button', { name: actionName })); | |||
}); | |||
}, | |||
clickWebhookLatestDelivery: async (rowIndex: number, webhookName: string) => { | |||
const row = ui.getWebhookRow(rowIndex); | |||
await act(async () => { | |||
await user.click( | |||
within(row).getByRole('button', { | |||
name: `webhooks.last_execution.open_for_x.${webhookName}`, | |||
}) | |||
); | |||
}); | |||
}, | |||
checkWebhookRow: ( | |||
index: number, | |||
expected: { name: string; url: string; secret: boolean; lastDeliveryDate?: string } | |||
) => { | |||
const row = ui.getWebhookRow(index); | |||
const [name, url, secret, lastDelivery] = within(row).getAllByRole('cell'); | |||
expect(name).toHaveTextContent(expected.name); | |||
expect(url).toHaveTextContent(expected.url); | |||
expect(secret).toHaveTextContent(expected.secret ? 'yes' : 'no'); | |||
expect(lastDelivery).toHaveTextContent( | |||
expected.lastDeliveryDate | |||
? new RegExp(expected.lastDeliveryDate) | |||
: 'webhooks.last_execution.none' | |||
); | |||
}, | |||
// Creation/Update form | |||
fillUpdateForm: async (name?: string, url?: string, secret?: string) => { | |||
await act(async () => { | |||
if (name !== undefined) { | |||
await user.clear(selectors.formNameInput.get()); | |||
await user.type(selectors.formNameInput.get(), name); | |||
} | |||
if (url !== undefined) { | |||
await user.clear(selectors.formUrlInput.get()); | |||
await user.type(selectors.formUrlInput.get(), url); | |||
} | |||
if (secret !== undefined) { | |||
// Do we have to update secret | |||
const secretMaskButton = selectors.formSecretInputMaskButton.get(); | |||
if (secretMaskButton) { | |||
await user.click(secretMaskButton); | |||
} | |||
await user.clear(selectors.formSecretInput.get()); | |||
if (secret) { | |||
await user.type(selectors.formSecretInput.get(), secret); | |||
} | |||
} | |||
}); | |||
}, | |||
getFormSubmitButton: () => { | |||
const form = selectors.formDialog.get(); | |||
return within(form) | |||
.getAllByRole('button') | |||
.filter((b: HTMLButtonElement) => b.type === 'submit')[0]; | |||
}, | |||
formShouldNotBeValid: () => { | |||
expect(ui.getFormSubmitButton()).toBeDisabled(); | |||
}, | |||
submitForm: async () => { | |||
await act(async () => { | |||
const submitBtn = ui.getFormSubmitButton(); | |||
await user.click(submitBtn); | |||
}); | |||
await waitFor(() => expect(selectors.formDialog.query()).not.toBeInTheDocument()); | |||
}, | |||
// Deliveries | |||
getDeliveryRow: (index: number) => { | |||
const dialog = selectors.formDialog.get(); | |||
const rows = within(dialog).getAllByRole('listitem'); | |||
return rows[index]; | |||
}, | |||
checkDeliveryRow: (index: number, expected: { date: string; status: 'success' | 'error' }) => { | |||
const row = ui.getDeliveryRow(index); | |||
const date = within(row).getByRole('button'); | |||
expect(date).toHaveTextContent(new RegExp(expected.date)); | |||
const status = within(row).getByLabelText(expected.status); | |||
expect(status).toBeInTheDocument(); | |||
}, | |||
toggleDeliveryRow: async (index: number) => { | |||
const row = ui.getDeliveryRow(index); | |||
await user.click(within(row).getByRole('button')); | |||
}, | |||
}; | |||
return { | |||
ui, | |||
user, | |||
}; | |||
} |
@@ -1,176 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { | |||
createWebhook, | |||
deleteWebhook, | |||
searchWebhooks, | |||
updateWebhook, | |||
} from '../../../../api/webhooks'; | |||
import { mockComponent } from '../../../../helpers/mocks/component'; | |||
import { App } from '../App'; | |||
jest.mock('../../../../api/webhooks', () => ({ | |||
createWebhook: jest.fn(() => | |||
Promise.resolve({ webhook: { key: '3', name: 'baz', url: 'http://baz', hasSecret: false } }) | |||
), | |||
deleteWebhook: jest.fn(() => Promise.resolve()), | |||
searchWebhooks: jest.fn(() => | |||
Promise.resolve({ | |||
webhooks: [ | |||
{ key: '1', name: 'foo', url: 'http://foo', hasSecret: false }, | |||
{ key: '2', name: 'bar', url: 'http://bar', hasSecret: false }, | |||
], | |||
}) | |||
), | |||
updateWebhook: jest.fn(() => Promise.resolve()), | |||
})); | |||
const component = mockComponent({ key: 'bar', qualifier: 'TRK' }); | |||
beforeEach(() => { | |||
(createWebhook as jest.Mock<any>).mockClear(); | |||
(deleteWebhook as jest.Mock<any>).mockClear(); | |||
(searchWebhooks as jest.Mock<any>).mockClear(); | |||
(updateWebhook as jest.Mock<any>).mockClear(); | |||
}); | |||
it('should be in loading status', () => { | |||
expect(shallow(<App />)).toMatchSnapshot(); | |||
}); | |||
it('should fetch webhooks and display them', async () => { | |||
const wrapper = shallow(<App />); | |||
expect(wrapper.state('loading')).toBe(true); | |||
await new Promise(setImmediate); | |||
expect(searchWebhooks).toHaveBeenCalledWith({}); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
describe('should correctly fetch webhooks when', () => { | |||
it('on global scope', async () => { | |||
shallow(<App />); | |||
await new Promise(setImmediate); | |||
expect(searchWebhooks).toHaveBeenCalledWith({ projects: undefined }); | |||
}); | |||
it('on project scope', async () => { | |||
shallow(<App component={component} />); | |||
await new Promise(setImmediate); | |||
expect(searchWebhooks).toHaveBeenCalledWith({ | |||
project: component.key, | |||
}); | |||
}); | |||
}); | |||
it('should correctly handle webhook creation', async () => { | |||
const webhook = { name: 'baz', url: 'http://baz' }; | |||
const wrapper = shallow(<App />); | |||
(wrapper.instance() as App).handleCreate({ ...webhook }); | |||
expect(createWebhook).toHaveBeenLastCalledWith({ | |||
...webhook, | |||
project: undefined, | |||
}); | |||
await new Promise(setImmediate); | |||
wrapper.update(); | |||
expect(wrapper.state('webhooks')).toEqual([ | |||
{ key: '1', name: 'foo', url: 'http://foo', hasSecret: false }, | |||
{ key: '2', name: 'bar', url: 'http://bar', hasSecret: false }, | |||
{ key: '3', name: 'baz', url: 'http://baz', hasSecret: false }, | |||
]); | |||
}); | |||
it('should correctly handle webhook deletion', async () => { | |||
const wrapper = shallow(<App />); | |||
(wrapper.instance() as App).handleDelete('2'); | |||
expect(deleteWebhook).toHaveBeenLastCalledWith({ webhook: '2' }); | |||
await new Promise(setImmediate); | |||
wrapper.update(); | |||
expect(wrapper.state('webhooks')).toEqual([ | |||
{ key: '1', name: 'foo', url: 'http://foo', hasSecret: false }, | |||
]); | |||
}); | |||
it('should correctly handle webhook update', async () => { | |||
const newValues = { webhook: '1', name: 'Cfoo', url: 'http://cfoo', secret: undefined }; | |||
const wrapper = shallow(<App />); | |||
(wrapper.instance() as App).handleUpdate(newValues); | |||
expect(updateWebhook).toHaveBeenLastCalledWith(newValues); | |||
await new Promise(setImmediate); | |||
wrapper.update(); | |||
expect(wrapper.state('webhooks')).toEqual([ | |||
{ key: '1', name: 'Cfoo', url: 'http://cfoo', hasSecret: false }, | |||
{ key: '2', name: 'bar', url: 'http://bar', hasSecret: false }, | |||
]); | |||
}); | |||
it('should correctly handle webhook secret update', async () => { | |||
const newValuesWithSecret = { webhook: '2', name: 'bar', url: 'http://bar', secret: 'secret' }; | |||
const newValuesWithoutSecret = { | |||
webhook: '2', | |||
name: 'bar', | |||
url: 'http://bar', | |||
secret: undefined, | |||
}; | |||
const newValuesWithEmptySecret = { webhook: '2', name: 'bar', url: 'http://bar', secret: '' }; | |||
const wrapper = shallow(<App />); | |||
// With secret | |||
(wrapper.instance() as App).handleUpdate(newValuesWithSecret); | |||
expect(updateWebhook).toHaveBeenLastCalledWith(newValuesWithSecret); | |||
await new Promise(setImmediate); | |||
wrapper.update(); | |||
expect(wrapper.state('webhooks')).toEqual([ | |||
{ key: '1', name: 'foo', url: 'http://foo', hasSecret: false }, | |||
{ key: '2', name: 'bar', url: 'http://bar', hasSecret: true }, | |||
]); | |||
// Without secret | |||
(wrapper.instance() as App).handleUpdate(newValuesWithoutSecret); | |||
expect(updateWebhook).toHaveBeenLastCalledWith(newValuesWithoutSecret); | |||
await new Promise(setImmediate); | |||
wrapper.update(); | |||
expect(wrapper.state('webhooks')).toEqual([ | |||
{ key: '1', name: 'foo', url: 'http://foo', hasSecret: false }, | |||
{ key: '2', name: 'bar', url: 'http://bar', hasSecret: true }, | |||
]); | |||
// With empty secret | |||
(wrapper.instance() as App).handleUpdate(newValuesWithEmptySecret); | |||
expect(updateWebhook).toHaveBeenLastCalledWith(newValuesWithEmptySecret); | |||
await new Promise(setImmediate); | |||
wrapper.update(); | |||
expect(wrapper.state('webhooks')).toEqual([ | |||
{ key: '1', name: 'foo', url: 'http://foo', hasSecret: false }, | |||
{ key: '2', name: 'bar', url: 'http://bar', hasSecret: false }, | |||
]); | |||
}); |
@@ -1,145 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import userEvent from '@testing-library/user-event'; | |||
import * as React from 'react'; | |||
import { renderComponent } from '../../../../helpers/testReactTestingUtils'; | |||
import { byLabelText, byRole } from '../../../../helpers/testSelector'; | |||
import CreateWebhookForm from '../CreateWebhookForm'; | |||
const ui = { | |||
nameInput: byRole('textbox', { name: 'webhooks.name field_required' }), | |||
urlInput: byRole('textbox', { name: 'webhooks.url field_required' }), | |||
secretInput: byLabelText('webhooks.secret'), | |||
secretInputMaskButton: byRole('button', { name: 'webhooks.secret.field_mask.link' }), | |||
createButton: byRole('button', { name: 'create' }), | |||
updateButton: byRole('button', { name: 'update_verb' }), | |||
}; | |||
describe('Webhook form', () => { | |||
it('should correctly submit creation form', async () => { | |||
const user = userEvent.setup(); | |||
const webhook = { | |||
name: 'foo', | |||
url: 'http://bar', | |||
secret: '', | |||
}; | |||
const onDone = jest.fn(); | |||
renderCreateWebhookForm({ onDone }); | |||
expect(ui.nameInput.get()).toHaveValue(''); | |||
expect(ui.urlInput.get()).toHaveValue(''); | |||
expect(ui.secretInput.get()).toHaveValue(''); | |||
expect(ui.createButton.get()).toBeDisabled(); | |||
await user.type(ui.nameInput.get(), webhook.name); | |||
await user.type(ui.urlInput.get(), webhook.url); | |||
expect(ui.createButton.get()).toBeEnabled(); | |||
await user.click(ui.createButton.get()); | |||
expect(onDone).toHaveBeenCalledWith(webhook); | |||
}); | |||
it('should correctly submit update form', async () => { | |||
const user = userEvent.setup(); | |||
const webhook = { | |||
hasSecret: false, | |||
key: 'test-webhook-key', | |||
name: 'foo', | |||
url: 'http://bar', | |||
}; | |||
const nameExtension = 'bar'; | |||
const url = 'http://bar'; | |||
const onDone = jest.fn(); | |||
renderCreateWebhookForm({ onDone, webhook }); | |||
expect(ui.nameInput.get()).toHaveValue(webhook.name); | |||
expect(ui.urlInput.get()).toHaveValue(webhook.url); | |||
expect(ui.secretInput.query()).not.toBeInTheDocument(); | |||
expect(ui.secretInputMaskButton.get()).toBeInTheDocument(); | |||
expect(ui.updateButton.get()).toBeDisabled(); | |||
await user.type(ui.nameInput.get(), nameExtension); | |||
await user.clear(ui.urlInput.get()); | |||
await user.type(ui.urlInput.get(), url); | |||
expect(ui.updateButton.get()).toBeEnabled(); | |||
await user.click(ui.updateButton.get()); | |||
expect(onDone).toHaveBeenCalledWith({ | |||
name: `${webhook.name}${nameExtension}`, | |||
url, | |||
secret: undefined, | |||
}); | |||
}); | |||
it('should correctly submit update form with empty secret', async () => { | |||
const user = userEvent.setup(); | |||
const webhook = { | |||
hasSecret: false, | |||
key: 'test-webhook-key', | |||
name: 'foo', | |||
url: 'http://bar', | |||
}; | |||
const onDone = jest.fn(); | |||
renderCreateWebhookForm({ onDone, webhook }); | |||
await user.click(ui.secretInputMaskButton.get()); | |||
expect(ui.updateButton.get()).toBeEnabled(); | |||
await user.click(ui.updateButton.get()); | |||
expect(onDone).toHaveBeenCalledWith({ | |||
name: webhook.name, | |||
url: webhook.url, | |||
secret: '', | |||
}); | |||
}); | |||
it('should correctly submit update form with updated secret', async () => { | |||
const user = userEvent.setup(); | |||
const webhook = { | |||
hasSecret: false, | |||
key: 'test-webhook-key', | |||
name: 'foo', | |||
url: 'http://bar', | |||
}; | |||
const secret = 'test-webhook-secret'; | |||
const onDone = jest.fn(); | |||
renderCreateWebhookForm({ onDone, webhook }); | |||
await user.click(ui.secretInputMaskButton.get()); | |||
await user.type(ui.secretInput.get(), secret); | |||
await user.click(ui.updateButton.get()); | |||
expect(onDone).toHaveBeenCalledWith({ | |||
name: webhook.name, | |||
url: webhook.url, | |||
secret, | |||
}); | |||
}); | |||
}); | |||
function renderCreateWebhookForm(props = {}) { | |||
return renderComponent( | |||
<CreateWebhookForm onClose={jest.fn()} onDone={jest.fn(() => Promise.resolve())} {...props} /> | |||
); | |||
} |
@@ -1,77 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { searchDeliveries } from '../../../../api/webhooks'; | |||
import DeliveriesForm from '../DeliveriesForm'; | |||
jest.mock('../../../../api/webhooks', () => ({ | |||
searchDeliveries: jest.fn(() => | |||
Promise.resolve({ | |||
deliveries: [ | |||
{ | |||
at: '12.02.2018', | |||
durationMs: 20, | |||
httpStatus: 200, | |||
id: '2', | |||
success: true, | |||
}, | |||
{ | |||
at: '11.02.2018', | |||
durationMs: 122, | |||
httpStatus: 500, | |||
id: '1', | |||
success: false, | |||
}, | |||
], | |||
paging: { | |||
pageIndex: 1, | |||
pageSize: 10, | |||
total: 15, | |||
}, | |||
}) | |||
), | |||
})); | |||
const webhook = { key: '1', name: 'foo', url: 'http://foo.bar', hasSecret: false }; | |||
beforeEach(() => { | |||
(searchDeliveries as jest.Mock<any>).mockClear(); | |||
}); | |||
it('should render correctly', async () => { | |||
const wrapper = getWrapper(); | |||
expect(wrapper).toMatchSnapshot(); | |||
await new Promise(setImmediate); | |||
expect(searchDeliveries as jest.Mock<any>).toHaveBeenLastCalledWith({ | |||
webhook: webhook.key, | |||
ps: 10, | |||
}); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
wrapper.find('ListFooter').prop<Function>('loadMore')(); | |||
expect(searchDeliveries).toHaveBeenLastCalledWith({ webhook: webhook.key, p: 2, ps: 10 }); | |||
}); | |||
function getWrapper(props = {}) { | |||
return shallow(<DeliveriesForm onClose={jest.fn()} webhook={webhook} {...props} />); | |||
} |
@@ -1,72 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { screen } from '@testing-library/react'; | |||
import userEvent from '@testing-library/user-event'; | |||
import * as React from 'react'; | |||
import { getDelivery } from '../../../../api/webhooks'; | |||
import { mockWebhookDelivery } from '../../../../helpers/mocks/webhook'; | |||
import { HttpStatus } from '../../../../helpers/request'; | |||
import { renderComponent } from '../../../../helpers/testReactTestingUtils'; | |||
import DeliveryAccordion from '../DeliveryAccordion'; | |||
jest.mock('../../../../api/webhooks', () => ({ | |||
getDelivery: jest.fn().mockResolvedValue({ | |||
delivery: { payload: '{ "message": "This was successful" }' }, | |||
}), | |||
})); | |||
beforeEach(jest.clearAllMocks); | |||
it('should render correctly for successful payloads', async () => { | |||
const user = userEvent.setup(); | |||
renderDeliveryAccordion(); | |||
expect(screen.getByLabelText('success')).toBeInTheDocument(); | |||
await user.click(screen.getByRole('button')); | |||
expect(screen.getByText(`webhooks.delivery.response_x.${HttpStatus.Ok}`)).toBeInTheDocument(); | |||
expect(screen.getByText('webhooks.delivery.duration_x.20ms')).toBeInTheDocument(); | |||
expect(screen.getByText('webhooks.delivery.payload')).toBeInTheDocument(); | |||
const codeSnippet = await screen.findByText('{ "message": "This was successful" }'); | |||
expect(codeSnippet).toBeInTheDocument(); | |||
}); | |||
it('should render correctly for errored payloads', async () => { | |||
const user = userEvent.setup(); | |||
(getDelivery as jest.Mock).mockResolvedValueOnce({ | |||
delivery: { payload: '503 Service Unavailable' }, | |||
}); | |||
renderDeliveryAccordion({ | |||
delivery: mockWebhookDelivery({ httpStatus: undefined, success: false }), | |||
}); | |||
expect(screen.getByLabelText('error')).toBeInTheDocument(); | |||
await user.click(screen.getByRole('button')); | |||
expect( | |||
screen.getByText('webhooks.delivery.response_x.webhooks.delivery.server_unreachable') | |||
).toBeInTheDocument(); | |||
const codeSnippet = await screen.findByText('503 Service Unavailable'); | |||
expect(codeSnippet).toBeInTheDocument(); | |||
}); | |||
function renderDeliveryAccordion(props: Partial<DeliveryAccordion['props']> = {}) { | |||
return renderComponent(<DeliveryAccordion delivery={mockWebhookDelivery()} {...props} />); | |||
} |
@@ -1,61 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { getDelivery } from '../../../../api/webhooks'; | |||
import LatestDeliveryForm from '../LatestDeliveryForm'; | |||
jest.mock('../../../../api/webhooks', () => ({ | |||
getDelivery: jest.fn(() => | |||
Promise.resolve({ | |||
delivery: { payload: '{ "success": true }' }, | |||
}) | |||
), | |||
})); | |||
const delivery = { | |||
at: '12.02.2018', | |||
durationMs: 20, | |||
httpStatus: 200, | |||
id: '2', | |||
success: true, | |||
}; | |||
const webhook = { key: '1', name: 'foo', url: 'http://foo.bar', hasSecret: false }; | |||
beforeEach(() => { | |||
(getDelivery as jest.Mock<any>).mockClear(); | |||
}); | |||
it('should render correctly', async () => { | |||
const wrapper = getWrapper(); | |||
expect(wrapper).toMatchSnapshot(); | |||
await new Promise(setImmediate); | |||
expect(getDelivery).toHaveBeenLastCalledWith({ deliveryId: delivery.id }); | |||
wrapper.update(); | |||
expect(wrapper).toMatchSnapshot(); | |||
}); | |||
function getWrapper(props = {}) { | |||
return shallow( | |||
<LatestDeliveryForm delivery={delivery} onClose={jest.fn()} webhook={webhook} {...props} /> | |||
); | |||
} |
@@ -1,51 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { click } from '../../../../helpers/testUtils'; | |||
import PageActions from '../PageActions'; | |||
it('should render correctly', () => { | |||
expect(getWrapper()).toMatchSnapshot(); | |||
}); | |||
it('should not render', () => { | |||
expect(getWrapper({ loading: true }).type()).toBeNull(); | |||
}); | |||
it('should not allow to create a new webhook', () => { | |||
expect(getWrapper({ webhooksCount: 10 })).toMatchSnapshot(); | |||
}); | |||
it('should display the create form', () => { | |||
const onCreate = jest.fn(); | |||
const wrapper = getWrapper({ onCreate }); | |||
click(wrapper.find('.js-webhook-create')); | |||
expect(wrapper.find('CreateWebhookForm').exists()).toBe(true); | |||
wrapper.find('CreateWebhookForm').prop<Function>('onDone')({ | |||
name: 'foo', | |||
url: 'http://foo.bar', | |||
}); | |||
expect(onCreate).toHaveBeenLastCalledWith({ name: 'foo', url: 'http://foo.bar' }); | |||
}); | |||
function getWrapper(props = {}) { | |||
return shallow(<PageActions loading={false} onCreate={jest.fn()} webhooksCount={5} {...props} />); | |||
} |
@@ -1,32 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import PageHeader from '../PageHeader'; | |||
it('should render correctly', () => { | |||
expect( | |||
shallow( | |||
<PageHeader loading> | |||
<div /> | |||
</PageHeader> | |||
) | |||
).toMatchSnapshot(); | |||
}); |
@@ -1,85 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { click } from '../../../../helpers/testUtils'; | |||
import WebhookActions from '../WebhookActions'; | |||
const webhook = { | |||
key: '1', | |||
name: 'foo', | |||
url: 'http://foo.bar', | |||
hasSecret: false, | |||
}; | |||
const delivery = { | |||
at: '12.02.2018', | |||
durationMs: 20, | |||
httpStatus: 200, | |||
id: '2', | |||
success: true, | |||
}; | |||
it('should render correctly', () => { | |||
expect(getWrapper()).toMatchSnapshot(); | |||
}); | |||
it('should display the update webhook form', () => { | |||
const onUpdate = jest.fn(() => Promise.resolve()); | |||
const wrapper = getWrapper({ onUpdate }); | |||
click(wrapper.find('.js-webhook-update')); | |||
expect(wrapper.find('CreateWebhookForm').exists()).toBe(true); | |||
wrapper.find('CreateWebhookForm').prop<Function>('onDone')({ | |||
name: webhook.name, | |||
url: webhook.url, | |||
}); | |||
expect(onUpdate).toHaveBeenLastCalledWith({ | |||
webhook: webhook.key, | |||
name: webhook.name, | |||
url: webhook.url, | |||
}); | |||
}); | |||
it('should display the delete webhook form', () => { | |||
const onDelete = jest.fn(() => Promise.resolve()); | |||
const wrapper = getWrapper({ onDelete }); | |||
click(wrapper.find('.js-webhook-delete')); | |||
expect(wrapper.find('DeleteWebhookForm').exists()).toBe(true); | |||
wrapper.find('DeleteWebhookForm').prop<Function>('onSubmit')(); | |||
expect(onDelete).toHaveBeenLastCalledWith(webhook.key); | |||
}); | |||
it('should display the deliveries form', () => { | |||
const wrapper = getWrapper({ webhook: { ...webhook, latestDelivery: delivery } }); | |||
expect(wrapper).toMatchSnapshot(); | |||
click(wrapper.find('.js-webhook-deliveries')); | |||
expect(wrapper.find('DeliveriesForm').exists()).toBe(true); | |||
}); | |||
function getWrapper(props = {}) { | |||
return shallow( | |||
<WebhookActions | |||
onDelete={jest.fn(() => Promise.resolve())} | |||
onUpdate={jest.fn(() => Promise.resolve())} | |||
webhook={webhook} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,57 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import WebhookItem from '../WebhookItem'; | |||
const webhookWithoutSecret = { | |||
key: '1', | |||
name: 'my webhook', | |||
url: 'http://webhook.target', | |||
hasSecret: false, | |||
}; | |||
const webhookWithSecret = { | |||
key: '1', | |||
name: 'my webhook', | |||
url: 'http://webhook.target', | |||
hasSecret: true, | |||
}; | |||
it('should render correctly', () => { | |||
expect( | |||
shallow( | |||
<WebhookItem | |||
onDelete={jest.fn(() => Promise.resolve())} | |||
onUpdate={jest.fn(() => Promise.resolve())} | |||
webhook={webhookWithoutSecret} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
expect( | |||
shallow( | |||
<WebhookItem | |||
onDelete={jest.fn(() => Promise.resolve())} | |||
onUpdate={jest.fn(() => Promise.resolve())} | |||
webhook={webhookWithSecret} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); |
@@ -1,68 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import { click } from '../../../../helpers/testUtils'; | |||
import WebhookItemLatestDelivery from '../WebhookItemLatestDelivery'; | |||
const latestDelivery = { | |||
at: '12.02.2018', | |||
durationMs: 20, | |||
httpStatus: 200, | |||
id: '2', | |||
success: true, | |||
}; | |||
const webhook = { | |||
key: '1', | |||
name: 'my webhook', | |||
url: 'http://webhook.target', | |||
hasSecret: false, | |||
latestDelivery, | |||
}; | |||
it('should render correctly a success delivery', () => { | |||
expect(shallow(<WebhookItemLatestDelivery webhook={webhook} />)).toMatchSnapshot(); | |||
}); | |||
it('should render correctly when no latest delivery', () => { | |||
expect( | |||
shallow(<WebhookItemLatestDelivery webhook={{ ...webhook, latestDelivery: undefined }} />) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should render correctly a failed delivery', () => { | |||
expect( | |||
shallow( | |||
<WebhookItemLatestDelivery | |||
webhook={{ | |||
...webhook, | |||
latestDelivery: { ...latestDelivery, httpStatus: 500, success: false }, | |||
}} | |||
/> | |||
) | |||
).toMatchSnapshot(); | |||
}); | |||
it('should display the latest delivery form', () => { | |||
const wrapper = shallow(<WebhookItemLatestDelivery webhook={webhook} />); | |||
click(wrapper.find('ButtonIcon')); | |||
expect(wrapper.find('LatestDeliveryForm').exists()).toBe(true); | |||
}); |
@@ -1,46 +0,0 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2023 SonarSource SA | |||
* mailto:info AT sonarsource DOT com | |||
* | |||
* This program is free software; you can redistribute it and/or | |||
* modify it under the terms of the GNU Lesser General Public | |||
* License as published by the Free Software Foundation; either | |||
* version 3 of the License, or (at your option) any later version. | |||
* | |||
* This program is distributed in the hope that it will be useful, | |||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | |||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |||
* Lesser General Public License for more details. | |||
* | |||
* You should have received a copy of the GNU Lesser General Public License | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { shallow } from 'enzyme'; | |||
import * as React from 'react'; | |||
import WebhooksList from '../WebhooksList'; | |||
const webhooks = [ | |||
{ key: '1', name: 'my webhook', url: 'http://webhook.target', hasSecret: false }, | |||
{ key: '2', name: 'jenkins webhook', url: 'http://jenkins.target', hasSecret: false }, | |||
]; | |||
it('should correctly render empty webhook list', () => { | |||
expect(getWrapper({ webhooks: [] })).toMatchSnapshot(); | |||
}); | |||
it('should correctly render the webhooks', () => { | |||
expect(getWrapper()).toMatchSnapshot(); | |||
}); | |||
function getWrapper(props = {}) { | |||
return shallow( | |||
<WebhooksList | |||
onDelete={jest.fn(() => Promise.resolve())} | |||
onUpdate={jest.fn(() => Promise.resolve())} | |||
webhooks={webhooks} | |||
{...props} | |||
/> | |||
); | |||
} |
@@ -1,79 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should be in loading status 1`] = ` | |||
<Fragment> | |||
<Suggestions | |||
suggestions="webhooks" | |||
/> | |||
<Helmet | |||
defer={false} | |||
encodeSpecialCharacters={true} | |||
prioritizeSeoTags={false} | |||
title="webhooks.page" | |||
/> | |||
<div | |||
className="page page-limited" | |||
> | |||
<PageHeader | |||
loading={true} | |||
> | |||
<PageActions | |||
loading={true} | |||
onCreate={[Function]} | |||
webhooksCount={0} | |||
/> | |||
</PageHeader> | |||
</div> | |||
</Fragment> | |||
`; | |||
exports[`should fetch webhooks and display them 1`] = ` | |||
<Fragment> | |||
<Suggestions | |||
suggestions="webhooks" | |||
/> | |||
<Helmet | |||
defer={false} | |||
encodeSpecialCharacters={true} | |||
prioritizeSeoTags={false} | |||
title="webhooks.page" | |||
/> | |||
<div | |||
className="page page-limited" | |||
> | |||
<PageHeader | |||
loading={false} | |||
> | |||
<PageActions | |||
loading={false} | |||
onCreate={[Function]} | |||
webhooksCount={2} | |||
/> | |||
</PageHeader> | |||
<div | |||
className="boxed-group boxed-group-inner" | |||
> | |||
<WebhooksList | |||
onDelete={[Function]} | |||
onUpdate={[Function]} | |||
webhooks={ | |||
[ | |||
{ | |||
"hasSecret": false, | |||
"key": "1", | |||
"name": "foo", | |||
"url": "http://foo", | |||
}, | |||
{ | |||
"hasSecret": false, | |||
"key": "2", | |||
"name": "bar", | |||
"url": "http://bar", | |||
}, | |||
] | |||
} | |||
/> | |||
</div> | |||
</div> | |||
</Fragment> | |||
`; |
@@ -1,104 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<Modal | |||
contentLabel="webhooks.deliveries_for_x.foo" | |||
onRequestClose={[MockFunction]} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
webhooks.deliveries_for_x.foo | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body modal-container" | |||
> | |||
<div | |||
className="text-center" | |||
> | |||
<DeferredSpinner | |||
loading={true} | |||
/> | |||
</div> | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<ResetButtonLink | |||
className="js-modal-close" | |||
onClick={[MockFunction]} | |||
> | |||
close | |||
</ResetButtonLink> | |||
</footer> | |||
</Modal> | |||
`; | |||
exports[`should render correctly 2`] = ` | |||
<Modal | |||
contentLabel="webhooks.deliveries_for_x.foo" | |||
onRequestClose={[MockFunction]} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
webhooks.deliveries_for_x.foo | |||
</h2> | |||
</header> | |||
<div | |||
className="modal-body modal-container" | |||
> | |||
<DeliveryAccordion | |||
delivery={ | |||
{ | |||
"at": "12.02.2018", | |||
"durationMs": 20, | |||
"httpStatus": 200, | |||
"id": "2", | |||
"success": true, | |||
} | |||
} | |||
key="2" | |||
/> | |||
<DeliveryAccordion | |||
delivery={ | |||
{ | |||
"at": "11.02.2018", | |||
"durationMs": 122, | |||
"httpStatus": 500, | |||
"id": "1", | |||
"success": false, | |||
} | |||
} | |||
key="1" | |||
/> | |||
<div | |||
className="text-center" | |||
> | |||
<DeferredSpinner | |||
loading={false} | |||
/> | |||
</div> | |||
<ListFooter | |||
className="little-spacer-bottom" | |||
count={2} | |||
loadMore={[Function]} | |||
ready={true} | |||
total={15} | |||
/> | |||
</div> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<ResetButtonLink | |||
className="js-modal-close" | |||
onClick={[MockFunction]} | |||
> | |||
close | |||
</ResetButtonLink> | |||
</footer> | |||
</Modal> | |||
`; |
@@ -1,78 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<Modal | |||
contentLabel="webhooks.latest_delivery_for_x.foo" | |||
onRequestClose={[MockFunction]} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
webhooks.latest_delivery_for_x.foo | |||
</h2> | |||
</header> | |||
<DeliveryItem | |||
className="modal-body modal-container" | |||
delivery={ | |||
{ | |||
"at": "12.02.2018", | |||
"durationMs": 20, | |||
"httpStatus": 200, | |||
"id": "2", | |||
"success": true, | |||
} | |||
} | |||
loading={true} | |||
/> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<ResetButtonLink | |||
className="js-modal-close" | |||
onClick={[MockFunction]} | |||
> | |||
close | |||
</ResetButtonLink> | |||
</footer> | |||
</Modal> | |||
`; | |||
exports[`should render correctly 2`] = ` | |||
<Modal | |||
contentLabel="webhooks.latest_delivery_for_x.foo" | |||
onRequestClose={[MockFunction]} | |||
> | |||
<header | |||
className="modal-head" | |||
> | |||
<h2> | |||
webhooks.latest_delivery_for_x.foo | |||
</h2> | |||
</header> | |||
<DeliveryItem | |||
className="modal-body modal-container" | |||
delivery={ | |||
{ | |||
"at": "12.02.2018", | |||
"durationMs": 20, | |||
"httpStatus": 200, | |||
"id": "2", | |||
"success": true, | |||
} | |||
} | |||
loading={false} | |||
payload="{ "success": true }" | |||
/> | |||
<footer | |||
className="modal-foot" | |||
> | |||
<ResetButtonLink | |||
className="js-modal-close" | |||
onClick={[MockFunction]} | |||
> | |||
close | |||
</ResetButtonLink> | |||
</footer> | |||
</Modal> | |||
`; |
@@ -1,30 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should not allow to create a new webhook 1`] = ` | |||
<div | |||
className="page-actions" | |||
> | |||
<Tooltip | |||
overlay="webhooks.maximum_reached.10" | |||
> | |||
<Button | |||
className="js-webhook-create disabled" | |||
> | |||
create | |||
</Button> | |||
</Tooltip> | |||
</div> | |||
`; | |||
exports[`should render correctly 1`] = ` | |||
<div | |||
className="page-actions" | |||
> | |||
<Button | |||
className="js-webhook-create" | |||
onClick={[Function]} | |||
> | |||
create | |||
</Button> | |||
</div> | |||
`; |
@@ -1,34 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<header | |||
className="page-header" | |||
> | |||
<h1 | |||
className="page-title" | |||
> | |||
webhooks.page | |||
</h1> | |||
<i | |||
className="spinner" | |||
/> | |||
<div /> | |||
<p | |||
className="page-description" | |||
> | |||
<FormattedMessage | |||
defaultMessage="webhooks.description" | |||
id="webhooks.description" | |||
values={ | |||
{ | |||
"url": <DocLink | |||
to="/project-administration/webhooks/" | |||
> | |||
webhooks.documentation_link | |||
</DocLink>, | |||
} | |||
} | |||
/> | |||
</p> | |||
</header> | |||
`; |
@@ -1,55 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should display the deliveries form 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown | |||
className="big-spacer-left" | |||
label="webhooks.show_actions.foo" | |||
> | |||
<ActionsDropdownItem | |||
className="js-webhook-update" | |||
onClick={[Function]} | |||
> | |||
update_verb | |||
</ActionsDropdownItem> | |||
<ActionsDropdownItem | |||
className="js-webhook-deliveries" | |||
onClick={[Function]} | |||
> | |||
webhooks.deliveries.show | |||
</ActionsDropdownItem> | |||
<ActionsDropdownDivider /> | |||
<ActionsDropdownItem | |||
className="js-webhook-delete" | |||
destructive={true} | |||
onClick={[Function]} | |||
> | |||
delete | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly 1`] = ` | |||
<Fragment> | |||
<ActionsDropdown | |||
className="big-spacer-left" | |||
label="webhooks.show_actions.foo" | |||
> | |||
<ActionsDropdownItem | |||
className="js-webhook-update" | |||
onClick={[Function]} | |||
> | |||
update_verb | |||
</ActionsDropdownItem> | |||
<ActionsDropdownDivider /> | |||
<ActionsDropdownItem | |||
className="js-webhook-delete" | |||
destructive={true} | |||
onClick={[Function]} | |||
> | |||
delete | |||
</ActionsDropdownItem> | |||
</ActionsDropdown> | |||
</Fragment> | |||
`; |
@@ -1,85 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly 1`] = ` | |||
<tr> | |||
<td> | |||
my webhook | |||
</td> | |||
<td> | |||
http://webhook.target | |||
</td> | |||
<td> | |||
no | |||
</td> | |||
<td> | |||
<WebhookItemLatestDelivery | |||
webhook={ | |||
{ | |||
"hasSecret": false, | |||
"key": "1", | |||
"name": "my webhook", | |||
"url": "http://webhook.target", | |||
} | |||
} | |||
/> | |||
</td> | |||
<td | |||
className="sw-text-right" | |||
> | |||
<WebhookActions | |||
onDelete={[MockFunction]} | |||
onUpdate={[MockFunction]} | |||
webhook={ | |||
{ | |||
"hasSecret": false, | |||
"key": "1", | |||
"name": "my webhook", | |||
"url": "http://webhook.target", | |||
} | |||
} | |||
/> | |||
</td> | |||
</tr> | |||
`; | |||
exports[`should render correctly 2`] = ` | |||
<tr> | |||
<td> | |||
my webhook | |||
</td> | |||
<td> | |||
http://webhook.target | |||
</td> | |||
<td> | |||
yes | |||
</td> | |||
<td> | |||
<WebhookItemLatestDelivery | |||
webhook={ | |||
{ | |||
"hasSecret": true, | |||
"key": "1", | |||
"name": "my webhook", | |||
"url": "http://webhook.target", | |||
} | |||
} | |||
/> | |||
</td> | |||
<td | |||
className="sw-text-right" | |||
> | |||
<WebhookActions | |||
onDelete={[MockFunction]} | |||
onUpdate={[MockFunction]} | |||
webhook={ | |||
{ | |||
"hasSecret": true, | |||
"key": "1", | |||
"name": "my webhook", | |||
"url": "http://webhook.target", | |||
} | |||
} | |||
/> | |||
</td> | |||
</tr> | |||
`; |
@@ -1,49 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should render correctly a failed delivery 1`] = ` | |||
<Fragment> | |||
<AlertErrorIcon | |||
className="text-text-top" | |||
/> | |||
<span | |||
className="spacer-left display-inline-flex-center" | |||
> | |||
<DateTimeFormatter | |||
date="12.02.2018" | |||
/> | |||
<ButtonIcon | |||
className="button-small little-spacer-left" | |||
onClick={[Function]} | |||
> | |||
<BulletListIcon /> | |||
</ButtonIcon> | |||
</span> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly a success delivery 1`] = ` | |||
<Fragment> | |||
<AlertSuccessIcon | |||
className="text-text-top" | |||
/> | |||
<span | |||
className="spacer-left display-inline-flex-center" | |||
> | |||
<DateTimeFormatter | |||
date="12.02.2018" | |||
/> | |||
<ButtonIcon | |||
className="button-small little-spacer-left" | |||
onClick={[Function]} | |||
> | |||
<BulletListIcon /> | |||
</ButtonIcon> | |||
</span> | |||
</Fragment> | |||
`; | |||
exports[`should render correctly when no latest delivery 1`] = ` | |||
<span> | |||
webhooks.last_execution.none | |||
</span> | |||
`; |
@@ -1,63 +0,0 @@ | |||
// Jest Snapshot v1, https://goo.gl/fbAQLP | |||
exports[`should correctly render empty webhook list 1`] = ` | |||
<p> | |||
webhooks.no_result | |||
</p> | |||
`; | |||
exports[`should correctly render the webhooks 1`] = ` | |||
<table | |||
className="data zebra" | |||
> | |||
<thead> | |||
<tr> | |||
<th> | |||
name | |||
</th> | |||
<th> | |||
webhooks.url | |||
</th> | |||
<th> | |||
webhooks.secret_header | |||
</th> | |||
<th> | |||
webhooks.last_execution | |||
</th> | |||
<th | |||
className="sw-text-right" | |||
> | |||
actions | |||
</th> | |||
</tr> | |||
</thead> | |||
<tbody> | |||
<WebhookItem | |||
key="2" | |||
onDelete={[MockFunction]} | |||
onUpdate={[MockFunction]} | |||
webhook={ | |||
{ | |||
"hasSecret": false, | |||
"key": "2", | |||
"name": "jenkins webhook", | |||
"url": "http://jenkins.target", | |||
} | |||
} | |||
/> | |||
<WebhookItem | |||
key="1" | |||
onDelete={[MockFunction]} | |||
onUpdate={[MockFunction]} | |||
webhook={ | |||
{ | |||
"hasSecret": false, | |||
"key": "1", | |||
"name": "my webhook", | |||
"url": "http://webhook.target", | |||
} | |||
} | |||
/> | |||
</tbody> | |||
</table> | |||
`; |
@@ -4791,6 +4791,7 @@ webhooks.delivery.server_unreachable=Server Unreachable | |||
webhooks.documentation_link=Webhooks documentation | |||
webhooks.last_execution=Last delivery | |||
webhooks.last_execution.none=Never | |||
webhooks.last_execution.open_for_x=Open last delivery of {0} | |||
webhooks.latest_delivery_for_x=Last delivery of {0} | |||
webhooks.maximum_reached=You reached your maximum number of {0} webhooks. You can still update or delete an existing one. | |||
webhooks.name=Name |