|  | <!DOCTYPE html> | 
|  | <!-- | 
|  | Copyright (C) 2017 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-repo-access</title> | 
|  |  | 
|  | <script src="../../../bower_components/page/page.js"></script> | 
|  | <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"/> | 
|  | <link rel="import" href="gr-repo-access.html"> | 
|  |  | 
|  | <script>void(0);</script> | 
|  |  | 
|  | <test-fixture id="basic"> | 
|  | <template> | 
|  | <gr-repo-access></gr-repo-access> | 
|  | </template> | 
|  | </test-fixture> | 
|  |  | 
|  | <script> | 
|  | suite('gr-repo-access tests', () => { | 
|  | let element; | 
|  | let sandbox; | 
|  | let repoStub; | 
|  |  | 
|  | const accessRes = { | 
|  | local: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | owner: { | 
|  | rules: { | 
|  | 234: {action: 'ALLOW'}, | 
|  | 123: {action: 'DENY'}, | 
|  | }, | 
|  | }, | 
|  | read: { | 
|  | rules: { | 
|  | 234: {action: 'ALLOW'}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | groups: { | 
|  | Administrators: { | 
|  | name: 'Administrators', | 
|  | }, | 
|  | Maintainers: { | 
|  | name: 'Maintainers', | 
|  | }, | 
|  | }, | 
|  | config_web_links: [{ | 
|  | name: 'gitiles', | 
|  | target: '_blank', | 
|  | url: 'https://my/site/+log/123/project.config', | 
|  | }], | 
|  | can_upload: true, | 
|  | }; | 
|  | const accessRes2 = { | 
|  | local: { | 
|  | GLOBAL_CAPABILITIES: { | 
|  | permissions: { | 
|  | accessDatabase: { | 
|  | rules: { | 
|  | group1: { | 
|  | action: 'ALLOW', | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | const repoRes = { | 
|  | labels: { | 
|  | 'Code-Review': { | 
|  | values: { | 
|  | ' 0': 'No score', | 
|  | '-1': 'I would prefer this is not merged as is', | 
|  | '-2': 'This shall not be merged', | 
|  | '+1': 'Looks good to me, but someone else must approve', | 
|  | '+2': 'Looks good to me, approved', | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | const capabilitiesRes = { | 
|  | accessDatabase: { | 
|  | id: 'accessDatabase', | 
|  | name: 'Access Database', | 
|  | }, | 
|  | createAccount: { | 
|  | id: 'createAccount', | 
|  | name: 'Create Account', | 
|  | }, | 
|  | }; | 
|  | setup(() => { | 
|  | sandbox = sinon.sandbox.create(); | 
|  | element = fixture('basic'); | 
|  | stub('gr-rest-api-interface', { | 
|  | getAccount() { return Promise.resolve(null); }, | 
|  | }); | 
|  | repoStub = sandbox.stub(element.$.restAPI, 'getRepo').returns( | 
|  | Promise.resolve(repoRes)); | 
|  | element._loading = false; | 
|  | }); | 
|  |  | 
|  | teardown(() => { | 
|  | sandbox.restore(); | 
|  | }); | 
|  |  | 
|  | test('_repoChanged called when repo name changes', () => { | 
|  | sandbox.stub(element, '_repoChanged'); | 
|  | element.repo = 'New Repo'; | 
|  | assert.isTrue(element._repoChanged.called); | 
|  | }); | 
|  |  | 
|  | test('_repoChanged', done => { | 
|  | const accessStub = sandbox.stub(element.$.restAPI, | 
|  | 'getRepoAccessRights'); | 
|  |  | 
|  | accessStub.withArgs('New Repo').returns( | 
|  | Promise.resolve(JSON.parse(JSON.stringify(accessRes)))); | 
|  | accessStub.withArgs('Another New Repo') | 
|  | .returns(Promise.resolve(JSON.parse(JSON.stringify(accessRes2)))); | 
|  | const capabilitiesStub = sandbox.stub(element.$.restAPI, | 
|  | 'getCapabilities'); | 
|  | capabilitiesStub.returns(Promise.resolve(capabilitiesRes)); | 
|  | const adminStub = sandbox.stub(element.$.restAPI, 'getIsAdmin').returns( | 
|  | Promise.resolve(true)); | 
|  |  | 
|  | element._repoChanged('New Repo').then(() => { | 
|  | assert.isTrue(accessStub.called); | 
|  | assert.isTrue(capabilitiesStub.called); | 
|  | assert.isTrue(repoStub.called); | 
|  | assert.isTrue(adminStub.called); | 
|  | assert.isNotOk(element._inheritsFrom); | 
|  | assert.deepEqual(element._local, accessRes.local); | 
|  | assert.deepEqual(element._sections, | 
|  | element.toSortedArray(accessRes.local)); | 
|  | assert.deepEqual(element._labels, repoRes.labels); | 
|  | assert.equal(getComputedStyle(element.$$('.weblinks')).display, | 
|  | 'block'); | 
|  | return element._repoChanged('Another New Repo'); | 
|  | }) | 
|  | .then(() => { | 
|  | assert.deepEqual(element._sections, | 
|  | element.toSortedArray(accessRes2.local)); | 
|  | assert.equal(getComputedStyle(element.$$('.weblinks')).display, | 
|  | 'none'); | 
|  | done(); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | test('_repoChanged when repo changes to undefined returns', done => { | 
|  | const capabilitiesRes = { | 
|  | accessDatabase: { | 
|  | id: 'accessDatabase', | 
|  | name: 'Access Database', | 
|  | }, | 
|  | }; | 
|  | const accessStub = sandbox.stub(element.$.restAPI, 'getRepoAccessRights') | 
|  | .returns(Promise.resolve(JSON.parse(JSON.stringify(accessRes2)))); | 
|  | const capabilitiesStub = sandbox.stub(element.$.restAPI, | 
|  | 'getCapabilities').returns(Promise.resolve(capabilitiesRes)); | 
|  |  | 
|  | element._repoChanged().then(() => { | 
|  | assert.isFalse(accessStub.called); | 
|  | assert.isFalse(capabilitiesStub.called); | 
|  | assert.isFalse(repoStub.called); | 
|  | done(); | 
|  | }); | 
|  | }); | 
|  |  | 
|  | test('_computeParentHref', () => { | 
|  | const repoName = 'test-repo'; | 
|  | assert.equal(element._computeParentHref(repoName), | 
|  | '/admin/repos/test-repo,access'); | 
|  | }); | 
|  |  | 
|  | test('_computeAdminClass', () => { | 
|  | let isAdmin = true; | 
|  | assert.equal(element._computeAdminClass(isAdmin), 'admin'); | 
|  | isAdmin = false; | 
|  | assert.equal(element._computeAdminClass(isAdmin), ''); | 
|  | }); | 
|  |  | 
|  | test('inherit section', () => { | 
|  | sandbox.stub(element, '_computeParentHref'); | 
|  | assert.isNotOk(Polymer.dom(element.root).querySelector('#inheritsFrom')); | 
|  | assert.isFalse(element._computeParentHref.called); | 
|  | element._inheritsFrom = { | 
|  | name: 'another-repo', | 
|  | }; | 
|  | flushAsynchronousOperations(); | 
|  | assert.isOk(Polymer.dom(element.root).querySelector('#inheritsFrom')); | 
|  | assert.isTrue(element._computeParentHref.called); | 
|  | }); | 
|  |  | 
|  | test('_computeLoadingClass', () => { | 
|  | assert.equal(element._computeLoadingClass(true), 'loading'); | 
|  | assert.equal(element._computeLoadingClass(false), ''); | 
|  | }); | 
|  |  | 
|  | suite('with defined sections', () => { | 
|  | const testEditSaveCancelBtns = () => { | 
|  | // Edit button is visible and Save button is hidden. | 
|  | assert.equal(getComputedStyle(element.$.saveBtn).display, 'none'); | 
|  | assert.notEqual(getComputedStyle(element.$.editBtn).display, 'none'); | 
|  | assert.equal(element.$.editBtn.innerText, 'EDIT'); | 
|  |  | 
|  | MockInteractions.tap(element.$.editBtn); | 
|  |  | 
|  | // Edit button changes to Cancel button, and Save button is visible but | 
|  | // disabled. | 
|  | assert.equal(element.$.editBtn.innerText, 'CANCEL'); | 
|  | assert.notEqual(getComputedStyle(element.$.saveBtn).display, 'none'); | 
|  | assert.isTrue(element.$.saveBtn.disabled); | 
|  |  | 
|  | // Save button should be enabled after access is modified | 
|  | element.fire('access-modified'); | 
|  | assert.isFalse(element.$.saveBtn.disabled); | 
|  | }; | 
|  |  | 
|  | setup(() => { | 
|  | // Create deep copies of these objects so the originals are not modified | 
|  | // by any tests. | 
|  | element._local = JSON.parse(JSON.stringify(accessRes.local)); | 
|  | element._sections = element.toSortedArray(element._local); | 
|  | element._groups = JSON.parse(JSON.stringify(accessRes.groups)); | 
|  | element._capabilities = JSON.parse(JSON.stringify(capabilitiesRes)); | 
|  | element._labels = JSON.parse(JSON.stringify(repoRes.labels)); | 
|  | flushAsynchronousOperations(); | 
|  | }); | 
|  |  | 
|  | test('button visibility for non admin', () => { | 
|  | assert.equal(getComputedStyle(element.$.saveBtn).display, 'none'); | 
|  | assert.equal(getComputedStyle(element.$.editBtn).display, 'none'); | 
|  | }); | 
|  |  | 
|  | test('button visibility for non admin with upload privilege', () => { | 
|  | element._canUpload = true; | 
|  | testEditSaveCancelBtns(); | 
|  | }); | 
|  |  | 
|  | test('button visibility for admin', () => { | 
|  | element._isAdmin = true; | 
|  | testEditSaveCancelBtns(); | 
|  | }); | 
|  |  | 
|  | test('_handleAccessModified called with event fired', () => { | 
|  | sandbox.spy(element, '_handleAccessModified'); | 
|  | element.fire('access-modified'); | 
|  | assert.isTrue(element._handleAccessModified.called); | 
|  | }); | 
|  |  | 
|  | test('_handleAccessModified called with event fired', () => { | 
|  | const saveStub = | 
|  | sandbox.stub(element.$.restAPI, 'setProjectAccessRightsForReview'); | 
|  | sandbox.stub(element, '_computeAddAndRemove').returns({ | 
|  | add: {}, | 
|  | remove: {}, | 
|  | }); | 
|  | element._handleSaveForReview(); | 
|  | assert.isFalse(saveStub.called); | 
|  | }); | 
|  |  | 
|  | test('_recursivelyRemoveDeleted', () => { | 
|  | const obj = { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | owner: { | 
|  | rules: { | 
|  | 234: {action: 'ALLOW'}, | 
|  | 123: {action: 'DENY', deleted: true}, | 
|  | }, | 
|  | }, | 
|  | read: { | 
|  | deleted: true, | 
|  | rules: { | 
|  | 234: {action: 'ALLOW'}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | const expectedResult = { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | owner: { | 
|  | rules: { | 
|  | 234: {action: 'ALLOW'}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | element._recursivelyRemoveDeleted(obj); | 
|  | assert.deepEqual(obj, expectedResult); | 
|  | }); | 
|  |  | 
|  | test('_handleSaveForReview with no changes', () => { | 
|  | assert.deepEqual(element._computeAddAndRemove(), {add: {}, remove: {}}); | 
|  | }); | 
|  |  | 
|  | test('_handleSaveForReview rules', () => { | 
|  | // Delete a rule. | 
|  | element._local['refs/*'].permissions.owner.rules[123].deleted = true; | 
|  | let expectedInput = { | 
|  | add: {}, | 
|  | remove: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | owner: { | 
|  | rules: { | 
|  | 123: {}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  |  | 
|  | // Undo deleting a rule. | 
|  | delete element._local['refs/*'].permissions.owner.rules[123].deleted; | 
|  |  | 
|  | // Modify a rule. | 
|  | element._local['refs/*'].permissions.owner.rules[123].modified = true; | 
|  | expectedInput = { | 
|  | add: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | owner: { | 
|  | rules: { | 
|  | 123: {action: 'DENY', modified: true}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | owner: { | 
|  | rules: { | 
|  | 123: {}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  | }); | 
|  |  | 
|  | test('_computeAddAndRemove permissions', () => { | 
|  | // Add a new rule to a permission. | 
|  | let expectedInput = { | 
|  | add: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | owner: { | 
|  | rules: { | 
|  | Maintainers: { | 
|  | action: 'ALLOW', | 
|  | added: true, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: {}, | 
|  | }; | 
|  |  | 
|  | element.$$('gr-access-section').$$('gr-permission')._handleAddRuleItem( | 
|  | {detail: {value: {id: 'Maintainers'}}}); | 
|  |  | 
|  | flushAsynchronousOperations(); | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  |  | 
|  | // Remove the added rule. | 
|  | delete element._local['refs/*'].permissions.owner.rules.Maintainers; | 
|  |  | 
|  | // Delete a permission. | 
|  | element._local['refs/*'].permissions.owner.deleted = true; | 
|  | expectedInput = { | 
|  | add: {}, | 
|  | remove: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | owner: {rules: {}}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  |  | 
|  | // Undo delete permission. | 
|  | delete element._local['refs/*'].permissions.owner.deleted; | 
|  |  | 
|  | // Modify a permission. | 
|  | element._local['refs/*'].permissions.owner.modified = true; | 
|  | expectedInput = { | 
|  | add: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | owner: { | 
|  | modified: true, | 
|  | rules: { | 
|  | 234: {action: 'ALLOW'}, | 
|  | 123: {action: 'DENY'}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | owner: {rules: {}}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  | }); | 
|  |  | 
|  | test('_computeAddAndRemove sections', () => { | 
|  | // Add a new permission to a section | 
|  | expectedInput = { | 
|  | add: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | 'label-Code-Review': { | 
|  | added: true, | 
|  | rules: {}, | 
|  | label: 'Code-Review', | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: {}, | 
|  | }; | 
|  | element.$$('gr-access-section')._handleAddPermission(); | 
|  | flushAsynchronousOperations(); | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  |  | 
|  | // Add a new rule to the new permission. | 
|  | expectedInput = { | 
|  | add: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | 'label-Code-Review': { | 
|  | added: true, | 
|  | rules: { | 
|  | Maintainers: { | 
|  | min: -2, | 
|  | max: 2, | 
|  | action: 'ALLOW', | 
|  | added: true, | 
|  | }, | 
|  | }, | 
|  | label: 'Code-Review', | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: {}, | 
|  | }; | 
|  | const newPermission = | 
|  | Polymer.dom(element.$$('gr-access-section').root).querySelectorAll( | 
|  | 'gr-permission')[2]; | 
|  | newPermission._handleAddRuleItem( | 
|  | {detail: {value: {id: 'Maintainers'}}}); | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  |  | 
|  | // Modify a section reference. | 
|  | element._local['refs/*'].updatedId = 'refs/for/bar'; | 
|  | element._local['refs/*'].modified = true; | 
|  | expectedInput = { | 
|  | add: { | 
|  | 'refs/for/bar': { | 
|  | modified: true, | 
|  | updatedId: 'refs/for/bar', | 
|  | permissions: { | 
|  | 'owner': { | 
|  | rules: { | 
|  | 234: {action: 'ALLOW'}, | 
|  | 123: {action: 'DENY'}, | 
|  | }, | 
|  | }, | 
|  | 'read': { | 
|  | rules: { | 
|  | 234: {action: 'ALLOW'}, | 
|  | }, | 
|  | }, | 
|  | 'label-Code-Review': { | 
|  | added: true, | 
|  | rules: { | 
|  | Maintainers: { | 
|  | min: -2, | 
|  | max: 2, | 
|  | action: 'ALLOW', | 
|  | added: true, | 
|  | }, | 
|  | }, | 
|  | label: 'Code-Review', | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: { | 
|  | 'refs/*': { | 
|  | permissions: {}, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  |  | 
|  | // Delete a section. | 
|  | element._local['refs/*'].deleted = true; | 
|  | expectedInput = { | 
|  | add: {}, | 
|  | remove: { | 
|  | 'refs/*': { | 
|  | permissions: {}, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  | }); | 
|  |  | 
|  | test('_computeAddAndRemove new section', () => { | 
|  | // Add a new permission to a section | 
|  | expectedInput = { | 
|  | add: { | 
|  | 'refs/for/*': { | 
|  | added: true, | 
|  | permissions: {}, | 
|  | }, | 
|  | }, | 
|  | remove: {}, | 
|  | }; | 
|  | MockInteractions.tap(element.$.addReferenceBtn); | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  |  | 
|  | expectedInput = { | 
|  | add: { | 
|  | 'refs/for/*': { | 
|  | added: true, | 
|  | permissions: { | 
|  | 'label-Code-Review': { | 
|  | added: true, | 
|  | rules: {}, | 
|  | label: 'Code-Review', | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: {}, | 
|  | }; | 
|  | const newSection = Polymer.dom(element.root) | 
|  | .querySelectorAll('gr-access-section')[1]; | 
|  | newSection._handleAddPermission(); | 
|  | flushAsynchronousOperations(); | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  |  | 
|  | // Add rule to the new permission. | 
|  | expectedInput = { | 
|  | add: { | 
|  | 'refs/for/*': { | 
|  | added: true, | 
|  | permissions: { | 
|  | 'label-Code-Review': { | 
|  | added: true, | 
|  | rules: { | 
|  | Maintainers: { | 
|  | action: 'ALLOW', | 
|  | added: true, | 
|  | max: 2, | 
|  | min: -2, | 
|  | }, | 
|  | }, | 
|  | label: 'Code-Review', | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: {}, | 
|  | }; | 
|  |  | 
|  | newSection.$$('gr-permission')._handleAddRuleItem( | 
|  | {detail: {value: {id: 'Maintainers'}}}); | 
|  |  | 
|  | flushAsynchronousOperations(); | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  |  | 
|  | // Modify a the reference from the default value. | 
|  | element._local['refs/for/*'].updatedId = 'refs/for/new'; | 
|  | expectedInput = { | 
|  | add: { | 
|  | 'refs/for/new': { | 
|  | added: true, | 
|  | updatedId: 'refs/for/new', | 
|  | permissions: { | 
|  | 'label-Code-Review': { | 
|  | added: true, | 
|  | rules: { | 
|  | Maintainers: { | 
|  | action: 'ALLOW', | 
|  | added: true, | 
|  | max: 2, | 
|  | min: -2, | 
|  | }, | 
|  | }, | 
|  | label: 'Code-Review', | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: {}, | 
|  | }; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  | }); | 
|  |  | 
|  | test('_computeAddAndRemove combinations', () => { | 
|  | // Modify rule and delete permission that it is inside of. | 
|  | element._local['refs/*'].permissions.owner.rules[123].modified = true; | 
|  | element._local['refs/*'].permissions.owner.deleted = true; | 
|  | let expectedInput = { | 
|  | add: {}, | 
|  | remove: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | owner: {rules: {}}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  | // Delete rule and delete permission that it is inside of. | 
|  | element._local['refs/*'].permissions.owner.rules[123].modified = false; | 
|  | element._local['refs/*'].permissions.owner.rules[123].deleted = true; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  |  | 
|  | // Also modify a different rule inside of another permission. | 
|  | element._local['refs/*'].permissions.read.modified = true; | 
|  | expectedInput = { | 
|  | add: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | read: { | 
|  | modified: true, | 
|  | rules: { | 
|  | 234: {action: 'ALLOW'}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | owner: {rules: {}}, | 
|  | read: {rules: {}}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  | // Modify both permissions with an exclusive bit. Owner is still | 
|  | // deleted. | 
|  | element._local['refs/*'].permissions.owner.exclusive = true; | 
|  | element._local['refs/*'].permissions.owner.modified = true; | 
|  | element._local['refs/*'].permissions.read.exclusive = true; | 
|  | element._local['refs/*'].permissions.read.modified = true; | 
|  | expectedInput = { | 
|  | add: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | read: { | 
|  | exclusive: true, | 
|  | modified: true, | 
|  | rules: { | 
|  | 234: {action: 'ALLOW'}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | owner: {rules: {}}, | 
|  | read: {rules: {}}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  |  | 
|  | // Add a rule to the existing permission; | 
|  | const readPermission = | 
|  | Polymer.dom(element.$$('gr-access-section').root).querySelectorAll( | 
|  | 'gr-permission')[1]; | 
|  | readPermission._handleAddRuleItem( | 
|  | {detail: {value: {id: 'Maintainers'}}}); | 
|  |  | 
|  | expectedInput = { | 
|  | add: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | read: { | 
|  | exclusive: true, | 
|  | modified: true, | 
|  | rules: { | 
|  | 234: {action: 'ALLOW'}, | 
|  | Maintainers: {action: 'ALLOW', added: true}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | owner: {rules: {}}, | 
|  | read: {rules: {}}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  |  | 
|  | // Change one of the refs | 
|  | element._local['refs/*'].updatedId = 'refs/for/bar'; | 
|  | element._local['refs/*'].modified = true; | 
|  |  | 
|  | expectedInput = { | 
|  | add: { | 
|  | 'refs/for/bar': { | 
|  | modified: true, | 
|  | updatedId: 'refs/for/bar', | 
|  | permissions: { | 
|  | read: { | 
|  | exclusive: true, | 
|  | modified: true, | 
|  | rules: { | 
|  | 234: {action: 'ALLOW'}, | 
|  | Maintainers: {action: 'ALLOW', added: true}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: { | 
|  | 'refs/*': { | 
|  | permissions: {}, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  |  | 
|  | expectedInput = { | 
|  | add: {}, | 
|  | remove: { | 
|  | 'refs/*': { | 
|  | permissions: {}, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | element._local['refs/*'].deleted = true; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  |  | 
|  | // Add a new section. | 
|  | MockInteractions.tap(element.$.addReferenceBtn); | 
|  | let newSection = Polymer.dom(element.root) | 
|  | .querySelectorAll('gr-access-section')[1]; | 
|  | newSection._handleAddPermission(); | 
|  | flushAsynchronousOperations(); | 
|  | newSection.$$('gr-permission')._handleAddRuleItem( | 
|  | {detail: {value: {id: 'Maintainers'}}}); | 
|  | // Modify a the reference from the default value. | 
|  | element._local['refs/for/*'].updatedId = 'refs/for/new'; | 
|  |  | 
|  | expectedInput = { | 
|  | add: { | 
|  | 'refs/for/new': { | 
|  | added: true, | 
|  | updatedId: 'refs/for/new', | 
|  | permissions: { | 
|  | 'label-Code-Review': { | 
|  | added: true, | 
|  | rules: { | 
|  | Maintainers: { | 
|  | action: 'ALLOW', | 
|  | added: true, | 
|  | max: 2, | 
|  | min: -2, | 
|  | }, | 
|  | }, | 
|  | label: 'Code-Review', | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: { | 
|  | 'refs/*': { | 
|  | permissions: {}, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  |  | 
|  | // Modify newly added rule inside new ref. | 
|  | element._local['refs/for/*'].permissions['label-Code-Review']. | 
|  | rules['Maintainers'].modified = true; | 
|  | expectedInput = { | 
|  | add: { | 
|  | 'refs/for/new': { | 
|  | added: true, | 
|  | updatedId: 'refs/for/new', | 
|  | permissions: { | 
|  | 'label-Code-Review': { | 
|  | added: true, | 
|  | rules: { | 
|  | Maintainers: { | 
|  | action: 'ALLOW', | 
|  | added: true, | 
|  | modified: true, | 
|  | max: 2, | 
|  | min: -2, | 
|  | }, | 
|  | }, | 
|  | label: 'Code-Review', | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: { | 
|  | 'refs/*': { | 
|  | permissions: {}, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  |  | 
|  | // Add a second new section. | 
|  | MockInteractions.tap(element.$.addReferenceBtn); | 
|  | newSection = Polymer.dom(element.root) | 
|  | .querySelectorAll('gr-access-section')[2]; | 
|  | newSection._handleAddPermission(); | 
|  | flushAsynchronousOperations(); | 
|  | newSection.$$('gr-permission')._handleAddRuleItem( | 
|  | {detail: {value: {id: 'Maintainers'}}}); | 
|  | // Modify a the reference from the default value. | 
|  | element._local['refs/for/**'].updatedId = 'refs/for/new2'; | 
|  | expectedInput = { | 
|  | add: { | 
|  | 'refs/for/new': { | 
|  | added: true, | 
|  | updatedId: 'refs/for/new', | 
|  | permissions: { | 
|  | 'label-Code-Review': { | 
|  | added: true, | 
|  | rules: { | 
|  | Maintainers: { | 
|  | action: 'ALLOW', | 
|  | added: true, | 
|  | modified: true, | 
|  | max: 2, | 
|  | min: -2, | 
|  | }, | 
|  | }, | 
|  | label: 'Code-Review', | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | 'refs/for/new2': { | 
|  | added: true, | 
|  | updatedId: 'refs/for/new2', | 
|  | permissions: { | 
|  | 'label-Code-Review': { | 
|  | added: true, | 
|  | rules: { | 
|  | Maintainers: { | 
|  | action: 'ALLOW', | 
|  | added: true, | 
|  | max: 2, | 
|  | min: -2, | 
|  | }, | 
|  | }, | 
|  | label: 'Code-Review', | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: { | 
|  | 'refs/*': { | 
|  | permissions: {}, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | assert.deepEqual(element._computeAddAndRemove(), expectedInput); | 
|  | }); | 
|  |  | 
|  | test('Unsaved added refs are discarded when edit cancelled', () => { | 
|  | // Unsaved changes are discarded when editing is cancelled. | 
|  | MockInteractions.tap(element.$.editBtn); | 
|  | assert.equal(element._sections.length, 1); | 
|  | assert.equal(Object.keys(element._local).length, 1); | 
|  | MockInteractions.tap(element.$.addReferenceBtn); | 
|  | assert.equal(element._sections.length, 2); | 
|  | assert.equal(Object.keys(element._local).length, 2); | 
|  | MockInteractions.tap(element.$.editBtn); | 
|  | assert.equal(element._sections.length, 1); | 
|  | assert.equal(Object.keys(element._local).length, 1); | 
|  | }); | 
|  |  | 
|  | test('_handleSaveForReview', done => { | 
|  | const repoAccessInput = { | 
|  | add: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | owner: { | 
|  | rules: { | 
|  | 123: {action: 'DENY', modified: true}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | remove: { | 
|  | 'refs/*': { | 
|  | permissions: { | 
|  | owner: { | 
|  | rules: { | 
|  | 123: {}, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }, | 
|  | }; | 
|  | sandbox.stub(element.$.restAPI, 'getRepoAccessRights').returns( | 
|  | Promise.resolve(JSON.parse(JSON.stringify(accessRes)))); | 
|  | sandbox.stub(Gerrit.Nav, 'navigateToChange'); | 
|  | const saveForReviewStub = sandbox.stub(element.$.restAPI, | 
|  | 'setProjectAccessRightsForReview') | 
|  | .returns(Promise.resolve({_number: 1})); | 
|  |  | 
|  | element.repo = 'test-repo'; | 
|  | sandbox.stub(element, '_computeAddAndRemove').returns(repoAccessInput); | 
|  |  | 
|  | element._handleSaveForReview().then(() => { | 
|  | assert.isTrue(saveForReviewStub.called); | 
|  | assert.isTrue(Gerrit.Nav.navigateToChange | 
|  | .lastCall.calledWithExactly({_number: 1})); | 
|  | done(); | 
|  | }); | 
|  | }); | 
|  | }); | 
|  | }); | 
|  | </script> |