blob: 1165f1eb47b61e3f872486a810da4c17b9bdc729 [file] [log] [blame]
/**
* @license
* 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.
*/
import '../../../test/common-test-setup-karma';
import {getComputedStyleValue} from '../../../utils/dom-util';
import './gr-settings-view';
import {GrSettingsView} from './gr-settings-view';
import {flush} from '@polymer/polymer/lib/legacy/polymer.dom';
import {GerritView} from '../../../services/router/router-model';
import {queryAll, queryAndAssert, stubRestApi} from '../../../test/test-utils';
import {
AuthInfo,
AccountDetailInfo,
EmailAddress,
PreferencesInfo,
ServerInfo,
TopMenuItemInfo,
} from '../../../types/common';
import {
createDefaultPreferences,
DateFormat,
DefaultBase,
DiffViewMode,
EmailFormat,
EmailStrategy,
TimeFormat,
} from '../../../constants/constants';
import * as MockInteractions from '@polymer/iron-test-helpers/mock-interactions';
import {
createAccountDetailWithId,
createPreferences,
createServerInfo,
} from '../../../test/test-data-generators';
import {GrSelect} from '../../shared/gr-select/gr-select';
import {AppElementSettingsParam} from '../../gr-app-types';
const basicFixture = fixtureFromElement('gr-settings-view');
const blankFixture = fixtureFromElement('div');
suite('gr-settings-view tests', () => {
let element: GrSettingsView;
let account: AccountDetailInfo;
let preferences: PreferencesInfo;
let config: ServerInfo;
function valueOf(title: string, id: string) {
const sections = element.root?.querySelectorAll(`#${id} section`) ?? [];
let titleEl;
for (let i = 0; i < sections.length; i++) {
titleEl = sections[i].querySelector('.title');
if (titleEl?.textContent?.trim() === title) {
const el = sections[i].querySelector('.value');
if (el) return el;
}
}
assert.fail(`element with title ${title} not found`);
}
// Because deepEqual isn't behaving in Safari.
function assertMenusEqual(
actual?: TopMenuItemInfo[],
expected?: TopMenuItemInfo[]
) {
if (actual === undefined) {
assert.fail("assertMenusEqual 'actual' param is undefined");
} else if (expected === undefined) {
assert.fail("assertMenusEqual 'expected' param is undefined");
}
assert.equal(actual.length, expected.length);
for (let i = 0; i < actual.length; i++) {
assert.equal(actual[i].name, expected[i].name);
assert.equal(actual[i].url, expected[i].url);
}
}
function stubAddAccountEmail(statusCode: number) {
return stubRestApi('addAccountEmail').callsFake(() =>
Promise.resolve({status: statusCode} as Response)
);
}
setup(async () => {
account = {
...createAccountDetailWithId(123),
name: 'user name',
email: 'user@email' as EmailAddress,
username: 'user username',
};
preferences = {
...createPreferences(),
changes_per_page: 25,
date_format: DateFormat.UK,
time_format: TimeFormat.HHMM_12,
diff_view: DiffViewMode.UNIFIED,
email_strategy: EmailStrategy.ENABLED,
email_format: EmailFormat.HTML_PLAINTEXT,
default_base_for_merges: DefaultBase.FIRST_PARENT,
relative_date_in_change_table: false,
size_bar_in_change_table: true,
my: [
{url: '/first/url', name: 'first name', target: '_blank'},
{url: '/second/url', name: 'second name', target: '_blank'},
] as TopMenuItemInfo[],
change_table: [],
};
config = createServerInfo();
stubRestApi('getAccount').returns(Promise.resolve(account));
stubRestApi('getPreferences').returns(Promise.resolve(preferences));
stubRestApi('getAccountEmails').returns(Promise.resolve(undefined));
stubRestApi('getConfig').returns(Promise.resolve(config));
element = basicFixture.instantiate();
// Allow the element to render.
if (element._testOnly_loadingPromise)
await element._testOnly_loadingPromise;
});
test('theme changing', () => {
window.localStorage.removeItem('dark-theme');
assert.isFalse(window.localStorage.getItem('dark-theme') === 'true');
const themeToggle = queryAndAssert(
element,
'.darkToggle paper-toggle-button'
);
/* const themeToggle = element.shadowRoot
.querySelector('.darkToggle paper-toggle-button'); */
MockInteractions.tap(themeToggle);
assert.isTrue(window.localStorage.getItem('dark-theme') === 'true');
assert.equal(
getComputedStyleValue('--primary-text-color', document.body),
'#e8eaed'
);
MockInteractions.tap(themeToggle);
assert.isFalse(window.localStorage.getItem('dark-theme') === 'true');
});
test('calls the title-change event', () => {
const titleChangedStub = sinon.stub();
// Create a new view.
const newElement = document.createElement('gr-settings-view');
newElement.addEventListener('title-change', titleChangedStub);
const blank = blankFixture.instantiate();
blank.appendChild(newElement);
flush();
assert.isTrue(titleChangedStub.called);
assert.equal(titleChangedStub.getCall(0).args[0].detail.title, 'Settings');
});
test('user preferences', async () => {
// Rendered with the expected preferences selected.
assert.equal(
Number(
(
valueOf('Changes per page', 'preferences')!
.firstElementChild as GrSelect
).bindValue
),
preferences.changes_per_page
);
assert.equal(
(
valueOf('Date/time format', 'preferences')!
.firstElementChild as GrSelect
).bindValue,
preferences.date_format
);
assert.equal(
(valueOf('Date/time format', 'preferences')!.lastElementChild as GrSelect)
.bindValue,
preferences.time_format
);
assert.equal(
(
valueOf('Email notifications', 'preferences')!
.firstElementChild as GrSelect
).bindValue,
preferences.email_strategy
);
assert.equal(
(valueOf('Email format', 'preferences')!.firstElementChild as GrSelect)
.bindValue,
preferences.email_format
);
assert.equal(
(
valueOf('Default Base For Merges', 'preferences')!
.firstElementChild as GrSelect
).bindValue,
preferences.default_base_for_merges
);
assert.equal(
(
valueOf('Show Relative Dates In Changes Table', 'preferences')!
.firstElementChild as HTMLInputElement
).checked,
false
);
assert.equal(
(valueOf('Diff view', 'preferences')!.firstElementChild as GrSelect)
.bindValue,
preferences.diff_view
);
assert.equal(
(
valueOf('Show size bars in file list', 'preferences')!
.firstElementChild as HTMLInputElement
).checked,
true
);
assert.equal(
(
valueOf('Publish comments on push', 'preferences')!
.firstElementChild as HTMLInputElement
).checked,
false
);
assert.equal(
(
valueOf(
'Set new changes to "work in progress" by default',
'preferences'
)!.firstElementChild as HTMLInputElement
).checked,
false
);
assert.equal(
(
valueOf('Disable token highlighting on hover', 'preferences')!
.firstElementChild as HTMLInputElement
).checked,
false
);
assert.equal(
(
valueOf(
'Insert Signed-off-by Footer For Inline Edit Changes',
'preferences'
)!.firstElementChild as HTMLInputElement
).checked,
false
);
assert.isFalse(element._prefsChanged);
assert.isFalse(element._menuChanged);
const publishOnPush = valueOf('Publish comments on push', 'preferences')!
.firstElementChild!;
MockInteractions.tap(publishOnPush);
assert.isTrue(element._prefsChanged);
assert.isFalse(element._menuChanged);
stubRestApi('savePreferences').callsFake(prefs => {
assertMenusEqual(prefs.my, preferences.my);
assert.equal(prefs.publish_comments_on_push, true);
return Promise.resolve(createDefaultPreferences());
});
// Save the change.
await element._handleSavePreferences();
assert.isFalse(element._prefsChanged);
assert.isFalse(element._menuChanged);
});
test('publish comments on push', async () => {
const publishCommentsOnPush = valueOf(
'Publish comments on push',
'preferences'
)!.firstElementChild!;
MockInteractions.tap(publishCommentsOnPush);
assert.isFalse(element._menuChanged);
assert.isTrue(element._prefsChanged);
stubRestApi('savePreferences').callsFake(prefs => {
assert.equal(prefs.publish_comments_on_push, true);
return Promise.resolve(createDefaultPreferences());
});
// Save the change.
await element._handleSavePreferences();
assert.isFalse(element._prefsChanged);
assert.isFalse(element._menuChanged);
});
test('set new changes work-in-progress', async () => {
const newChangesWorkInProgress = valueOf(
'Set new changes to "work in progress" by default',
'preferences'
)!.firstElementChild!;
MockInteractions.tap(newChangesWorkInProgress);
assert.isFalse(element._menuChanged);
assert.isTrue(element._prefsChanged);
stubRestApi('savePreferences').callsFake(prefs => {
assert.equal(prefs.work_in_progress_by_default, true);
return Promise.resolve(createDefaultPreferences());
});
// Save the change.
await element._handleSavePreferences();
assert.isFalse(element._prefsChanged);
assert.isFalse(element._menuChanged);
});
test('menu', async () => {
assert.isFalse(element._menuChanged);
assert.isFalse(element._prefsChanged);
assertMenusEqual(element._localMenu, preferences.my);
const menu = element.$.menu.firstElementChild!;
let tableRows = queryAll(menu, 'tbody tr');
// let tableRows = menu.root.querySelectorAll('tbody tr');
assert.equal(tableRows.length, preferences.my.length);
// Add a menu item:
element.splice('_localMenu', 1, 0, {name: 'foo', url: 'bar', target: ''});
flush();
// tableRows = menu.root.querySelectorAll('tbody tr');
tableRows = queryAll(menu, 'tbody tr');
assert.equal(tableRows.length, preferences.my.length + 1);
assert.isTrue(element._menuChanged);
assert.isFalse(element._prefsChanged);
stubRestApi('savePreferences').callsFake(prefs => {
assertMenusEqual(prefs.my, element._localMenu);
return Promise.resolve(createDefaultPreferences());
});
await element._handleSaveMenu();
assert.isFalse(element._menuChanged);
assert.isFalse(element._prefsChanged);
assertMenusEqual(element.prefs.my, element._localMenu);
});
test('add email validation', () => {
assert.isFalse(element._isNewEmailValid('invalid email'));
assert.isTrue(element._isNewEmailValid('vaguely@valid.email'));
assert.isFalse(
element._computeAddEmailButtonEnabled('invalid email', true)
);
assert.isFalse(
element._computeAddEmailButtonEnabled('vaguely@valid.email', true)
);
assert.isTrue(
element._computeAddEmailButtonEnabled('vaguely@valid.email', false)
);
});
test('add email does not save invalid', () => {
const addEmailStub = stubAddAccountEmail(201);
assert.isFalse(element._addingEmail);
assert.isNotOk(element._lastSentVerificationEmail);
element._newEmail = 'invalid email';
element._handleAddEmailButton();
assert.isFalse(element._addingEmail);
assert.isFalse(addEmailStub.called);
assert.isNotOk(element._lastSentVerificationEmail);
assert.isFalse(addEmailStub.called);
});
test('add email does save valid', async () => {
const addEmailStub = stubAddAccountEmail(201);
assert.isFalse(element._addingEmail);
assert.isNotOk(element._lastSentVerificationEmail);
element._newEmail = 'valid@email.com';
element._handleAddEmailButton();
assert.isTrue(element._addingEmail);
assert.isTrue(addEmailStub.called);
assert.isTrue(addEmailStub.called);
await addEmailStub.lastCall.returnValue;
assert.isOk(element._lastSentVerificationEmail);
});
test('add email does not set last-email if error', async () => {
const addEmailStub = stubAddAccountEmail(500);
assert.isNotOk(element._lastSentVerificationEmail);
element._newEmail = 'valid@email.com';
element._handleAddEmailButton();
assert.isTrue(addEmailStub.called);
await addEmailStub.lastCall.returnValue;
assert.isNotOk(element._lastSentVerificationEmail);
});
test('emails are loaded without emailToken', () => {
const emailEditorLoadDataStub = sinon.stub(
element.$.emailEditor,
'loadData'
);
element.params = {
view: GerritView.SETTINGS,
} as AppElementSettingsParam;
element.connectedCallback();
assert.isTrue(emailEditorLoadDataStub.calledOnce);
});
test('_handleSaveChangeTable', () => {
let newColumns = ['Owner', 'Project', 'Branch'];
element._localChangeTableColumns = newColumns.slice(0);
element._showNumber = false;
element._handleSaveChangeTable();
assert.deepEqual(element.prefs.change_table, newColumns);
assert.isNotOk(element.prefs.legacycid_in_change_table);
newColumns = ['Size'];
element._localChangeTableColumns = newColumns;
element._showNumber = true;
element._handleSaveChangeTable();
assert.deepEqual(element.prefs.change_table, newColumns);
assert.isTrue(element.prefs.legacycid_in_change_table);
});
test('reset menu item back to default', async () => {
const originalMenu = {
...createDefaultPreferences(),
my: [
{url: '/first/url', name: 'first name', target: '_blank'},
{url: '/second/url', name: 'second name', target: '_blank'},
{url: '/third/url', name: 'third name', target: '_blank'},
] as TopMenuItemInfo[],
};
stubRestApi('getDefaultPreferences').returns(Promise.resolve(originalMenu));
const updatedMenu = [
{url: '/first/url', name: 'first name', target: '_blank'},
{url: '/second/url', name: 'second name', target: '_blank'},
{url: '/third/url', name: 'third name', target: '_blank'},
{url: '/fourth/url', name: 'fourth name', target: '_blank'},
];
element.set('_localMenu', updatedMenu);
await element._handleResetMenuButton();
assertMenusEqual(element._localMenu, originalMenu.my);
});
test('test that reset button is called', () => {
const overlayOpen = sinon.stub(element, '_handleResetMenuButton');
MockInteractions.tap(element.$.resetButton);
assert.isTrue(overlayOpen.called);
});
test('_showHttpAuth', () => {
const serverConfig: ServerInfo = {
...createServerInfo(),
auth: {
git_basic_auth_policy: 'HTTP',
} as AuthInfo,
};
assert.isTrue(element._showHttpAuth(serverConfig));
serverConfig.auth.git_basic_auth_policy = 'HTTP_LDAP';
assert.isTrue(element._showHttpAuth(serverConfig));
serverConfig.auth.git_basic_auth_policy = 'LDAP';
assert.isFalse(element._showHttpAuth(serverConfig));
serverConfig.auth.git_basic_auth_policy = 'OAUTH';
assert.isFalse(element._showHttpAuth(serverConfig));
assert.isFalse(element._showHttpAuth(undefined));
});
suite('_getFilterDocsLink', () => {
test('with http: docs base URL', () => {
const base = 'http://example.com/';
const result = element._getFilterDocsLink(base);
assert.equal(result, 'http://example.com/user-notify.html');
});
test('with http: docs base URL without slash', () => {
const base = 'http://example.com';
const result = element._getFilterDocsLink(base);
assert.equal(result, 'http://example.com/user-notify.html');
});
test('with https: docs base URL', () => {
const base = 'https://example.com/';
const result = element._getFilterDocsLink(base);
assert.equal(result, 'https://example.com/user-notify.html');
});
test('without docs base URL', () => {
const result = element._getFilterDocsLink(null);
assert.equal(
result,
'https://gerrit-review.googlesource.com/' +
'Documentation/user-notify.html'
);
});
test('ignores non HTTP links', () => {
const base = 'javascript://alert("evil");';
const result = element._getFilterDocsLink(base);
assert.equal(
result,
'https://gerrit-review.googlesource.com/' +
'Documentation/user-notify.html'
);
});
});
suite('when email verification token is provided', () => {
let resolveConfirm: (
value: string | PromiseLike<string | null> | null
) => void;
let confirmEmailStub: sinon.SinonStub;
let emailEditorLoadDataStub: sinon.SinonStub;
setup(() => {
emailEditorLoadDataStub = sinon.stub(element.$.emailEditor, 'loadData');
confirmEmailStub = stubRestApi('confirmEmail').returns(
new Promise(resolve => {
resolveConfirm = resolve;
})
);
element.params = {view: GerritView.SETTINGS, emailToken: 'foo'};
element.connectedCallback();
});
test('it is used to confirm email via rest API', () => {
assert.isTrue(confirmEmailStub.calledOnce);
assert.isTrue(confirmEmailStub.calledWith('foo'));
});
test('emails are not loaded initially', () => {
assert.isFalse(emailEditorLoadDataStub.called);
});
test('user emails are loaded after email confirmed', async () => {
resolveConfirm('bar');
await element._testOnly_loadingPromise;
assert.isTrue(emailEditorLoadDataStub.calledOnce);
});
test('show-alert is fired when email is confirmed', async () => {
const dispatchEventSpy = sinon.spy(element, 'dispatchEvent');
resolveConfirm('bar');
await element._testOnly_loadingPromise;
assert.equal(
(dispatchEventSpy.lastCall.args[0] as CustomEvent).type,
'show-alert'
);
assert.deepEqual(
(dispatchEventSpy.lastCall.args[0] as CustomEvent).detail,
{message: 'bar'}
);
});
});
});