Add user preferences api and legacyhandler

This commit is contained in:
Ross Moroney 2020-03-25 10:15:37 +00:00 committed by Dillon-Brown
parent 1ab3d65b36
commit 6b2c10092d
31 changed files with 1159 additions and 111 deletions

View file

@ -27,6 +27,7 @@ services:
$legacyActionNameMap: '%legacy.action_name_map%'
$menuItemMap: '%legacy.menu_item_map%'
$legacyAssetPaths: '%legacy.asset_paths%'
$exposedUserPreferences: '%legacy.exposed_user_preferences%'
_instanceof:
App\Service\ProcessHandlerInterface:
tags: ['app.process.handler']

View file

@ -0,0 +1,47 @@
parameters:
legacy.exposed_user_preferences:
global:
sort_modules_by_name: true
navigation_paradigm: true
swap_shortcuts: true
subpanel_tabs: true
count_collapsed_subpanels: true
user_theme: true
module_favicon: true
no_opps: true
reminder_time: true
email_reminder_time: true
reminder_checked: true
email_reminder_checked: true
timezone: true
ut: true
currency: true
default_currency_significant_digits: true
num_grp_sep: true
dec_sep: true
fdow: true
datef: true
timef: true
mail_fromname: true
mail_fromaddress: true
mail_sendtype: true
mail_smtpserver: true
mail_smtpport: true
mail_smtpuser: true
mail_smtppass: true
default_locale_name_format: true
export_delimiter: true
default_export_charset: true
use_real_names: true
mail_smtpauth_req: true
mail_smtpssl: true
signature_default: true
signature_prepend: true
email_link_type: true
editor_type: true
email_show_counts: true
email_editor_option: true
default_email_charset: true
calendar_publish_key: true
subtheme: true
syncGCal: true

View file

