mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2026-05-13 06:20:24 +00:00
Add the ability to remove workflow runs, either using the UI or the HTTP API. Workflow runs can only be removed once a workflow run has completed. For security reasons, only a repository administrator or a token with `write:repository` permissions can remove runs. Resolves https://codeberg.org/forgejo/forgejo/issues/2184. ## Checklist The [contributor guide](https://forgejo.org/docs/next/contributor/) contains information that will be helpful to first time contributors. All work and communication must conform to Forgejo's [AI Agreement](https://codeberg.org/forgejo/governance/src/branch/main/AIAgreement.md). There also are a few [conditions for merging Pull Requests in Forgejo repositories](https://codeberg.org/forgejo/governance/src/branch/main/PullRequestsAgreement.md). You are also welcome to join the [Forgejo development chatroom](https://matrix.to/#/#forgejo-development:matrix.org). ### Tests for Go changes (can be removed for JavaScript changes) - I added test coverage for Go changes... - [x] in their respective `*_test.go` for unit tests. - [x] in the `tests/integration` directory if it involves interactions with a live Forgejo server. - I ran... - [x] `make pr-go` before pushing ### Tests for JavaScript changes (can be removed for Go changes) - I added test coverage for JavaScript changes... - [ ] in `web_src/js/*.test.js` if it can be unit tested. - [ ] in `tests/e2e/*.test.e2e.js` if it requires interactions with a live Forgejo server (see also the [developer guide for JavaScript testing](https://codeberg.org/forgejo/forgejo/src/branch/forgejo/tests/e2e/README.md#end-to-end-tests)). ### Documentation - [ ] I created a pull request [to the documentation](https://codeberg.org/forgejo/docs) to explain to Forgejo users how to use this change. - [x] I did not document these changes and I do not expect someone else to do it. ### Release notes - [x] This change will be noticed by a Forgejo user or admin (feature, bug fix, performance, etc.). I suggest to include a release note for this change. - [ ] This change is not visible to a Forgejo user or admin (refactor, dependency upgrade, etc.). I think there is no need to add a release note for this change. *The decision if the pull request will be shown in the release notes is up to the mergers / release team.* The content of the `release-notes/<pull request number>.md` file will serve as the basis for the release notes. If the file does not exist, the title of the pull request will be used instead. <!--start release-notes-assistant--> ## Release notes <!--URL:https://codeberg.org/forgejo/forgejo--> - Features - [PR](https://codeberg.org/forgejo/forgejo/pulls/12478): <!--number 12478 --><!--line 0 --><!--description bWFrZSBpdCBwb3NzaWJsZSB0byByZW1vdmUgd29ya2Zsb3cgcnVucw==-->make it possible to remove workflow runs<!--description--> <!--end release-notes-assistant--> Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/12478 Reviewed-by: Mathieu Fenniak <mfenniak@noreply.codeberg.org>
614 lines
21 KiB
JavaScript
614 lines
21 KiB
JavaScript
import {mount, flushPromises} from '@vue/test-utils';
|
|
import {toAbsoluteUrl} from '../utils.js';
|
|
import RepoActionView from './RepoActionView.vue';
|
|
|
|
const testLocale = {
|
|
approve: 'Locale Approve',
|
|
cancel: 'Locale Cancel',
|
|
rerun: 'Locale Re-run',
|
|
delete: 'Locale Delete',
|
|
confirmDelete: '',
|
|
artifactsTitle: 'artifactTitleHere',
|
|
areYouSure: '',
|
|
confirmDeleteArtifact: '',
|
|
rerun_all: '',
|
|
showTimeStamps: '',
|
|
showLogSeconds: '',
|
|
showFullScreen: '',
|
|
downloadLogs: '',
|
|
runAttemptLabel: 'Run attempt %[1]s %[2]s',
|
|
viewingOutOfDateRun: 'oh no, out of date since %[1]s give or take or so',
|
|
viewMostRecentRun: '',
|
|
preExecutionError: 'pre-execution error',
|
|
status: {
|
|
unknown: '',
|
|
waiting: '',
|
|
running: '',
|
|
success: '',
|
|
failure: '',
|
|
cancelled: '',
|
|
skipped: '',
|
|
blocked: '',
|
|
},
|
|
};
|
|
const minimalInitialJobData = {
|
|
state: {
|
|
run: {
|
|
status: 'success',
|
|
commit: {
|
|
pusher: {},
|
|
},
|
|
},
|
|
currentJob: {
|
|
steps: [
|
|
{
|
|
summary: 'Test Job',
|
|
duration: '1s',
|
|
status: 'success',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
logs: {
|
|
stepsLog: [],
|
|
},
|
|
};
|
|
const minimalInitialArtifactData = {
|
|
artifacts: [],
|
|
};
|
|
const defaultTestProps = {
|
|
actionsURL: 'https://example.com/example-org/example-repo/actions',
|
|
jobIndex: '1',
|
|
attemptNumber: '1',
|
|
runIndex: '10',
|
|
runID: '1001',
|
|
initialJobData: minimalInitialJobData,
|
|
initialArtifactData: minimalInitialArtifactData,
|
|
locale: testLocale,
|
|
workflowName: 'workflow name',
|
|
workflowURL: 'https://example.com/example-org/example-repo/actions?workflow=test.yml',
|
|
workflowSourceURL: 'https://example.com/example-org/example-repo/src/commit/023babec384/.forgejo/workflows/test.yml',
|
|
};
|
|
|
|
test('load multiple steps on a finished action', async () => {
|
|
Object.defineProperty(document.documentElement, 'lang', {value: 'en'});
|
|
vi.spyOn(global, 'fetch').mockImplementation((url, opts) => {
|
|
if (url.endsWith('/artifacts')) {
|
|
return Promise.resolve({
|
|
ok: true,
|
|
json: vi.fn().mockResolvedValue(
|
|
{
|
|
artifacts: [],
|
|
},
|
|
),
|
|
});
|
|
}
|
|
|
|
const postBody = JSON.parse(opts.body);
|
|
const stepsLog_value = [];
|
|
for (const cursor of postBody.logCursors) {
|
|
if (cursor.expanded) {
|
|
stepsLog_value.push(
|
|
{
|
|
step: cursor.step,
|
|
cursor: 0,
|
|
lines: [
|
|
{index: 1, message: `Step #${cursor.step + 1} Log #1`, timestamp: 0},
|
|
{index: 1, message: `Step #${cursor.step + 1} Log #2`, timestamp: 0},
|
|
{index: 1, message: `Step #${cursor.step + 1} Log #3`, timestamp: 0},
|
|
],
|
|
},
|
|
);
|
|
}
|
|
}
|
|
const jobs_value = {
|
|
state: {
|
|
run: {
|
|
status: 'success',
|
|
commit: {
|
|
pusher: {},
|
|
},
|
|
},
|
|
currentJob: {
|
|
title: 'test',
|
|
steps: [
|
|
{
|
|
summary: 'Test Step #1',
|
|
duration: '1s',
|
|
status: 'success',
|
|
},
|
|
{
|
|
summary: 'Test Step #2',
|
|
duration: '1s',
|
|
status: 'success',
|
|
},
|
|
],
|
|
allAttempts: [{number: 1, time_since_started_html: '', status: 'success', status_diagnostics: ['Success']}],
|
|
},
|
|
},
|
|
logs: {
|
|
stepsLog: opts.body?.includes('"cursor":null') ? stepsLog_value : [],
|
|
},
|
|
};
|
|
|
|
return Promise.resolve({
|
|
ok: true,
|
|
json: vi.fn().mockResolvedValue(
|
|
jobs_value,
|
|
),
|
|
});
|
|
});
|
|
|
|
const wrapper = mount(RepoActionView, {
|
|
props: defaultTestProps,
|
|
});
|
|
wrapper.vm.loadJob(); // simulate intermittent reload immediately so UI switches from minimalInitialJobData to the mock data from the test's fetch spy.
|
|
await flushPromises();
|
|
// Click on both steps to start their log loading in fast succession...
|
|
await wrapper.get('.job-step-section:nth-of-type(1) .job-step-summary').trigger('click');
|
|
await wrapper.get('.job-step-section:nth-of-type(2) .job-step-summary').trigger('click');
|
|
await flushPromises();
|
|
|
|
// Verify both step's logs were loaded
|
|
expect(wrapper.get('.job-step-section:nth-of-type(1) .job-log-line:nth-of-type(1) .log-msg').text()).toEqual('Step #1 Log #1');
|
|
expect(wrapper.get('.job-step-section:nth-of-type(1) .job-log-line:nth-of-type(2) .log-msg').text()).toEqual('Step #1 Log #2');
|
|
expect(wrapper.get('.job-step-section:nth-of-type(1) .job-log-line:nth-of-type(3) .log-msg').text()).toEqual('Step #1 Log #3');
|
|
expect(wrapper.get('.job-step-section:nth-of-type(2) .job-log-line:nth-of-type(1) .log-msg').text()).toEqual('Step #2 Log #1');
|
|
expect(wrapper.get('.job-step-section:nth-of-type(2) .job-log-line:nth-of-type(2) .log-msg').text()).toEqual('Step #2 Log #2');
|
|
expect(wrapper.get('.job-step-section:nth-of-type(2) .job-log-line:nth-of-type(3) .log-msg').text()).toEqual('Step #2 Log #3');
|
|
|
|
// Attempt status
|
|
expect(wrapper.get('.job-info-header h3').text()).toEqual('test');
|
|
expect(wrapper.findAll('ul.job-info-header-detail li').length).toEqual(1);
|
|
expect(wrapper.get('ul.job-info-header-detail li:nth-child(1)').text()).toEqual('Success');
|
|
});
|
|
|
|
function configureForMultipleAttemptTests({viewHistorical}) {
|
|
Object.defineProperty(document.documentElement, 'lang', {value: 'en'});
|
|
const myJobState = {
|
|
run: {
|
|
canApprove: true,
|
|
canCancel: true,
|
|
canRerun: true,
|
|
canDelete: false,
|
|
status: 'success',
|
|
commit: {
|
|
pusher: {},
|
|
},
|
|
},
|
|
currentJob: {
|
|
title: 'test',
|
|
steps: [
|
|
{
|
|
summary: 'Test Job',
|
|
duration: '1s',
|
|
status: 'success',
|
|
},
|
|
],
|
|
allAttempts: [
|
|
{number: 3, time_since_started_html: 'yesterday', status: 'success', status_diagnostics: ['Success']},
|
|
// Omit one attempt to simulate the case when a job isn't run because a `needs:` failed.
|
|
{number: 1, time_since_started_html: 'two days ago', status: 'failure', status_diagnostics: ['Failure']},
|
|
],
|
|
},
|
|
};
|
|
vi.spyOn(global, 'fetch').mockImplementation((url, opts) => {
|
|
const artifacts_value = {
|
|
artifacts: [],
|
|
};
|
|
const stepsLog_value = [
|
|
{
|
|
step: 0,
|
|
cursor: 0,
|
|
lines: [],
|
|
},
|
|
];
|
|
const jobs_value = {
|
|
state: myJobState,
|
|
logs: {
|
|
stepsLog: opts.body?.includes('"cursor":null') ? stepsLog_value : [],
|
|
},
|
|
};
|
|
|
|
return Promise.resolve({
|
|
ok: true,
|
|
json: vi.fn().mockResolvedValue(
|
|
url.endsWith('/artifacts') ? artifacts_value : jobs_value,
|
|
),
|
|
});
|
|
});
|
|
|
|
const wrapper = mount(RepoActionView, {
|
|
props: {
|
|
...defaultTestProps,
|
|
runIndex: '123',
|
|
attemptNumber: viewHistorical ? '1' : '3',
|
|
actionsURL: toAbsoluteUrl('/user1/repo2/actions'),
|
|
initialJobData: {...minimalInitialJobData, state: myJobState},
|
|
},
|
|
});
|
|
return wrapper;
|
|
}
|
|
|
|
test('display baseline with most-recent attempt', async () => {
|
|
const wrapper = configureForMultipleAttemptTests({viewHistorical: false});
|
|
await flushPromises();
|
|
|
|
// Warning dialog for viewing an out-of-date attempt...
|
|
expect(wrapper.findAll('.job-out-of-date-warning').length).toEqual(0);
|
|
|
|
// Approve button should be visible; can't have all three at once but at least this verifies the inverse of the
|
|
// historical attempt test below.
|
|
expect(wrapper.findAll('button').filter((button) => button.text() === 'Locale Approve').length).toEqual(1);
|
|
|
|
// Job list will be visible...
|
|
expect(wrapper.findAll('.job-group-section').length).toEqual(1);
|
|
|
|
// Attempt selector dropdown...
|
|
expect(wrapper.findAll('.job-attempt-dropdown').length).toEqual(1);
|
|
expect(wrapper.findAll('.job-attempt-dropdown .svg.octicon-check-circle-fill.text.green').length).toEqual(1);
|
|
expect(wrapper.get('.job-attempt-dropdown .ui.dropdown').text()).toEqual('Run attempt 3 yesterday');
|
|
|
|
// Attempt status
|
|
expect(wrapper.get('.job-info-header h3').text()).toEqual('test');
|
|
expect(wrapper.findAll('ul.job-info-header-detail li').length).toEqual(1);
|
|
expect(wrapper.get('ul.job-info-header-detail li:nth-child(1)').text()).toEqual('Success');
|
|
});
|
|
|
|
test('display reconfigured for historical attempt', async () => {
|
|
const wrapper = configureForMultipleAttemptTests({viewHistorical: true});
|
|
await flushPromises();
|
|
|
|
// Warning dialog for viewing an out-of-date attempt...
|
|
expect(wrapper.findAll('.job-out-of-date-warning').length).toEqual(1);
|
|
expect(wrapper.get('.job-out-of-date-warning').text()).toEqual('oh no, out of date since two days ago give or take or so');
|
|
await wrapper.get('.job-out-of-date-warning button').trigger('click');
|
|
expect(window.location.href).toEqual(toAbsoluteUrl('/user1/repo2/actions/runs/123/jobs/1'));
|
|
// eslint-disable-next-line no-restricted-globals
|
|
history.back();
|
|
await flushPromises();
|
|
|
|
// Approve, Cancel, Re-run all buttons should all be suppressed...
|
|
expect(wrapper.findAll('button').filter((button) => button.text() === 'Locale Approve').length).toEqual(0);
|
|
expect(wrapper.findAll('button').filter((button) => button.text() === 'Locale Cancel').length).toEqual(0);
|
|
expect(wrapper.findAll('button').filter((button) => button.text() === 'Locale Re-run').length).toEqual(0);
|
|
|
|
// Job list will be suppressed...
|
|
expect(wrapper.findAll('.job-group-section').length).toEqual(0);
|
|
|
|
// Attempt selector dropdown...
|
|
expect(wrapper.findAll('.job-attempt-dropdown').length).toEqual(1);
|
|
expect(wrapper.findAll('.job-attempt-dropdown .svg.octicon-x-circle-fill.text.red').length).toEqual(1);
|
|
expect(wrapper.get('.job-attempt-dropdown .ui.dropdown').text()).toEqual('Run attempt 1 two days ago');
|
|
|
|
// Attempt status
|
|
expect(wrapper.get('.job-info-header h3').text()).toEqual('test');
|
|
expect(wrapper.findAll('ul.job-info-header-detail li').length).toEqual(1);
|
|
expect(wrapper.get('ul.job-info-header-detail li:nth-child(1)').text()).toEqual('Failure');
|
|
});
|
|
|
|
test('historical attempt dropdown interactions', async () => {
|
|
const wrapper = configureForMultipleAttemptTests({viewHistorical: true});
|
|
await flushPromises();
|
|
|
|
// Check dropdown exists, but isn't expanded.
|
|
const attemptsNotExpanded = () => {
|
|
expect(wrapper.findAll('.job-attempt-dropdown').length).toEqual(1);
|
|
expect(wrapper.findAll('.job-attempt-dropdown .action-job-menu').length).toEqual(0, 'dropdown content not yet visible');
|
|
};
|
|
attemptsNotExpanded();
|
|
|
|
// Click on attempt dropdown
|
|
wrapper.get('.job-attempt-dropdown .ui.dropdown').trigger('click');
|
|
await flushPromises();
|
|
|
|
// Check dropdown is expanded and both options are displayed
|
|
const attemptsExpanded = () => {
|
|
expect(wrapper.findAll('.job-attempt-dropdown .action-job-menu').length).toEqual(1);
|
|
expect(wrapper.get('.job-attempt-dropdown .action-job-menu').isVisible()).toBe(true);
|
|
expect(wrapper.findAll('.job-attempt-dropdown .action-job-menu a').filter((a) => a.text() === 'Run attempt 3 yesterday').length).toEqual(1);
|
|
expect(wrapper.findAll('.job-attempt-dropdown .action-job-menu a').filter((a) => a.text() === 'Run attempt 1 two days ago').length).toEqual(1);
|
|
};
|
|
attemptsExpanded();
|
|
|
|
// Normally dismiss occurs on a body click event; simulate that by calling `closeDropdown()`
|
|
wrapper.vm.closeDropdown();
|
|
await flushPromises();
|
|
|
|
// Should return to not expanded.
|
|
attemptsNotExpanded();
|
|
|
|
// Click on the gear dropdown
|
|
wrapper.get('.job-gear-dropdown').trigger('click');
|
|
await flushPromises();
|
|
|
|
// Check that gear's menu is expanded, and attempt dropdown isn't.
|
|
expect(wrapper.findAll('.job-gear-dropdown .action-job-menu').length).toEqual(1);
|
|
expect(wrapper.get('.job-gear-dropdown .action-job-menu').isVisible()).toBe(true);
|
|
attemptsNotExpanded();
|
|
|
|
// Click on attempt dropdown
|
|
wrapper.get('.job-attempt-dropdown .ui.dropdown').trigger('click');
|
|
await flushPromises();
|
|
|
|
// Check that attempt dropdown expanded again, gear dropdown disappeared (mutually exclusive)
|
|
expect(wrapper.findAll('.job-gear-dropdown .action-job-menu').length).toEqual(0);
|
|
attemptsExpanded();
|
|
|
|
// Click on the other option in the dropdown to verify it navigates to the target attempt
|
|
wrapper.findAll('.job-attempt-dropdown .action-job-menu a').find((a) => a.text() === 'Run attempt 3 yesterday').trigger('click');
|
|
expect(window.location.href).toEqual(toAbsoluteUrl('/user1/repo2/actions/runs/123/jobs/1/attempt/3'));
|
|
});
|
|
|
|
test('run approval interaction', async () => {
|
|
const pullRequestLink = '/example-org/example-repo/pulls/456';
|
|
const wrapper = mount(RepoActionView, {
|
|
props: {
|
|
...defaultTestProps,
|
|
initialJobData: {
|
|
state: {
|
|
run: {
|
|
canApprove: true,
|
|
status: 'waiting',
|
|
commit: {
|
|
pusher: {},
|
|
branch: {
|
|
link: toAbsoluteUrl(pullRequestLink),
|
|
},
|
|
},
|
|
},
|
|
currentJob: {
|
|
steps: [
|
|
{
|
|
summary: 'Test Job',
|
|
duration: '1s',
|
|
status: 'success',
|
|
},
|
|
],
|
|
},
|
|
},
|
|
logs: {
|
|
stepsLog: [],
|
|
},
|
|
},
|
|
},
|
|
});
|
|
await flushPromises();
|
|
const approve = wrapper.findAll('button').filter((button) => button.text() === 'Locale Approve');
|
|
expect(approve.length).toEqual(1);
|
|
approve[0].trigger('click');
|
|
expect(window.location.href).toEqual(toAbsoluteUrl(`${pullRequestLink}#pull-request-trust-panel`));
|
|
});
|
|
|
|
test('artifacts download links', async () => {
|
|
Object.defineProperty(document.documentElement, 'lang', {value: 'en'});
|
|
vi.spyOn(global, 'fetch').mockImplementation((url, opts) => {
|
|
if (url.endsWith('/artifacts')) {
|
|
return Promise.resolve({
|
|
ok: true,
|
|
json: vi.fn().mockResolvedValue(
|
|
{
|
|
artifacts: [
|
|
{name: 'artifactname1', size: 111, status: 'completed'},
|
|
{name: 'artifactname2', size: 222, status: 'expired'},
|
|
],
|
|
},
|
|
),
|
|
});
|
|
}
|
|
|
|
const postBody = JSON.parse(opts.body);
|
|
const stepsLog_value = [];
|
|
for (const cursor of postBody.logCursors) {
|
|
if (cursor.expanded) {
|
|
stepsLog_value.push(
|
|
{
|
|
step: cursor.step,
|
|
cursor: 0,
|
|
lines: [
|
|
{index: 1, message: `Step #${cursor.step + 1} Log #1`, timestamp: 0},
|
|
],
|
|
},
|
|
);
|
|
}
|
|
}
|
|
const jobs_value = {
|
|
state: {
|
|
run: {
|
|
status: 'success',
|
|
commit: {
|
|
pusher: {},
|
|
},
|
|
},
|
|
currentJob: {
|
|
title: 'test',
|
|
steps: [
|
|
{
|
|
summary: 'Test Step #1',
|
|
duration: '1s',
|
|
status: 'success',
|
|
},
|
|
],
|
|
allAttempts: [{number: 1, time_since_started_html: '', status: 'success', status_diagnostics: ['Success']}],
|
|
},
|
|
},
|
|
logs: {
|
|
stepsLog: opts.body?.includes('"cursor":null') ? stepsLog_value : [],
|
|
},
|
|
};
|
|
|
|
return Promise.resolve({
|
|
ok: true,
|
|
json: vi.fn().mockResolvedValue(
|
|
jobs_value,
|
|
),
|
|
});
|
|
});
|
|
|
|
const wrapper = mount(RepoActionView, {
|
|
props: defaultTestProps,
|
|
});
|
|
wrapper.vm.loadJob(); // simulate intermittent reload immediately so UI switches from minimalInitialJobData to the mock data from the test's fetch spy.
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('.job-artifacts .job-artifacts-title').text()).toEqual('artifactTitleHere');
|
|
expect(wrapper.get('.job-artifacts .job-artifacts-item:nth-of-type(1) .job-artifacts-link').attributes('href')).toEqual('https://example.com/example-org/example-repo/actions/runs/1001/artifacts/artifactname1');
|
|
expect(wrapper.get('.job-artifacts .job-artifacts-item:nth-of-type(2) .job-artifacts-link').attributes('href')).toEqual('https://example.com/example-org/example-repo/actions/runs/1001/artifacts/artifactname2');
|
|
});
|
|
|
|
test('initial load schedules refresh when job is not done', async () => {
|
|
Object.defineProperty(document.documentElement, 'lang', {value: 'en'});
|
|
vi.spyOn(global, 'fetch').mockImplementation((url, _opts) => {
|
|
return Promise.resolve({
|
|
ok: true,
|
|
json: vi.fn().mockResolvedValue(
|
|
url.endsWith('/artifacts') ? minimalInitialArtifactData : minimalInitialJobData,
|
|
),
|
|
});
|
|
});
|
|
|
|
// Provide a job that is "done" so that the component doesn't start incremental refresh...
|
|
{
|
|
const doneInitialJobData = structuredClone(minimalInitialJobData);
|
|
doneInitialJobData.state.run.done = true;
|
|
const wrapper = mount(RepoActionView, {
|
|
props: {
|
|
...defaultTestProps,
|
|
initialJobData: doneInitialJobData,
|
|
},
|
|
});
|
|
await flushPromises();
|
|
const container = wrapper.find('.action-view-container');
|
|
expect(container.exists()).toBe(true);
|
|
expect(container.classes()).not.toContain('interval-pending');
|
|
wrapper.unmount();
|
|
}
|
|
|
|
// Provide a job that is *not* "done" so that the component does start incremental refresh...
|
|
{
|
|
const runningInitialJobData = structuredClone(minimalInitialJobData);
|
|
runningInitialJobData.state.run.done = false;
|
|
const wrapper = mount(RepoActionView, {
|
|
props: defaultTestProps,
|
|
});
|
|
await flushPromises();
|
|
const container = wrapper.find('.action-view-container');
|
|
expect(container.exists()).toBe(true);
|
|
expect(container.classes()).toContain('interval-pending');
|
|
wrapper.unmount();
|
|
}
|
|
});
|
|
|
|
test('initial load data is used without calling fetch()', async () => {
|
|
Object.defineProperty(document.documentElement, 'lang', {value: 'en'});
|
|
const fetchSpy = vi.spyOn(global, 'fetch').mockImplementation((url, _opts) => {
|
|
return Promise.resolve({
|
|
ok: true,
|
|
json: vi.fn().mockResolvedValue(
|
|
url.endsWith('/artifacts') ? minimalInitialArtifactData : minimalInitialJobData,
|
|
),
|
|
});
|
|
});
|
|
|
|
mount(RepoActionView, {
|
|
props: defaultTestProps,
|
|
});
|
|
await flushPromises();
|
|
expect(fetchSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('view non-picked action run job', async () => {
|
|
Object.defineProperty(document.documentElement, 'lang', {value: 'en'});
|
|
const wrapper = mount(RepoActionView, {
|
|
props: {
|
|
...defaultTestProps,
|
|
initialJobData: {
|
|
...minimalInitialJobData,
|
|
// Definitions here should match the same type of content as the related backend test,
|
|
// "TestActionsViewViewPost/attempt out-of-bounds on non-picked task", so that we're sure we can handle in the
|
|
// UI what the backend will deliver in this case.
|
|
state: {
|
|
run: {
|
|
done: false,
|
|
status: 'waiting',
|
|
commit: {
|
|
pusher: {},
|
|
},
|
|
jobs: [
|
|
{
|
|
id: 161,
|
|
name: 'check-1',
|
|
status: 'waiting',
|
|
canRerun: false,
|
|
duration: '0s',
|
|
},
|
|
{
|
|
id: 162,
|
|
name: 'check-2',
|
|
status: 'waiting',
|
|
canRerun: false,
|
|
duration: '0s',
|
|
},
|
|
{
|
|
id: 163,
|
|
name: 'check-3',
|
|
status: 'waiting',
|
|
canRerun: false,
|
|
duration: '0s',
|
|
},
|
|
],
|
|
},
|
|
currentJob: {
|
|
title: 'check-1',
|
|
details: ['waiting (locale)'], // locale-specific, not exact match to backend test
|
|
steps: [],
|
|
allAttempts: null,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
await flushPromises();
|
|
|
|
expect(wrapper.get('.job-info-header-detail li:first-child').text()).toEqual('waiting (locale)');
|
|
expect(wrapper.get('.job-brief-list .job-brief-item:nth-of-type(1) .job-brief-name').text()).toEqual('check-1');
|
|
expect(wrapper.get('.job-brief-list .job-brief-item:nth-of-type(2) .job-brief-name').text()).toEqual('check-2');
|
|
expect(wrapper.get('.job-brief-list .job-brief-item:nth-of-type(3) .job-brief-name').text()).toEqual('check-3');
|
|
|
|
// Attempt status
|
|
expect(wrapper.get('.job-info-header h3').text()).toEqual('check-1');
|
|
expect(wrapper.findAll('ul.job-info-header-detail li').length).toEqual(1);
|
|
expect(wrapper.get('ul.job-info-header-detail li:nth-child(1)').text()).toEqual('waiting (locale)');
|
|
});
|
|
|
|
test('view without pre-execution error', async () => {
|
|
Object.defineProperty(document.documentElement, 'lang', {value: 'en'});
|
|
const wrapper = mount(RepoActionView, {
|
|
props: defaultTestProps,
|
|
});
|
|
await flushPromises();
|
|
expect(wrapper.find('.pre-execution-error').exists()).toBe(false);
|
|
});
|
|
|
|
test('view with pre-execution error', async () => {
|
|
Object.defineProperty(document.documentElement, 'lang', {value: 'en'});
|
|
const wrapper = mount(RepoActionView, {
|
|
props: {
|
|
...defaultTestProps,
|
|
initialJobData: {
|
|
...minimalInitialJobData,
|
|
state: {
|
|
...minimalInitialJobData.state,
|
|
run: {
|
|
...minimalInitialJobData.state.run,
|
|
preExecutionError: 'Oops, I dropped it.',
|
|
},
|
|
},
|
|
},
|
|
},
|
|
});
|
|
await flushPromises();
|
|
const block = wrapper.find('.pre-execution-error');
|
|
expect(block.exists()).toBe(true);
|
|
expect(block.text()).toBe('pre-execution error Oops, I dropped it.');
|
|
});
|