KEYCLOAK-12936 only change the locale in the AccountPage.

This commit is contained in:
Erik Jan de Wit 2020-04-20 15:02:41 +02:00 committed by Stan Silvert
parent fd9c4e9228
commit db8cb63565
15 changed files with 79 additions and 277 deletions

View file

@ -19,12 +19,12 @@ package org.keycloak.testsuite.ui.account2;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.models.UserModel;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.ui.account2.page.PersonalInfoPage;
import org.keycloak.testsuite.ui.account2.page.WelcomeScreen;
import org.keycloak.testsuite.util.WaitUtils;
import java.util.List;
import java.util.Map;
@ -36,7 +36,6 @@ import static org.junit.Assert.assertEquals;
/**
* @author Vaclav Muzikar <vmuzikar@redhat.com>
*/
@Ignore // TODO remove this once KEYCLOAK-12936 is resolved
public class InternationalizationTest extends AbstractAccountTest {
@Page
private WelcomeScreen welcomeScreen;
@ -53,20 +52,6 @@ public class InternationalizationTest extends AbstractAccountTest {
@Before
public void beforeI18nTest() {
assertTestUserLocale(null);
assertEquals(DEFAULT_LOCALE_NAME, welcomeScreen.header().getCurrentLocaleName());
}
@Test
public void welcomeScreenTest() {
welcomeScreen.header().selectLocale(CUSTOM_LOCALE);
assertCustomLocaleWelcomeScreen();
// check if selected locale is preserved
welcomeScreen.clickPersonalInfoLink();
assertCustomLocaleLoginPage();
loginToAccount();
assertTestUserLocale(CUSTOM_LOCALE);
assertCustomLocalePersonalInfo();
}
@Test
@ -74,8 +59,10 @@ public class InternationalizationTest extends AbstractAccountTest {
personalInfoPage.navigateTo();
loginToAccount();
assertTestUserLocale(null);
assertEquals(DEFAULT_LOCALE_NAME, personalInfoPage.header().getCurrentLocaleName());
personalInfoPage.header().selectLocale(CUSTOM_LOCALE);
personalInfoPage.selectLocale(CUSTOM_LOCALE);
personalInfoPage.clickSave(false);
WaitUtils.waitForPageToLoad();
assertTestUserLocale(CUSTOM_LOCALE);
assertCustomLocalePersonalInfo();
@ -95,13 +82,11 @@ public class InternationalizationTest extends AbstractAccountTest {
}
@Test
@SuppressWarnings("unchecked")
public void userAttributeTest() {
testUser.setAttributes(singletonMap(UserModel.LOCALE, singletonList(CUSTOM_LOCALE)));
testUserResource().update(testUser);
welcomeScreen.navigateTo();
assertEquals(DEFAULT_LOCALE_NAME, welcomeScreen.header().getCurrentLocaleName());
welcomeScreen.clickPersonalInfoLink();
assertEquals(DEFAULT_LOCALE_NAME, loginPage.localeDropdown().getSelected());
loginToAccount();
@ -109,14 +94,10 @@ public class InternationalizationTest extends AbstractAccountTest {
}
private void assertCustomLocaleWelcomeScreen() {
welcomeScreen.header().assertLocaleVisible(true);
assertEquals(CUSTOM_LOCALE_NAME, welcomeScreen.header().getCurrentLocaleName());
assertEquals("Vítejte v Keycloaku", welcomeScreen.getWelcomeMessage());
}
private void assertCustomLocalePersonalInfo() {
personalInfoPage.header().assertLocaleVisible(true);
assertEquals(CUSTOM_LOCALE_NAME, personalInfoPage.header().getCurrentLocaleName());
assertEquals("Osobní údaje", personalInfoPage.getPageTitle());
}
@ -126,7 +107,7 @@ public class InternationalizationTest extends AbstractAccountTest {
private void assertTestUserLocale(String expectedLocale) {
String actualLocale = null;
List <String> userLocales = null;
List <String> userLocales;
Map<String, List<String>> userAttributes = testUserResource().toRepresentation().getAttributes();
if (userAttributes != null) {

View file

@ -18,7 +18,6 @@
package org.keycloak.testsuite.ui.account2;
import org.jboss.arquillian.graphene.page.Page;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
@ -123,31 +122,6 @@ public class ReferrerTest extends AbstractAccountTest {
testReferrer(personalInfoPage.header(), false);
}
/**
* Test that i18n and referrer work well together
*/
@Test
@Ignore // TODO remove this once KEYCLOAK-12936 is resolved
public void i18nTest() {
RealmRepresentation realm = testRealmResource().toRepresentation();
configureInternationalizationForRealm(realm);
testRealmResource().update(realm);
testContext.setTestRealmReps(Collections.emptyList()); // a small hack; we want realm re-import after this test
welcomeScreen.navigateTo(FAKE_CLIENT_ID, getFakeClientUrl());
welcomeScreen.header().assertReferrerLinkVisible(true);
welcomeScreen.header().selectLocale(CUSTOM_LOCALE);
welcomeScreen.header().assertReferrerLinkVisible(true);
welcomeScreen.clickPersonalInfoLink();
assertEquals(CUSTOM_LOCALE_NAME, loginPage.localeDropdown().getSelected());
loginToAccount();
personalInfoPage.header().assertReferrerLinkVisible(true);
personalInfoPage.header().selectLocale(DEFAULT_LOCALE);
assertEquals(DEFAULT_LOCALE_NAME, personalInfoPage.header().getCurrentLocaleName());
testReferrer(personalInfoPage.header(), true);
}
private void testReferrer(AbstractHeader header, boolean expectReferrerVisible) {
if (expectReferrerVisible) {
assertEquals(REFERRER_LINK_TEXT, header.getReferrerLinkText());

View file

@ -43,7 +43,6 @@ public class WelcomeScreenTest extends AbstractAccountTest {
public void loginLogoutTest() {
accountWelcomeScreen.assertCurrent();
accountWelcomeScreen.header().assertLogoutBtnVisible(false);
accountWelcomeScreen.header().assertLocaleVisible(false);
// login
accountWelcomeScreen.header().clickLoginBtn();

View file

@ -20,6 +20,7 @@ package org.keycloak.testsuite.ui.account2.page;
import org.keycloak.representations.idm.UserRepresentation;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.ui.Select;
import static org.keycloak.testsuite.util.UIAssert.assertElementDisabled;
import static org.keycloak.testsuite.util.UIAssert.assertInputElementValid;
@ -38,6 +39,8 @@ public class PersonalInfoPage extends AbstractLoggedInPage {
private WebElement firstName;
@FindBy(id = "last-name")
private WebElement lastName;
@FindBy(id = "locale-select")
private Select localeSelector;
@FindBy(id = "save-btn")
private WebElement saveBtn;
@FindBy(id = "cancel-btn")
@ -105,8 +108,14 @@ public class PersonalInfoPage extends AbstractLoggedInPage {
}
public void clickSave() {
clickSave(true);
}
public void clickSave(boolean assertAlert) {
saveBtn.click();
alert().assertIsDisplayed();
if (assertAlert) {
alert().assertIsDisplayed();
}
}
public void clickCancel() {
@ -126,4 +135,8 @@ public class PersonalInfoPage extends AbstractLoggedInPage {
&& user.getFirstName().equals(getFirstName())
&& user.getLastName().equals(getLastName());
}
public void selectLocale(String customLocale) {
localeSelector.selectByValue(customLocale);
}
}

View file

@ -60,36 +60,8 @@ public abstract class AbstractHeader extends AbstractFragmentWithMobileLayout {
return getToolsBtnText(getReferrerLink());
}
public void selectLocale(String locale) {
if (isMobileLayout()) {
clickMobileKebab();
}
getLocaleBtn().click();
clickLink(getLocaleDropdown().findElement(By.id(getLocaleElementIdPrefix() + locale)));
}
public String getCurrentLocaleName() {
if (!isMobileLayout()) {
return getTextFromElement(getLocaleBtn());
}
else {
clickMobileKebab();
String ret = getTextFromElement(getLocaleBtn());
clickMobileKebab(); // hide the dropdown again
return ret;
}
}
public void assertLocaleVisible(boolean expected) {
assertToolsBtnVisible(expected, getLocaleBtn());
}
public abstract void clickMobileKebab();
protected abstract WebElement getLocaleBtn();
protected abstract WebElement getLocaleDropdown();
protected abstract WebElement getLogoutBtn ();
protected abstract WebElement getReferrerLink();

View file

@ -54,16 +54,6 @@ public class LoggedInPageHeader extends AbstractHeader {
clickLink(mobileKebab);
}
@Override
protected WebElement getLocaleBtn() {
return isMobileLayout() ? localeBtnMobile : localeBtn;
}
@Override
protected WebElement getLocaleDropdown() {
return isMobileLayout() ? localeDropdownMobile : localeDropdown;
}
@Override
protected WebElement getLogoutBtn() {
return isMobileLayout() ? logoutBtnMobile : logoutBtn;

View file

@ -41,11 +41,6 @@ public class WelcomeScreenHeader extends AbstractHeader {
@FindBy(id = "landing-mobile-local-toggle")
private WebElement localeBtnMobile;
@FindBy(id = "landing-locale-dropdown-list")
private WebElement localeDropdown;
@FindBy(id = "landingMobileDropdown") // the mobile locale menu is integrated with the generic mobile menu
private WebElement localeDropdownMobile;
@FindBy(id = "landingReferrerLink")
private WebElement referrerLink;
@FindBy(id = "landingMobileReferrerLink")
@ -67,16 +62,6 @@ public class WelcomeScreenHeader extends AbstractHeader {
assertToolsBtnVisible(expected, isMobileLayout() ? loginBtnMobile : loginBtn);
}
@Override
protected WebElement getLocaleBtn() {
return isMobileLayout() ? localeBtnMobile : localeBtn;
}
@Override
protected WebElement getLocaleDropdown() {
return isMobileLayout() ? localeDropdownMobile : localeDropdown;
}
@Override
protected WebElement getLogoutBtn() {
return isMobileLayout() ? logoutBtnMobile : logoutBtn;

View file

@ -216,6 +216,10 @@
<include>**/node_modules/@patternfly/react-core/dist/umd/components/Form/FormHelperText.js</include>
<include>**/node_modules/@patternfly/react-core/dist/umd/components/Form/Form.js</include>
<include>**/node_modules/@patternfly/react-core/dist/umd/components/Form/index.js</include>
<include>**/node_modules/@patternfly/react-core/dist/umd/components/FormSelect/index.js</include>
<include>**/node_modules/@patternfly/react-core/dist/umd/components/FormSelect/FormSelect.js</include>
<include>**/node_modules/@patternfly/react-core/dist/umd/components/FormSelect/FormSelectOption.js</include>
<include>**/node_modules/@patternfly/react-core/dist/umd/components/FormSelect/FormSelectOptionGroup.js</include>
<include>**/node_modules/@patternfly/react-core/dist/umd/components/Modal/ModalBoxBody.js</include>
<include>**/node_modules/@patternfly/react-core/dist/umd/components/Modal/ModalBoxCloseButton.js</include>
<include>**/node_modules/@patternfly/react-core/dist/umd/components/Modal/ModalBoxFooter.js</include>

View file

@ -29,7 +29,7 @@
var features = {
isRegistrationEmailAsUsername : ${realm.registrationEmailAsUsername?c},
isEditUserNameAllowed : ${realm.editUsernameAllowed?c},
isInternationalizationEnabled : false,
isInternationalizationEnabled : ${realm.isInternationalizationEnabled()?c},
isLinkedAccountsEnabled : ${realm.identityFederationEnabled?c},
isEventsEnabled : ${isEventsEnabled?c},
isMyResourcesEnabled : ${(realm.userManagedAccessAllowed && isAuthorizationEnabled)?c},

View file

@ -11,6 +11,7 @@ remove=Remove
update=Update
loadingMessage=Account Console loading ...
selectLocale=Select a locale
# Device Activity Page
signedInDevices=Signed In Devices
signedInDevicesExplanation=Sign out any device that is unfamiliar.

View file

@ -53,60 +53,6 @@ var loadPatternFly = function () {
patternFlyHasLoaded = true;
}
var toggleLocaleDropdown = function () {
var localeDropdownList = document.getElementById("landing-locale-dropdown-list");
if (localeDropdownList.hasAttribute("hidden")) {
localeDropdownList.removeAttribute("hidden");
document.getElementById("landing-locale-dropdown-button").setAttribute("aria-expanded", true);
} else {
localeDropdownList.setAttribute("hidden", true);
document.getElementById("landing-locale-dropdown-button").setAttribute("aria-expanded", false);
}
};
var toggleMobileDropdown = function () {
var mobileDropdown = document.getElementById("landingMobileDropdown");
var mobileKebab = document.getElementById("landingMobileKebab");
var mobileKebabButton = document.getElementById("landingMobileKebabButton");
if (mobileDropdown.style.display === 'none') {
mobileDropdown.style.display = 'block';
mobileKebab.classList.add("pf-m-expanded");
mobileKebabButton.setAttribute("aria-expanded", "true");
} else {
mobileDropdown.style.display = 'none';
mobileKebab.classList.remove("pf-m-expanded");
mobileKebabButton.setAttribute("aria-expanded", "false");
}
};
var toggleMobileChooseLocale = function() {
var mobileLocaleSelectedIcon = document.getElementById("landingMobileLocaleSelectedIcon");
var isDropdownClosed = mobileLocaleSelectedIcon.classList.contains("fa-angle-right");
var mobileLocaleSeparator = document.getElementById("landingMobileLocaleSeparator");
if (isDropdownClosed) {
mobileLocaleSelectedIcon.classList.remove("fa-angle-right");
mobileLocaleSelectedIcon.classList.add("fa-angle-down");
mobileLocaleSeparator.style.display = 'block';
} else {
mobileLocaleSelectedIcon.classList.add("fa-angle-right");
mobileLocaleSelectedIcon.classList.remove("fa-angle-down");
mobileLocaleSeparator.style.display = 'none';
}
for (var i=0; i < availableLocales.length; i++) {
if (locale === availableLocales[i].locale) continue; // don't unhide current locale
var mobileLocaleSelection = document.getElementById("landing-mobile-locale-" + availableLocales[i].locale);
if (isDropdownClosed) {
mobileLocaleSelection.style.display= 'inline';
} else {
mobileLocaleSelection.style.display= 'none';
}
}
toggleMobileDropdown();
}
var loadjs = function (url, loadListener) {
const script = document.createElement("script");
script.src = resourceUrl + url;

View file

@ -21,7 +21,6 @@ import {Dropdown, KebabToggle, Toolbar, ToolbarGroup, ToolbarItem} from '@patter
import {ReferrerDropdownItem} from './widgets/ReferrerDropdownItem';
import {ReferrerLink} from './widgets/ReferrerLink';
import {Features} from './widgets/features';
import {LocaleNav,LocaleDropdown} from './widgets/LocaleSelectors';
import {LogoutButton,LogoutDropdownItem} from './widgets/Logout';
declare const referrerName: string;
@ -54,10 +53,6 @@ export class PageToolbar extends React.Component<PageToolbarProps, PageToolbarSt
)
}
if (features.isInternationalizationEnabled) {
kebabDropdownItems.push(<LocaleNav key='kebabLocaleNav'/>);
}
kebabDropdownItems.push(<LogoutDropdownItem key='LogoutDropdownItem'/>);
return (
@ -71,12 +66,6 @@ export class PageToolbar extends React.Component<PageToolbarProps, PageToolbarSt
}
<ToolbarGroup key='secondGroup'>
{features.isInternationalizationEnabled &&
<ToolbarItem className="pf-m-icons" key='locale'>
<LocaleDropdown/>
</ToolbarItem>
}
<ToolbarItem className="pf-m-icons" key='logout'>
<LogoutButton/>
</ToolbarItem>

View file

@ -23,8 +23,10 @@ import { Features } from '../../widgets/features';
import { Msg } from '../../widgets/Msg';
import { ContentPage } from '../ContentPage';
import { ContentAlert } from '../ContentAlert';
import { LocaleSelector } from '../../widgets/LocaleSelectors';
declare const features: Features;
declare const locale: string;
interface AccountPageProps {
}
@ -34,6 +36,7 @@ interface FormFields {
readonly firstName?: string;
readonly lastName?: string;
readonly email?: string;
attributes?: { locale?: [string] };
}
interface AccountPageState {
@ -58,7 +61,8 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
username: '',
firstName: '',
lastName: '',
email: ''
email: '',
attributes: {}
}
};
@ -73,7 +77,12 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
AccountServiceClient.Instance.doGet("/")
.then((response: AxiosResponse<FormFields>) => {
this.setState(this.DEFAULT_STATE);
this.setState({...{ formFields: response.data }});
const formFields = response.data;
if (!formFields.attributes || !formFields.attributes.locale) {
formFields.attributes = { locale: [locale] };
}
this.setState({...{ formFields: formFields }});
});
}
@ -100,6 +109,9 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
AccountServiceClient.Instance.doPost("/", { data: reqData })
.then(() => { // to use response, say ((response: AxiosResponse<FormFields>) => {
ContentAlert.success('accountUpdatedMessage');
if (locale !== this.state.formFields.attributes!.locale![0]) {
window.location.reload();
}
});
} else {
const formData = new FormData(form);
@ -187,6 +199,19 @@ export class AccountPage extends React.Component<AccountPageProps, AccountPageSt
>
</TextInput>
</FormGroup>
{features.isInternationalizationEnabled && <FormGroup
label={Msg.localize('selectLocale')}
isRequired
fieldId="locale"
>
<LocaleSelector id="locale-selector"
value={fields.attributes!.locale || ''}
onChange={value => this.setState({
errors: this.state.errors,
formFields: { ...this.state.formFields, attributes: { ...this.state.formFields.attributes, locale: [value] }}
})}
/>
</FormGroup>}
<ActionGroup>
<Button
type="submit"

View file

@ -13,122 +13,45 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import * as React from 'react';
import {withRouter, RouteComponentProps} from 'react-router-dom';
import {Msg} from './Msg';
import {
Dropdown,
DropdownItem,
DropdownToggle,
Nav,
NavExpandable,
NavItem,
NavList
FormSelect,
FormSelectOption,
FormSelectProps
} from '@patternfly/react-core';
declare const locale: string;
declare const baseUrl: string;
declare const referrer: string;
declare const referrerUri: string;
import { Msg } from './Msg';
interface AvailableLocale {
locale: string;
label: string;
};
declare const availableLocales: [AvailableLocale];
// remove entry for current locale
const availLocales = availableLocales.filter((availableLocale: AvailableLocale) => availableLocale.locale !== locale);
let referrerFragment = '';
if ((typeof referrer !== 'undefined') &&
(typeof referrerUri !== 'undefined')) {
referrerFragment = '&referrer=' + referrer + '&referrer_uri=' + encodeURIComponent(referrerUri);
}
interface LocaleSelectorProps extends FormSelectProps { }
interface LocaleSelectorState { }
export class LocaleSelector extends React.Component<LocaleSelectorProps, LocaleSelectorState> {
interface LocaleKebabItemProps extends RouteComponentProps {}
interface LocaleKebabItemState {activeGroup: string; activeItem: string}
class LocaleKebabItem extends React.Component<LocaleKebabItemProps, LocaleKebabItemState> {
public constructor(props: LocaleKebabItemProps) {
constructor(props: LocaleSelectorProps) {
super(props);
this.state = {
activeGroup: 'locale-group',
activeItem: ''
};
}
public render(): React.ReactNode {
const appPath = this.props.location.pathname;
const localeNavItems = availLocales.map((availableLocale: AvailableLocale) => {
const url = baseUrl + '?kc_locale=' + availableLocale.locale + referrerFragment + '#' + appPath;
return (<NavItem
id={`mobile-locale-${availableLocale.locale}`}
key={availableLocale.locale}
to={url}>
{availableLocale.label}
</NavItem> );
});
render(): React.ReactNode {
return (
<Nav>
<NavList>
<NavExpandable id="mobile-locale" title={Msg.localize('locale_' + locale)} isActive={false} groupId="locale-group">
{localeNavItems}
</NavExpandable>
</NavList>
</Nav>
<FormSelect
id="locale-select"
value={this.props.value}
onChange={(value, event) => { if (this.props.onChange) this.props.onChange(value, event) }}
aria-label={Msg.localize('selectLocale')}
>
{availableLocales.map((locale, index) =>
<FormSelectOption
key={index}
value={locale.locale}
label={locale.label}
/>)
}
</FormSelect>
);
}
};
interface LocaleDropdownComponentProps extends RouteComponentProps {}
interface LocaleDropdownComponentState {isDropdownOpen: boolean}
class LocaleDropdownComponent extends React.Component<LocaleDropdownComponentProps, LocaleDropdownComponentState> {
public constructor(props: LocaleDropdownComponentProps) {
super(props);
this.state = {isDropdownOpen: false};
}
private onDropdownToggle = (isDropdownOpen: boolean) => {
this.setState({
isDropdownOpen
});
};
private onDropdownSelect = () => {
this.setState({
isDropdownOpen: !this.state.isDropdownOpen
});
};
public render(): React.ReactNode {
const appPath = this.props.location.pathname;
const localeDropdownItems = availLocales.map((availableLocale: AvailableLocale) => {
const url = baseUrl + '?kc_locale=' + availableLocale.locale + referrerFragment + '#' + appPath;
return (<DropdownItem
id={`locale-${availableLocale.locale}`}
key={availableLocale.locale}
href={url}>
{availableLocale.label}
</DropdownItem> );
});
if (localeDropdownItems.length < 2) return (<React.Fragment/>);
return (
<Dropdown
isPlain
position="right"
onSelect={this.onDropdownSelect}
isOpen={this.state.isDropdownOpen}
toggle={<DropdownToggle id="locale-dropdown-toggle" onToggle={this.onDropdownToggle}><Msg msgKey={'locale_' + locale}/></DropdownToggle>}
dropdownItems={localeDropdownItems}
/>
);
}
};
export const LocaleDropdown = withRouter(LocaleDropdownComponent);
export const LocaleNav = withRouter(LocaleKebabItem);
}

View file

@ -105,7 +105,7 @@
'./Expandable': '@empty', //'./Expandable/index.js',
'./Form': './Form/index.js',
'./FormGroup': './FormGroup/index.js',
'./FormSelect': '@empty', //'./FormSelect/index.js',
'./FormSelect': './FormSelect/index.js',
'./InputGroup': '@empty', //'./InputGroup/index.js',
'./Label': '@empty', //'./Label/index.js',
'./List': '@empty', //'./List/index.js',