refactor: replace TSLint with ESLint

This commit is contained in:
Wieland Schöbl
2021-06-30 13:53:44 +02:00
committed by Jovan Krunić
parent 67fb4a43c9
commit d696215d08
147 changed files with 5471 additions and 2704 deletions

View File

@@ -31,7 +31,6 @@ import {SettingsProvider} from '../settings.provider';
templateUrl: 'settings-item.html',
})
export class SettingsItemComponent {
/**
* If set the setting will be shown as compact view
*/
@@ -41,6 +40,7 @@ export class SettingsItemComponent {
* Flag for workaround for selected 'select option' not updating translation
*/
isVisible = true;
/**
* The setting to handle
*/
@@ -52,13 +52,15 @@ export class SettingsItemComponent {
* @param translateService TranslateService
* @param settingsProvider SettingProvider
*/
constructor(private readonly alertCtrl: AlertController,
private readonly translateService: TranslateService,
private readonly settingsProvider: SettingsProvider) {
constructor(
private readonly alertCtrl: AlertController,
private readonly translateService: TranslateService,
private readonly settingsProvider: SettingsProvider,
) {
translateService.onLangChange.subscribe((_event: LangChangeEvent) => {
this.isVisible = false;
// TODO: Issue #53 check workaround for selected 'select option' not updating translation
setTimeout(() => this.isVisible = true);
setTimeout(() => (this.isVisible = true));
});
}
@@ -81,8 +83,10 @@ export class SettingsItemComponent {
* Handles value changes of the setting
*/
async settingChanged(): Promise<void> {
if (typeof this.setting.value !== 'undefined'
&& SettingsProvider.validateValue(this.setting, this.setting.value)) {
if (
typeof this.setting.value !== 'undefined' &&
SettingsProvider.validateValue(this.setting, this.setting.value)
) {
// handle general settings, with special actions
switch (this.setting.name) {
case 'language':
@@ -90,23 +94,25 @@ export class SettingsItemComponent {
break;
default:
}
await this.settingsProvider
.setSettingValue(this.setting.categories[0],
this.setting.name,
this.setting.value);
await this.settingsProvider.setSettingValue(
this.setting.categories[0],
this.setting.name,
this.setting.value,
);
} else {
// reset setting
this.setting.value =
await this.settingsProvider
.getValue(this.setting.categories[0], this.setting.name) as (SCSettingValue | SCSettingValues);
this.setting.value = (await this.settingsProvider.getValue(
this.setting.categories[0],
this.setting.name,
)) as SCSettingValue | SCSettingValues;
}
}
/**
* Mapping of typeOf for Html usage
*/
// tslint:disable-next-line:prefer-function-over-method
typeOf(val: unknown) {
return typeof (val);
// eslint-disable-next-line class-methods-use-this
typeOf(value: unknown) {
return typeof value;
}
}

View File

@@ -1,37 +1,80 @@
<ion-card *ngIf="setting">
<ion-card-header *ngIf="{ name: ('name' | thingTranslate: setting | titlecase), desc: ('description' | thingTranslate: setting | titlecase) } let vals">
<ion-card-header
*ngIf="
{
name: ('name' | thingTranslate: setting | titlecase),
desc: ('description' | thingTranslate: setting | titlecase)
};
let vals
"
>
<ion-card-subtitle>
{{ vals.name }}
<ion-icon *ngIf="compactView" name="information-circle-outline" (click)="presentAlert( vals.name , vals.desc )"></ion-icon>
<ion-icon
*ngIf="compactView"
name="information-circle-outline"
(click)="presentAlert(vals.name, vals.desc)"
></ion-icon>
</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
<ion-note *ngIf="!compactView">{{ 'description' | thingTranslate: setting | titlecase }}</ion-note>
<ion-note *ngIf="!compactView">{{
'description' | thingTranslate: setting | titlecase
}}</ion-note>
<div [ngSwitch]="setting.inputType" *ngIf="isVisible" >
<div [ngSwitch]="setting.inputType" *ngIf="isVisible">
<ion-item *ngSwitchCase="'number'">
<ion-label></ion-label>
<ion-input type='number' [(ngModel)]="setting.value" value={{setting.value}} (ionChange)="settingChanged()"></ion-input>
<ion-input
type="number"
[(ngModel)]="setting.value"
value="{{ setting.value }}"
(ionChange)="settingChanged()"
></ion-input>
</ion-item>
<ion-item *ngSwitchCase="'text'">
<ion-label></ion-label>
<ion-input type="text" [(ngModel)]="setting.value" value={{setting.value}} (ionChange)="settingChanged()"></ion-input>
<ion-input
type="text"
[(ngModel)]="setting.value"
value="{{ setting.value }}"
(ionChange)="settingChanged()"
></ion-input>
</ion-item>
<ion-item *ngSwitchCase="'password'">
<ion-label></ion-label>
<ion-input type="password" [(ngModel)]="setting.value" value={{setting.value}} (ionChange)="settingChanged()"></ion-input>
<ion-input
type="password"
[(ngModel)]="setting.value"
value="{{ setting.value }}"
(ionChange)="settingChanged()"
></ion-input>
</ion-item>
<ion-item *ngSwitchCase="'single choice'">
<ion-label></ion-label>
<!-- if values are boolean show as toggle -->
<ion-toggle *ngIf="typeOf(setting.defaultValue) === 'boolean'" [(ngModel)]="setting.value" (ionChange)="settingChanged()"></ion-toggle>
<ion-toggle
*ngIf="typeOf(setting.defaultValue) === 'boolean'"
[(ngModel)]="setting.value"
(ionChange)="settingChanged()"
></ion-toggle>
<!-- else show select input -->
<ion-select *ngIf="typeOf(setting.defaultValue) !== 'boolean'" interface="popover" [(ngModel)]="setting.value" (ionChange)="settingChanged()">
<ion-select-option *ngFor="let val of setting.values, index as i" [value]="val">
<div *ngIf="typeOf(val) !== 'number'">{{ ('values' | thingTranslate: setting)[i] | titlecase }}</div>
<ion-select
*ngIf="typeOf(setting.defaultValue) !== 'boolean'"
interface="popover"
[(ngModel)]="setting.value"
(ionChange)="settingChanged()"
>
<ion-select-option
*ngFor="let val of setting.values; index as i"
[value]="val"
>
<div *ngIf="typeOf(val) !== 'number'">
{{ ('values' | thingTranslate: setting)[i] | titlecase }}
</div>
<div *ngIf="typeOf(val) === 'number'">{{ val }}</div>
</ion-select-option>
</ion-select>
@@ -39,9 +82,18 @@
<ion-item *ngSwitchCase="'multiple choice'">
<ion-label></ion-label>
<ion-select [(ngModel)]="setting.value" multiple="true" (ionChange)="settingChanged()">
<ion-select-option *ngFor="let val of setting.values, index as i" [value]="val">
<div *ngIf="typeOf(val) !== 'number'">{{ ('values' | thingTranslate: setting)[i] | titlecase }}</div>
<ion-select
[(ngModel)]="setting.value"
multiple="true"
(ionChange)="settingChanged()"
>
<ion-select-option
*ngFor="let val of setting.values; index as i"
[value]="val"
>
<div *ngIf="typeOf(val) !== 'number'">
{{ ('values' | thingTranslate: setting)[i] | titlecase }}
</div>
<div *ngIf="typeOf(val) === 'number'">{{ val }}</div>
</ion-select-option>
</ion-select>

View File

@@ -12,31 +12,35 @@
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {ChangeDetectorRef, Component} from '@angular/core';
import {ChangeDetectorRef, Component, OnInit} from '@angular/core';
import {AlertController, ToastController} from '@ionic/angular';
import {TranslateService} from '@ngx-translate/core';
import {SCSettingMeta} from '@openstapps/core';
import {SettingsCache, SettingsProvider} from '../settings.provider';
/**
* Settings page component
*/
/**
* Settings page component
*/
@Component({
selector: 'stapps-settings-page',
templateUrl: 'settings-page.html',
})
export class SettingsPageComponent {
export class SettingsPageComponent implements OnInit {
/**
* Order of the categories
*/
categoriesOrder: string[];
/**
* Meta information about settings
*/
meta = SCSettingMeta;
/**
* Mapping of Object.keys for Html usage
*/
objectKeys = Object.keys;
/**
* Container to cache settings from provider
*/
@@ -50,11 +54,13 @@ export class SettingsPageComponent {
* @param translateService TranslateService
* @param changeDetectorRef ChangeDetectorRef
*/
constructor(private readonly alertController: AlertController,
private readonly settingsProvider: SettingsProvider,
private readonly toastController: ToastController,
private readonly translateService: TranslateService,
private readonly changeDetectorRef: ChangeDetectorRef) {
constructor(
private readonly alertController: AlertController,
private readonly settingsProvider: SettingsProvider,
private readonly toastController: ToastController,
private readonly translateService: TranslateService,
private readonly changeDetectorRef: ChangeDetectorRef,
) {
this.settingsCache = {};
}
@@ -92,13 +98,17 @@ export class SettingsPageComponent {
* Presents an alert to the user to reset settings to default values
*/
async presentResetAlert() {
const cancelText = await this.translateService.get('settings.resetAlert.buttonCancel')
const cancelText = await this.translateService
.get('settings.resetAlert.buttonCancel')
.toPromise();
const yesText = await this.translateService.get('settings.resetAlert.buttonYes')
const yesText = await this.translateService
.get('settings.resetAlert.buttonYes')
.toPromise();
const title = await this.translateService.get('settings.resetAlert.title')
const title = await this.translateService
.get('settings.resetAlert.title')
.toPromise();
const message = await this.translateService.get('settings.resetAlert.message')
const message = await this.translateService
.get('settings.resetAlert.message')
.toPromise();
const alert = await this.alertController.create({
@@ -117,7 +127,7 @@ export class SettingsPageComponent {
header: title,
message: message,
});
alert.present();
await alert.present();
}
/**
@@ -129,15 +139,17 @@ export class SettingsPageComponent {
await this.presentSettingsResetToast();
}
/**
* Shows alert to reset settings
*/
/**
* Shows alert to reset settings
*/
async showResetAlert() {
const alert = await this.alertController.create({
buttons: [
{
role: 'cancel',
text: this.translateService.instant('settings.resetAlert.buttonCancel'),
text: this.translateService.instant(
'settings.resetAlert.buttonCancel',
),
},
{
handler: async () => {
@@ -150,6 +162,6 @@ export class SettingsPageComponent {
message: this.translateService.instant('settings.resetAlert.message'),
});
alert.present();
await alert.present();
}
}

View File

@@ -4,21 +4,40 @@
<ion-back-button></ion-back-button>
<ion-menu-button></ion-menu-button>
</ion-buttons>
<ion-title>{{'settings.title' | translate | titlecase}}</ion-title>
<ion-title>{{ 'settings.title' | translate | titlecase }}</ion-title>
</ion-toolbar>
</ion-header>
<ion-content>
<ion-list *ngFor="let categoryKey of categoriesOrder ">
<ion-list *ngFor="let categoryKey of categoriesOrder">
<div *ngIf="objectKeys(settingsCache).includes(categoryKey)">
<ion-item-divider>
<h5>{{ 'categories[0]' | thingTranslate: settingsCache[categoryKey]?.settings[objectKeys(settingsCache[categoryKey]?.settings)[0]] | titlecase }} </h5>
<h5>
{{
'categories[0]'
| thingTranslate
: settingsCache[categoryKey]?.settings[
objectKeys(settingsCache[categoryKey]?.settings)[0]
]
| titlecase
}}
</h5>
</ion-item-divider>
<stapps-settings-item *ngFor="let settingKeys of objectKeys(settingsCache[categoryKey].settings)" [setting]="settingsCache[categoryKey].settings[settingKeys]"></stapps-settings-item>
<stapps-settings-item
*ngFor="
let settingKeys of objectKeys(settingsCache[categoryKey].settings)
"
[setting]="settingsCache[categoryKey].settings[settingKeys]"
></stapps-settings-item>
</div>
</ion-list>
<ion-button color="medium" expand="block" fill="outline" (click)="presentResetAlert()">
{{'settings.resetSettings' | translate}}
<ion-button
color="medium"
expand="block"
fill="outline"
(click)="presentResetAlert()"
>
{{ 'settings.resetSettings' | translate }}
<ion-icon slot="start" name="undo"></ion-icon>
</ion-button>
</ion-content>

View File

@@ -33,24 +33,16 @@ const settingsRoutes: Routes = [
* Settings Module
*/
@NgModule({
declarations: [
SettingsPageComponent,
SettingsItemComponent,
],
exports: [
SettingsItemComponent,
],
declarations: [SettingsPageComponent, SettingsItemComponent],
exports: [SettingsItemComponent],
imports: [
CommonModule,
FormsModule,
IonicModule.forRoot(),
TranslateModule.forChild(),
ThingTranslateModule.forChild(),
RouterModule.forChild(settingsRoutes),
ThingTranslateModule.forChild(),
TranslateModule.forChild(),
],
providers: [
ConfigProvider,
SettingsProvider,
],
providers: [ConfigProvider, SettingsProvider],
})
export class SettingsModule {}

View File

@@ -13,10 +13,19 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {TestBed} from '@angular/core/testing';
import {SCSetting, SCThingOriginType, SCThingType, SCSettingInputType} from '@openstapps/core';
import {
SCSetting,
SCThingOriginType,
SCThingType,
SCSettingInputType,
} from '@openstapps/core';
import {ConfigProvider} from '../config/config.provider';
import {StorageProvider} from '../storage/storage.provider';
import {SettingsProvider, SettingValuesContainer, STORAGE_KEY_SETTING_VALUES} from './settings.provider';
import {
SettingsProvider,
SettingValuesContainer,
STORAGE_KEY_SETTING_VALUES,
} from './settings.provider';
describe('SettingsProvider', () => {
let configProviderSpy: jasmine.SpyObj<ConfigProvider>;
@@ -24,50 +33,73 @@ describe('SettingsProvider', () => {
let storageProviderSpy: jasmine.SpyObj<StorageProvider>;
beforeEach(async () => {
const storageProviderMethodSpy = jasmine.createSpyObj('StorageProvider', ['init', 'get', 'has', 'put']);
const configProviderMethodSpy = jasmine.createSpyObj('ConfigProvider', ['getValue']);
const storageProviderMethodSpy = jasmine.createSpyObj('StorageProvider', [
'init',
'get',
'has',
'put',
]);
const configProviderMethodSpy = jasmine.createSpyObj('ConfigProvider', [
'getValue',
]);
TestBed.configureTestingModule({
imports: [],
providers: [
SettingsProvider,
{
provide: StorageProvider, useValue: storageProviderMethodSpy,
provide: StorageProvider,
useValue: storageProviderMethodSpy,
},
{
provide: ConfigProvider, useValue: configProviderMethodSpy,
provide: ConfigProvider,
useValue: configProviderMethodSpy,
},
],
});
configProviderSpy = TestBed.get(ConfigProvider);
// set settings returned from config
configProviderSpy.getValue.and.returnValue(Promise.resolve(CONFIG_SETTINGS_MOCK));
configProviderSpy.getValue.and.returnValue(
Promise.resolve(CONFIG_SETTINGS_MOCK),
);
settingsProvider = TestBed.get(SettingsProvider);
storageProviderSpy = TestBed.get(StorageProvider);
storageProviderMethodSpy.has.and.returnValue(false);
});
it('should provide and get setting', async () => {
await settingsProvider.provideSetting(JSON.parse(JSON.stringify(CONFIG_SETTINGS_MOCK[0])));
const setting: SCSetting = await settingsProvider
.getSetting(CONFIG_SETTINGS_MOCK[0].categories[0], CONFIG_SETTINGS_MOCK[0].name);
await settingsProvider.provideSetting(
JSON.parse(JSON.stringify(CONFIG_SETTINGS_MOCK[0])),
);
const setting: SCSetting = await settingsProvider.getSetting(
CONFIG_SETTINGS_MOCK[0].categories[0],
CONFIG_SETTINGS_MOCK[0].name,
);
await expect(setting.value).toBeDefined();
});
it('should provide and get settings value', async () => {
await settingsProvider.provideSetting(JSON.parse(JSON.stringify(CONFIG_SETTINGS_MOCK[0])));
const value = await settingsProvider
.getValue(CONFIG_SETTINGS_MOCK[0].categories[0], CONFIG_SETTINGS_MOCK[0].name);
await settingsProvider.provideSetting(
JSON.parse(JSON.stringify(CONFIG_SETTINGS_MOCK[0])),
);
const value = await settingsProvider.getValue(
CONFIG_SETTINGS_MOCK[0].categories[0],
CONFIG_SETTINGS_MOCK[0].name,
);
await expect(value).toEqual(CONFIG_SETTINGS_MOCK[0].defaultValue);
});
it('should get persisted setting value', async () => {
// set return values of storage
storageProviderSpy.has.and.returnValue(Promise.resolve(true));
storageProviderSpy.get.and.returnValue(Promise.resolve(SETTING_VALUES_MOCK));
storageProviderSpy.get.and.returnValue(
Promise.resolve(SETTING_VALUES_MOCK),
);
const value = await settingsProvider
.getValue(CONFIG_SETTINGS_MOCK[3].categories[0], CONFIG_SETTINGS_MOCK[3].name);
const value = await settingsProvider.getValue(
CONFIG_SETTINGS_MOCK[3].categories[0],
CONFIG_SETTINGS_MOCK[3].name,
);
await expect(value).toEqual(SETTING_VALUES_MOCK.profile.group);
});
@@ -75,74 +107,100 @@ describe('SettingsProvider', () => {
// set return values of spy objects
storageProviderSpy.has.and.returnValue(Promise.resolve(true));
storageProviderSpy.get.and.returnValue(Promise.resolve([]));
const value = await settingsProvider
.getValue(CONFIG_SETTINGS_MOCK[3].categories[0], CONFIG_SETTINGS_MOCK[3].name);
const value = await settingsProvider.getValue(
CONFIG_SETTINGS_MOCK[3].categories[0],
CONFIG_SETTINGS_MOCK[3].name,
);
await expect(value).toEqual(CONFIG_SETTINGS_MOCK[3].defaultValue);
});
it('should keep persisted setting values from settings that are not contained in loaded config', async () => {
const settings = [
CONFIG_SETTINGS_MOCK[4],
CONFIG_SETTINGS_MOCK[5],
];
const settings = [CONFIG_SETTINGS_MOCK[4], CONFIG_SETTINGS_MOCK[5]];
configProviderSpy.getValue.and.returnValue(Promise.resolve(settings));
storageProviderSpy.has.and.returnValue(Promise.resolve(true));
storageProviderSpy.get.and.returnValue(Promise.resolve(SETTING_VALUES_MOCK));
storageProviderSpy.get.and.returnValue(
Promise.resolve(SETTING_VALUES_MOCK),
);
await settingsProvider.init();
await expect(storageProviderSpy.put).toHaveBeenCalledWith(STORAGE_KEY_SETTING_VALUES, SETTING_VALUES_MOCK);
await expect(storageProviderSpy.put).toHaveBeenCalledWith(
STORAGE_KEY_SETTING_VALUES,
SETTING_VALUES_MOCK,
);
});
it('should set value of a provided setting', async () => {
await settingsProvider.provideSetting(JSON.parse(JSON.stringify(CONFIG_SETTINGS_MOCK[1])));
await settingsProvider
.setSettingValue(CONFIG_SETTINGS_MOCK[1].categories[0], CONFIG_SETTINGS_MOCK[1].name, 'updated');
const value = await settingsProvider
.getValue(CONFIG_SETTINGS_MOCK[1].categories[0], CONFIG_SETTINGS_MOCK[1].name);
await settingsProvider.provideSetting(
JSON.parse(JSON.stringify(CONFIG_SETTINGS_MOCK[1])),
);
await settingsProvider.setSettingValue(
CONFIG_SETTINGS_MOCK[1].categories[0],
CONFIG_SETTINGS_MOCK[1].name,
'updated',
);
const value = await settingsProvider.getValue(
CONFIG_SETTINGS_MOCK[1].categories[0],
CONFIG_SETTINGS_MOCK[1].name,
);
await expect(value).toEqual('updated');
});
it('should return copy of settingsCache', async () => {
const category = CONFIG_SETTINGS_MOCK[0].categories[0];
const name = CONFIG_SETTINGS_MOCK[0].name;
await settingsProvider.provideSetting(JSON.parse(JSON.stringify(CONFIG_SETTINGS_MOCK[0])));
await settingsProvider.provideSetting(
JSON.parse(JSON.stringify(CONFIG_SETTINGS_MOCK[0])),
);
const settings = await settingsProvider.getCache();
settings[category].settings[name].value = 'testValue';
// cached setting value should still be defaultValue
await expect((await settingsProvider.getValue(category, name)))
.toEqual(CONFIG_SETTINGS_MOCK[0].defaultValue);
await expect(await settingsProvider.getValue(category, name)).toEqual(
CONFIG_SETTINGS_MOCK[0].defaultValue,
);
});
it('should call storage put on setSettingValue', async () => {
await settingsProvider.provideSetting(JSON.parse(JSON.stringify(CONFIG_SETTINGS_MOCK[0])));
await settingsProvider
.setSettingValue(CONFIG_SETTINGS_MOCK[0].categories[0], CONFIG_SETTINGS_MOCK[0].name, '');
await settingsProvider.provideSetting(
JSON.parse(JSON.stringify(CONFIG_SETTINGS_MOCK[0])),
);
await settingsProvider.setSettingValue(
CONFIG_SETTINGS_MOCK[0].categories[0],
CONFIG_SETTINGS_MOCK[0].name,
'',
);
await expect(storageProviderSpy.put).toHaveBeenCalled();
});
it('should clear settings', async () => {
await settingsProvider.reset();
await expect(storageProviderSpy.put).toHaveBeenCalledWith(STORAGE_KEY_SETTING_VALUES, {});
await expect(storageProviderSpy.put).toHaveBeenCalledWith(
STORAGE_KEY_SETTING_VALUES,
{},
);
});
it('should reset settings', async () => {
const category = CONFIG_SETTINGS_MOCK[0].categories[0];
const name = CONFIG_SETTINGS_MOCK[0].name;
await settingsProvider.provideSetting(JSON.parse(JSON.stringify(CONFIG_SETTINGS_MOCK[0])));
await settingsProvider.provideSetting(
JSON.parse(JSON.stringify(CONFIG_SETTINGS_MOCK[0])),
);
await settingsProvider.setSettingValue(category, name, 'guest');
await settingsProvider.resetDefault();
const value = await settingsProvider
.getValue(CONFIG_SETTINGS_MOCK[0].categories[0], CONFIG_SETTINGS_MOCK[0].name);
const value = await settingsProvider.getValue(
CONFIG_SETTINGS_MOCK[0].categories[0],
CONFIG_SETTINGS_MOCK[0].name,
);
await expect(value).toEqual(CONFIG_SETTINGS_MOCK[0].defaultValue);
});
it('should validate wrong values for inputType text', async () => {
await testValue(CONFIG_SETTINGS_MOCK[0], 123456789);
await testValue(CONFIG_SETTINGS_MOCK[0], 123_456_789);
await testValue(CONFIG_SETTINGS_MOCK[0], false);
await testValue(CONFIG_SETTINGS_MOCK[0], []);
});
it('should validate wrong values for inputType password', async () => {
await testValue(CONFIG_SETTINGS_MOCK[0], 123456789);
await testValue(CONFIG_SETTINGS_MOCK[0], 123_456_789);
await testValue(CONFIG_SETTINGS_MOCK[0], false);
await testValue(CONFIG_SETTINGS_MOCK[0], []);
});
@@ -155,34 +213,43 @@ describe('SettingsProvider', () => {
it('should validate wrong values for inputType singleChoice text', async () => {
await testValue(CONFIG_SETTINGS_MOCK[3], '');
await testValue(CONFIG_SETTINGS_MOCK[3], 123456);
await testValue(CONFIG_SETTINGS_MOCK[3], 123_456);
await testValue(CONFIG_SETTINGS_MOCK[3], false);
await testValue(CONFIG_SETTINGS_MOCK[3], []);
});
it('should validate wrong values for inputType singleChoice boolean', async () => {
await testValue(CONFIG_SETTINGS_MOCK[5], '');
await testValue(CONFIG_SETTINGS_MOCK[5], 123456);
await testValue(CONFIG_SETTINGS_MOCK[5], 123_456);
await testValue(CONFIG_SETTINGS_MOCK[5], []);
});
it('should validate wrong values for inputType multipleChoice', async () => {
await testValue(CONFIG_SETTINGS_MOCK[6], '');
await testValue(CONFIG_SETTINGS_MOCK[6], 123456);
await testValue(CONFIG_SETTINGS_MOCK[6], 123_456);
await testValue(CONFIG_SETTINGS_MOCK[6], false);
await testValue(CONFIG_SETTINGS_MOCK[6], [1, 9]);
});
async function testValue(setting: SCSetting, value: any) {
/**
* TODO
*/
async function testValue(setting: SCSetting, value: unknown) {
let error: Error;
await settingsProvider.provideSetting(JSON.parse(JSON.stringify(setting)));
try {
await settingsProvider.setSettingValue(setting.categories[0], setting.name, value);
} catch (err) {
error = err;
await settingsProvider.setSettingValue(
setting.categories[0],
setting.name,
value as never,
);
} catch (error_) {
error = error_;
}
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
await expect(error).toBeDefined();
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
await expect(error.message).toMatch(/is not valid/);
}
@@ -270,13 +337,15 @@ describe('SettingsProvider', () => {
},
translations: {
de: {
description: 'Mit welcher Benutzergruppe soll die App verwendet werden?'
+ ' Die Einstellung wird beispielsweise für die Vorauswahl der Preiskategorie der Mensa verwendet.',
description:
'Mit welcher Benutzergruppe soll die App verwendet werden?' +
' Die Einstellung wird beispielsweise für die Vorauswahl der Preiskategorie der Mensa verwendet.',
name: 'Gruppe',
},
en: {
description: 'The user group the app is going to be used.'
+ 'This settings for example is getting used for the predefined price category of mensa meals.',
description:
'The user group the app is going to be used.' +
'This settings for example is getting used for the predefined price category of mensa meals.',
name: 'Group',
},
},

View File

@@ -13,11 +13,7 @@
* this program. If not, see <https://www.gnu.org/licenses/>.
*/
import {Injectable} from '@angular/core';
import {
SCSetting,
SCSettingValue,
SCSettingValues,
} from '@openstapps/core';
import {SCSetting, SCSettingValue, SCSettingValues} from '@openstapps/core';
import deepMerge from 'deepmerge';
import {Subject} from 'rxjs';
import {ConfigProvider} from '../config/config.provider';
@@ -38,7 +34,7 @@ export interface CategoryWithSettings {
/**
* Settings that belong in this category
*/
settings: { [key: string]: SCSetting; };
settings: {[key: string]: SCSetting};
}
/**
@@ -99,14 +95,17 @@ export class SettingsProvider {
* Source of settings actions
*/
private settingsActionSource = new Subject<SettingsAction>();
/**
* Order of the setting categories
*/
categoriesOrder: string[];
/**
* Settings actions observable
*/
settingsActionChanged$ = this.settingsActionSource.asObservable();
/**
* Cache for the imported settings
*/
@@ -114,6 +113,7 @@ export class SettingsProvider {
/**
* Return true if all given values are valid to possible values in given settingInput
*
* @param possibleValues Possible values
* @param enteredValues Entered value
*/
@@ -121,7 +121,7 @@ export class SettingsProvider {
possibleValues: SCSettingValues | undefined,
enteredValues: SCSettingValues,
): boolean {
if ( typeof possibleValues === 'undefined' ) {
if (typeof possibleValues === 'undefined') {
return false;
}
@@ -136,6 +136,7 @@ export class SettingsProvider {
/**
* Returns true if given value is valid to possible values in given settingInput
*
* @param possibleValues Possible values
* @param enteredValue Entered value
*/
@@ -143,21 +144,27 @@ export class SettingsProvider {
possibleValues: SCSettingValues | undefined,
enteredValue: SCSettingValue,
): boolean {
if ( typeof possibleValues === 'undefined' ) {
if (typeof possibleValues === 'undefined') {
return false;
}
return possibleValues !== undefined
&& (Array.isArray(possibleValues)
&& possibleValues.includes(enteredValue));
return (
possibleValues !== undefined &&
Array.isArray(possibleValues) &&
possibleValues.includes(enteredValue)
);
}
/**
* Validates value for given settings inputType. Returns true if value is valid.
*
* @param setting setting to check value against
* @param value value to validate
*/
public static validateValue(setting: SCSetting, value: SCSettingValue | SCSettingValues): boolean {
public static validateValue(
setting: SCSetting,
value: SCSettingValue | SCSettingValues,
): boolean {
let isValueValid = false;
switch (setting.inputType) {
case 'number':
@@ -166,11 +173,9 @@ export class SettingsProvider {
}
break;
case 'multiple choice':
if (!Array.isArray(value)) {
isValueValid = false;
} else {
isValueValid = SettingsProvider.checkMultipleChoiceValue(setting.values, value);
}
isValueValid = !Array.isArray(value)
? false
: SettingsProvider.checkMultipleChoiceValue(setting.values, value);
break;
case 'password':
case 'text':
@@ -179,11 +184,9 @@ export class SettingsProvider {
}
break;
case 'single choice':
if (Array.isArray(value)) {
isValueValid = false;
} else {
isValueValid = SettingsProvider.checkSingleChoiceValue(setting.values, value);
}
isValueValid = Array.isArray(value)
? false
: SettingsProvider.checkSingleChoiceValue(setting.values, value);
break;
default:
}
@@ -196,14 +199,17 @@ export class SettingsProvider {
* @param storage TODO
* @param configProvider TODO
*/
constructor(private readonly storage: StorageProvider,
private readonly configProvider: ConfigProvider) {
constructor(
private readonly storage: StorageProvider,
private readonly configProvider: ConfigProvider,
) {
this.categoriesOrder = [];
this.settingsCache = {};
}
/**
* Add an Setting to the Cache if not exist and set undefined value to defaultValue
*
* @param setting Setting with categories, defaultValue, name, input type and valid values
*/
private addSetting(setting: SCSetting): void {
@@ -214,7 +220,8 @@ export class SettingsProvider {
if (setting.value === undefined) {
setting.value = setting.defaultValue;
}
this.settingsCache[setting.categories[0]].settings[setting.name] = setting;
this.settingsCache[setting.categories[0]].settings[setting.name] =
setting;
}
}
@@ -226,7 +233,9 @@ export class SettingsProvider {
// iterate through keys of categories
for (const categoryKey of Object.keys(this.settingsCache)) {
// iterate through keys of settingValueContainer
for (const settingKey of Object.keys(this.settingsCache[categoryKey].settings)) {
for (const settingKey of Object.keys(
this.settingsCache[categoryKey].settings,
)) {
if (typeof settingValuesContainer[categoryKey] === 'undefined') {
settingValuesContainer[categoryKey] = {};
}
@@ -240,6 +249,7 @@ export class SettingsProvider {
/**
* Add category if not exists
*
* @param category the category to provide
*/
private provideCategory(category: string): void {
@@ -256,6 +266,7 @@ export class SettingsProvider {
/**
* Returns true if category exists
*
* @param category Category key name
*/
public categoryExists(category: string): boolean {
@@ -280,32 +291,39 @@ export class SettingsProvider {
/**
* Returns copy of a setting if exist
*
* @param category the category of requested setting
* @param name the name of requested setting
*
* @throws Exception if setting is not provided
*/
public async getSetting(category: string, name: string): Promise<SCSetting> {
await this.init();
if (this.settingExists(category, name)) {
// return a copy of the settings
return JSON.parse(JSON.stringify(this.settingsCache[category].settings[name]));
return JSON.parse(
JSON.stringify(this.settingsCache[category].settings[name]),
);
}
throw new Error(`Setting "${name}" not provided`);
}
/**
* Returns copy of a settings value if exist
*
* @param category the category of requested setting
* @param name the name of requested setting
*
* @throws Exception if setting is not provided
*/
public async getValue(category: string, name: string): Promise<SCSettingValue | SCSettingValues> {
public async getValue(
category: string,
name: string,
): Promise<SCSettingValue | SCSettingValues> {
await this.init();
if (this.settingExists(category, name)) {
// return a copy of the settings value
return JSON.parse(JSON.stringify(this.settingsCache[category].settings[name].value));
return JSON.parse(
JSON.stringify(this.settingsCache[category].settings[name].value),
);
}
throw new Error(`Setting "${name}" not provided`);
}
@@ -313,31 +331,39 @@ export class SettingsProvider {
/**
* Initializes settings from config and stored values if exist
*/
public async init(): Promise<void> {
public async init(): Promise<void> {
try {
const settings: SCSetting[] = (await this.configProvider.getValue('settings')) as SCSetting[];
settings.forEach((setting) => this.addSetting(setting));
const settings: SCSetting[] = (await this.configProvider.getValue(
'settings',
)) as SCSetting[];
for (const setting of settings) this.addSetting(setting);
for (const category of Object.keys(this.settingsCache)) {
if (!this.categoriesOrder.includes(category)) {
this.categoriesOrder.push(category);
}
}
} catch (error) {
} catch {
this.settingsCache = {};
}
if (await this.storage.has(STORAGE_KEY_SETTING_VALUES)) {
// get setting values from StorageProvider into settingsCache
const valuesContainer: SettingValuesContainer =
await this.storage.get<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES);
await this.storage.get<SettingValuesContainer>(
STORAGE_KEY_SETTING_VALUES,
);
// iterate through keys of categories
for (const categoryKey of Object.keys(this.settingsCache)) {
// iterate through setting keys of category
for (const settingKey of Object.keys(this.settingsCache[categoryKey].settings)) {
for (const settingKey of Object.keys(
this.settingsCache[categoryKey].settings,
)) {
// if saved setting value exists set it, otherwise set to default value
if (typeof valuesContainer[categoryKey] !== 'undefined'
&& typeof valuesContainer[categoryKey][settingKey] !== 'undefined') {
if (
typeof valuesContainer[categoryKey] !== 'undefined' &&
typeof valuesContainer[categoryKey][settingKey] !== 'undefined'
) {
this.settingsCache[categoryKey].settings[settingKey].value =
valuesContainer[categoryKey][settingKey];
} else {
@@ -352,6 +378,7 @@ export class SettingsProvider {
/**
* Adds given setting and its category if not exist
*
* @param setting the setting to add
*/
public provideSetting(setting: SCSetting): void {
@@ -371,7 +398,9 @@ export class SettingsProvider {
*/
async resetDefault(): Promise<void> {
for (const catKey of Object.keys(this.settingsCache)) {
for (const settingKey of Object.keys(this.settingsCache[catKey].settings)) {
for (const settingKey of Object.keys(
this.settingsCache[catKey].settings,
)) {
const settingInput = this.settingsCache[catKey].settings[settingKey];
settingInput.value = settingInput.defaultValue;
}
@@ -385,18 +414,29 @@ export class SettingsProvider {
public async saveSettingValues(): Promise<void> {
if (await this.storage.has(STORAGE_KEY_SETTING_VALUES)) {
const savedSettingsValues: SettingValuesContainer =
await this.storage.get<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES);
await this.storage.get<SettingValuesContainer>(
STORAGE_KEY_SETTING_VALUES,
);
const cacheSettingsValues = this.getSettingValuesFromCache();
const mergedSettingValues = deepMerge(savedSettingsValues, cacheSettingsValues);
await this.storage
.put<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES, mergedSettingValues);
const mergedSettingValues = deepMerge(
savedSettingsValues,
cacheSettingsValues,
);
await this.storage.put<SettingValuesContainer>(
STORAGE_KEY_SETTING_VALUES,
mergedSettingValues,
);
} else {
await this.storage.put<SettingValuesContainer>(STORAGE_KEY_SETTING_VALUES, this.getSettingValuesFromCache());
await this.storage.put<SettingValuesContainer>(
STORAGE_KEY_SETTING_VALUES,
this.getSettingValuesFromCache(),
);
}
}
/**
* Sets the order the given categories show up in the settings page
*
* @param categoryNames the order of the categories
*/
public setCategoriesOrder(categoryNames: string[]) {
@@ -409,11 +449,13 @@ export class SettingsProvider {
* @param category Category key name
* @param name Setting key name
* @param value Value to be set
*
* @throws Exception if setting is not provided or value not valid to the settings inputType
*/
public async setSettingValue(category: string, name: string,
value: SCSettingValue | SCSettingValues): Promise<void> {
public async setSettingValue(
category: string,
name: string,
value: SCSettingValue | SCSettingValues,
): Promise<void> {
await this.init();
if (this.settingExists(category, name)) {
const setting: SCSetting = this.settingsCache[category].settings[name];
@@ -423,7 +465,10 @@ export class SettingsProvider {
this.settingsCache[category].settings[name].value = value;
await this.saveSettingValues();
// publish setting changes
this.settingsActionSource.next({type: 'stapps.settings.changed', payload: {category, name, value}});
this.settingsActionSource.next({
type: 'stapps.settings.changed',
payload: {category, name, value},
});
} else {
throw new Error(`Value "${value}" of type
${typeof value} is not valid for ${setting.inputType}`);
@@ -435,10 +480,14 @@ export class SettingsProvider {
/**
* Returns true if setting in category exists
*
* @param category Category key name
* @param setting Setting key name
*/
public settingExists(category: string, setting: string): boolean {
return this.categoryExists(category) && this.settingsCache[category].settings[setting] !== undefined;
return (
this.categoryExists(category) &&
this.settingsCache[category].settings[setting] !== undefined
);
}
}