refactor(data): adjust data detail component

This commit is contained in:
Jovan Krunić
2019-02-12 14:09:09 +01:00
parent 017fc67765
commit 8c3c2810e5
9 changed files with 134 additions and 50 deletions

View File

@@ -12,16 +12,15 @@
* You should have received a copy of the GNU General Public License along with * You should have received a copy of the GNU General Public License along with
* this program. If not, see <https://www.gnu.org/licenses/>. * this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
import {CommonModule, registerLocaleData} from '@angular/common';
import {HttpClient} from '@angular/common/http';
import localeDe from '@angular/common/locales/de';
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
import {BrowserModule} from '@angular/platform-browser'; import {BrowserModule} from '@angular/platform-browser';
import {RouteReuseStrategy} from '@angular/router'; import {RouteReuseStrategy} from '@angular/router';
import {SplashScreen} from '@ionic-native/splash-screen/ngx'; import {SplashScreen} from '@ionic-native/splash-screen/ngx';
import {StatusBar} from '@ionic-native/status-bar/ngx'; import {StatusBar} from '@ionic-native/status-bar/ngx';
import {IonicModule, IonicRouteStrategy} from '@ionic/angular'; import {IonicModule, IonicRouteStrategy} from '@ionic/angular';
import {CommonModule} from '@angular/common';
import {HttpClient} from '@angular/common/http';
import {TranslateLoader, TranslateModule} from '@ngx-translate/core'; import {TranslateLoader, TranslateModule} from '@ngx-translate/core';
import {TranslateHttpLoader} from '@ngx-translate/http-loader'; import {TranslateHttpLoader} from '@ngx-translate/http-loader';
import {AppRoutingModule} from './app-routing.module'; import {AppRoutingModule} from './app-routing.module';
@@ -32,6 +31,8 @@ import {MenuModule} from './modules/menu/menu.module';
import {SettingsModule} from './modules/settings/settings.module'; import {SettingsModule} from './modules/settings/settings.module';
import {StorageModule} from './modules/storage/storage.module'; import {StorageModule} from './modules/storage/storage.module';
registerLocaleData(localeDe);
export function createTranslateLoader(http: HttpClient) { export function createTranslateLoader(http: HttpClient) {
return new TranslateHttpLoader(http, './assets/i18n/', '.json'); return new TranslateHttpLoader(http, './assets/i18n/', '.json');
} }

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2018 StApps * Copyright (C) 2018, 2019 StApps
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free * under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3. * Software Foundation, version 3.
@@ -19,16 +19,15 @@ import {DataListComponent} from './list/data-list.component';
const dataRoutes: Routes = [ const dataRoutes: Routes = [
{path: 'search', component: DataListComponent}, {path: 'search', component: DataListComponent},
{path: 'data-detail/:uid', component: DataDetailComponent} {path: 'data-detail/:uid', component: DataDetailComponent},
]; ];
@NgModule({ @NgModule({
imports: [
RouterModule.forChild(dataRoutes)
],
// tslint:disable-next-line:object-literal-sort-keys
exports: [ exports: [
RouterModule RouterModule,
] ],
imports: [
RouterModule.forChild(dataRoutes),
],
}) })
export class DataRoutingModule {} export class DataRoutingModule {}

View File

@@ -17,6 +17,7 @@ import {HttpClientModule} from '@angular/common/http';
import {NgModule} from '@angular/core'; import {NgModule} from '@angular/core';
import {FormsModule} from '@angular/forms'; import {FormsModule} from '@angular/forms';
import {IonicModule} from '@ionic/angular'; import {IonicModule} from '@ionic/angular';
import {TranslateModule} from '@ngx-translate/core';
import {StorageModule} from '../storage/storage.module'; import {StorageModule} from '../storage/storage.module';
import {DataRoutingModule} from './data-routing.module'; import {DataRoutingModule} from './data-routing.module';
import {DataProvider} from './data.provider'; import {DataProvider} from './data.provider';
@@ -50,10 +51,12 @@ import {EventListItemComponent} from './types/event/event-list-item.component';
DataRoutingModule, DataRoutingModule,
HttpClientModule, HttpClientModule,
StorageModule, StorageModule,
TranslateModule.forChild(),
], ],
providers: [ providers: [
DataProvider, DataProvider,
StAppsWebHttpClient, StAppsWebHttpClient,
], ],
}) })
export class DataModule {} export class DataModule {
}

View File

