blob: 08415956f493666be4b87859fcdb775e7bcde0f6 [file] [log] [blame]
/**
* @license
* Copyright 2017 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import '../../../test/common-test-setup';
import './gr-group-members';
import {GrGroupMembers, ItemType} from './gr-group-members';
import {
addListenerForTest,
mockPromise,
queryAll,
queryAndAssert,
stubBaseUrl,
stubRestApi,
waitUntil,
} from '../../../test/test-utils';
import {
AccountId,
AccountInfo,
EmailAddress,
GroupId,
GroupInfo,
GroupName,
} from '../../../types/common';
import {GrButton} from '../../shared/gr-button/gr-button';
import {GrAutocomplete} from '../../shared/gr-autocomplete/gr-autocomplete';
import {EventType, PageErrorEvent} from '../../../types/events';
import {getAccountSuggestions} from '../../../utils/account-util';
import {getAppContext} from '../../../services/app-context';
import {fixture, html, assert} from '@open-wc/testing';
import {createServerInfo} from '../../../test/test-data-generators';
suite('gr-group-members tests', () => {
let element: GrGroupMembers;
let groups: GroupInfo;
let groupMembers: AccountInfo[];
let includedGroups: GroupInfo[];
let groupStub: sinon.SinonStub;
setup(async () => {
groups = {
id: 'testId1' as GroupId,
name: 'Administrators' as GroupName,
owner: 'Administrators',
group_id: 1,
};
groupMembers = [
{
_account_id: 1000097 as AccountId,
name: 'Jane Roe',
email: 'jane.roe@example.com' as EmailAddress,
username: 'jane',
},
{
_account_id: 1000096 as AccountId,
name: 'Test User',
email: 'john.doe@example.com' as EmailAddress,
},
{
_account_id: 1000095 as AccountId,
name: 'Gerrit',
},
{
_account_id: 1000098 as AccountId,
},
];
includedGroups = [
{
url: 'https://group/url',
options: {
visible_to_all: false,
},
id: 'testId' as GroupId,
name: 'testName' as GroupName,
},
{
url: '/group/url',
options: {
visible_to_all: false,
},
id: 'testId2' as GroupId,
name: 'testName2' as GroupName,
},
{
url: '#/group/url',
options: {
visible_to_all: false,
},
id: 'testId3' as GroupId,
name: 'testName3' as GroupName,
},
];
stubRestApi('getSuggestedAccounts').callsFake(input => {
if (input.startsWith('test')) {
return Promise.resolve([
{
_account_id: 1000096 as AccountId,
name: 'test-account',
email: 'test.account@example.com' as EmailAddress,
username: 'test123',
display_name: 'display-test-account',
},
{
_account_id: 1001439 as AccountId,
name: 'test-admin',
email: 'test.admin@example.com' as EmailAddress,
username: 'test_admin',
},
{
_account_id: 1001439 as AccountId,
name: 'test-git',
username: 'test_git',
},
]);
} else {
return Promise.resolve([]);
}
});
stubRestApi('getSuggestedGroups').callsFake(input => {
if (input.startsWith('test')) {
return Promise.resolve({
'test-admin': {
id: '1ce023d3fb4e4260776fb92cd08b52bbd21ce70a',
},
'test/Administrator (admin)': {
id: 'test%3Aadmin',
},
});
} else {
return Promise.resolve({});
}
});
stubRestApi('getGroupMembers').returns(Promise.resolve(groupMembers));
stubRestApi('getIsGroupOwner').returns(Promise.resolve(true));
stubRestApi('getIncludedGroup').returns(Promise.resolve(includedGroups));
element = await fixture(html`<gr-group-members></gr-group-members>`);
stubBaseUrl('https://test/site');
element.groupId = 'testId1' as GroupId;
groupStub = stubRestApi('getGroupConfig').returns(Promise.resolve(groups));
return element.loadGroupDetails();
});
test('render', () => {
assert.shadowDom.equal(
element,
/* HTML */ `
<div class="gr-form-styles main">
<div id="loading">Loading...</div>
<div id="loadedContent">
<h1 class="heading-1" id="Title">Administrators</h1>
<div id="form">
<h3 class="heading-3" id="members">Members</h3>
<fieldset>
<span class="value">
<gr-autocomplete
id="groupMemberSearchInput"
placeholder="Name Or Email"
>
</gr-autocomplete>
</span>
<gr-button
aria-disabled="true"
disabled=""
id="saveGroupMember"
role="button"
tabindex="-1"
>
Add
</gr-button>
<table id="groupMembers">
<tbody>
<tr class="headerRow">
<th class="nameHeader">Name</th>
<th class="emailAddressHeader">Email Address</th>
<th class="deleteHeader">Delete Member</th>
</tr>
</tbody>
<tbody>
<tr>
<td class="nameColumn">
<gr-account-label clickable="" deselected="">
</gr-account-label>
</td>
<td>jane.roe@example.com</td>
<td class="deleteColumn">
<gr-button
aria-disabled="false"
class="deleteMembersButton"
data-index="0"
role="button"
tabindex="0"
>
Delete
</gr-button>
</td>
</tr>
<tr>
<td class="nameColumn">
<gr-account-label clickable="" deselected="">
</gr-account-label>
</td>
<td>john.doe@example.com</td>
<td class="deleteColumn">
<gr-button
aria-disabled="false"
class="deleteMembersButton"
data-index="1"
role="button"
tabindex="0"
>
Delete
</gr-button>
</td>
</tr>
<tr>
<td class="nameColumn">
<gr-account-label clickable="" deselected="">
</gr-account-label>
</td>
<td></td>
<td class="deleteColumn">
<gr-button
aria-disabled="false"
class="deleteMembersButton"
data-index="2"
role="button"
tabindex="0"
>
Delete
</gr-button>
</td>
</tr>
<tr>
<td class="nameColumn">
<gr-account-label clickable="" deselected="">
</gr-account-label>
</td>
<td></td>
<td class="deleteColumn">
<gr-button
aria-disabled="false"
class="deleteMembersButton"
data-index="3"
role="button"
tabindex="0"
>
Delete
</gr-button>
</td>
</tr>
</tbody>
</table>
</fieldset>
<h3 class="heading-3" id="includedGroups">Included Groups</h3>
<fieldset>
<span class="value">
<gr-autocomplete
id="includedGroupSearchInput"
placeholder="Group Name"
>
</gr-autocomplete>
</span>
<gr-button
aria-disabled="true"
disabled=""
id="saveIncludedGroups"
role="button"
tabindex="-1"
>
Add
</gr-button>
<table id="includedGroups">
<tbody>
<tr class="headerRow">
<th class="groupNameHeader">Group Name</th>
<th class="descriptionHeader">Description</th>
<th class="deleteIncludedHeader">Delete Group</th>
</tr>
</tbody>
<tbody>
<tr>
<td class="nameColumn">
<a href="https://group/url" rel="noopener">
testName
</a>
</td>
<td></td>
<td class="deleteColumn">
<gr-button
aria-disabled="false"
class="deleteIncludedGroupButton"
data-index="0"
role="button"
tabindex="0"
>
Delete
</gr-button>
</td>
</tr>
<tr>
<td class="nameColumn">
<a href="https://test/site/group/url" rel="noopener">
testName2
</a>
</td>
<td></td>
<td class="deleteColumn">
<gr-button
aria-disabled="false"
class="deleteIncludedGroupButton"
data-index="1"
role="button"
tabindex="0"
>
Delete
</gr-button>
</td>
</tr>
<tr>
<td class="nameColumn">
<a href="https://test/site/group/url" rel="noopener">
testName3
</a>
</td>
<td></td>
<td class="deleteColumn">
<gr-button
aria-disabled="false"
class="deleteIncludedGroupButton"
data-index="2"
role="button"
tabindex="0"
>
Delete
</gr-button>
</td>
</tr>
</tbody>
</table>
</fieldset>
</div>
</div>
</div>
<dialog id="modal" tabindex="-1">
<gr-confirm-delete-item-dialog class="confirmDialog">
</gr-confirm-delete-item-dialog>
</dialog>
`
);
});
test('includedGroups', () => {
assert.equal(element.includedGroups!.length, 3);
assert.equal(
queryAll<HTMLAnchorElement>(element, '.nameColumn a')[0].href,
includedGroups[0].url
);
assert.equal(
queryAll<HTMLAnchorElement>(element, '.nameColumn a')[1].href,
'https://test/site/group/url'
);
assert.equal(
queryAll<HTMLAnchorElement>(element, '.nameColumn a')[2].href,
'https://test/site/group/url'
);
});
test('save members correctly', async () => {
element.groupOwner = true;
const memberName = 'test-admin';
const saveStub = stubRestApi('saveGroupMember').callsFake(() =>
Promise.resolve({})
);
const button = queryAndAssert<GrButton>(element, '#saveGroupMember');
assert.isTrue(button.hasAttribute('disabled'));
const groupMemberSearchInput = queryAndAssert<GrAutocomplete>(
element,
'#groupMemberSearchInput'
);
groupMemberSearchInput.text = memberName;
groupMemberSearchInput.value = '1234';
await waitUntil(() => !button.hasAttribute('disabled'));
return element.handleSavingGroupMember().then(() => {
assert.isTrue(button.hasAttribute('disabled'));
assert.isFalse(
queryAndAssert<HTMLHeadingElement>(
element,
'#Title'
).classList.contains('edited')
);
assert.equal(saveStub.lastCall.args[0], 'Administrators');
assert.equal(saveStub.lastCall.args[1], 1234);
});
});
test('save included groups correctly', async () => {
element.groupOwner = true;
const includedGroupName = 'testName';
const saveIncludedGroupStub = stubRestApi('saveIncludedGroup').callsFake(
() => Promise.resolve({id: '0' as GroupId})
);
const button = queryAndAssert<GrButton>(element, '#saveIncludedGroups');
await waitUntil(() => button.hasAttribute('disabled'));
const includedGroupSearchInput = queryAndAssert<GrAutocomplete>(
element,
'#includedGroupSearchInput'
);
includedGroupSearchInput.text = includedGroupName;
includedGroupSearchInput.value = 'testId';
await waitUntil(() => !button.hasAttribute('disabled'));
return element.handleSavingIncludedGroups().then(() => {
assert.isTrue(button.hasAttribute('disabled'));
assert.isFalse(
queryAndAssert<HTMLHeadingElement>(
element,
'#Title'
).classList.contains('edited')
);
assert.equal(saveIncludedGroupStub.lastCall.args[0], 'Administrators');
assert.equal(saveIncludedGroupStub.lastCall.args[1], 'testId');
});
});
test('add included group 404 shows helpful error text', async () => {
element.groupOwner = true;
element.groupName = 'test' as GroupName;
const memberName = 'bad-name';
const alertStub = sinon.stub();
element.addEventListener(EventType.SHOW_ALERT, alertStub);
const errorResponse = {...new Response(), status: 404, ok: false};
stubRestApi('saveIncludedGroup').callsFake((_, _non, errFn) => {
if (errFn !== undefined) {
errFn(errorResponse);
} else {
assert.fail('errFn is undefined');
}
return Promise.resolve(undefined);
});
const groupMemberSearchInput = queryAndAssert<GrAutocomplete>(
element,
'#groupMemberSearchInput'
);
groupMemberSearchInput.text = memberName;
groupMemberSearchInput.value = '1234';
await element.updateComplete;
element.handleSavingIncludedGroups().then(() => {
assert.isTrue(alertStub.called);
});
});
test('add included group network-error throws an exception', async () => {
element.groupOwner = true;
const memberName = 'bad-name';
stubRestApi('saveIncludedGroup').throws(new Error());
const groupMemberSearchInput = queryAndAssert<GrAutocomplete>(
element,
'#groupMemberSearchInput'
);
groupMemberSearchInput.text = memberName;
groupMemberSearchInput.value = '1234';
let exceptionThrown = false;
try {
await element.handleSavingIncludedGroups();
} catch (e) {
exceptionThrown = true;
}
assert.isTrue(exceptionThrown);
});
test('getAccountSuggestions empty', async () => {
const accounts = await getAccountSuggestions(
'nonexistent',
getAppContext().restApiService,
createServerInfo()
);
assert.equal(accounts.length, 0);
});
test('getAccountSuggestions non-empty', async () => {
const accounts = await getAccountSuggestions(
'test-',
getAppContext().restApiService,
createServerInfo()
);
assert.equal(accounts.length, 3);
assert.equal(
accounts[0].name,
'display-test-account <test.account@example.com>'
);
assert.equal(accounts[1].name, 'test-admin <test.admin@example.com>');
assert.equal(accounts[2].name, 'test-git');
});
test('getGroupSuggestions empty', async () => {
const groups = await element.getGroupSuggestions('nonexistent');
assert.equal(groups.length, 0);
});
test('getGroupSuggestions non-empty', async () => {
const groups = await element.getGroupSuggestions('test');
assert.equal(groups.length, 2);
assert.equal(groups[0].name, 'test-admin');
assert.equal(groups[1].name, 'test/Administrator (admin)');
});
test('delete member', () => {
const deleteBtns = queryAll<GrButton>(element, '.deleteMembersButton');
deleteBtns[0].click();
assert.equal(element.itemId, 1000097 as AccountId);
assert.equal(element.itemName, 'jane');
queryAndAssert<HTMLDialogElement>(element, 'dialog').close();
deleteBtns[1].click();
assert.equal(element.itemId, 1000096 as AccountId);
assert.equal(element.itemName, 'Test User');
queryAndAssert<HTMLDialogElement>(element, 'dialog').close();
deleteBtns[2].click();
assert.equal(element.itemId, 1000095 as AccountId);
assert.equal(element.itemName, 'Gerrit');
queryAndAssert<HTMLDialogElement>(element, 'dialog').close();
deleteBtns[3].click();
assert.equal(element.itemId, 1000098 as AccountId);
assert.equal(element.itemName, '1000098');
});
test('delete included groups', () => {
const deleteBtns = queryAll<GrButton>(
element,
'.deleteIncludedGroupButton'
);
deleteBtns[0].click();
assert.equal(element.itemId, 'testId' as GroupId);
assert.equal(element.itemName, 'testName');
queryAndAssert<HTMLDialogElement>(element, 'dialog').close();
deleteBtns[1].click();
assert.equal(element.itemId, 'testId2' as GroupId);
assert.equal(element.itemName, 'testName2');
queryAndAssert<HTMLDialogElement>(element, 'dialog').close();
deleteBtns[2].click();
assert.equal(element.itemId, 'testId3' as GroupId);
assert.equal(element.itemName, 'testName3');
});
test('computeGroupUrl', () => {
assert.isUndefined(element.computeGroupUrl(undefined));
let url = '#/admin/groups/uuid-529b3c2605bb1029c8146f9de4a91c776fe64498';
assert.equal(
element.computeGroupUrl(url),
'https://test/site/admin/groups/' +
'uuid-529b3c2605bb1029c8146f9de4a91c776fe64498'
);
url =
'https://gerrit.local/admin/groups/' +
'uuid-529b3c2605bb1029c8146f9de4a91c776fe64498';
assert.equal(element.computeGroupUrl(url), url);
});
test('fires page-error', async () => {
groupStub.restore();
element.groupId = 'testId1' as GroupId;
const response = {...new Response(), status: 404};
stubRestApi('getGroupConfig').callsFake((_, errFn) => {
if (errFn !== undefined) {
errFn(response);
}
return Promise.resolve(undefined);
});
const promise = mockPromise();
addListenerForTest(document, 'page-error', e => {
assert.deepEqual((e as PageErrorEvent).detail.response, response);
promise.resolve();
});
element.loadGroupDetails();
await promise;
});
test('_computeItemName', () => {
assert.equal(element.computeItemTypeName(ItemType.MEMBER), 'Member');
assert.equal(
element.computeItemTypeName(ItemType.INCLUDED_GROUP),
'Included Group'
);
});
});