blob: 7ebee278f90b714a36c45cb599581a63c79d1e40 [file] [log] [blame]
import './index';
import { expect } from 'chai';
import { $, $$ } from '../../../infra-sk/modules/dom';
import {
setUpElementUnderTest,
eventPromise,
setQueryString,
} from '../../../infra-sk/modules/test_util';
import {
incrementalResponse0,
responseMultiCommitTask,
responseNoncontiguousCommitsTask,
responseTasksToFilter,
branch0,
branch1,
commentCommit,
commentTask,
commentTaskSpec,
incrementalResponse1,
resetResponse0,
} from '../rpc-mock/test_data';
import {
GetIncrementalCommitsRequest,
GetIncrementalCommitsResponse,
} from '../rpc';
import { CommitsTableSk } from './commits-table-sk';
import { MockStatusService, SetupMocks } from '../rpc-mock';
import { SetTestSettings } from '../settings';
describe('commits-table-sk', () => {
const newTableInstance = setUpElementUnderTest('commits-table-sk');
SetTestSettings({
swarmingUrl: 'example.com/swarming',
treeStatusBaseUrl: 'example.com/treestatus',
logsUrlTemplate:
'https://ci.chromium.org/raw/build/logs.chromium.org/skia/{{TaskID}}/+/annotations',
taskSchedulerUrl: 'example.com/ts',
defaultRepo: 'skia',
repos: new Map([
['skia', 'https://skia.googlesource.com/skia/+show/'],
['infra', 'https://skia.googlesource.com/buildbot/+show/'],
['skcms', 'https://skia.googlesource.com/skcms/+show/'],
]),
});
let mocks: MockStatusService;
beforeEach(async () => {
mocks = SetupMocks();
// Clear Url between tests.
history.replaceState(
null,
'',
window.location.origin + window.location.pathname
);
});
afterEach(async () => {
expect(mocks.exhausted()).to.be.true;
});
const setupWithResponse = async (
resp: GetIncrementalCommitsResponse,
validator?: (req: GetIncrementalCommitsRequest) => void
) => {
mocks.expectGetIncrementalCommits(resp, validator);
const ep = eventPromise('end-task');
const table = newTableInstance(
(el) => ((<CommitsTableSk>el).filter = 'All')
) as CommitsTableSk;
await ep;
return table;
};
it('displays multiple commit tasks', async () => {
const table = await setupWithResponse(responseMultiCommitTask);
expect($('.task', table)).to.have.length(1);
expect($$('.task', table)!.classList.value).to.include('bg-failure');
});
it('displays noncontiguous tasks', async () => {
const table = await setupWithResponse(responseNoncontiguousCommitsTask);
expect($('.multicommit-task', table)).to.have.length(1);
const multicommitDiv = $$('.multicommit-task', table)!;
// Parent div holds one div per commit, and one for the gap.
expect($('.task', multicommitDiv)).to.have.length(3);
expect($('.task.dashed-bottom', multicommitDiv)).to.have.length(1);
expect($('.task.hidden', multicommitDiv)).to.have.length(1);
expect($('.task.dashed-top', multicommitDiv)).to.have.length(1);
});
it('displays commits', async () => {
const table = await setupWithResponse(incrementalResponse0);
const commitDivs = $('.commit', table);
expect(commitDivs).to.have.length(5);
// The commit divs, when sorted by vertical position, match the order of the original commits.
expect(
commitDivs
.sort(
(a, b) =>
a.getBoundingClientRect().top - b.getBoundingClientRect().top
)
// Get hash from class list.
.map((el) => el.classList.item(1))
).to.deep.equal(
incrementalResponse0.update!.commits!.map((c) => `commit-${c.hash}`)
);
});
it('displays icons', async () => {
const table = await setupWithResponse(incrementalResponse0);
expect($('.tasksTable comment-icon-sk', table)).to.have.length(3);
expect(
$('.commit-parentofabc123.commit comment-icon-sk', table)
).to.have.length(1);
expect(
$('.commit-parentofabc123.commit block-icon-sk', table)
).to.have.length(1);
expect(
$('.task-spec[title="Build-Some-Stuff"] comment-icon-sk', table)
).to.have.length(1);
expect(
$('.task[title="Build-Some-Stuff @ abc123"] comment-icon-sk', table)
).to.have.length(1);
});
it('highlights reverts/relands', async () => {
const table = await setupWithResponse(incrementalResponse0);
expect($('.commit-bad.commit undo-icon-sk', table)).to.have.length(1);
const revertedCommitDiv = $$('.commit-1revertbad.commit', table)!;
$$('.commit-bad.commit undo-icon-sk', table)!.dispatchEvent(
new Event('mouseenter', {})
);
expect(revertedCommitDiv.classList.value).to.include('highlight-revert');
$$('.commit-bad.commit undo-icon-sk', table)!.dispatchEvent(
new Event('mouseleave', {})
);
expect(revertedCommitDiv.classList.value).to.not.include(
'highlight-revert'
);
const relandCommitDiv = $$('.commit-relandbad.commit', table)!;
$$('.commit-bad.commit redo-icon-sk', table)!.dispatchEvent(
new Event('mouseenter', {})
);
expect(relandCommitDiv.classList.value).to.include('highlight-reland');
$$('.commit-bad.commit redo-icon-sk', table)!.dispatchEvent(
new Event('mouseleave', {})
);
expect(relandCommitDiv.classList.value).to.not.include('highlight-reland');
});
it('filters task specs', async () => {
const table = await setupWithResponse(responseTasksToFilter);
expect(
$('.task-spec', table).map((el) => el.getAttribute('title'))
).to.have.deep.members([
'Always-Green-Spec',
'Always-Red-Spec',
'Interesting-Spec',
'Only-Failed-On-Commented-Commit-Spec',
]);
const clickLabel = (i: number, expectText: string) => {
const label = $('tabs-sk button', table)[i] as HTMLLabelElement;
expect(label.innerText).to.contain(expectText);
label.click();
};
clickLabel(0, 'Interesting');
expect(
$('.task-spec', table).map((el) => el.getAttribute('title'))
).to.have.deep.members(['Interesting-Spec']);
clickLabel(1, 'Failures');
expect(
$('.task-spec', table).map((el) => el.getAttribute('title'))
).to.have.deep.members([
'Always-Red-Spec',
'Interesting-Spec',
'Only-Failed-On-Commented-Commit-Spec',
]);
clickLabel(2, 'Comments');
expect(
$('.task-spec', table).map((el) => el.getAttribute('title'))
).to.have.deep.members(['Always-Red-Spec']);
clickLabel(3, 'Failing w/o comment');
expect(
$('.task-spec', table).map((el) => el.getAttribute('title'))
).to.have.deep.members(['Interesting-Spec']);
const searchbox = $$('.controls input-sk input', table) as HTMLInputElement;
searchbox.value = 'Always';
const ep = eventPromise('change');
searchbox.dispatchEvent(new Event('change', { bubbles: true }));
await ep;
expect(
$('.task-spec', table).map((el) => el.getAttribute('title'))
).to.have.deep.members(['Always-Green-Spec', 'Always-Red-Spec']);
});
it('incorporates incremental update', async () => {
const mocker =
SetupMocks().expectGetIncrementalCommits(incrementalResponse0);
const ep = eventPromise('end-task');
const table = newTableInstance(
(el) => ((<CommitsTableSk>el).filter = 'All')
) as CommitsTableSk;
await ep;
let commitDivs = $('.commit', table);
expect(commitDivs).to.have.length(5);
expect(
$('.task[title="Test-Some-Stuff @ parentofabc123"]', table)
).to.have.length(1);
expect(
$$('.task[title="Test-Some-Stuff @ parentofabc123"]', table)?.classList
.value
).to.contain('bg-failure');
// Mock an incremental update, and change the reload interval to trigger it.
mocker.expectGetIncrementalCommits(incrementalResponse1);
const reloadInput = $$('#reloadInput input', table) as HTMLInputElement;
reloadInput.dispatchEvent(new Event('change', { bubbles: true }));
// eventPromise for the same event 'end-task' seems to instantly resolve, so hack the delay.
await new Promise((resolve) => setTimeout(resolve, 0));
commitDivs = $('.commit', table);
expect(commitDivs).to.have.length(6);
// The commit divs, when sorted by vertical position, match the order of new commits followed
// by the the original commits.
expect(
commitDivs
.sort(
(a, b) =>
a.getBoundingClientRect().top - b.getBoundingClientRect().top
)
// Get hash from class list.
.map((el) => el.classList.item(1))
).to.deep.equal(
incrementalResponse1
.update!.commits!.map((c) => `commit-${c.hash}`)
.concat(
incrementalResponse0.update!.commits!.map((c) => `commit-${c.hash}`)
)
);
// New task is present.
expect(
$('.task[title="Build-Some-Stuff @ childofabc123"]', table)
).to.have.length(1);
// Old task is updated.
expect(
$$('.task[title="Test-Some-Stuff @ parentofabc123"]', table)?.classList
.value
).to.contain('bg-success');
});
it('resets with startOver update', async () => {
const mocker =
SetupMocks().expectGetIncrementalCommits(incrementalResponse0);
const ep = eventPromise('end-task');
const table = newTableInstance(
(el) => ((<CommitsTableSk>el).filter = 'All')
) as CommitsTableSk;
await ep;
let commitDivs = $('.commit', table);
expect(commitDivs).to.have.length(5);
// Mock an incremental update, and change the reload interval to trigger it.
mocker.expectGetIncrementalCommits(resetResponse0);
const reloadInput = $$('#reloadInput input', table) as HTMLInputElement;
reloadInput.dispatchEvent(new Event('change', { bubbles: true }));
// eventPromise for the same event 'end-task' seems to instantly resolve, so hack the delay.
await new Promise((resolve) => setTimeout(resolve, 0));
commitDivs = $('.commit', table);
expect(commitDivs).to.have.length(1);
// Only the new commit and it's single task are present.
expect(commitDivs[0].classList.toString()).to.contain(
resetResponse0.update!.commits![0].hash
);
expect(
$('.task[title="Build-Some-Stuff @ childofabc123"]', table)
).to.have.length(1);
});
it('initial request uses repo from query string', async () => {
setQueryString('?repo=infra');
const table = await setupWithResponse(
responseMultiCommitTask,
(req: GetIncrementalCommitsRequest) => {
expect(req.repoPath).to.equal('infra');
}
);
});
describe('dialog', () => {
it('opens and closes properly', async () => {
const table = await setupWithResponse(incrementalResponse0);
expect($$('details-dialog-sk', table)).to.have.nested.property(
'style.display',
''
);
(<HTMLDivElement>$$('[data-task-id="99999"]', table)).click();
expect($$('details-dialog-sk', table)).to.have.nested.property(
'style.display',
'block'
);
// Clicking somewhere in the dialog doesn't close it.
(<HTMLTableCellElement>$$('details-dialog-sk td', table)).click();
expect($$('details-dialog-sk', table)).to.have.nested.property(
'style.display',
'block'
);
// Clicking elsewhere does close it.
(<HTMLDivElement>$$('div.tasksTable', table)).click();
expect($$('details-dialog-sk', table)).to.have.nested.property(
'style.display',
'none'
);
});
it('displays tasks', async () => {
const table = await setupWithResponse(incrementalResponse0);
expect($('[data-task-id="99999"]', table)).to.have.length(1);
(<HTMLDivElement>$$('[data-task-id="99999"]', table)).click();
expect($$('details-dialog-sk .dialog h3', table)).to.have.property(
'innerText',
'Build-Some-Stuff'
);
expect(
$('details-dialog-sk .dialog table.blamelist tr', table)
).to.have.length(1);
expect(
$('details-dialog-sk .dialog table.comments tr.comment', table)
).to.have.length(1);
});
it('displays taskSpecs', async () => {
const table = await setupWithResponse(incrementalResponse0);
expect($('[title="Build-Some-Stuff"]', table)).to.have.length(1);
(<HTMLDivElement>$$('[title="Build-Some-Stuff"]', table)).click();
expect($$('details-dialog-sk .dialog h3', table)).to.have.property(
'innerText',
'Build-Some-Stuff'
);
expect(
$('details-dialog-sk .dialog table.comments tr.comment', table)
).to.have.length(1);
});
it('displays commits', async () => {
const table = await setupWithResponse(incrementalResponse0);
expect($('[data-commit-index="1"]', table)).to.have.length(1);
(<HTMLDivElement>$$('[data-commit-index="1"]', table)).click();
expect($$('details-dialog-sk .dialog h3', table)).to.have.property(
'innerText',
'2nd from HEAD'
);
expect(
$('details-dialog-sk .dialog table.comments tr.comment', table)
).to.have.length(1);
});
});
/**
* Extra set of tests that break TS rules to peek at the underlying data.
*/
describe('internal data', () => {
const internalData = async (): Promise<any> => {
const table = (await setupWithResponse(incrementalResponse0)) as any;
return table.data;
};
it('loads tasks correctly', async () => {
const commitsData = await internalData();
expect(commitsData.tasks.get('99999')).to.deep.equal({
commits: ['abc123'],
name: 'Build-Some-Stuff',
id: '99999',
revision: 'abc123',
status: 'SUCCESS',
swarmingTaskId: 'swarmy',
});
expect(commitsData.tasks.get('11111')).to.deep.equal({
commits: ['parentofabc123'],
id: '11111',
name: 'Test-Some-Stuff',
revision: 'parentofabc123',
status: 'FAILURE',
swarmingTaskId: 'swarmy',
});
expect(commitsData.tasks.get('77777')).to.deep.equal({
commits: ['acommitthatisnotlisted'],
id: '77777',
name: 'Upload-Some-Stuff',
revision: 'acommitthatisnotlisted',
status: 'SUCCESS',
swarmingTaskId: 'swarmy',
});
expect(commitsData.tasks).to.have.keys('99999', '11111', '77777');
});
it('loads ancillary data correctly', async () => {
const commitsData = await internalData();
expect(commitsData.branchHeads).to.deep.equal([branch0, branch1]);
});
it('extracts reverts and relands correctly', async () => {
const commitsData = await internalData();
expect(commitsData.revertedMap.get('bad')).to.include({
hash: '1revertbad',
});
expect(commitsData.relandedMap.get('bad')).to.include({
hash: 'relandbad',
});
});
it('extracts categories', async () => {
const commitsData = await internalData();
// Category 'Upload' is not included since no listed commits reference it.
expect(commitsData.categories).to.have.keys('Build', 'Test');
});
it('loads tasks by commit', async () => {
const commitsData = await internalData();
expect(commitsData.tasksByCommit).to.have.keys(
'abc123',
'parentofabc123',
'acommitthatisnotlisted'
);
expect(commitsData.tasksByCommit.get('abc123')).to.have.keys(
'Build-Some-Stuff'
);
// Task by Commit/TaskSpec reference same underlying object as task by id.
expect(
commitsData.tasksByCommit.get('abc123')!.get('Build-Some-Stuff')
).equal(commitsData.tasks.get('99999'));
});
it('loads comments', async () => {
const commitsData = await internalData();
// Category 'Upload' is not included since no listed commits reference it.
expect(commitsData.comments).to.have.keys(
commentCommit.commit,
commentTask.commit,
''
);
// TaskSpec comment.
expect(commitsData.comments.get('')).to.have.keys(
commentTaskSpec.taskSpecName
);
expect(
commitsData.comments.get('')!.get(commentTaskSpec.taskSpecName)![0]
).to.deep.include({
message: commentTaskSpec.message,
});
// Commit comment.
expect(commitsData.comments.get(commentCommit.commit)).to.have.keys('');
expect(
commitsData.comments.get(commentCommit.commit)!.get('')![0]
).to.deep.include({
message: commentCommit.message,
});
// Task comment.
expect(commitsData.comments.get(commentTask.commit)).to.have.keys(
commentTask.taskSpecName
);
expect(
commitsData.comments
.get(commentTask.commit)!
.get(commentTask.taskSpecName)![0]
).to.deep.include({ message: commentTask.message });
});
});
});