@ -3,6 +3,7 @@ import {Routes, RouterModule} from '@angular/router';
import {ClassicViewUiComponent} from '@components/classic-view/classic-view.component';
import {ClassicViewResolver} from "@services/classic-view/classic-view.resolver";
import {BaseMetadataResolver} from '@services/metadata/base-metadata.resolver';
import {UserPreferenceResolver} from '@base/facades/user-preference/user-preference.resolver';
import {AuthGuard} from '../services/auth/auth-guard.service';
import {ListComponent} from '@views/list/list.component';
@ -11,13 +12,16 @@ const routes: Routes = [
path: 'Listview',
component: ListComponent,
resolve: {
metadata: BaseMetadataResolver
},
view: BaseMetadataResolver,
userPreference: UserPreferenceResolver
}
},
{
path: 'Login',
loadChildren: () => import('../components/login/login.module').then(m => m.LoginUiModule),
resolve: {metadata: BaseMetadataResolver},
resolve: {
metadata: BaseMetadataResolver
},
data: {
load: {
navigation: false,
@ -30,6 +34,7 @@ const routes: Routes = [
loadChildren: () => import('../components/home/home.module').then(m => m.HomeUiModule),
resolve: {
metadata: BaseMetadataResolver,
userPreference: UserPreferenceResolver
}
},
{
@ -39,7 +44,8 @@ const routes: Routes = [
runGuardsAndResolvers: 'always',
resolve: {
metadata: BaseMetadataResolver,
view: ClassicViewResolver
view: ClassicViewResolver,
userPreference: UserPreferenceResolver
},
data: {
reuseRoute: false
@ -52,7 +58,8 @@ const routes: Routes = [
runGuardsAndResolvers: 'always',
resolve: {
metadata: BaseMetadataResolver,
view: ClassicViewResolver
view: ClassicViewResolver,
userPreference: UserPreferenceResolver
},
data: {
reuseRoute: false
@ -65,7 +72,8 @@ const routes: Routes = [
runGuardsAndResolvers: 'always',
resolve: {
metadata: BaseMetadataResolver,
view: ClassicViewResolver
view: ClassicViewResolver,
userPreference: UserPreferenceResolver
},
data: {
reuseRoute: false

View file

@ -54,7 +54,7 @@
</div>
<button class="login-button"
<button id="login-button" class="login-button"
(click)="loginForm.control.markAllAsTouched(); loginForm.valid && doLogin()">
{{vm.appStrings['LBL_LOGIN_BUTTON_LABEL']}}
</button>
@ -105,7 +105,7 @@
{{vm.appStrings['LBL_GENERATE_PASSWORD_BUTTON_TITLE']}}
</button>
<div>
<a href="#" class="forgotten-password-link" (click)="flipCard()">
<a href="#" class="back-link forgotten-password-link" (click)="flipCard()">
{{vm.appStrings['LBL_BACK']}}
</a>
</div>

View file

@ -1,23 +1,30 @@
import {async, ComponentFixture, TestBed, inject} from '@angular/core/testing';
import {async, ComponentFixture, TestBed, inject, tick, fakeAsync} from '@angular/core/testing';
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {FormsModule} from '@angular/forms';
import {RouterTestingModule} from '@angular/router/testing';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {LoginUiComponent} from './login.component';
import {ApiService} from '../../services/api/api.service';
import {LanguageFacade} from '@base/facades/language/language.facade';
import {languageFacadeMock} from '@base/facades/language/language.facade.spec.mock';
import {ApolloTestingModule} from 'apollo-angular/testing';
import {systemConfigFacadeMock} from '@base/facades/system-config/system-config.facade.spec.mock';
import {SystemConfigFacade} from '@base/facades/system-config/system-config.facade';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {RecoverPasswordService} from '@services/process/processes/recover-password/recover-password';
import {languageFacadeMock} from '@base/facades/language/language.facade.spec.mock';
import {systemConfigFacadeMock} from '@base/facades/system-config/system-config.facade.spec.mock';
import {
recoverPasswordMock,
recoverPasswordMockData
} from '@services/process/processes/recover-password/recover-passoword.spec.mock';
import {By} from '@angular/platform-browser';
import {Observable, of} from 'rxjs';
describe('LoginComponent', () => {
let component: LoginUiComponent;
let fixture: ComponentFixture<LoginUiComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [
@ -30,6 +37,7 @@ describe('LoginComponent', () => {
providers: [
{provide: SystemConfigFacade, useValue: systemConfigFacadeMock},
{provide: LanguageFacade, useValue: languageFacadeMock},
{provide: RecoverPasswordService, useValue: recoverPasswordMock},
],
})
.compileComponents();
@ -45,4 +53,29 @@ describe('LoginComponent', () => {
(router: RouterTestingModule, http: HttpTestingController, api: ApiService) => {
expect(component).toBeTruthy();
})));
it('should flip on forgot password click', async(inject([HttpTestingController],
(router: RouterTestingModule, http: HttpTestingController, api: ApiService) => {
fixture.whenStable().then(() => {
expect(component).toBeTruthy();
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('[name="email"]'))).toBeNull();
expect(fixture.debugElement.query(By.css('.submit-button'))).toBeNull();
expect(fixture.debugElement.query(By.css('.back-link'))).toBeNull();
expect(fixture.debugElement.query(By.css('[name="password"]'))).toBeTruthy();
expect(fixture.debugElement.query(By.css('#login-button'))).toBeTruthy();
component.flipCard();
// same test codmockRecoverPasswordServicee here
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('[name="password"]'))).toBeNull();
expect(fixture.debugElement.query(By.css('#login-button'))).toBeNull();
expect(fixture.debugElement.query(By.css('.forgotten-password'))).toBeNull();
expect(fixture.debugElement.query(By.css('[name="email"]'))).toBeTruthy();
expect(fixture.debugElement.query(By.css('.submit-button'))).toBeTruthy();
expect(fixture.debugElement.query(By.css('.back-link'))).toBeTruthy();
});
})));
});

View file

@ -10,7 +10,7 @@ import {fadeIn} from 'ng-animate';
import {AuthService} from '@services/auth/auth.service';
import {MessageService} from '@services/message/message.service';
import {ApiService} from '@services/api/api.service';
import {RecoverPasswordService} from '@services/process/processes/recover-password';
import {RecoverPasswordService} from '@services/process/processes/recover-password/recover-password';
import {SystemConfigFacade, SystemConfigMap} from '@base/facades/system-config/system-config.facade';
import {LanguageFacade, LanguageStringMap} from '@base/facades/language/language.facade';

View file

@ -2,7 +2,7 @@ import {ActionLinkModel} from './action-link-model';
import {CurrentUserModel} from './current-user-model';
import {AllMenuModel} from './all-menu-model';
import {LogoModel} from '../logo/logo-model';
import {NavbarModuleMap} from '@base/facades/navigation/navigation.facade';
import {NavbarModuleMap, GroupedTab} from '@base/facades/navigation/navigation.facade';
import {LanguageListStringMap, LanguageStringMap} from '@base/facades/language/language.facade';
import {MenuItem} from '@components/navbar/navbar.abstract';
@ -17,11 +17,21 @@ export interface NavbarModel {
resetMenu(): void;
buildMenu(
buildGroupTabMenu(
items: string[],
modules: NavbarModuleMap,
appStrings: LanguageStringMap,
modStrings: LanguageListStringMap,
appListStrings: LanguageListStringMap,
menuItemThreshold: number): void;
menuItemThreshold: number,
groupedTabs: GroupedTab[]
): void;
buildTabMenu(
items: string[],
modules: NavbarModuleMap,
appStrings: LanguageStringMap,
modStrings: LanguageListStringMap,
appListStrings: LanguageListStringMap,
menuItemThreshold: number): void;
}

View file

@ -1,7 +1,8 @@
import {NavbarModel} from './navbar-model';
import {LogoAbstract} from '../logo/logo-abstract';
import {NavbarModule, NavbarModuleMap} from "@base/facades/navigation/navigation.facade";
import {NavbarModule, NavbarModuleMap, GroupedTab} from "@base/facades/navigation/navigation.facade";
import {LanguageListStringMap, LanguageStringMap} from '@base/facades/language/language.facade';
import {CurrentUserModel} from './current-user-model';
import {ActionLinkModel} from './action-link-model';
@ -22,7 +23,6 @@ export interface MenuItem {
recentRecords?: RecentRecordsMenuItem[];
}
const ROUTE_PREFIX = './#';
export class NavbarAbstract implements NavbarModel {
@ -70,12 +70,40 @@ export class NavbarAbstract implements NavbarModel {
this.all.modules = [];
}
public buildMenu(items: string[],
public buildGroupTabMenu(items: string[], modules: NavbarModuleMap, appStrings: LanguageStringMap,
modStrings: LanguageListStringMap, appListStrings: LanguageListStringMap, threshold: number, groupedTabs: GroupedTab[]): void
{
const navItems = [];
const moreItems = [];
if (!items || items.length === 0) {
this.menu = navItems;
this.all.extra = moreItems;
return;
}
let count = 0;
groupedTabs.forEach((groupedTab: any) => {
if (count <= threshold) {
navItems.push(this.buildTabGroupedMenuItem(groupedTab.labelKey, groupedTab.modules, modules, appStrings, modStrings, appListStrings));
}
count++;
});
this.menu = navItems;
this.all.modules = moreItems;
this.all.extra = moreItems;
}
public buildTabMenu(items: string[],
modules: NavbarModuleMap,
appStrings: LanguageStringMap,
modStrings: LanguageListStringMap,
appListStrings: LanguageListStringMap,
threshold: number): void {
threshold: number
): void {
const navItems = [];
const moreItems = [];
@ -94,9 +122,9 @@ export class NavbarAbstract implements NavbarModel {
}
if (count <= threshold) {
navItems.push(this.buildMenuItem(module, modules[module], appStrings, modStrings, appListStrings));
navItems.push(this.buildTabMenuItem(module, modules[module], appStrings, modStrings, appListStrings));
} else {
moreItems.push(this.buildMenuItem(module, modules[module], appStrings, modStrings, appListStrings));
moreItems.push(this.buildTabMenuItem(module, modules[module], appStrings, modStrings, appListStrings));
}
count++;
@ -107,22 +135,77 @@ export class NavbarAbstract implements NavbarModel {
this.all.extra = moreItems;
}
public buildMenuItem(
module: string,
moduleInfo: NavbarModule,
public buildTabGroupedMenuItem(
moduleLabel: string,
groupedModules: any[],
modules: NavbarModuleMap,
appStrings: LanguageStringMap,
modStrings: LanguageListStringMap,
appListStrings: LanguageListStringMap): MenuItem {
appListStrings: LanguageListStringMap
): any {
let moduleUrl = '';
let moduleRoute = null;
let moduleUrl = moduleInfo.defaultRoute;
if (moduleUrl.startsWith(ROUTE_PREFIX)) {
moduleRoute = moduleUrl.replace(ROUTE_PREFIX, '');
moduleUrl = null;
}
const menuItem = {
link: {
label: (appStrings && appStrings[moduleLabel]) || moduleLabel,
url: moduleUrl,
route: moduleRoute,
params: null
},
icon: '',
submenu: this.buildGroupedMenu(groupedModules, modules, appStrings, modStrings, appListStrings)
};
return menuItem;
}
public buildGroupedMenu(
groupedModules: any[],
modules: NavbarModuleMap,
appStrings: LanguageStringMap,
modStrings: LanguageListStringMap,
appListStrings: LanguageListStringMap
): MenuItem[] {
const groupedItems = [];
groupedModules.forEach((groupedModule) => {
let module = modules[groupedModule];
if (module === undefined) {
return;
}
groupedItems.push(this.buildTabMenuItem(groupedModule, module, appStrings, modStrings, appListStrings));
});
return groupedItems;
}
public buildTabMenuItem(
module: string,
moduleInfo: any,
appStrings: LanguageStringMap,
modStrings: LanguageListStringMap,
appListStrings: LanguageListStringMap
): MenuItem {
let moduleUrl = (moduleInfo && moduleInfo.defaultRoute) || moduleInfo.defaultRoute;
let moduleRoute = null;
if (moduleUrl.startsWith(ROUTE_PREFIX)) {
moduleRoute = moduleUrl.replace(ROUTE_PREFIX, '');
moduleUrl = null;
}
const moduleLabel = moduleInfo.labelKey;
const moduleLabel = (moduleInfo && moduleInfo.labelKey) || moduleInfo.labelKey;
const menuItem = {
link: {
label: (appListStrings && appListStrings.moduleList[moduleInfo.labelKey]) || moduleLabel,
@ -134,37 +217,39 @@ export class NavbarAbstract implements NavbarModel {
submenu: []
};
moduleInfo.menu.forEach((subMenu) => {
let label = modStrings[module][subMenu.labelKey];
if (moduleInfo) {
moduleInfo.menu.forEach((subMenu) => {
let label = modStrings[module][subMenu.labelKey];
if (!label) {
label = appStrings[subMenu.labelKey];
}
let actionUrl = subMenu.url;
let actionRoute = null;
let actionParams = null;
if (actionUrl.startsWith(ROUTE_PREFIX)) {
actionRoute = actionUrl.replace(ROUTE_PREFIX, '');
actionUrl = null;
if (subMenu.params) {
actionParams = subMenu.params;
if (!label) {
label = appStrings[subMenu.labelKey];
}
let actionUrl = subMenu.url;
let actionRoute = null;
let actionParams = null;
if (actionUrl.startsWith(ROUTE_PREFIX)) {
actionRoute = actionUrl.replace(ROUTE_PREFIX, '');
actionUrl = null;
if (subMenu.params) {
actionParams = subMenu.params;
}
}
}
menuItem.submenu.push({
link: {
label,
url: actionUrl,
route: actionRoute,
params: actionParams
},
icon: subMenu.icon,
submenu: []
menuItem.submenu.push({
link: {
label,
url: actionUrl,
route: actionRoute,
params: actionParams
},
icon: subMenu.icon,
submenu: []
});
});
});
}
return menuItem;
}

View file

@ -1,15 +1,17 @@
<!-- Start of main navbar section -->
<div class="top-panel fixed-top" *ngIf="(vm$ | async) as vm">
<!-- Start of empty navbar section until data is loaded -->
<!-- Start of empty navbar section until data is loaded -->
<ng-template [ngIf]="!loaded">
<nav class="navbar navbar-expand-lg">
<div class="navbar-collapse collapse order-4 order-md-0 collapsenav">
<ul class="navbar-nav">
<li class="top-nav nav-item">&nbsp;
</li>
</ul>
<ng-template [ngIf]="!loaded">
<nav class="navbar navbar-expand-lg">
<div class="navbar-collapse collapse order-4 order-md-0 collapsenav">
<ul class="navbar-nav">
<li class="top-nav nav-item">&nbsp;
</li>
</ul>
</div>
</nav>
</ng-template>
@ -60,11 +62,11 @@
</a>
<ul *ngIf="sub.submenu.length" class="">
<li *ngFor="let subitem of sub.submenu" class="nav-item">
<a class="mobile-nav-link action-link" href="{{ subitem.link.url }}">
<a class="mobile-nav-link action-link" href="/#{{ subitem.link.route }}">
{{ subitem.link.label }}
</a>
</li>
<ng-template [ngIf]="sub.recentRecords.length">
<ng-template [ngIf]="sub.recentRecords && sub.recentRecords.length">
<h4 class="recently-viewed-header">RECENTLY VIEWED</h4>
<li class="nav-item" *ngFor="let rec of sub.recentRecords">
<a class="mobile-nav-link action-link"
@ -126,16 +128,16 @@
<!-- Navbar with grouped tabs -->
<ul *ngIf="navbar.useGroupTabs" class="navbar-nav grouped">
<ul *ngIf="vm.userPreferences['navigation_paradigm'] == 'gm'" class="navbar-nav grouped">
<li class="top-nav nav-item dropdown main-grouped" *ngFor="let item of navbar.menu">
<span data-toggle="collapse" data-target=".navbar-collapse">
<a class="nav-link-grouped dropdown-toggle" data-toggle="dropdown" href="{{ item.link.url }}">
<a class="nav-link-grouped dropdown-toggle" data-toggle="dropdown" href="{{ item.link.route }}">
{{ item.link.label }}
</a>
</span>
<ul class="dropdown-menu main" aria-labelledby="navbarDropdownMenuLink" [ngbCollapse]="subNavCollapse">
<li class="nav-item dropdown-submenu submenu" *ngFor="let sub of item.submenu">
<a class="nav-link action-link" *ngIf="!sub.submenu.length" [href]="sub.link.url">
<a class="nav-link action-link" *ngIf="!sub.submenu.length" href="{{sub.link.route}}">
{{ sub.link.label }}
</a>
<a class="nav-link action-link dropdown-item dropdown-toggle" *ngIf="sub.submenu.length"
@ -144,13 +146,13 @@
</a>
<ul *ngIf="sub.submenu.length" class="dropdown-menu submenu">
<li *ngFor="let subitem of sub.submenu" class="nav-item">
<a class="nav-link action-link" href="{{ subitem.link.url }}">
<a class="nav-link action-link" href="#{{ subitem.link.route }}">
<svg-icon *ngIf="subitem.icon" src="public/themes/suite8/images/{{ subitem.icon }}.svg">
</svg-icon>
{{ subitem.link.label }}
</a>
</li>
<ng-template [ngIf]="sub.recentRecords.length">
<ng-template [ngIf]="sub.recentRecords && sub.recentRecords.length">
<h4 class="recently-viewed-header">RECENTLY VIEWED</h4>
<li class="nav-item" *ngFor="let rec of sub.recentRecords">
<a class="nav-link action-link"
@ -165,20 +167,20 @@
<!-- Navbar with non-grouped tabs -->
<ul *ngIf="!navbar.useGroupTabs" class="navbar-nav">
<ul *ngIf="vm.userPreferences['navigation_paradigm'] != 'gm'" class="navbar-nav">
<li class="top-nav nav-item dropdown non-grouped" *ngFor="let item of navbar.menu">
<!-- TODO: Implement a cleaner solution than hard coded ngIf -->
<ng-template [ngIf]="item.link.label !== 'Home'">
<span data-toggle="collapse" data-target=".navbar-collapse">
<a class="nav-link-nongrouped dropdown-toggle"
[href]="item.link.url"
[routerLink]="item.link.route"
[queryParams]="item.link.params"
routerLinkActive="active">
{{ item.link.label }}
</a>
<a class="nav-link-nongrouped dropdown-toggle"
[href]="item.link.url"
[routerLink]="item.link.route"
[queryParams]="item.link.params"
routerLinkActive="active">
{{ item.link.label }}
</a>
</span>
<div aria-labelledby="navbarDropdownMenuLink" class="dropdown-menu submenu">
<div class="nav-item" *ngFor="let sub of item.submenu">
@ -201,7 +203,7 @@
</ng-template>
</li>
</ul>
<ul *ngIf="!navbar.useGroupTabs && navbar.all.modules && navbar.all.modules.length > 0"
<ul *ngIf="vm.userPreferences['navigation_paradigm'] != 'gm' && navbar.all.modules && navbar.all.modules.length > 0"
class="navbar-nav">
<li class="top-nav nav-item dropdown non-grouped">
<a class="nav-link-nongrouped dropdown-toggle">More</a>

View file

@ -10,6 +10,8 @@ import {NavigationFacade} from '@base/facades/navigation/navigation.facade';
import {LanguageFacade} from '@base/facades/language/language.facade';
import {navigationMock} from '@base/facades/navigation/navigation.facade.spec.mock';
import {languageFacadeMock} from '@base/facades/language/language.facade.spec.mock';
import {UserPreferenceFacade} from "@base/facades/user-preference/user-preference.facade";
import {userPreferenceFacadeMock} from "@base/facades/user-preference/user-preference.facade.spec.mock";
describe('NavbarUiComponent', () => {
@ -29,6 +31,7 @@ describe('NavbarUiComponent', () => {
providers: [
{provide: NavigationFacade, useValue: navigationMock},
{provide: LanguageFacade, useValue: languageFacadeMock},
{provide: UserPreferenceFacade, useValue: userPreferenceFacadeMock},
],
declarations: [NavbarUiComponent]
}).compileComponents();

View file

@ -4,9 +4,13 @@ import {NavbarModel} from './navbar-model';
import {NavbarAbstract} from './navbar.abstract';
import {combineLatest, Observable} from 'rxjs';
import {NavbarModuleMap, NavigationFacade} from '@base/facades/navigation/navigation.facade';
import {NavbarModuleMap, NavigationFacade, GroupedTab} from '@base/facades/navigation/navigation.facade';
import {LanguageFacade, LanguageListStringMap, LanguageStringMap} from '@base/facades/language/language.facade';
import {UserPreferenceMap, UserPreferenceFacade} from '@base/facades/user-preference/user-preference.facade';
import {map} from 'rxjs/operators';
import { exists } from 'fs';
@Component({
selector: 'scrm-navbar-ui',
@ -36,24 +40,49 @@ export class NavbarUiComponent implements OnInit {
appStrings$: Observable<LanguageStringMap> = this.languageFacade.appStrings$;
modStrings$: Observable<LanguageListStringMap> = this.languageFacade.modStrings$;
appListStrings$: Observable<LanguageListStringMap> = this.languageFacade.appListStrings$;
userPreferences$: Observable<UserPreferenceMap> = this.userPreferenceFacade.userPreferences$;
groupedTabs$: Observable<any> = this.navigationFacade.groupedTabs$;
vm$ = combineLatest([this.tabs$, this.modules$, this.appStrings$, this.appListStrings$, this.modStrings$]).pipe(
vm$ = combineLatest([
this.tabs$,
this.modules$,
this.appStrings$,
this.appListStrings$,
this.modStrings$,
this.userPreferences$,
this.groupedTabs$
]).pipe(
map((
[
tabs,
modules,
appStrings,
appListStrings,
modStrings
modStrings,
userPreferences,
groupedTabs
]) => {
if (tabs && tabs.length > 0 &&
modules && Object.keys(modules).length > 0 &&
appStrings && Object.keys(appStrings).length > 0 &&
modStrings && Object.keys(modStrings).length > 0 &&
appListStrings && Object.keys(appListStrings).length > 0) {
appListStrings && Object.keys(appListStrings).length > 0 &&
userPreferences['navigation_paradigm'] && userPreferences['navigation_paradigm'].toString() == "m"
) {
this.navbar.resetMenu();
this.navbar.buildMenu(tabs, modules, appStrings, modStrings, appListStrings, this.menuItemThreshold);
this.navbar.buildTabMenu(tabs, modules, appStrings, modStrings, appListStrings, this.menuItemThreshold);
}
if (tabs && tabs.length > 0 &&
modules && Object.keys(modules).length > 0 &&
appStrings && Object.keys(appStrings).length > 0 &&
modStrings && Object.keys(modStrings).length > 0 &&
appListStrings && Object.keys(appListStrings).length > 0 &&
userPreferences['navigation_paradigm'] && userPreferences['navigation_paradigm'].toString() == "gm"
) {
this.navbar.resetMenu();
this.navbar.buildGroupTabMenu(tabs, modules, appStrings, modStrings, appListStrings, this.menuItemThreshold, groupedTabs);
}
return {
@ -61,7 +90,9 @@ export class NavbarUiComponent implements OnInit {
modules,
appStrings,
appListStrings,
modStrings
modStrings,
userPreferences,
groupedTabs
};
})
);
@ -70,7 +101,8 @@ export class NavbarUiComponent implements OnInit {
constructor(protected navigationFacade: NavigationFacade,
protected languageFacade: LanguageFacade,
protected api: ApiService) {
protected api: ApiService,
protected userPreferenceFacade: UserPreferenceFacade) {
const navbar = new NavbarAbstract();
this.setNavbar(navbar);
@ -110,6 +142,7 @@ export class NavbarUiComponent implements OnInit {
ngOnInit(): void {
const navbar = new NavbarAbstract();
this.setNavbar(navbar);
window.dispatchEvent(new Event('resize'));
}

View file

@ -1,6 +1,7 @@
import {getTestBed, TestBed} from '@angular/core/testing';
import {AppStateFacade} from '@base/facades/app-state/app-state.facade';
import {appStateFacadeMock} from '@base/facades/app-state/app-state.facade.spec.mock';
import {take} from "rxjs/operators";
describe('AppState Facade', () => {
let injector: TestBed;
@ -15,7 +16,7 @@ describe('AppState Facade', () => {
it('#updateLoading',
(done: DoneFn) => {
service.updateLoading(true);
service.loading$.subscribe(loading => {
service.loading$.pipe(take(1)).subscribe(loading => {
expect(loading).toEqual(true);
done();
});

View file

@ -32,7 +32,6 @@ let cache$: Observable<any> = null;
providedIn: 'root',
})
export class SystemConfigFacade {
protected store = new BehaviorSubject<SystemConfigs>(internalState);
protected state$ = this.store.asObservable();
protected resourceName = 'systemConfigs';

View file

@ -0,0 +1,79 @@
import {Observable, of} from 'rxjs';
import {shareReplay} from 'rxjs/operators';
import {CollectionGQL} from '@services/api/graphql-api/api.collection.get';
import {UserPreferenceFacade} from '@base/facades/user-preference/user-preference.facade';
export const userPreferenceMockData = {
userPreferences: {
global: {
id: '/api/user-preferences/global',
_id: 'global',
value: null,
items: {
calendar_publish_key: 'cc360e7b-c31c-5a12-4716-5e692263f2c8',
swap_shortcuts: '',
navigation_paradigm: 'gm',
sort_modules_by_name: '',
subpanel_tabs: '',
count_collapsed_subpanels: '',
module_favicon: '',
no_opps: 'off',
timezone: 'UTC',
ut: '1',
mail_smtpserver: '',
mail_smtpport: '25',
mail_smtpuser: '',
mail_smtppass: '',
use_real_names: 'off',
mail_smtpauth_req: '',
mail_smtpssl: 0,
email_show_counts: 0,
user_theme: 'SuiteP',
editor_type: 'mozaik',
reminder_time: '60',
email_reminder_time: '60',
reminder_checked: '0',
email_reminder_checked: '0',
currency: '-99',
default_currency_significant_digits: '2',
num_grp_sep: ',',
dec_sep: '.',
fdow: '0',
datef: 'm/d/Y',
timef: 'H:i',
default_locale_name_format: 's f l',
export_delimiter: ',',
default_export_charset: 'UTF-8',
email_link_type: 'sugar',
subtheme: 'Dawn'
}
}
}
};
class UserPreferenceRecordGQLSpy extends CollectionGQL {
constructor() {
super(null);
}
public fetchAll(module: string, metadata: { fields: string[] }): Observable<any> {
const data = {
data: {
userPreferences: {
edges: []
}
}
};
Object.keys(userPreferenceMockData.userPreferences).forEach(key => {
data.data.userPreferences.edges.push({
node: userPreferenceMockData.userPreferences[key]
});
});
return of(data).pipe(shareReplay());
}
}
export const userPreferenceFacadeMock = new UserPreferenceFacade(new UserPreferenceRecordGQLSpy());

View file

@ -0,0 +1,129 @@
import {Injectable} from '@angular/core';
import {BehaviorSubject, Observable} from 'rxjs';
import {map, distinctUntilChanged, tap, shareReplay} from 'rxjs/operators';
import {CollectionGQL} from '@services/api/graphql-api/api.collection.get';
export interface UserPreference {
id: string;
_id: string;
value: string;
items: { [key: string]: any };
}
export interface UserPreferenceMap {
[key: string]: UserPreference;
}
export interface UserPreferences {
userPreferences: UserPreferenceMap;
loading: boolean;
}
let internalState: UserPreferences = {
userPreferences: {},
loading: false
};
let cache$: Observable<any> = null;
@Injectable({
providedIn: 'root',
})
export class UserPreferenceFacade {
protected store = new BehaviorSubject<UserPreferences>(internalState);
protected state$ = this.store.asObservable();
protected resourceName = 'userPreferences';
protected fieldsMetadata = {
fields: [
'id',
'_id',
'value',
'items'
]
};
/**
* Public long-lived observable streams
*/
userPreferences$ = this.state$.pipe(map(state => state.userPreferences), distinctUntilChanged());
loading$ = this.state$.pipe(map(state => state.loading));
constructor(private collectionGQL: CollectionGQL) {
}
/**
* Public Api
*/
/**
* Initial UserPreferences load if not cached and update state.
* Returns observable to be used in resolver if needed
*
* @returns Observable<any>
*/
public load(): Observable<any> {
this.updateState({...internalState, loading: true});
return this.getUserPreferences().pipe(
tap(userPreferences => {
this.updateState({...internalState, userPreferences, loading: false});
})
);
}
/**
* Internal API
*/
/**
* Update the state
*
* @param state
*/
protected updateState(state: UserPreferences) {
this.store.next(internalState = state);
}
/**
* Get UserPreferences cached Observable or call the backend
*
* @return Observable<any>
*/
protected getUserPreferences(): Observable<any> {
if (cache$ == null) {
cache$ = this.fetch().pipe(
shareReplay(1)
);
}
return cache$;
}
/**
* Fetch the User Preferences from the backend
*
* @returns Observable<any>
*/
protected fetch(): Observable<any> {
return this.collectionGQL
.fetchAll(this.resourceName, this.fieldsMetadata).pipe(map(({data}) => {
const userPreferences: UserPreferenceMap = {};
if (data.userPreferences && data.userPreferences.edges) {
data.userPreferences.edges.forEach((edge) => {
for (const key in edge.node.items) {
userPreferences[key] = edge.node.items[key];
}
});
}
return userPreferences;
}));
}
}

View file

@ -0,0 +1,20 @@
import {Injectable} from '@angular/core';
import {
Resolve,
ActivatedRouteSnapshot,
RouterStateSnapshot
} from '@angular/router';
import {UserPreferenceFacade} from '@base/facades/user-preference/user-preference.facade';
import {flatMap} from 'rxjs/operators';
@Injectable({providedIn: 'root'})
export class UserPreferenceResolver implements Resolve<any> {
constructor(private userPreferenceFacade: UserPreferenceFacade) {
}
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.userPreferenceFacade.load();
}
}

View file

@ -0,0 +1,42 @@
import {Observable, of} from 'rxjs';
import {shareReplay} from 'rxjs/operators';
import {RecordMutationGQL} from '@services/api/graphql-api/api.record.create';
import {FetchResult} from 'apollo-link';
import {ProcessService} from '@services/process/process.service';
import {appStateFacadeMock} from '@base/facades/app-state/app-state.facade.spec.mock';
import {RecoverPasswordService} from '@services/process/processes/recover-password/recover-password';
export const recoverPasswordMockData = {
data: {
createProcess: {
process: {
_id: 'recover-password',
status: 'success',
async: false,
type: 'recover-password',
messages: [
'LBL_RECOVER_PASSWORD_SUCCESS'
]
},
clientMutationId: null
}
}
};
class RecoverPasswordRecordMutationGQLSpy extends RecordMutationGQL {
constructor() {
super(null);
}
public create(graphqlModuleName: string,
coreModuleName: string,
input: { [key: string]: any },
metadata: { fields: string[] }): Observable<FetchResult<any>> {
return of(recoverPasswordMockData).pipe(shareReplay());
}
}
const processServiceMock = new ProcessService(new RecoverPasswordRecordMutationGQLSpy());
export const recoverPasswordMock = new RecoverPasswordService(processServiceMock, appStateFacadeMock);

View file

@ -0,0 +1,26 @@
import {getTestBed, TestBed} from '@angular/core/testing';
import {
recoverPasswordMock,
recoverPasswordMockData
} from '@services/process/processes/recover-password/recover-passoword.spec.mock';
import {RecoverPasswordService} from '@services/process/processes/recover-password/recover-password';
describe('Reset Password service', () => {
let injector: TestBed;
const service: RecoverPasswordService = recoverPasswordMock;
beforeEach(() => {
TestBed.configureTestingModule({});
injector = getTestBed();
});
it('#run',
(done: DoneFn) => {
service.run('john.doe', 'jonh.doe@example.com').subscribe(data => {
expect(data).toEqual(jasmine.objectContaining(recoverPasswordMockData.data.createProcess.process));
done();
});
});
});

View file

@ -38,7 +38,7 @@
}
.action-group .dropdown-menu li a:hover {
background: none;
background: green;
text-decoration: none;
}
@ -156,7 +156,7 @@ ul.main li.nav-item,
padding: 0em 1em;
}
.dropdown-menu .nav-item:hover {
.dropdown-menu .nav-item:hover, .dropdown-menu .nav-item .action-link:hover {
background-color: $dull-orange;
}

View file

@ -73,8 +73,8 @@ class NavbarHandler extends LegacyHandler
$nameMap = $this->createFrontendNameMap($legacyTabNames);
$navbar->tabs = array_values($nameMap);
$navbar->groupedTabs = $this->fetchGroupedNavTabs($nameMap);
$navbar->modules = $this->buildModuleInfo($sugarView, $nameMap);
$navbar->groupedTabs = $this->fetchGroupedNavTabs();
$navbar->userActionMenu = $this->fetchUserActionMenu();
$this->close();
@ -95,9 +95,10 @@ class NavbarHandler extends LegacyHandler
/**
* Fetch Grouped Navigation tabs
* @param array $nameMap
* @return array
*/
protected function fetchGroupedNavTabs(): array
protected function fetchGroupedNavTabs(array &$nameMap): array
{
$output = [];
global $current_language;
@ -119,6 +120,11 @@ class NavbarHandler extends LegacyHandler
foreach ($subModules['modules'] as $submodule => $submoduleLabel) {
$submoduleArray[] = $moduleNameMap[$submodule];
//Add missing module to the module info list
if (empty($nameMap[$submodule])){
$nameMap[$submodule] = $moduleNameMap[$submodule];
}
}
sort($submoduleArray);

View file

@ -0,0 +1,194 @@
<?php
namespace SuiteCRM\Core\Legacy;
use ApiPlatform\Core\Exception\ItemNotFoundException;
use App\Entity\UserPreference;
use DBManagerFactory;
use Exception;
class UserPreferenceHandler extends LegacyHandler
{
protected const MSG_USER_PREFERENCE_NOT_FOUND = 'Not able to find user preference key: ';
/**
* @var array
*/
protected $exposedUserPreferences = [];
/**
* @var User
*/
protected $current_user = null;
/**
*
* @var array
*/
protected $userPreference = [];
/**
* UserPreferenceHandler constructor.
* @param string $projectDir
* @param string $legacyDir
* @param string $legacySessionName
* @param string $defaultSessionName
* @param array $exposedUserPreferences
*/
public function __construct(
string $projectDir,
string $legacyDir,
string $legacySessionName,
string $defaultSessionName,
array $exposedUserPreferences
) {
parent::__construct($projectDir, $legacyDir, $legacySessionName, $defaultSessionName);
$this->init();
$this->exposedUserPreferences = $exposedUserPreferences;
global $current_user;
get_sugar_config_defaults();
$user = new \User();
$current_user = $user->retrieve(1);
$db = DBManagerFactory::getInstance();
$quotedUserId = $db->quoted($current_user->id);
$result = $db->query("SELECT contents, category FROM user_preferences WHERE assigned_user_id=" . $quotedUserId . " AND deleted = 0", false, 'Failed to load user preferences');
while ($row = $db->fetchByAssoc($result)) {
$category = $row['category'];
$this->preferences[$category] = unserialize(base64_decode($row['contents']));
}
$this->close();
}
/**
* Get all exposed user preferences
* @return array
*/
public function getAllUserPreferences(): array
{
$this->init();
$userPreferences = [];
foreach ($this->exposedUserPreferences as $key => $value) {
$userPreference = $this->loadUserPreference($key);
if (!empty($userPreference)) {
$userPreferences[] = $userPreference;
}
}
$this->close();
return $userPreferences;
}
/**
* Get user preference
* @param string $key
* @return UserPreference|null
*/
public function getUserPreference(string $key): ?UserPreference
{
$this->init();
$userPreference = $this->loadUserPreference($key);
$this->close();
return $userPreference;
}
/**
* Load user preference with given $key
* @param $key
* @return UserPreference|null
*/
protected function loadUserPreference(string $key): ?UserPreference
{
global $current_user;
if (empty($key)) {
return null;
}
if (!isset($current_user->id)) {
throw new Exception('No user logged in.');
}
if (!isset($this->preferences[$key])) {
throw new ItemNotFoundException(self::MSG_USER_PREFERENCE_NOT_FOUND . "'$key'");
}
$userPreference = new UserPreference();
$userPreference->setId($key);
if (!isset($this->preferences[$key])) {
return $userPreference;
}
if (is_array($this->preferences[$key])) {
$items = $this->preferences[$key];
if (is_array($this->exposedUserPreferences[$key])) {
$items = $this->filterItems($this->preferences[$key], $this->exposedUserPreferences[$key]);
}
$userPreference->setItems($items);
return $userPreference;
}
$userPreference->setValue($this->preferences[$key]);
return $userPreference;
}
/**
* Filter to retrieve only exposed items
* @param array $allItems
* @param array $exposed
* @return array
*/
protected function filterItems(array $allItems, array $exposed): array
{
$items = [];
if (empty($exposed)) {
return $items;
}
foreach ($allItems as $key => $value) {
if (!isset($exposed[$key])) {
continue;
}
if (is_array($allItems[$key])) {
$subItems = $allItems[$key];
if (is_array($exposed[$key])) {
$subItems = $this->filterItems($allItems[$key], $exposed[$key]);
}
$items[$key] = $subItems;
continue;
}
$items[$key] = $value;
}
return $items;
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace App\DataProvider;
use ApiPlatform\Core\DataProvider\ArrayPaginator;
use ApiPlatform\Core\DataProvider\ContextAwareCollectionDataProviderInterface;
use ApiPlatform\Core\DataProvider\PaginatorInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use App\Entity\UserPreference;
use SuiteCRM\Core\Legacy\UserPreferenceHandler;
/**
* Class UserPreferenceCollectionDataProvider
*/
class UserPreferenceCollectionDataProvider implements ContextAwareCollectionDataProviderInterface,
RestrictedDataProviderInterface
{
/**
* @var UserPreferenceHandler
*/
private $userPreferenceHandler;
/**
* UserPreferenceCollectionDataProvider constructor.
* @param UserPreferenceHandler $userPreferenceHandler
*/
public function __construct(UserPreferenceHandler $userPreferenceHandler)
{
$this->userPreferenceHandler = $userPreferenceHandler;
}
/**
* Define supported Resource Classes
* @param string $resourceClass
* @param string|null $operationName
* @param array $context
* @return bool
*/
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return UserPreference::class === $resourceClass;
}
/**
* {@inheritdoc}
*/
public function getCollection(
string $resourceClass,
string $operationName = null,
array $context = []
): PaginatorInterface {
$userPreferences = $this->userPreferenceHandler->getAllUserPreferences();
return new ArrayPaginator($userPreferences, 0, count($userPreferences));
}
}

View file

@ -0,0 +1,58 @@
<?php
namespace App\DataProvider;
use ApiPlatform\Core\DataProvider\ItemDataProviderInterface;
use ApiPlatform\Core\DataProvider\RestrictedDataProviderInterface;
use App\Entity\UserPreference;
use SuiteCRM\Core\Legacy\UserPreferenceHandler;
/**
* Class UserPreferenceItemDataProvider
*/
final class UserPreferenceItemDataProvider implements ItemDataProviderInterface, RestrictedDataProviderInterface
{
/**
* @var UserPreferenceHandler
*/
private $userPreferenceHandler;
/**
* UserPreferenceItemDataProvider constructor.
* @param UserPreferenceHandler $userPreferenceHandler
*/
public function __construct(UserPreferenceHandler $userPreferenceHandler)
{
$this->userPreferenceHandler = $userPreferenceHandler;
}
/**
* Defined supported resources
* @param string $resourceClass
* @param string|null $operationName
* @param array $context
* @return bool
*/
public function supports(string $resourceClass, string $operationName = null, array $context = []): bool
{
return UserPreference::class === $resourceClass;
}
/**
* Get user preference
* @param string $resourceClass
* @param array|int|string $id
* @param string|null $operationName
* @param array $context
* @return UserPreference|null
*/
public function getItem(
string $resourceClass,
$id,
string $operationName = null,
array $context = []
): ?UserPreference {
return $this->userPreferenceHandler->getUserPreference($id);
}
}

View file

@ -0,0 +1,105 @@
<?php
namespace App\Entity;
use ApiPlatform\Core\Annotation\ApiResource;
use ApiPlatform\Core\Annotation\ApiProperty;
/**
* @ApiResource(
* attributes={"security"="is_granted('ROLE_USER')"},
* itemOperations={
* "get"
* },
* collectionOperations={
* "get"
* },
* graphql={
* "item_query",
* "collection_query"
* },
* )
*/
class UserPreference
{
/**
* @ApiProperty(identifier=true)
* @var string|null
*/
protected $id;
/**
* @ApiProperty
* @var string|null
*/
protected $value;
/**
* @ApiProperty
* @var array
*/
protected $items = [];
/**
* Get Id
* @return string|null
*/
public function getId(): ?string
{
return $this->id;
}
/**
* Set Id
* @param string|null $id
* @return UserPreference
*/
public function setId(?string $id): UserPreference
{
$this->id = $id;
return $this;
}
/**
* Get value
* @return string|null
*/
public function getValue(): ?string
{
return $this->value;
}
/**
* Set value
* @param string|null $value
* @return UserPreference
*/
public function setValue(?string $value): UserPreference
{
$this->value = $value;
return $this;
}
/**
* Get items
* @return array
*/
public function getItems(): array
{
return $this->items;
}
/**
* Set Items
* @param array $items
* @return UserPreference
*/
public function setItems(array $items): UserPreference
{
$this->items = $items;
return $this;
}
}

View file

@ -17,7 +17,7 @@ class AppListStringsHandlerTest extends Unit
*/
protected $handler;
protected function _before()
protected function _before(): void
{
$projectDir = codecept_root_dir();
$legacyDir = $projectDir . '/legacy';
@ -31,7 +31,7 @@ class AppListStringsHandlerTest extends Unit
/**
* Test Invalid language handling in AppListStringsHandler
*/
public function testInvalidLanguageCheck()
public function testInvalidLanguageCheck(): void
{
$this->expectException(ItemNotFoundException::class);
$this->handler->getAppListStrings('invalid_lang');
@ -40,7 +40,7 @@ class AppListStringsHandlerTest extends Unit
/**
* Test default language retrieval in AppListStringsHandler
*/
public function testDefaultLanguageKey()
public function testDefaultLanguageKey(): void
{
$appListStrings = $this->handler->getAppListStrings('en_us');
static::assertNotNull($appListStrings);
@ -56,7 +56,7 @@ class AppListStringsHandlerTest extends Unit
* @param string $labelKey
* @param AppListStrings $appStrings
*/
protected function assertLanguageListKey(string $listKey, string $labelKey, AppListStrings $appStrings)
protected function assertLanguageListKey(string $listKey, string $labelKey, AppListStrings $appStrings): void
{
static::assertArrayHasKey($listKey, $appStrings->getItems());
static::assertNotEmpty($appStrings->getItems()[$listKey]);

View file

@ -17,7 +17,7 @@ class AppStringsHandlerTest extends Unit
*/
protected $handler;
protected function _before()
protected function _before(): void
{
$projectDir = codecept_root_dir();
$legacyDir = $projectDir . '/legacy';
@ -31,7 +31,7 @@ class AppStringsHandlerTest extends Unit
/**
* Test Invalid language handling in AppStringsHandler
*/
public function testInvalidLanguageCheck()
public function testInvalidLanguageCheck(): void
{
$this->expectException(ItemNotFoundException::class);
$this->handler->getAppStrings('invalid_lang');
@ -40,7 +40,7 @@ class AppStringsHandlerTest extends Unit
/**
* Test default language retrieval in AppStringsHandler
*/
public function testDefaultLanguageKey()
public function testDefaultLanguageKey(): void
{
$appStrings = $this->handler->getAppStrings('en_us');
static::assertNotNull($appStrings);
@ -62,7 +62,7 @@ class AppStringsHandlerTest extends Unit
* @param string $key
* @param AppStrings $appStrings
*/
protected function assertLanguageKey($key, AppStrings $appStrings)
protected function assertLanguageKey($key, AppStrings $appStrings): void
{
static::assertArrayHasKey($key, $appStrings->getItems());
static::assertNotEmpty($appStrings->getItems()[$key]);

View file

@ -18,7 +18,7 @@ class ModStringsHandlerTest extends Unit
*/
protected $handler;
protected function _before()
protected function _before(): void
{
$legacyModuleNameMap = [
'Home' => [
@ -366,7 +366,7 @@ class ModStringsHandlerTest extends Unit
/**
* Test Invalid language handling in ModStringsHandler
*/
public function testInvalidLanguageCheck()
public function testInvalidLanguageCheck(): void
{
$this->expectException(ItemNotFoundException::class);
$this->handler->getModStrings('invalid_lang');
@ -375,7 +375,7 @@ class ModStringsHandlerTest extends Unit
/**
* Test default language retrieval in ModStringsHandler
*/
public function testDefaultLanguageKey()
public function testDefaultLanguageKey(): void
{
$modStrings = $this->handler->getModStrings('en_us');
static::assertNotNull($modStrings);
@ -392,7 +392,7 @@ class ModStringsHandlerTest extends Unit
* @param string $key
* @param ModStrings $modStrings
*/
protected function assertLanguageKey($module, $key, ModStrings $modStrings)
protected function assertLanguageKey($module, $key, ModStrings $modStrings): void
{
static::assertArrayHasKey($module, $modStrings->getItems());
static::assertNotEmpty($modStrings->getItems()[$module]);

View file

@ -2146,6 +2146,45 @@ final class NavbarTest extends Unit
"icon" => "view"
]
]
],
'bugs' => [
'path' => 'bugs',
'defaultRoute' => './#/bugs/index',
'name' => 'bugs',
'labelKey' => 'Bugs',
'menu' => [
0 => [
'name' => 'Create',
'labelKey' => 'LNK_NEW_BUG',
'url' => './#/bugs/edit',
'params' => [
'return_module' => 'Bugs',
'return_action' => 'DetailView',
],
'icon' => 'plus'
],
1 => [
'name' => 'List',
'labelKey' => 'LNK_BUG_LIST',
'url' => './#/bugs/index',
'params' => [
'return_module' => 'Bugs',
'return_action' => 'DetailView'
],
'icon' => 'view'
],
2 => [
'name' => 'Import',
'labelKey' => 'LNK_IMPORT_BUGS',
'url' => './#/import/step1',
'params' => [
'import_module' => 'Bugs',
'return_module' => 'Bugs',
'return_action' => 'index'
],
'icon' => 'download'
]
]
]
];

View file

@ -16,7 +16,7 @@ class SystemConfigHandlerTest extends Unit
*/
protected $handler;
protected function _before()
protected function _before(): void
{
$exposedSystemConfigs = [
'default_language' => true,
@ -46,7 +46,7 @@ class SystemConfigHandlerTest extends Unit
/**
* Test empty config key handling in SystemConfigHandler
*/
public function testEmptySystemConfigKeyCheck()
public function testEmptySystemConfigKeyCheck(): void
{
$defaultLanguage = $this->handler->getSystemConfig('');
static::assertNull($defaultLanguage);
@ -55,7 +55,7 @@ class SystemConfigHandlerTest extends Unit
/**
* Test invalid config key handling in SystemConfigHandler
*/
public function testInvalidExposedSystemConfigCheck()
public function testInvalidExposedSystemConfigCheck(): void
{
$this->expectException(ItemNotFoundException::class);
$this->handler->getSystemConfig('dbconfig');
@ -64,7 +64,7 @@ class SystemConfigHandlerTest extends Unit
/**
* Test retrieval of first level system config in SystemConfigHandler
*/
public function testGetValidOneLevelSystemConfig()
public function testGetValidOneLevelSystemConfig(): void
{
$defaultLanguage = $this->handler->getSystemConfig('default_language');
static::assertNotNull($defaultLanguage);
@ -76,7 +76,7 @@ class SystemConfigHandlerTest extends Unit
/**
* Test retrieval of second level deep system config in SystemConfigHandler
*/
public function testGetValidTwoLevelSystemConfig()
public function testGetValidTwoLevelSystemConfig(): void
{
$defaultLanguage = $this->handler->getSystemConfig('passwordsetting');
static::assertNotNull($defaultLanguage);
@ -97,7 +97,7 @@ class SystemConfigHandlerTest extends Unit
/**
* Test retrieval of third level deep system config in SystemConfigHandler
*/
public function testGetValidThreeLevelSystemConfig()
public function testGetValidThreeLevelSystemConfig(): void
{
$defaultLanguage = $this->handler->getSystemConfig('search');
static::assertNotNull($defaultLanguage);

View file

@ -0,0 +1,70 @@
<?php namespace App\Tests;
use ApiPlatform\Core\Exception\ItemNotFoundException;
use Codeception\Test\Unit;
use SuiteCRM\Core\Legacy\UserPreferenceHandler;
class UserPreferencesHandlerTest extends Unit
{
/**
* @var UnitTester
*/
protected $tester;
/**
* @var UserPreferenceHandler
*/
protected $handler;
protected function _before(): void
{
$exposedUserPreferences = [
'global' => [
'timezone' => true
]
];
$projectDir = codecept_root_dir();
$legacyDir = $projectDir . '/legacy';
$legacySessionName = 'LEGACYSESSID';
$defaultSessionName = 'PHPSESSID';
$this->handler = new UserPreferenceHandler($projectDir, $legacyDir, $legacySessionName, $defaultSessionName,
$exposedUserPreferences);
}
// tests
/**
* Test empty config key handling in UserPreferenceHandler
*/
public function testEmptyUserPreferenceKeyCheck(): void
{
$noKeyUserPreference = $this->handler->getUserPreference('');
static::assertNull($noKeyUserPreference);
}
/**
* Test invalid config key handling in UserPreferenceHandler
*/
public function testInvalidExposedUserPreferenceCheck(): void
{
$this->expectException(ItemNotFoundException::class);
$this->handler->getUserPreference('dbconfig');
}
/**
* Test retrieval of first level user preference config in UserPreferenceHandler
*/
public function testGetUserPreferenceTimezone(): void
{
$userPref = $this->handler->getUserPreference('global');
static::assertNotNull($userPref);
static::assertEquals('global', $userPref->getId());
$userPreferences = $userPref->getItems();
static::assertEquals('UTC', $userPreferences['timezone']);
}
}