| <!DOCTYPE html> | |
| <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes"> | |
| <script src="/node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js"></script> | |
| <script src="../node_modules/@webcomponents/webcomponentsjs/webcomponents-loader.js"></script> | |
| <script src="../bower_components/web-component-tester/browser.js"></script> | |
| <title>gr-checks-item</title> | |
| <!-- Gr-overlay does not exist in the test framework | |
| It is expected to be provided by Gerrit core --> | |
| <script type="module"> | |
| import '../test/common-test-setup.js'; | |
| class GrOverlay extends Polymer.Element { | |
| static get is() { return 'gr-overlay'; } | |
| refit() {} | |
| open() {} | |
| } | |
| customElements.define(GrOverlay.is, GrOverlay); | |
| </script> | |
| <test-fixture id="basic"> | |
| <template> | |
| <gr-checks-view></gr-checks-view> | |
| </template> | |
| </test-fixture> | |
| <script type="module"> | |
| import './gr-checks-view.js'; | |
| import {Statuses} from './gr-checks-all-statuses.js'; | |
| const CHECK1 = { | |
| checkId: 'test-check-id', | |
| logUrl: 'http://example.com/test-log-url', | |
| startTime: '2019-02-06T22:25:19.269Z', | |
| finishTime: '2019-02-06T22:25:44.574Z', | |
| checker_name: 'test checker', | |
| state: 'RUNNING', | |
| checker_status: 'ENABLED', | |
| blocking: [], | |
| checker_description: 'No-op jobs for testing purposes', | |
| message: '\n\nChange-Id: I8df212a28ae23cc239afd10ee4f506887e03ab70\n', | |
| showCheckMessage: undefined, | |
| checker_uuid: 'codestyle-a6a0e4682515f3521897c5f950d1394f4619d928', | |
| }; | |
| const CHECK2 = { | |
| checkId: 'test-check-id2', | |
| logUrl: 'http://example.com/test-log-url', | |
| startTime: '2019-02-06T22:25:19.269Z', | |
| finishTime: '2019-02-06T22:25:44.574Z', | |
| checker_name: 'test checker2', | |
| state: 'RUNNING', | |
| checker_status: 'ENABLED', | |
| blocking: [], | |
| checker_description: 'No-op jobs for testing purposes 2', | |
| message: '\n\nChange-Id: I8df212a28ae23cc239afd10ee4f506887e03ab70\n', | |
| showCheckMessage: undefined, | |
| checker_uuid: 'polygerrit-a6a0e4682515f3521897c5f950d1394f4619d928', | |
| }; | |
| const REVISION = { | |
| kind: 'REWORK', | |
| _number: 3, | |
| created: '2018-05-15 21:56:13.000000000', | |
| uploader: { | |
| _account_id: 1000000, | |
| }, | |
| ref: 'refs/changes/00/1000/1', | |
| commit: { | |
| parents: [], | |
| subject: '', | |
| message: '\n\nChange-Id: I8df212a28ae23cc239afd10ee4f506887e03ab70\n', | |
| commit: '1c9a1dfd38ea51dc7880f3ddf669100710f0c91b', | |
| }, | |
| }; | |
| const REVISION2 = { | |
| kind: 'REWORK', | |
| _number: 2, | |
| created: '2018-05-15 21:56:13.000000000', | |
| uploader: { | |
| _account_id: 1000000, | |
| }, | |
| ref: 'refs/changes/00/1000/2', | |
| commit: { | |
| parents: [], | |
| subject: '', | |
| message: '\n\nChange-Id: I8df212a28ae23cc239afd10ee4f506887e03ab70\n', | |
| commit: '1c9a1dfd38ea51dc6880f3ddf669100710f0c91b', | |
| }, | |
| }; | |
| const CHECKS_POLL_INTERVAL_MS = 60 * 1000; | |
| const STATE_ALL = 'ALL'; | |
| const CHECKS_LIMIT = 20; | |
| suite('gr-checks-view tests', () => { | |
| let element; | |
| let sandbox; | |
| let getChecksSpy; | |
| let getChecksResolve; | |
| let retryCheckSpy; | |
| let isConfiguredSpy; | |
| let isConfiguredResolve; | |
| let getAccountSpy; | |
| let getAccountResolve; | |
| let fetchJSONSpy; | |
| let getAccountCapabilitiesSpy; | |
| let getAccountCapabilitiesResolve; | |
| let postSpy; | |
| let postReject; | |
| setup(done => { | |
| sandbox = sinon.sandbox.create(); | |
| getChecksSpy = sinon.stub(); | |
| const getChecksPromise = new Promise((resolve, reject) => { | |
| getChecksResolve = resolve; | |
| }); | |
| getChecksSpy.returns(getChecksPromise); | |
| isConfiguredSpy = sinon.stub(); | |
| const isConfiguredPromise = new Promise((resolve, reject) => { | |
| isConfiguredResolve = resolve; | |
| }); | |
| isConfiguredSpy.returns(isConfiguredPromise); | |
| retryCheckSpy = sinon.stub(); | |
| retryCheckSpy.returns(Promise.resolve()); | |
| const plugin = {}; | |
| getAccountSpy = sinon.stub(); | |
| const getAccountPromise = new Promise((resolve, reject) => { | |
| getAccountResolve = resolve; | |
| }); | |
| getAccountSpy.returns(getAccountPromise); | |
| postSpy = sinon.stub(); | |
| const postPromise = new Promise((resolve, reject) => { | |
| postReject = reject; | |
| }); | |
| postSpy.returns(postPromise); | |
| fetchJSONSpy = sinon.stub(); | |
| const fetchJSONPromise = new Promise(() => {}); | |
| fetchJSONSpy.returns(fetchJSONPromise); | |
| getAccountCapabilitiesSpy = sinon.stub(); | |
| const getAccountCapabilitiesPromise = new Promise((resolve, reject) => { | |
| getAccountCapabilitiesResolve = resolve; | |
| }); | |
| getAccountCapabilitiesSpy.returns(getAccountCapabilitiesPromise); | |
| plugin.restApi = () => ({ | |
| getAccount: getAccountSpy, | |
| fetchJSON: fetchJSONSpy, | |
| getAccountCapabilities: getAccountCapabilitiesSpy, | |
| post: postSpy, | |
| }); | |
| element = fixture('basic'); | |
| Object.assign(element, { | |
| retryCheck: retryCheckSpy, | |
| getChecks: getChecksSpy, | |
| isConfigured: isConfiguredSpy, | |
| change: { | |
| project: 'test-repository', | |
| _number: 2, | |
| revisions: { | |
| 'first-sha': REVISION2, | |
| 'second-sha': REVISION, | |
| }, | |
| }, | |
| plugin, | |
| revision: REVISION, | |
| }); | |
| flush(done); | |
| }); | |
| teardown(() => { sandbox.restore(); }); | |
| test('renders loading', () => { | |
| // Element also contains the hidden gr-overlay hence use includes | |
| assert(element.shadowRoot.textContent.trim().includes('Loading...')); | |
| }); | |
| test('queries the checks', () => { | |
| assert.isTrue(getChecksSpy.called); | |
| assert.isTrue(getChecksSpy.calledWith(2, 3)); | |
| }); | |
| suite('no checks returned', () => { | |
| setup(done => { | |
| getChecksResolve([]); | |
| flush(done); | |
| }); | |
| test('it calls to check if the checks are configured', () => { | |
| assert.isTrue(isConfiguredSpy.called); | |
| assert.isTrue(isConfiguredSpy.calledWith('test-repository')); | |
| }); | |
| test('no configure button renders', () => { | |
| assert(!element.$$('gr-button')); | |
| }); | |
| suite('not configured', () => { | |
| setup(done => { | |
| isConfiguredResolve(false); | |
| flush(done); | |
| }); | |
| test('renders checks not configured', () => { | |
| const header = element.$$('h2'); | |
| assert.equal(header.textContent.trim(), | |
| 'Code review checks not configured'); | |
| }); | |
| suite('create checker capability false', () => { | |
| setup(done => { | |
| getAccountResolve(true); | |
| getAccountCapabilitiesResolve({'checks-administrateCheckers': false}); | |
| flush(done); | |
| }); | |
| test('checker button does not render', () => { | |
| assert(!element.$$('gr-button')); | |
| }); | |
| }); | |
| suite('create checker capability true', () => { | |
| setup(done => { | |
| getAccountResolve(true); | |
| getAccountCapabilitiesResolve({'checks-administrateCheckers': true}); | |
| flush(done); | |
| }); | |
| test('checker button renders', () => { | |
| assert(element.$$('gr-button')); | |
| }); | |
| }); | |
| }); | |
| suite('no checks ran', () => { | |
| setup(done => { | |
| isConfiguredResolve(true); | |
| flush(done); | |
| }); | |
| test('renders checks not configured', () => { | |
| const header = element.shadowRoot.querySelector('h2'); | |
| assert.equal(header.textContent.trim(), | |
| 'No checks ran for this code review'); | |
| }); | |
| }); | |
| }); | |
| suite('checks updated properly', () => { | |
| setup(done => { | |
| element._checks = [CHECK1, CHECK2]; | |
| flush(done); | |
| }); | |
| test('message is updated', () => { | |
| const NEW_CHECKS = [Object.assign({}, CHECK1), | |
| Object.assign({}, CHECK2)]; | |
| NEW_CHECKS[0].message = 'New message 1'; | |
| NEW_CHECKS[1].message = 'New message 2'; | |
| const EXPECTED_CHECKS = [Object.assign({}, CHECK1), | |
| Object.assign({}, CHECK2)]; | |
| EXPECTED_CHECKS[0].message = 'New message 1'; | |
| EXPECTED_CHECKS[1].message = 'New message 2'; | |
| const UPDATED_CHECKS = element._updateChecks(NEW_CHECKS); | |
| assert.equal(UPDATED_CHECKS[0].message, NEW_CHECKS[0].message); | |
| assert.equal(UPDATED_CHECKS[1].message, NEW_CHECKS[1].message); | |
| }); | |
| test('total checks updated if one is deleted', () => { | |
| const NEW_CHECKS = [Object.assign({}, CHECK1)]; | |
| const EXPECTED_CHECKS = [Object.assign({}, CHECK1)]; | |
| const UPDATED_CHECKS = element._updateChecks(NEW_CHECKS); | |
| assert.equal(UPDATED_CHECKS.length, 1); | |
| assert.deepEqual(UPDATED_CHECKS, EXPECTED_CHECKS); | |
| }); | |
| test('status is updated', () => { | |
| const NEW_CHECKS = [Object.assign({}, CHECK1), | |
| Object.assign({}, CHECK2)]; | |
| NEW_CHECKS[0].state = 'SUCCESSFUL'; | |
| NEW_CHECKS[1].state = 'FAILED'; | |
| const UPDATED_CHECKS = element._updateChecks(NEW_CHECKS); | |
| assert.deepEqual(UPDATED_CHECKS, NEW_CHECKS); | |
| }); | |
| test('showMessage property is retained', () => { | |
| element._checks[0].showCheckMessage = true; | |
| element._checks[1].showCheckMessage = false; | |
| const NEW_CHECKS = [Object.assign({}, CHECK1), | |
| Object.assign({}, CHECK2)]; | |
| const UPDATED_CHECKS = element._updateChecks(NEW_CHECKS); | |
| assert.equal(UPDATED_CHECKS[0].showCheckMessage, | |
| CHECK1.showCheckMessage); | |
| assert.equal(UPDATED_CHECKS[1].showCheckMessage, | |
| CHECK2.showCheckMessage); | |
| element._checks[0].showCheckMessage = undefined; | |
| element._checks[1].showCheckMessage = undefined; | |
| }); | |
| test('url is not set if new check has no url', () => { | |
| const NEW_CHECKS = [Object.assign({}, CHECK1), | |
| Object.assign({}, CHECK2)]; | |
| delete NEW_CHECKS[0]['url']; | |
| delete NEW_CHECKS[1]['url']; | |
| const UPDATED_CHECKS = element._updateChecks(NEW_CHECKS); | |
| assert.isNotOk(UPDATED_CHECKS[0].url); | |
| assert.isNotOk(UPDATED_CHECKS[1].url); | |
| }); | |
| test('url is not set if new check has no url', () => { | |
| const NEW_CHECKS = [Object.assign({}, CHECK1), | |
| Object.assign({}, CHECK2)]; | |
| delete NEW_CHECKS[0]['url']; | |
| delete NEW_CHECKS[1]['url']; | |
| const UPDATED_CHECKS = element._updateChecks(NEW_CHECKS); | |
| assert.isNotOk(UPDATED_CHECKS[0].url); | |
| assert.isNotOk(UPDATED_CHECKS[1].url); | |
| }); | |
| test('message is not shown if new check has no message', () => { | |
| const NEW_CHECKS = [Object.assign({}, CHECK1), | |
| Object.assign({}, CHECK2)]; | |
| NEW_CHECKS[0].message = ''; | |
| const UPDATED_CHECKS = element._updateChecks(NEW_CHECKS); | |
| assert.equal(UPDATED_CHECKS[0].message, NEW_CHECKS[0].message); | |
| }); | |
| }); | |
| suite('with checks', () => { | |
| setup(done => { | |
| const CHECK3 = Object.assign({}, CHECK1, {state: 'FAILED'}); | |
| const CHECK4 = Object.assign({}, CHECK1, {state: 'FAILED', | |
| blocking: [1, 2, 3]}); | |
| getChecksResolve([CHECK1, CHECK2, CHECK3, CHECK4]); | |
| flush(done); | |
| }); | |
| test('it calls to check if the checks are configured', () => { | |
| assert.isFalse(isConfiguredSpy.called); | |
| }); | |
| test('renders a table of all the checks', () => { | |
| const tbody = element.$$('table > tbody'); | |
| assert.lengthOf(tbody.querySelectorAll('gr-checks-item'), 4); | |
| }); | |
| test('retry fires show-error event', done => { | |
| postReject(new Error('random error')); | |
| const fireStub = sandbox.stub(element, 'dispatchEvent'); | |
| Polymer.dom.flush(); | |
| const checksItem = element.shadowRoot.querySelectorAll( | |
| 'table > tbody > gr-checks-item' | |
| )[0]; | |
| const reRun = checksItem.shadowRoot.querySelectorAll('td')[7]; | |
| const reRunButton = reRun.querySelector('gr-button'); | |
| reRunButton.click(); | |
| flush(() => { | |
| assert.equal(fireStub.firstCall.args[0].type, ('show-error')); | |
| done(); | |
| }); | |
| }); | |
| suite('message is rendered', () => { | |
| setup(done => { | |
| element._checks = [CHECK1, CHECK2]; | |
| flush(done); | |
| }); | |
| test('messsage icon is displayed', () => { | |
| const checkItems = element.shadowRoot | |
| .querySelectorAll('gr-checks-item'); | |
| const messagesCount = [...checkItems].reduce((count, item) => { | |
| return count += item.shadowRoot | |
| .querySelectorAll('.expand-message').length; | |
| }, 0); | |
| assert.equal(messagesCount, | |
| element._visibleChecks.length); | |
| }); | |
| test('message displayed on clicking icon', done => { | |
| const checkItem = element.shadowRoot.querySelector('gr-checks-item'); | |
| checkItem.shadowRoot | |
| .querySelector('.expand-message').click(); | |
| flush(() => { | |
| const msg = element.shadowRoot | |
| .querySelector('.check-message').innerText | |
| .trim(); | |
| assert.equal(msg, element._visibleChecks[0].message.trim()); | |
| done(); | |
| }); | |
| }); | |
| }); | |
| suite('create checker capability false', () => { | |
| setup(done => { | |
| getAccountResolve(true); | |
| getAccountCapabilitiesResolve({'checks-administrateCheckers': false}); | |
| flush(done); | |
| }); | |
| test('checker button does not render', () => { | |
| assert(!element.$$('gr-button')); | |
| }); | |
| }); | |
| test('retry checks', done => { | |
| const checkItems = element.shadowRoot | |
| .querySelectorAll('gr-checks-item'); | |
| const retryButton = checkItems[0].shadowRoot | |
| .querySelectorAll('gr-button')[1]; | |
| MockInteractions.tap(retryButton); | |
| const expectedUrl = '/changes/2/revisions/3/checks/codestyle-' | |
| + 'a6a0e4682515f3521897c5f950d1394f4619d928/rerun'; | |
| flush(() => { | |
| assert.isTrue(postSpy.calledWith(expectedUrl)); | |
| done(); | |
| }); | |
| }); | |
| suite('create checker capability true', () => { | |
| setup(done => { | |
| getAccountResolve(true); | |
| getAccountCapabilitiesResolve({ | |
| 'checks-administrateCheckers': true, | |
| }); | |
| flush(done); | |
| }); | |
| test('checker button renders', () => { | |
| assert(element.$$('gr-button')); | |
| }); | |
| }); | |
| suite('patchset navigation', () => { | |
| test('renders the dropdown', () => { | |
| assert.isNotNull(element.shadowRoot | |
| .querySelector('.patch-set-dropdown')); | |
| }); | |
| test('when patchset updated it fetches new checks', done => { | |
| const clock = sinon.useFakeTimers(); | |
| const fetchChecksStub = sandbox.stub(element, | |
| '_fetchChecks'); | |
| assert.equal(element._currentPatchSet, 3); | |
| element.revision = REVISION2; | |
| flush(() => { | |
| const firstCallArgs = fetchChecksStub.args[0]; | |
| assert.equal(firstCallArgs[1], element._currentPatchSet); | |
| clock.tick(CHECKS_POLL_INTERVAL_MS + 1000); | |
| flush(() => { | |
| assert(fetchChecksStub.callCount === 2); | |
| const secondCallArgs = fetchChecksStub.args[1]; | |
| assert.equal(secondCallArgs[1], element._currentPatchSet); | |
| done(); | |
| }); | |
| }); | |
| }); | |
| test('update to revision updates currentPatchset', done => { | |
| assert.equal(element._currentPatchSet, 3); | |
| element.revision = REVISION2; | |
| flush(() => { | |
| assert.equal(element._currentPatchSet, 2); | |
| done(); | |
| }); | |
| }); | |
| }); | |
| suite('check state filter', () => { | |
| test('renders the filter dropdown', () => { | |
| assert.isNotNull(element.shadowRoot | |
| .querySelector('.check-state-filter')); | |
| }); | |
| test('default filter is all', () => { | |
| assert.equal(element._currentStatus, STATE_ALL); | |
| }); | |
| test('updating filter status filters checks', done => { | |
| assert.equal(element._visibleChecks.length, 4); | |
| element._currentStatus = Statuses.RUNNING; | |
| flush(() => { | |
| assert.equal(element._visibleChecks.length, 2); | |
| done(); | |
| }); | |
| }); | |
| }); | |
| suite('blocking checkbox', () => { | |
| test('renders the checkbox', () => { | |
| assert.isNotNull(element.shadowRoot | |
| .querySelector('input[name="blocking"]')); | |
| }); | |
| test('default filter is all', () => { | |
| assert.equal(element.shadowRoot | |
| .querySelector('input[name="blocking"]').checked, | |
| false); | |
| }); | |
| test('blocking checkbox filters checks', done => { | |
| assert.equal(element._visibleChecks.length, 4); | |
| element.shadowRoot.querySelector('input[name="blocking"]').click(); | |
| flush(() => { | |
| assert.equal(element._visibleChecks.length, 1); | |
| done(); | |
| }); | |
| }); | |
| }); | |
| }); | |
| suite('with large number of checks', () => { | |
| setup(done => { | |
| const CHECK3 = Object.assign({}, CHECK1, {state: 'FAILED'}); | |
| const CHECK4 = Object.assign({}, CHECK1, {state: 'FAILED', | |
| blocking: [1, 2, 3]}); | |
| const checks = []; | |
| for (let i = 1; i <= 20; i++) { | |
| checks.push(...[CHECK1, CHECK2, CHECK3, CHECK4]); | |
| } | |
| getChecksResolve(checks); | |
| flush(done); | |
| }); | |
| test('rendered checks are limited by default', () => { | |
| assert.equal(element._visibleChecks.length, CHECKS_LIMIT); | |
| }); | |
| test('show more button expands to show all checks', () => { | |
| const showMoreChecksButton = element.shadowRoot | |
| .querySelector('.show-more-checks'); | |
| assert.isOk(showMoreChecksButton); | |
| showMoreChecksButton.click(); | |
| flush(() => { | |
| assert.equal(element._visibleChecks.length, element._checks.length); | |
| }); | |
| }); | |
| test('show more button hides if checks are below limit', () => { | |
| element._currentStatus = Statuses.NOT_STARTED; | |
| flush(() => { | |
| const showMoreChecksButton = element.querySelector( | |
| '.show-more-checks'); | |
| assert.equal(showMoreChecksButton.style.display, 'none'); | |
| }); | |
| }); | |
| }); | |
| }); | |
| </script> |