Refactor directory structure of components
There is no change in functionality. Only moving things around.
+ Separate html from the js.
+ Place the unit test for a component within the same folder.
+ Organize the components in subfolders.
Change-Id: I51fdc510db75fc1b33f040ca63decbbdfd4d5513
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
new file mode 100644
index 0000000..0dc18ae
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -0,0 +1,174 @@
+<!--
+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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../../bower_components/iron-dropdown/iron-dropdown.html">
+<link rel="import" href="../../../behaviors/keyboard-shortcut-behavior.html">
+<link rel="import" href="../../../behaviors/rest-client-behavior.html">
+<link rel="import" href="../../shared/gr-ajax/gr-ajax.html">
+<link rel="import" href="../../shared/gr-button/gr-button.html">
+<link rel="import" href="../../shared/gr-request/gr-request.html">
+<link rel="import" href="../gr-diff/gr-diff.html">
+
+<dom-module id="gr-diff-view">
+ <template>
+ <style>
+ :host {
+ background-color: var(--view-background-color);
+ display: block;
+ }
+ h3 {
+ margin-top: 1em;
+ padding: .75em var(--default-horizontal-margin);
+ }
+ .reviewed {
+ display: inline-block;
+ margin: 0 .25em;
+ vertical-align: .15em;
+ }
+ .jumpToFileContainer {
+ display: inline-block;
+ }
+ .mobileJumpToFileContainer {
+ display: none;
+ }
+ .downArrow {
+ display: inline-block;
+ font-size: .6em;
+ vertical-align: middle;
+ }
+ .dropdown-trigger {
+ color: #00e;
+ cursor: pointer;
+ padding: 0;
+ }
+ .dropdown-content {
+ background-color: #fff;
+ box-shadow: 0 1px 5px rgba(0, 0, 0, .3);
+ }
+ .dropdown-content a {
+ cursor: pointer;
+ display: block;
+ font-weight: normal;
+ padding: .3em .5em;
+ }
+ .dropdown-content a:before {
+ color: #ccc;
+ content: attr(data-key-nav);
+ display: inline-block;
+ margin-right: .5em;
+ width: .3em;
+ }
+ .dropdown-content a:hover {
+ background-color: #00e;
+ color: #fff;
+ }
+ .dropdown-content a[selected] {
+ color: #000;
+ font-weight: bold;
+ pointer-events: none;
+ text-decoration: none;
+ }
+ .dropdown-content a[selected]:hover {
+ background-color: #fff;
+ color: #000;
+ }
+ gr-button {
+ font: inherit;
+ padding: .3em 0;
+ text-decoration: none;
+ }
+ @media screen and (max-width: 50em) {
+ .dash {
+ display: none;
+ }
+ .reviewed {
+ vertical-align: -.1em;
+ }
+ .jumpToFileContainer {
+ display: none;
+ }
+ .mobileJumpToFileContainer {
+ display: block;
+ width: 100%;
+ }
+ }
+ </style>
+ <gr-ajax id="changeDetailXHR"
+ auto
+ url="[[_computeChangeDetailPath(_changeNum)]]"
+ params="[[_computeChangeDetailQueryParams()]]"
+ last-response="{{_change}}"></gr-ajax>
+ <gr-ajax id="filesXHR"
+ auto
+ url="[[_computeFilesPath(_changeNum, _patchRange.patchNum)]]"
+ on-response="_handleFilesResponse"></gr-ajax>
+ <gr-ajax id="configXHR"
+ auto
+ url="[[_computeProjectConfigPath(_change.project)]]"
+ last-response="{{_projectConfig}}"></gr-ajax>
+ <h3>
+ <a href$="[[_computeChangePath(_changeNum, _patchRange.patchNum, _change.revisions)]]">
+ [[_changeNum]]</a><span>:</span>
+ <span>[[_change.subject]]</span>
+ <span class="dash">—</span>
+ <input id="reviewed"
+ class="reviewed"
+ type="checkbox"
+ on-change="_handleReviewedChange"
+ hidden$="[[!_loggedIn]]" hidden>
+ <div class="jumpToFileContainer">
+ <gr-button link class="dropdown-trigger" id="trigger" on-tap="_showDropdownTapHandler">
+ <span>[[_computeFileDisplayName(_path)]]</span>
+ <span class="downArrow">▼</span>
+ </gr-button>
+ <iron-dropdown id="dropdown" vertical-align="top" vertical-offset="25">
+ <div class="dropdown-content">
+ <template is="dom-repeat" items="[[_fileList]]" as="path">
+ <a href$="[[_computeDiffURL(_changeNum, _patchRange, path)]]"
+ selected$="[[_computeFileSelected(path, _path)]]"
+ data-key-nav$="[[_computeKeyNav(path, _path, _fileList)]]"
+ on-tap="_handleFileTap">
+ [[_computeFileDisplayName(path)]]
+ </a>
+ </template>
+ </div>
+ </iron-dropdown>
+ </div>
+ <div class="mobileJumpToFileContainer">
+ <select on-change="_handleMobileSelectChange">
+ <template is="dom-repeat" items="[[_fileList]]" as="path">
+ <option
+ value$="[[path]]"
+ selected$="[[_computeFileSelected(path, _path)]]">
+ [[_computeFileDisplayName(path)]]
+ </option>
+ </template>
+ </select>
+ </div>
+ </h3>
+ <gr-diff id="diff"
+ change-num="[[_changeNum]]"
+ prefs="{{prefs}}"
+ patch-range="[[_patchRange]]"
+ path="[[_path]]"
+ project-config="[[_projectConfig]]"
+ available-patches="[[_computeAvailablePatches(_change.revisions)]]"
+ on-render="_handleDiffRender">
+ </gr-diff>
+ </template>
+ <script src="gr-diff-view.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
new file mode 100644
index 0000000..847a641
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -0,0 +1,315 @@
+// Copyright (C) 2016 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.
+(function() {
+ 'use strict';
+
+ var COMMIT_MESSAGE_PATH = '/COMMIT_MSG';
+
+ Polymer({
+ is: 'gr-diff-view',
+
+ /**
+ * Fired when the title of the page should change.
+ *
+ * @event title-change
+ */
+
+ properties: {
+ prefs: {
+ type: Object,
+ notify: true,
+ },
+ /**
+ * URL params passed from the router.
+ */
+ params: {
+ type: Object,
+ observer: '_paramsChanged',
+ },
+ keyEventTarget: {
+ type: Object,
+ value: function() { return document.body; },
+ },
+ changeViewState: {
+ type: Object,
+ notify: true,
+ value: function() { return {}; },
+ },
+
+ _patchRange: Object,
+ _change: Object,
+ _changeNum: String,
+ _diff: Object,
+ _fileList: {
+ type: Array,
+ value: function() { return []; },
+ },
+ _path: {
+ type: String,
+ observer: '_pathChanged',
+ },
+ _loggedIn: {
+ type: Boolean,
+ value: false,
+ },
+ _xhrPromise: Object, // Used for testing.
+ },
+
+ behaviors: [
+ Gerrit.KeyboardShortcutBehavior,
+ Gerrit.RESTClientBehavior,
+ ],
+
+ ready: function() {
+ app.accountReady.then(function() {
+ this._loggedIn = app.loggedIn;
+ if (this._loggedIn) {
+ this._setReviewed(true);
+ }
+ }.bind(this));
+ },
+
+ attached: function() {
+ if (this._path) {
+ this.fire('title-change',
+ {title: this._computeFileDisplayName(this._path)});
+ }
+ window.addEventListener('resize', this._boundWindowResizeHandler);
+ },
+
+ detached: function() {
+ window.removeEventListener('resize', this._boundWindowResizeHandler);
+ },
+
+ _handleReviewedChange: function(e) {
+ this._setReviewed(Polymer.dom(e).rootTarget.checked);
+ },
+
+ _setReviewed: function(reviewed) {
+ this.$.reviewed.checked = reviewed;
+ var method = reviewed ? 'PUT' : 'DELETE';
+ var url = this.changeBaseURL(this._changeNum,
+ this._patchRange.patchNum) + '/files/' +
+ encodeURIComponent(this._path) + '/reviewed';
+ this._send(method, url).catch(function(err) {
+ alert('Couldn’t change file review status. Check the console ' +
+ 'and contact the PolyGerrit team for assistance.');
+ throw err;
+ }.bind(this));
+ },
+
+ _handleKey: function(e) {
+ if (this.shouldSupressKeyboardShortcut(e)) { return; }
+
+ switch (e.keyCode) {
+ case 219: // '['
+ e.preventDefault();
+ this._navToFile(this._fileList, -1);
+ break;
+ case 221: // ']'
+ e.preventDefault();
+ this._navToFile(this._fileList, 1);
+ break;
+ case 78: // 'n'
+ if (e.shiftKey) {
+ this.$.diff.scrollToNextCommentThread();
+ } else {
+ this.$.diff.scrollToNextDiffChunk();
+ }
+ break;
+ case 80: // 'p'
+ if (e.shiftKey) {
+ this.$.diff.scrollToPreviousCommentThread();
+ } else {
+ this.$.diff.scrollToPreviousDiffChunk();
+ }
+ break;
+ case 65: // 'a'
+ if (!this._loggedIn) { return; }
+
+ this.set('changeViewState.showReplyDialog', true);
+ /* falls through */ // required by JSHint
+ case 85: // 'u'
+ if (this._changeNum && this._patchRange.patchNum) {
+ e.preventDefault();
+ page.show(this._computeChangePath(
+ this._changeNum,
+ this._patchRange.patchNum,
+ this._change && this._change.revisions));
+ }
+ break;
+ case 188: // ','
+ this.$.diff.showDiffPreferences();
+ break;
+ }
+ },
+
+ _handleDiffRender: function() {
+ if (window.location.hash.length > 0) {
+ this.$.diff.scrollToLine(
+ parseInt(window.location.hash.substring(1), 10));
+ }
+ },
+
+ _navToFile: function(fileList, direction) {
+ if (fileList.length == 0) { return; }
+
+ var idx = fileList.indexOf(this._path) + direction;
+ if (idx < 0 || idx > fileList.length - 1) {
+ page.show(this._computeChangePath(
+ this._changeNum,
+ this._patchRange.patchNum,
+ this._change && this._change.revisions));
+ return;
+ }
+ page.show(this._computeDiffURL(this._changeNum,
+ this._patchRange,
+ fileList[idx]));
+ },
+
+ _paramsChanged: function(value) {
+ if (value.view != this.tagName.toLowerCase()) { return; }
+
+ this._changeNum = value.changeNum;
+ this._patchRange = {
+ patchNum: value.patchNum,
+ basePatchNum: value.basePatchNum || 'PARENT',
+ };
+ this._path = value.path;
+
+ this.fire('title-change',
+ {title: this._computeFileDisplayName(this._path)});
+
+ // When navigating away from the page, there is a possibility that the
+ // patch number is no longer a part of the URL (say when navigating to
+ // the top-level change info view) and therefore undefined in `params`.
+ if (!this._patchRange.patchNum) {
+ return;
+ }
+
+ this.$.diff.reload();
+ },
+
+ _pathChanged: function(path) {
+ if (this._fileList.length == 0) { return; }
+
+ this.set('changeViewState.selectedFileIndex',
+ this._fileList.indexOf(path));
+
+ if (this._loggedIn) {
+ this._setReviewed(true);
+ }
+ },
+
+ _computeDiffURL: function(changeNum, patchRange, path) {
+ var patchStr = patchRange.patchNum;
+ if (patchRange.basePatchNum != null &&
+ patchRange.basePatchNum != 'PARENT') {
+ patchStr = patchRange.basePatchNum + '..' + patchRange.patchNum;
+ }
+ return '/c/' + changeNum + '/' + patchStr + '/' + path;
+ },
+
+ _computeAvailablePatches: function(revisions) {
+ var patchNums = [];
+ for (var rev in revisions) {
+ patchNums.push(revisions[rev]._number);
+ }
+ return patchNums.sort(function(a, b) { return a - b; });
+ },
+
+ _computeChangePath: function(changeNum, patchNum, revisions) {
+ var base = '/c/' + changeNum + '/';
+
+ // The change may not have loaded yet, making revisions unavailable.
+ if (!revisions) {
+ return base + patchNum;
+ }
+
+ var latestPatchNum = -1;
+ for (var rev in revisions) {
+ latestPatchNum = Math.max(latestPatchNum, revisions[rev]._number);
+ }
+ if (parseInt(patchNum, 10) != latestPatchNum) {
+ return base + patchNum;
+ }
+
+ return base;
+ },
+
+ _computeFileDisplayName: function(path) {
+ return path == COMMIT_MESSAGE_PATH ? 'Commit message' : path;
+ },
+
+ _computeChangeDetailPath: function(changeNum) {
+ return '/changes/' + changeNum + '/detail';
+ },
+
+ _computeChangeDetailQueryParams: function() {
+ return {O: this.listChangesOptionsToHex(
+ this.ListChangesOption.ALL_REVISIONS
+ )};
+ },
+
+ _computeFilesPath: function(changeNum, patchNum) {
+ return this.changeBaseURL(changeNum, patchNum) + '/files';
+ },
+
+ _computeProjectConfigPath: function(project) {
+ return '/projects/' + encodeURIComponent(project) + '/config';
+ },
+
+ _computeFileSelected: function(path, currentPath) {
+ return path == currentPath;
+ },
+
+ _computeKeyNav: function(path, selectedPath, fileList) {
+ var selectedIndex = fileList.indexOf(selectedPath);
+ if (fileList.indexOf(path) == selectedIndex - 1) {
+ return '[';
+ }
+ if (fileList.indexOf(path) == selectedIndex + 1) {
+ return ']';
+ }
+ return '';
+ },
+
+ _handleFileTap: function(e) {
+ this.$.dropdown.close();
+ },
+
+ _handleMobileSelectChange: function(e) {
+ var path = Polymer.dom(e).rootTarget.value;
+ page.show(
+ this._computeDiffURL(this._changeNum, this._patchRange, path));
+ },
+
+ _handleFilesResponse: function(e, req) {
+ this._fileList = Object.keys(e.detail.response).sort();
+ },
+
+ _showDropdownTapHandler: function(e) {
+ this.$.dropdown.open();
+ },
+
+ _send: function(method, url) {
+ var xhr = document.createElement('gr-request');
+ this._xhrPromise = xhr.send({
+ method: method,
+ url: url,
+ });
+ return this._xhrPromise;
+ },
+ });
+})();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
new file mode 100644
index 0000000..bfe4906
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -0,0 +1,395 @@
+<!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-diff-view</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="../../../bower_components/page/page.js"></script>
+<script src="../../../scripts/fake-app.js"></script>
+<script src="../../../scripts/util.js"></script>
+
+<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="gr-diff-view.html">
+
+<test-fixture id="basic">
+ <template>
+ <gr-diff-view></gr-diff-view>
+ </template>
+</test-fixture>
+
+<script>
+ suite('gr-diff-view tests', function() {
+ var element;
+ var server;
+
+ setup(function() {
+ element = fixture('basic');
+ element.$.changeDetailXHR.auto = false;
+ element.$.filesXHR.auto = false;
+ element.$.configXHR.auto = false;
+ element.$.diff.auto = false;
+
+ server = sinon.fakeServer.create();
+ server.respondWith(
+ 'PUT',
+ '/changes/42/revisions/2/files/%2FCOMMIT_MSG/reviewed',
+ [
+ 201,
+ {'Content-Type': 'application/json'},
+ ')]}\'\n' +
+ '""',
+ ]
+ );
+ server.respondWith(
+ 'DELETE',
+ '/changes/42/revisions/2/files/%2FCOMMIT_MSG/reviewed',
+ [
+ 204,
+ {'Content-Type': 'application/json'},
+ '',
+ ]
+ );
+ });
+
+ teardown(function() {
+ server.restore();
+ });
+
+ test('keyboard shortcuts', function() {
+ element._changeNum = '42';
+ element._patchRange = {
+ patchNum: '10',
+ };
+ element._change = {
+ revisions: {
+ a: { _number: 10, },
+ },
+ };
+ element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
+ element._path = 'glados.txt';
+ element.changeViewState.selectedFileIndex = 1;
+
+ var showStub = sinon.stub(page, 'show');
+ MockInteractions.pressAndReleaseKeyOn(element, 85); // 'u'
+ assert(showStub.lastCall.calledWithExactly('/c/42/'),
+ 'Should navigate to /c/42/');
+
+ MockInteractions.pressAndReleaseKeyOn(element, 221); // ']'
+ assert(showStub.lastCall.calledWithExactly('/c/42/10/wheatley.md'),
+ 'Should navigate to /c/42/10/wheatley.md');
+ element._path = 'wheatley.md';
+ assert.equal(element.changeViewState.selectedFileIndex, 2);
+
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ assert(showStub.lastCall.calledWithExactly('/c/42/10/glados.txt'),
+ 'Should navigate to /c/42/10/glados.txt');
+ element._path = 'glados.txt';
+ assert.equal(element.changeViewState.selectedFileIndex, 1);
+
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ assert(showStub.lastCall.calledWithExactly('/c/42/10/chell.go'),
+ 'Should navigate to /c/42/10/chell.go');
+ element._path = 'chell.go';
+ assert.equal(element.changeViewState.selectedFileIndex, 0);
+
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ assert(showStub.lastCall.calledWithExactly('/c/42/'),
+ 'Should navigate to /c/42/');
+ assert.equal(element.changeViewState.selectedFileIndex, 0);
+
+ var showPrefsStub = sinon.stub(element.$.diff, 'showDiffPreferences');
+ MockInteractions.pressAndReleaseKeyOn(element, 188); // ','
+ assert(showPrefsStub.calledOnce);
+
+ var scrollStub = sinon.stub(element.$.diff, 'scrollToNextDiffChunk');
+ MockInteractions.pressAndReleaseKeyOn(element, 78); // 'n'
+ assert(scrollStub.calledOnce);
+ scrollStub.restore();
+
+ scrollStub = sinon.stub(element.$.diff, 'scrollToPreviousDiffChunk');
+ MockInteractions.pressAndReleaseKeyOn(element, 80); // 'p'
+ assert(scrollStub.calledOnce);
+ scrollStub.restore();
+
+ scrollStub = sinon.stub(element.$.diff, 'scrollToNextCommentThread');
+ MockInteractions.pressAndReleaseKeyOn(element, 78, ['shift']); // 'N'
+ assert(scrollStub.calledOnce);
+ scrollStub.restore();
+
+ scrollStub = sinon.stub(element.$.diff, 'scrollToPreviousCommentThread');
+ MockInteractions.pressAndReleaseKeyOn(element, 80, ['shift']); // 'P'
+ assert(scrollStub.calledOnce);
+ scrollStub.restore();
+
+ showPrefsStub.restore();
+ showStub.restore();
+ });
+
+ test('keyboard shortcuts with patch range', function() {
+ element._changeNum = '42';
+ element._patchRange = {
+ basePatchNum: '5',
+ patchNum: '10',
+ };
+ element._change = {
+ revisions: {
+ a: { _number: 10, },
+ },
+ };
+ element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
+ element._path = 'glados.txt';
+
+ var showStub = sinon.stub(page, 'show');
+
+ MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
+ assert.isTrue(showStub.notCalled, 'The `a` keyboard shortcut should ' +
+ 'only work when the user is logged in.');
+ assert.isNull(window.sessionStorage.getItem(
+ 'changeView.showReplyDialog'));
+
+ element._loggedIn = true;
+ MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
+ assert.isTrue(element.changeViewState.showReplyDialog);
+
+ assert(showStub.lastCall.calledWithExactly('/c/42/'),
+ 'Should navigate to /c/42/');
+
+ MockInteractions.pressAndReleaseKeyOn(element, 85); // 'u'
+ assert(showStub.lastCall.calledWithExactly('/c/42/'),
+ 'Should navigate to /c/42/');
+
+ MockInteractions.pressAndReleaseKeyOn(element, 221); // ']'
+ assert(showStub.lastCall.calledWithExactly('/c/42/5..10/wheatley.md'),
+ 'Should navigate to /c/42/5..10/wheatley.md');
+ element._path = 'wheatley.md';
+
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ assert(showStub.lastCall.calledWithExactly('/c/42/5..10/glados.txt'),
+ 'Should navigate to /c/42/5..10/glados.txt');
+ element._path = 'glados.txt';
+
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ assert(showStub.lastCall.calledWithExactly('/c/42/5..10/chell.go'),
+ 'Should navigate to /c/42/5..10/chell.go');
+ element._path = 'chell.go';
+
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ assert(showStub.lastCall.calledWithExactly('/c/42/'),
+ 'Should navigate to /c/42/');
+
+ showStub.restore();
+ });
+
+ test('keyboard shortcuts with old patch number', function() {
+ element._changeNum = '42';
+ element._patchRange = {
+ patchNum: '1',
+ };
+ element._change = {
+ revisions: {
+ a: { _number: 1, },
+ b: { _number: 2, },
+ },
+ };
+ element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
+ element._path = 'glados.txt';
+
+ var showStub = sinon.stub(page, 'show');
+
+ MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
+ assert.isTrue(showStub.notCalled, 'The `a` keyboard shortcut should ' +
+ 'only work when the user is logged in.');
+ assert.isNull(window.sessionStorage.getItem(
+ 'changeView.showReplyDialog'));
+
+ element._loggedIn = true;
+ MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
+ assert.isTrue(element.changeViewState.showReplyDialog);
+
+ assert(showStub.lastCall.calledWithExactly('/c/42/1'),
+ 'Should navigate to /c/42/1');
+
+ MockInteractions.pressAndReleaseKeyOn(element, 85); // 'u'
+ assert(showStub.lastCall.calledWithExactly('/c/42/1'),
+ 'Should navigate to /c/42/1');
+
+ MockInteractions.pressAndReleaseKeyOn(element, 221); // ']'
+ assert(showStub.lastCall.calledWithExactly('/c/42/1/wheatley.md'),
+ 'Should navigate to /c/42/1/wheatley.md');
+ element._path = 'wheatley.md';
+
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ assert(showStub.lastCall.calledWithExactly('/c/42/1/glados.txt'),
+ 'Should navigate to /c/42/1/glados.txt');
+ element._path = 'glados.txt';
+
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ assert(showStub.lastCall.calledWithExactly('/c/42/1/chell.go'),
+ 'Should navigate to /c/42/1/chell.go');
+ element._path = 'chell.go';
+
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ assert(showStub.lastCall.calledWithExactly('/c/42/1'),
+ 'Should navigate to /c/42/1');
+
+ showStub.restore();
+ });
+
+ test('go up to change via kb without change loaded', function() {
+ element._changeNum = '42';
+ element._patchRange = {
+ patchNum: '1',
+ };
+
+ element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
+ element._path = 'glados.txt';
+
+ var showStub = sinon.stub(page, 'show');
+
+ MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
+ assert.isTrue(showStub.notCalled, 'The `a` keyboard shortcut should ' +
+ 'only work when the user is logged in.');
+ assert.isNull(window.sessionStorage.getItem(
+ 'changeView.showReplyDialog'));
+
+ element._loggedIn = true;
+ MockInteractions.pressAndReleaseKeyOn(element, 65); // 'a'
+ assert.isTrue(element.changeViewState.showReplyDialog);
+
+ assert(showStub.lastCall.calledWithExactly('/c/42/1'),
+ 'Should navigate to /c/42/1');
+
+ MockInteractions.pressAndReleaseKeyOn(element, 85); // 'u'
+ assert(showStub.lastCall.calledWithExactly('/c/42/1'),
+ 'Should navigate to /c/42/1');
+
+ MockInteractions.pressAndReleaseKeyOn(element, 221); // ']'
+ assert(showStub.lastCall.calledWithExactly('/c/42/1/wheatley.md'),
+ 'Should navigate to /c/42/1/wheatley.md');
+ element._path = 'wheatley.md';
+
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ assert(showStub.lastCall.calledWithExactly('/c/42/1/glados.txt'),
+ 'Should navigate to /c/42/1/glados.txt');
+ element._path = 'glados.txt';
+
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ assert(showStub.lastCall.calledWithExactly('/c/42/1/chell.go'),
+ 'Should navigate to /c/42/1/chell.go');
+ element._path = 'chell.go';
+
+ MockInteractions.pressAndReleaseKeyOn(element, 219); // '['
+ assert(showStub.lastCall.calledWithExactly('/c/42/1'),
+ 'Should navigate to /c/42/1');
+
+ showStub.restore();
+ });
+
+ test('jump to file dropdown', function() {
+ element._changeNum = '42';
+ element._patchRange = {
+ patchNum: '10',
+ };
+ element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
+ element._path = 'glados.txt';
+ flushAsynchronousOperations();
+ var linkEls =
+ Polymer.dom(element.root).querySelectorAll('.dropdown-content > a');
+ assert.equal(linkEls.length, 3);
+ assert.isFalse(linkEls[0].hasAttribute('selected'));
+ assert.isTrue(linkEls[1].hasAttribute('selected'));
+ assert.isFalse(linkEls[2].hasAttribute('selected'));
+ assert.equal(linkEls[0].getAttribute('data-key-nav'), '[');
+ assert.equal(linkEls[1].getAttribute('data-key-nav'), '');
+ assert.equal(linkEls[2].getAttribute('data-key-nav'), ']');
+ assert.equal(linkEls[0].getAttribute('href'), '/c/42/10/chell.go');
+ assert.equal(linkEls[1].getAttribute('href'), '/c/42/10/glados.txt');
+ assert.equal(linkEls[2].getAttribute('href'), '/c/42/10/wheatley.md');
+
+ assert.equal(element._computeFileDisplayName('/foo/bar/baz'),
+ '/foo/bar/baz');
+ assert.equal(element._computeFileDisplayName('/COMMIT_MSG'),
+ 'Commit message');
+ });
+
+ test('jump to file dropdown with patch range', function() {
+ element._changeNum = '42';
+ element._patchRange = {
+ basePatchNum: '5',
+ patchNum: '10',
+ };
+ element._fileList = ['chell.go', 'glados.txt', 'wheatley.md'];
+ element._path = 'glados.txt';
+ flushAsynchronousOperations();
+ var linkEls =
+ Polymer.dom(element.root).querySelectorAll('.dropdown-content > a');
+ assert.equal(linkEls.length, 3);
+ assert.isFalse(linkEls[0].hasAttribute('selected'));
+ assert.isTrue(linkEls[1].hasAttribute('selected'));
+ assert.isFalse(linkEls[2].hasAttribute('selected'));
+ assert.equal(linkEls[0].getAttribute('data-key-nav'), '[');
+ assert.equal(linkEls[1].getAttribute('data-key-nav'), '');
+ assert.equal(linkEls[2].getAttribute('data-key-nav'), ']');
+ assert.equal(linkEls[0].getAttribute('href'), '/c/42/5..10/chell.go');
+ assert.equal(linkEls[1].getAttribute('href'), '/c/42/5..10/glados.txt');
+ assert.equal(linkEls[2].getAttribute('href'), '/c/42/5..10/wheatley.md');
+ });
+
+ test('file review status', function(done) {
+ element._loggedIn = true;
+ element._changeNum = '42';
+ element._patchRange = {
+ basePatchNum: '1',
+ patchNum: '2',
+ };
+ element._fileList = ['/COMMIT_MSG'];
+ element._path = '/COMMIT_MSG';
+
+ server.respond();
+
+ element.async(function() {
+ var commitMsg = Polymer.dom(element.root).querySelector(
+ 'input[type="checkbox"]');
+
+ assert.isTrue(commitMsg.checked);
+
+ MockInteractions.tap(commitMsg);
+ server.respond();
+ element._xhrPromise.then(function(req) {
+ assert.isFalse(commitMsg.checked);
+ assert.equal(req.status, 204);
+ assert.equal(req.url,
+ '/changes/42/revisions/2/files/%2FCOMMIT_MSG/reviewed');
+
+ MockInteractions.tap(commitMsg);
+ server.respond();
+ }).then(function() {
+ element._xhrPromise.then(function(req) {
+ assert.isTrue(commitMsg.checked);
+ assert.equal(req.status, 201);
+ assert.equal(req.url,
+ '/changes/42/revisions/2/files/%2FCOMMIT_MSG/reviewed');
+
+ done();
+ });
+ });
+ }, 1);
+ });
+ });
+</script>