@@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2018 StApps * Copyright (C) 2018, 2019 StApps
* This program is free software: you can redistribute it and/or modify it * This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free * under the terms of the GNU General Public License as published by the Free
* Software Foundation, version 3. * Software Foundation, version 3.
@@ -15,54 +15,98 @@
import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core'; import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
import {async, ComponentFixture, TestBed} from '@angular/core/testing'; import {async, ComponentFixture, TestBed} from '@angular/core/testing';
import {ActivatedRoute, RouterModule} from '@angular/router'; import {ActivatedRoute, RouterModule} from '@angular/router';
import {IonRefresher} from '@ionic/angular';
import {SCMessage, SCThingOriginType, SCThingType} from '@openstapps/core';
import {DataRoutingModule} from '../data-routing.module'; import {DataRoutingModule} from '../data-routing.module';
import {DataListComponent} from '../list/data-list.component'; import {DataModule} from '../data.module';
import {DataProvider} from '../data.provider';
import {DataDetailComponent} from './data-detail.component'; import {DataDetailComponent} from './data-detail.component';
import {TranslateModule, TranslateLoader, TranslateFakeLoader} from '@ngx-translate/core';
describe('DataDetailComponent', () => { describe('DataDetailComponent', () => {
let comp: DataDetailComponent; let comp: DataDetailComponent;
let fixture: ComponentFixture<DataDetailComponent>; let fixture: ComponentFixture<DataDetailComponent>;
let detailPage: HTMLElement; let detailPage: HTMLElement;
let dataProvider: DataProvider;
let refresher: IonRefresher;
// @Component({ selector: 'stapps-data-list-item', template: '' }) // @Component({ selector: 'stapps-data-list-item', template: '' })
// class DataListItemComponent { // class DataListItemComponent {
// @Input() item; // @Input() item;
// } // }
const sampleThing: SCMessage = {
audiences: ['students'],
message: 'Foo Message',
name: 'foo',
origin: {
indexed: 'SOME-DATE',
name: 'some name',
type: SCThingOriginType.Remote,
},
type: SCThingType.Message,
uid: '123',
};
const fakeActivatedRoute = { const fakeActivatedRoute = {
snapshot: { snapshot: {
paramMap: { paramMap: {
get: (url) => { get: () => {
return url; return sampleThing.uid;
} },
} },
} },
} as ActivatedRoute; };
beforeEach(async(() => { beforeEach(async(() => {
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [DataListComponent, DataDetailComponent], imports: [RouterModule.forRoot([]), DataRoutingModule, DataModule,
imports: [RouterModule, DataRoutingModule], TranslateModule.forRoot({
loader: {provide: TranslateLoader, useClass: TranslateFakeLoader},
})],
providers: [{provide: ActivatedRoute, useValue: fakeActivatedRoute}], providers: [{provide: ActivatedRoute, useValue: fakeActivatedRoute}],
schemas: [CUSTOM_ELEMENTS_SCHEMA], schemas: [CUSTOM_ELEMENTS_SCHEMA],
}) })
.compileComponents(); .compileComponents();
})); }));
beforeEach(async() => { beforeEach(async () => {
dataProvider = TestBed.get(DataProvider);
refresher = jasmine.createSpyObj('refresher', ['complete']);
spyOn(dataProvider, 'get').and.returnValue(Promise.resolve(sampleThing));
spyOn(DataDetailComponent.prototype, 'getItem').and.callThrough();
fixture = await TestBed.createComponent(DataDetailComponent); fixture = await TestBed.createComponent(DataDetailComponent);
comp = fixture.componentInstance; comp = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
detailPage = fixture.nativeElement;
await dataProvider.deleteAll();
}); });
it('should create component', () => it('should create component', () =>
expect(comp).toBeDefined(), expect(comp).toBeDefined(),
); );
it('should have apropriate title', () => { it('should have apropriate title', async () => {
detailPage = fixture.nativeElement; await fixture.whenStable();
const title: HTMLIonTitleElement | null = detailPage.querySelector('ion-title'); const title: HTMLIonTitleElement | null = detailPage.querySelector('ion-title');
expect(title).not.toBe(null); expect(title).not.toBe(null);
expect(title!.innerText).toContain('Detailansicht'); expect(title!.innerText).toContain('data.detail.TITLE');
});
it('should get a data item', () => {
comp.getItem(sampleThing.uid);
expect(DataDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid);
});
it('should get a data item when component is accessed', async () => {
await fixture.whenStable();
expect(DataDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid);
});
it('should update the data item when refresh is called', async () => {
await fixture.whenStable();
await comp.refresh(refresher);
expect(DataDetailComponent.prototype.getItem).toHaveBeenCalledWith(sampleThing.uid);
expect(refresher.complete).toHaveBeenCalled();
}); });
}); });

View File

