Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions tests/ui/job-view/details/summary/ActionBar_test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { render, screen } from '@testing-library/react';

import { ActionBar } from '../../../../../ui/job-view/details/summary/ActionBar';
import {
usePushesStore,
initialState as pushesInitialState,
} from '../../../../../ui/job-view/stores/pushesStore';

const baseProps = {
selectedJobFull: {
id: 1,
task_id: 'TASK_ID',
state: 'completed',
push_id: 1,
resultStatus: 'success',
job_group_name: 'Build',
job_type_name: 'build-linux',
job_type_symbol: 'B',
submit_timestamp: 0,
},
user: { isLoggedIn: true, email: 'me@example.com' },
logParseStatus: 'parsed',
currentRepo: { name: 'autoland', tc_root_url: 'https://tc.example' },
jobLogUrls: [],
jobDetails: [],
};

describe('ActionBar expired-task disabling', () => {
beforeEach(() => {
usePushesStore.setState({
...pushesInitialState,
decisionTaskMap: { 1: { id: 'DEC_TASK' } },
});
});

it('does not disable Retrigger by default', () => {
render(<ActionBar {...baseProps} />);

expect(screen.getByTitle(/^Retrigger job/)).not.toBeDisabled();
});

it('disables Retrigger when taskExpired is true', () => {
render(<ActionBar {...baseProps} taskExpired />);

const retrigger = screen.getByTitle(/^Retrigger job/);
expect(retrigger).toBeDisabled();
expect(retrigger.getAttribute('title')).toContain(
'Taskcluster task expired',
);
});
});
29 changes: 29 additions & 0 deletions tests/ui/job-view/details/summary/LogItem.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,35 @@ describe('LogItem', () => {
});
});

describe('expired Taskcluster task', () => {
it('renders a disabled button with an expired tooltip even when the log was parsed', () => {
const logUrls = [createLogUrl({ parse_status: 'parsed' })];

render(
<LogItem
logUrls={logUrls}
logKey="logviewer"
logDescription="log"
logViewerUrl="/logviewer/1"
logViewerFullUrl="https://example.com/logviewer/1"
taskExpired
>
View Log
</LogItem>,
);

// No active link should be rendered
expect(screen.queryByTestId('logviewer-btn')).not.toBeInTheDocument();

const button = screen.getByRole('button');
expect(button).toHaveClass('disabled');
expect(button).toHaveAttribute(
'title',
'Taskcluster task expired — log no longer available',
);
});
});

