 2cc999ada5
			
		
	
	2cc999ada5
	
	
	
		
			
			Change I7baad12a02e0e9e53b74537215ddbf21c7b2d006 introduced a regression where the cursor indicator disappears when moved below the first section containing results. This change fixes the root cause, refactors slightly, and adds tests. Bug: Issue 7118 Change-Id: Ide4f31af2af161dc1085167740b698c5507953ac
		
			
				
	
	
		
			516 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
			
		
		
	
	
			516 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			HTML
		
	
	
	
	
	
| <!DOCTYPE html>
 | |
| <!--
 | |
| Copyright (C) 2015 The Android Open Source Project
 | |
| 
 | |
| Licensed under the Apache License, Version 2.0 (the "License");
 | |
| you may not use this file except in compliance with the License.
 | |
| You may obtain a copy of the License at
 | |
| 
 | |
| http://www.apache.org/licenses/LICENSE-2.0
 | |
| 
 | |
| Unless required by applicable law or agreed to in writing, software
 | |
| distributed under the License is distributed on an "AS IS" BASIS,
 | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | |
| See the License for the specific language governing permissions and
 | |
| limitations under the License.
 | |
| -->
 | |
| 
 | |
| <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
 | |
| <title>gr-change-list</title>
 | |
| 
 | |
| <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 | |
| <script src="../../../bower_components/web-component-tester/browser.js"></script>
 | |
| <link rel="import" href="../../../test/common-test-setup.html"/>
 | |
| <script src="../../../bower_components/page/page.js"></script>
 | |
| 
 | |
| <link rel="import" href="gr-change-list.html">
 | |
| 
 | |
| <script>void(0);</script>
 | |
| 
 | |
| <test-fixture id="basic">
 | |
|   <template>
 | |
|     <gr-change-list></gr-change-list>
 | |
|   </template>
 | |
| </test-fixture>
 | |
| 
 | |
| <test-fixture id="grouped">
 | |
|   <template>
 | |
|     <gr-change-list></gr-change-list>
 | |
|   </template>
 | |
| </test-fixture>
 | |
| 
 | |
| <script>
 | |