@@ -14,7 +14,8 @@
*/ */
import {Component} from '@angular/core'; import {Component} from '@angular/core';
import {ActivatedRoute} from '@angular/router'; import {ActivatedRoute} from '@angular/router';
import {SCThing} from '@openstapps/core'; import {IonRefresher} from '@ionic/angular';
import {SCThing, SCUuid} from '@openstapps/core';
import {DataProvider, DataScope} from '../data.provider'; import {DataProvider, DataScope} from '../data.provider';
@Component({ @Component({
@@ -27,8 +28,26 @@ export class DataDetailComponent {
constructor(private route: ActivatedRoute, dataProvider: DataProvider) { constructor(private route: ActivatedRoute, dataProvider: DataProvider) {
this.dataProvider = dataProvider; this.dataProvider = dataProvider;
} }
/**
* Provides data item with given UID
*
* @param uid Unique identifier of a thing
*/
async getItem(uid: SCUuid): Promise<SCThing> {
return (await this.dataProvider.get(uid, DataScope.Remote));
}
async ngOnInit() { async ngOnInit() {
const uid = this.route.snapshot.paramMap.get('uid') || ''; this.item = await this.getItem(this.route.snapshot.paramMap.get('uid') || '');
this.item = (await this.dataProvider.get(uid, DataScope.Remote)); }
/**
* Updates the shown thing
*
* @param refresher Refresher component the triggers the update
*/
async refresh(refresher: IonRefresher) {
this.item = await this.getItem(this.item.uid);
refresher.complete();
} }
} }

View File

@@ -4,11 +4,15 @@
<ion-back-button></ion-back-button> <ion-back-button></ion-back-button>
<ion-menu-button></ion-menu-button> <ion-menu-button></ion-menu-button>
</ion-buttons> </ion-buttons>
<ion-title>Detailansicht</ion-title> <ion-title>{{'data.detail.TITLE' | translate}}</ion-title>
</ion-toolbar> </ion-toolbar>
</ion-header> </ion-header>
<ion-content padding *ngIf="item"> <ion-content padding *ngIf="item">
<ion-refresher slot="fixed" (ionRefresh)="refresh($event.target)">
<ion-refresher-content pullingIcon="arrow-dropdown" pullingText="{{'data.REFRESH_ACTION' | translate}}"
refreshingText="{{'data.REFRESHING' | translate}}">
</ion-refresher-content>
</ion-refresher>
<stapps-data-list-item [item]="item"></stapps-data-list-item> <stapps-data-list-item [item]="item"></stapps-data-list-item>
<div [ngSwitch]="item.type"> <div [ngSwitch]="item.type">

View File

@@ -29,15 +29,15 @@
<ion-grid *ngFor="let offer of item.offers"> <ion-grid *ngFor="let offer of item.offers">
<ion-row> <ion-row>
<ion-col>Student</ion-col> <ion-col>Student</ion-col>
<ion-col>{{offer.prices.student}}</ion-col> <ion-col>{{offer.prices.student | currency:'EUR':'symbol':undefined:'de'}}</ion-col>
</ion-row> </ion-row>
<ion-row> <ion-row>
<ion-col>Mitarbeiter</ion-col> <ion-col>Mitarbeiter</ion-col>
<ion-col>{{offer.prices.employee}}</ion-col> <ion-col>{{offer.prices.employee | currency:'EUR':'symbol':undefined:'de'}}</ion-col>
</ion-row> </ion-row>
<ion-row> <ion-row>
<ion-col>Standard / Gäste</ion-col> <ion-col>Standard / Gäste</ion-col>
<ion-col>{{offer.prices.guest}}</ion-col> <ion-col>{{offer.prices.guest | currency:'EUR':'symbol':undefined:'de'}}</ion-col>
</ion-row> </ion-row>
</ion-grid> </ion-grid>
</ion-card-content> </ion-card-content>

View File

@@ -1,4 +1,10 @@
{ {
"data": {
"REFRESH_ACTION": "Aktualisieren",
"REFRESHING": "Aktualisierung läuft...",
"detail": {
"TITLE": "Detailansicht"
},
"types": { "types": {
"dish": { "dish": {
"detailPage": { "detailPage": {
@@ -7,6 +13,7 @@
"CHARACTERISTICS_TITLE": "Characteristics" "CHARACTERISTICS_TITLE": "Characteristics"
} }
} }
}
}, },
"settings": { "settings": {
"title": "Einstellungen", "title": "Einstellungen",

View File

@@ -1,4 +1,10 @@
{ {
"data": {
"REFRESH_ACTION": "Refresh",
"REFRESHING": "Refreshing...",
"detail": {
"TITLE": "Details"
},
"types": { "types": {
"dish": { "dish": {
"detailPage": { "detailPage": {
@@ -7,6 +13,7 @@
"CHARACTERISTICS_TITLE": "Characteristics" "CHARACTERISTICS_TITLE": "Characteristics"
} }
} }
}
}, },
"settings": { "settings": {
"title": "Settings", "title": "Settings",