describe('list item wrapper', () => {
it('renders inside an li element', () => {
const logUrls = [createLogUrl()];
Expand Down
27 changes: 27 additions & 0 deletions tests/ui/job-view/details/summary/StatusPanel_test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { render, screen } from '@testing-library/react';

import StatusPanel from '../../../../../ui/job-view/details/summary/StatusPanel';

const baseJob = {
resultStatus: 'success',
result: 'success',
state: 'completed',
};

describe('StatusPanel', () => {
it('does not show the Taskcluster expired badge by default', () => {
render(<StatusPanel selectedJobFull={baseJob} />);

expect(
screen.queryByTestId('taskcluster-expired-badge'),
).not.toBeInTheDocument();
});

it('shows the Taskcluster expired badge when taskExpired is true', () => {
render(<StatusPanel selectedJobFull={baseJob} taskExpired />);

const badge = screen.getByTestId('taskcluster-expired-badge');
expect(badge).toBeInTheDocument();
expect(badge).toHaveTextContent('Expired');
});
});
65 changes: 65 additions & 0 deletions tests/ui/job-view/details/summary/SummaryPanel_test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { render, screen } from '@testing-library/react';
import { MemoryRouter } from 'react-router';

import SummaryPanel from '../../../../../ui/job-view/details/summary/SummaryPanel';
import {
usePushesStore,
initialState as pushesInitialState,
} from '../../../../../ui/job-view/stores/pushesStore';

const selectedJobFull = {
id: 1,
task_id: 'TASK_ID',
state: 'completed',
result: 'success',
resultStatus: 'success',
push_id: 1,
searchStr: 'test job',
submit_timestamp: 0,
job_type_name: 'build-linux',
job_group_name: 'Build',
job_type_symbol: 'B',
build_platform: 'linux',
};

const renderPanel = (extraProps = {}) =>
render(
<MemoryRouter>
<SummaryPanel
selectedJobFull={selectedJobFull}
currentRepo={{ name: 'autoland', tc_root_url: 'https://tc.example' }}
classificationMap={{}}
bugs={[]}
user={{ isLoggedIn: true, email: '' }}
jobLogUrls={[{ name: 'live_backing.log', parse_status: 'parsed' }]}
logParseStatus="parsed"
{...extraProps}
/>
</MemoryRouter>,
);

describe('SummaryPanel log parsing status', () => {
beforeEach(() => {
usePushesStore.setState({
...pushesInitialState,
decisionTaskMap: { 1: { id: 'DEC_TASK' } },
});
});

it('shows the parse status when the task is not expired', () => {
renderPanel();

expect(screen.getByText('Log parsing status:')).toBeInTheDocument();
expect(screen.getByText('parsed')).toBeInTheDocument();
expect(
screen.queryByText('Expired, not available'),
).not.toBeInTheDocument();
});

it('shows an expired message for the log status when the task is expired', () => {
renderPanel({ taskExpired: true });

expect(screen.getByText('Expired, not available')).toBeInTheDocument();
expect(screen.queryByText('parsed')).not.toBeInTheDocument();
});
});
54 changes: 54 additions & 0 deletions tests/ui/job-view/details/useJobDetails.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Queue } from 'taskcluster-client-web';

import { fetchTaskData } from '../../../../ui/job-view/details/useJobDetails';

describe('fetchTaskData', () => {
let consoleErrorSpy;

beforeEach(() => {
consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
});

afterEach(() => {
consoleErrorSpy.mockRestore();
});

it('returns defaults without args and does not mark expired', async () => {
expect(await fetchTaskData(null, null)).toEqual({
testGroups: [],
taskQueueId: null,
taskExpired: false,
});
});

it('marks taskExpired when the Taskcluster task lookup fails', async () => {
Queue.mockImplementationOnce(() => ({
task: jest.fn().mockRejectedValue(new Error('404: task not found')),
}));

const result = await fetchTaskData('EXPIRED_TASK_ID', 'https://tc.example');

expect(result).toEqual({
testGroups: [],
taskQueueId: null,
taskExpired: true,
});
});

it('returns task data with taskExpired false on success', async () => {
Queue.mockImplementationOnce(() => ({
task: jest.fn().mockResolvedValue({
taskQueueId: 'gecko-3/b-linux',
payload: { env: {} },
}),
}));

const result = await fetchTaskData('LIVE_TASK', 'https://tc.example');

expect(result).toEqual({
testGroups: [],
taskQueueId: 'gecko-3/b-linux',
taskExpired: false,
});
});
});
43 changes: 43 additions & 0 deletions tests/ui/logviewer/ClassicLogViewer_test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { render, screen } from '@testing-library/react';

import ClassicLogViewer from '../../../ui/logviewer/ClassicLogViewer';

describe('ClassicLogViewer error states', () => {
beforeEach(() => {
global.fetch = jest.fn();
});

afterEach(() => {
delete global.fetch;
});

it('shows an expired message when the log fetch returns 404', async () => {
global.fetch.mockResolvedValue({
ok: false,
status: 404,
statusText: 'Not Found',
});

render(<ClassicLogViewer url="https://example.com/expired.log" />);

expect(
await screen.findByText(
'This log has expired and is no longer available.',
),
).toBeInTheDocument();
});

it('shows the generic error message for non-404 failures', async () => {
global.fetch.mockResolvedValue({
ok: false,
status: 500,
statusText: 'Server Error',
});

render(<ClassicLogViewer url="https://example.com/broken.log" />);

expect(
await screen.findByText(/Error loading log:/),
).toBeInTheDocument();
});
});
12 changes: 12 additions & 0 deletions tests/ui/logviewer/useLogViewer_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,22 @@ describe('useLogViewer', () => {
await waitFor(() => expect(result.current.isLoading).toBe(false));

expect(result.current.error).toBe('Failed to fetch log: 404 Not Found');
expect(result.current.errorStatus).toBe(404);
expect(result.current.lines).toEqual([]);
expect(result.current.lineCount).toBe(0);
});

test('errorStatus is null for non-HTTP failures', async () => {
global.fetch.mockRejectedValue(new Error('Network down'));

const { result } = renderHook(() => useLogViewer({ url: 'http://bad.txt' }));

await waitFor(() => expect(result.current.isLoading).toBe(false));

expect(result.current.error).toBe('Network down');
expect(result.current.errorStatus).toBeNull();
});

test('returns empty state when no URL', () => {
const { result } = renderHook(() => useLogViewer({}));

Expand Down
2 changes: 2 additions & 0 deletions ui/job-view/details/DetailsPanel.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ function DetailsPanel({
classifications,
testGroups,
bugs,
taskExpired,
} = useJobDetails(selectedJob, currentRepo, pushList, frameworks);

const togglePinBoardVisibility = useCallback(() => {
Expand Down Expand Up @@ -92,6 +93,7 @@ function DetailsPanel({
logViewerFullUrl={logViewerFullUrl}
bugs={bugs}
user={user}
taskExpired={taskExpired}
/>
<span className="job-tabs-divider" />
<TabsPanel
Expand Down
Loading