|   suite('gr-change-list basic tests', () => {
 | |
|     let element;
 | |
|     let sandbox;
 | |
| 
 | |
|     setup(() => {
 | |
|       sandbox = sinon.sandbox.create();
 | |
|       element = fixture('basic');
 | |
|     });
 | |
| 
 | |
|     teardown(() => { sandbox.restore(); });
 | |
| 
 | |
|     function stubRestAPI(preferences) {
 | |
|       const loggedInPromise = Promise.resolve(preferences !== null);
 | |
|       const preferencesPromise = Promise.resolve(preferences);
 | |
|       stub('gr-rest-api-interface', {
 | |
|         getLoggedIn: sinon.stub().returns(loggedInPromise),
 | |
|         getPreferences: sinon.stub().returns(preferencesPromise),
 | |
|       });
 | |
|       return Promise.all([loggedInPromise, preferencesPromise]);
 | |
|     }
 | |
| 
 | |
|     suite('test show change number not logged in', () => {
 | |
|       setup(() => {
 | |
|         return stubRestAPI(null).then(() => {
 | |
|           element = fixture('basic');
 | |
|           return element._loadPreferences();
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       test('show number disabled', () => {
 | |
|         assert.isFalse(element.showNumber);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     suite('test show change number preference enabled', () => {
 | |
|       setup(() => {
 | |
|         return stubRestAPI({legacycid_in_change_table: true,
 | |
|           time_format: 'HHMM_12',
 | |
|           change_table: [],
 | |
|         }).then(() => {
 | |
|           element = fixture('basic');
 | |
|           return element._loadPreferences();
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       test('show number enabled', () => {
 | |
|         assert.isTrue(element.showNumber);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     suite('test show change number preference disabled', () => {
 | |
|       setup(() => {
 | |
|         // legacycid_in_change_table is not set when false.
 | |
|         return stubRestAPI({time_format: 'HHMM_12', change_table: []}).then(
 | |
|             () => {
 | |
|               element = fixture('basic');
 | |
|               return element._loadPreferences();
 | |
|             });
 | |
|       });
 | |
| 
 | |
|       test('show number disabled', () => {
 | |
|         assert.isFalse(element.showNumber);
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('computed fields', () => {
 | |
|       assert.equal(element._computeLabelNames(
 | |
|             [{results: [{_number: 0, labels: {}}]}]).length, 0);
 | |
|       assert.equal(element._computeLabelNames([
 | |
|         {results: [
 | |
|           {_number: 0, labels: {Verified: {approved: {}}}},
 | |
|           {
 | |
|             _number: 1,
 | |
|             labels: {
 | |
|               'Verified': {approved: {}},
 | |
|               'Code-Review': {approved: {}},
 | |
|             },
 | |
|           },
 | |
|           {
 | |
|             _number: 2,
 | |
|             labels: {
 | |
|               'Verified': {approved: {}},
 | |
|               'Library-Compliance': {approved: {}},
 | |
|             },
 | |
|           },
 | |
|         ]},
 | |
|       ]).length, 3);
 | |
| 
 | |
|       assert.equal(element._computeLabelShortcut('Code-Review'), 'CR');
 | |
|       assert.equal(element._computeLabelShortcut('Verified'), 'V');
 | |
|       assert.equal(element._computeLabelShortcut('Library-Compliance'), 'LC');
 | |
|       assert.equal(element._computeLabelShortcut('PolyGerrit-Review'), 'PR');
 | |
|       assert.equal(element._computeLabelShortcut('polygerrit-review'), 'PR');
 | |
|       assert.equal(element._computeLabelShortcut(
 | |
|           'Some-Special-Label-7'), 'SSL7');
 | |
|     });
 | |
| 
 | |
|     test('colspans', () => {
 | |
|       const thItemCount = Polymer.dom(element.root).querySelectorAll(
 | |
|           'th').length;
 | |
| 
 | |
|       const changeTableColumns = [];
 | |
|       const labelNames = [];
 | |
|       assert.equal(thItemCount, element._computeColspan(
 | |
|           changeTableColumns, labelNames));
 | |
|     });
 | |
| 
 | |
|     test('keyboard shortcuts', done => {
 | |
|       sandbox.stub(element, '_computeLabelNames');
 | |
|       element.sections = [
 | |
|         {results: new Array(1)},
 | |
|         {results: new Array(2)},
 | |
|       ];
 | |
|       element.selectedIndex = 0;
 | |
|       element.changes = [
 | |
|         {_number: 0},
 | |
|         {_number: 1},
 | |
|         {_number: 2},
 | |
|       ];
 | |
|       flushAsynchronousOperations();
 | |
|       const elementItems = Polymer.dom(element.root).querySelectorAll(
 | |
|           'gr-change-list-item');
 | |
|       assert.equal(elementItems.length, 3);
 | |
| 
 | |
|       flush(() => {
 | |
|         assert.isTrue(elementItems[0].hasAttribute('selected'));
 | |
|         MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
 | |
|         assert.equal(element.selectedIndex, 1);
 | |
|         assert.isTrue(elementItems[1].hasAttribute('selected'));
 | |
|         MockInteractions.pressAndReleaseKeyOn(element, 74, null, 'j');
 | |
|         assert.equal(element.selectedIndex, 2);
 | |
|         assert.isTrue(elementItems[2].hasAttribute('selected'));
 | |
| 
 | |
|         const navStub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
 | |
|         assert.equal(element.selectedIndex, 2);
 | |
|         MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
 | |
|         assert.deepEqual(navStub.lastCall.args[0], {_number: 2},
 | |
|             'Should navigate to /c/2/');
 | |
| 
 | |
|         MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
 | |
|         assert.equal(element.selectedIndex, 1);
 | |
|         MockInteractions.pressAndReleaseKeyOn(element, 13, null, 'enter');
 | |
|         assert.deepEqual(navStub.lastCall.args[0], {_number: 1},
 | |
|             'Should navigate to /c/1/');
 | |
| 
 | |
|         MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
 | |
|         MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
 | |
|         MockInteractions.pressAndReleaseKeyOn(element, 75, null, 'k');
 | |
|         assert.equal(element.selectedIndex, 0);
 | |
| 
 | |
|         done();
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     test('changes needing review', () => {
 | |
|       element.changes = [
 | |
|         {
 | |
|           _number: 0,
 | |
|           status: 'NEW',
 | |
|           reviewed: true,
 | |
|           owner: {_account_id: 0},
 | |
|         },
 | |
|         {
 | |
|           _number: 1,
 | |
|           status: 'NEW',
 | |
|           owner: {_account_id: 0},
 | |
|         },
 | |
|         {
 | |
|           _number: 2,
 | |
|           status: 'MERGED',
 | |
|           owner: {_account_id: 0},
 | |
|         },
 | |
|         {
 | |
|           _number: 3,
 | |
|           status: 'DRAFT',
 | |
|           owner: {_account_id: 42},
 | |
|         },
 | |
|         {
 | |
|           _number: 4,
 | |
|           status: 'ABANDONED',
 | |
|           owner: {_account_id: 0},
 | |
|         },
 | |
|       ];
 | |
|       flushAsynchronousOperations();
 | |
|       let elementItems = Polymer.dom(element.root).querySelectorAll(
 | |
|           'gr-change-list-item');
 | |
|       assert.equal(elementItems.length, 5);
 | |
|       for (let i = 0; i < elementItems.length; i++) {
 | |
|         assert.isFalse(elementItems[i].hasAttribute('needs-review'));
 | |
|       }
 | |
| 
 | |
|       element.showReviewedState = true;
 | |
|       elementItems = Polymer.dom(element.root).querySelectorAll(
 | |
|           'gr-change-list-item');
 | |
|       assert.equal(elementItems.length, 5);
 | |
|       assert.isFalse(elementItems[0].hasAttribute('needs-review'));
 | |
|       assert.isTrue(elementItems[1].hasAttribute('needs-review'));
 | |
|       assert.isFalse(elementItems[2].hasAttribute('needs-review'));
 | |
|       assert.isTrue(elementItems[3].hasAttribute('needs-review'));
 | |
|       assert.isFalse(elementItems[4].hasAttribute('needs-review'));
 | |
| 
 | |
|       element.account = {_account_id: 42};
 | |
|       elementItems = Polymer.dom(element.root).querySelectorAll(
 | |
|           'gr-change-list-item');
 | |
|       assert.equal(elementItems.length, 5);
 | |
|       assert.isFalse(elementItems[0].hasAttribute('needs-review'));
 | |
|       assert.isTrue(elementItems[1].hasAttribute('needs-review'));
 | |
|       assert.isFalse(elementItems[2].hasAttribute('needs-review'));
 | |
|       assert.isFalse(elementItems[3].hasAttribute('needs-review'));
 | |
|       assert.isFalse(elementItems[4].hasAttribute('needs-review'));
 | |
|     });
 | |
| 
 | |
|     test('no changes', () => {
 | |
|       element.changes = [];
 | |
|       flushAsynchronousOperations();
 | |
|       const listItems = Polymer.dom(element.root).querySelectorAll(
 | |
|           'gr-change-list-item');
 | |
|       assert.equal(listItems.length, 0);
 | |
|       const noChangesMsg =
 | |
|           Polymer.dom(element.root).querySelector('.noChanges');
 | |
|       assert.ok(noChangesMsg);
 | |
|     });
 | |
| 
 | |
|     test('empty sections', () => {
 | |
|       element.sections = [{results: []}, {results: []}];
 | |
|       flushAsynchronousOperations();
 | |
|       const listItems = Polymer.dom(element.root).querySelectorAll(
 | |
|           'gr-change-list-item');
 | |
|       assert.equal(listItems.length, 0);
 | |
|       const noChangesMsg = Polymer.dom(element.root).querySelectorAll(
 | |
|           '.noChanges');
 | |
|       assert.equal(noChangesMsg.length, 2);
 | |
|     });
 | |
| 
 | |
|     suite('empty column preference', () => {
 | |
|       let element;
 | |
| 
 | |
|       setup(() =>
 | |
|         stubRestAPI({
 | |
|           legacycid_in_change_table: true,
 | |
|           time_format: 'HHMM_12',
 | |
|           change_table: [],
 | |
|         }).then(() => {
 | |
|           element = fixture('basic');
 | |
|           return element._loadPreferences();
 | |
|         })
 | |
|       );
 | |
| 
 | |
|       test('show number enabled', () => {
 | |
|         assert.isTrue(element.showNumber);
 | |
|       });
 | |
| 
 | |
|       test('all columns visible', () => {
 | |
|         for (const column of element.columnNames) {
 | |
|           const elementClass = '.' + element._lowerCase(column);
 | |
|           assert.isFalse(element.$$(elementClass).hidden);
 | |
|         }
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     suite('full column preference', () => {
 | |
|       let element;
 | |
| 
 | |
|       setup(() => {
 | |
|         return stubRestAPI({
 | |
|           legacycid_in_change_table: true,
 | |
|           time_format: 'HHMM_12',
 | |
|           change_table: [
 | |
|             'Subject',
 | |
|             'Status',
 | |
|             'Owner',
 | |
|             'Project',
 | |
|             'Branch',
 | |
|             'Updated',
 | |
|             'Size',
 | |
|           ],
 | |
|         }).then(() => {
 | |
|           element = fixture('basic');
 | |
|           return element._loadPreferences();
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       test('all columns visible', () => {
 | |
|         for (const column of element.changeTableColumns) {
 | |
|           const elementClass = '.' + element._lowerCase(column);
 | |
|           assert.isFalse(element.$$(elementClass).hidden);
 | |
|         }
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     suite('partial column preference', () => {
 | |
|       let element;
 | |
| 
 | |
|       setup(() => {
 | |
|         return stubRestAPI({
 | |
|           legacycid_in_change_table: true,
 | |
|           time_format: 'HHMM_12',
 | |
|           change_table: [
 | |
|             'Subject',
 | |
|             'Status',
 | |
|             'Owner',
 | |
|             'Branch',
 | |
|             'Updated',
 | |
|             'Size',
 | |
|           ],
 | |
|         }).then(() => {
 | |
|           element = fixture('basic');
 | |
|           return element._loadPreferences();
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       test('all columns except project visible', () => {
 | |
|         for (const column of element.changeTableColumns) {
 | |
|           const elementClass = '.' + column.toLowerCase();
 | |
|           if (column === 'Project') {
 | |
|             assert.isTrue(element.$$(elementClass).hidden);
 | |
|           } else {
 | |
|             assert.isFalse(element.$$(elementClass).hidden);
 | |
|           }
 | |
|         }
 | |
|       });
 | |
|     });
 | |
| 
 | |
|     suite('random column does not exist', () => {
 | |
|       let element;
 | |
| 
 | |
|       /* This would only exist if somebody manually updated the config
 | |
|       file. */
 | |
|       setup(() => {
 | |
|         return stubRestAPI({
 | |
|           legacycid_in_change_table: true,
 | |
|           time_format: 'HHMM_12',
 | |
|           change_table: [
 | |
|             'Bad',
 | |
|           ],
 | |
|         }).then(() => {
 | |
|           element = fixture('basic');
 | |
|           return element._loadPreferences();
 | |
|         });
 | |
|       });
 | |
| 
 | |
|       test('bad column does not exist', () => {
 | |
|         const elementClass = '.bad';
 | |
|         assert.isNotOk(element.$$(elementClass));
 | |
|       });
 | |
|     });
 | |
|   });
 | |
| 
 | |
|   suite('gr-change-list sections', () => {
 | |
|     let element;
 | |
|     let sandbox;
 | |
| 
 | |
|     setup(() => {
 | |
|       sandbox = sinon.sandbox.create();
 | |
|       element = fixture('basic');
 | |
|     });
 | |
| 
 | |
|     teardown(() => { sandbox.restore(); });
 | |
| 
 | |
|     test('keyboard shortcuts', () => {
 | |
|       element.selectedIndex = 0;
 | |
|       element.sections = [
 | |
|         {
 | |
|           results: [
 | |
|             {_number: 0},
 | |
|             {_number: 1},
 | |
|             {_number: 2},
 | |
|           ],
 | |
|         },
 | |
|         {
 | |
|           results: [
 | |
|             {_number: 3},
 | |
|             {_number: 4},
 | |
|             {_number: 5},
 | |
|           ],
 | |
|         },
 | |
|         {
 | |
|           results: [
 | |
|             {_number: 6},
 | |
|             {_number: 7},
 | |
|             {_number: 8},
 | |
|           ],
 | |
|         },
 | |
|       ];
 | |
|       flushAsynchronousOperations();
 | |
|       const elementItems = Polymer.dom(element.root).querySelectorAll(
 | |
|           'gr-change-list-item');
 | |
|       assert.equal(elementItems.length, 9);
 | |
| 
 | |
|       MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
 | |
|       assert.equal(element.selectedIndex, 1);
 | |
|       MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
 | |
| 
 | |
|       const navStub = sandbox.stub(Gerrit.Nav, 'navigateToChange');
 | |
|       assert.equal(element.selectedIndex, 2);
 | |
| 
 | |
|       MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
 | |
|       assert.deepEqual(navStub.lastCall.args[0], {_number: 2},
 | |
|           'Should navigate to /c/2/');
 | |
| 
 | |
|       MockInteractions.pressAndReleaseKeyOn(element, 75); // 'k'
 | |
|       assert.equal(element.selectedIndex, 1);
 | |
|       MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
 | |
|       assert.deepEqual(navStub.lastCall.args[0], {_number: 1},
 | |
|           'Should navigate to /c/1/');
 | |
| 
 | |
|       MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
 | |
|       MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
 | |
|       MockInteractions.pressAndReleaseKeyOn(element, 74); // 'j'
 | |
|       assert.equal(element.selectedIndex, 4);
 | |
|       MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
 | |
|       assert.deepEqual(navStub.lastCall.args[0], {_number: 4},
 | |
|           'Should navigate to /c/4/');
 | |
|     });
 | |
| 
 | |
|     test('assigned attribute set in each item', () => {
 | |
|       element.changes = [
 | |
|         {
 | |
|           _number: 0,
 | |
|           status: 'NEW',
 | |
|           owner: {_account_id: 0},
 | |
|         },
 | |
|         {
 | |
|           _number: 1,
 | |
|           status: 'DRAFT',
 | |
|           owner: {_account_id: 42},
 | |
|         },
 | |
|         {
 | |
|           _number: 2,
 | |
|           status: 'ABANDONED',
 | |
|           owner: {_account_id: 0},
 | |
|         },
 | |
|       ];
 | |
|       element.account = {_account_id: 42};
 | |
|       flushAsynchronousOperations();
 | |
|       const items = element._getListItems();
 | |
|       assert.equal(items.length, 3);
 | |
|       for (let i = 0; i < items.length; i++) {
 | |
|         assert.equal(items[i].hasAttribute('assigned'),
 | |
|             items[i]._account_id === element.account._account_id);
 | |
|       }
 | |
|     });
 | |
| 
 | |
|     test('_sectionHref', () => {
 | |
|       assert.equal(
 | |
|           element._sectionHref('is:open owner:self'),
 | |
|           '/q/is:open+owner:self');
 | |
|       assert.equal(
 | |
|           element._sectionHref(
 | |
|               'is:open ((reviewer:self -is:ignored) OR assignee:self)'),
 | |
|           '/q/is:open+((reviewer:self+-is:ignored)+OR+assignee:self)');
 | |
|     });
 | |
| 
 | |
|     test('_computeItemAbsoluteIndex', () => {
 | |
|       sandbox.stub(element, '_computeLabelNames');
 | |
|       element.sections = [
 | |
|         {results: new Array(1)},
 | |
|         {results: new Array(2)},
 | |
|         {results: new Array(3)},
 | |
|       ];
 | |
| 
 | |
|       assert.equal(element._computeItemAbsoluteIndex(0, 0), 0);
 | |
|       // Out of range but no matter.
 | |
|       assert.equal(element._computeItemAbsoluteIndex(0, 1), 1);
 | |
| 
 | |
|       assert.equal(element._computeItemAbsoluteIndex(1, 0), 1);
 | |
|       assert.equal(element._computeItemAbsoluteIndex(1, 1), 2);
 | |
|       assert.equal(element._computeItemAbsoluteIndex(1, 2), 3);
 | |
|       assert.equal(element._computeItemAbsoluteIndex(2, 0), 3);
 | |
|       assert.equal(element._computeItemAbsoluteIndex(3, 0), 6);
 | |
|     });
 | |
|   });
 | |
| </script>
 |