From 7de528923640ff09d41139db79e3e3ad118e9857 Mon Sep 17 00:00:00 2001
From: Konstantin Schulz <schulzkx@hu-berlin.de>
Date: Tue, 17 Nov 2020 13:56:51 +0100
Subject: [PATCH] the pages for exercise list and authors may now be accessed
 using deep links

---
 README.md                                     |  5 ++-
 mc_frontend/src/app/app.component.spec.ts     |  7 +++-
 mc_frontend/src/app/app.component.ts          |  6 ++-
 .../src/app/author/author.page.spec.ts        | 39 +++++++++++++++----
 mc_frontend/src/app/author/author.page.ts     | 27 +++++++++----
 mc_frontend/src/app/corpus.service.spec.ts    | 19 +--------
 mc_frontend/src/app/corpus.service.ts         | 21 ++--------
 .../exercise-list/exercise-list.page.spec.ts  |  3 ++
 .../app/exercise-list/exercise-list.page.ts   | 10 +++--
 mc_frontend/src/app/helper.service.spec.ts    | 17 ++++++++
 mc_frontend/src/app/helper.service.ts         | 18 +++++++++
 11 files changed, 111 insertions(+), 61 deletions(-)

diff --git a/README.md b/README.md
index 5545c23..fa93595 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,10 @@ To generate class structures for this project automatically:
 
 ## Documentation
 ### API
-To view the API documentation, visit https://korpling.org/mc-service/mc/api/v1.0/ui/ .
+To view the official API documentation, visit https://korpling.org/mc-service/mc/api/v1.0/ui/ .
+
+If you make local changes to the source code, your own API documentation will be published at http://localhost:5000/mc/api/v1.0/ui/ . 
+The port (5000) and API path (/mc/api/v1.0/) may change depending on your configuration.
 ### Changelog
 To update the changelog, use: `git log --oneline --decorate > CHANGELOG`
 
diff --git a/mc_frontend/src/app/app.component.spec.ts b/mc_frontend/src/app/app.component.spec.ts
index 9714dcf..d8679f5 100644
--- a/mc_frontend/src/app/app.component.spec.ts
+++ b/mc_frontend/src/app/app.component.spec.ts
@@ -16,7 +16,7 @@ import {
     TranslateTestingModule
 } from './translate-testing/translate-testing.module';
 import {APP_BASE_HREF} from '@angular/common';
-import {Subscription} from 'rxjs';
+import {ReplaySubject, Subscription} from 'rxjs';
 import {HelperService} from './helper.service';
 import {CorpusService} from './corpus.service';
 import Spy = jasmine.Spy;
@@ -63,7 +63,10 @@ describe('AppComponent', () => {
                 {provide: Platform, useClass: PlatformStub},
                 {provide: APP_BASE_HREF, useValue: '/'},
                 {provide: MenuController},
-                {provide: CorpusService, useValue: {initCorpusService: () => Promise.resolve()}},
+                {
+                    provide: CorpusService,
+                    useValue: {initCorpusService: () => Promise.resolve(), isInitialized: new ReplaySubject<boolean>(1)}
+                },
                 {
                     provide: HelperService,
                     useValue: {makeGetRequest: () => Promise.resolve(MockMC.apiResponseCorporaGet)}
diff --git a/mc_frontend/src/app/app.component.ts b/mc_frontend/src/app/app.component.ts
index 8a667b4..d9e2512 100644
--- a/mc_frontend/src/app/app.component.ts
+++ b/mc_frontend/src/app/app.component.ts
@@ -24,8 +24,10 @@ export class AppComponent {
                 public menuCtrl: MenuController,
                 public corpusService: CorpusService,
     ) {
-        platform.ready().then(() => {
-            this.corpusService.initCorpusService().then();
+        platform.ready().then(async () => {
+            this.corpusService.initCorpusService().then(() => {
+                this.corpusService.isInitialized.next(true);
+            });
             // Okay, so the platform is ready and our plugins are available.
             // Here you can do any higher level native things you might need.
             this.statusBar.styleDefault();
diff --git a/mc_frontend/src/app/author/author.page.spec.ts b/mc_frontend/src/app/author/author.page.spec.ts
index 9e50c20..43d4dd7 100644
--- a/mc_frontend/src/app/author/author.page.spec.ts
+++ b/mc_frontend/src/app/author/author.page.spec.ts
@@ -11,6 +11,7 @@ import {APP_BASE_HREF} from '@angular/common';
 import {Author} from '../models/author';
 import Spy = jasmine.Spy;
 import MockMC from '../models/mockMC';
+import {CorpusService} from '../corpus.service';
 
 describe('AuthorPage', () => {
     let authorPage: AuthorPage;
@@ -30,10 +31,19 @@ describe('AuthorPage', () => {
             ],
             providers: [
                 {provide: APP_BASE_HREF, useValue: '/'},
+                {
+                    provide: CorpusService,
+                    useValue: {
+                        availableAuthors: [],
+                        getCorpora: () => Promise.resolve(),
+                        isTreebank: () => true,
+                        restoreLastCorpus: () => Promise.resolve()
+                    }
+                }
             ],
             schemas: [CUSTOM_ELEMENTS_SCHEMA],
         })
-            .compileComponents();
+            .compileComponents().then();
     }));
 
     beforeEach(() => {
@@ -63,13 +73,19 @@ describe('AuthorPage', () => {
         expect(authorPage.authorsDisplayed.length).toBe(1);
     });
 
-    it('should be initialized', () => {
-        authorPage.corpusService.availableAuthors = [new Author({
-            corpora: [{source_urn: 'proiel'}],
-            name: 'name'
-        })];
-        authorPage.ngOnInit();
-        expect(authorPage.baseAuthorList.length).toBe(1);
+    it('should be initialized', (done) => {
+        const storageSpy: Spy = spyOn(authorPage.storage, 'get').and.returnValue(Promise.resolve(null));
+        authorPage.ngOnInit().then(async () => {
+            expect(storageSpy).toHaveBeenCalledTimes(1);
+            authorPage.corpusService.availableAuthors = [new Author({
+                corpora: [{source_urn: 'proiel'}],
+                name: 'name'
+            })];
+            await authorPage.ngOnInit();
+            expect(storageSpy).toHaveBeenCalledTimes(1);
+            expect(authorPage.baseAuthorList.length).toBe(1);
+            done();
+        });
     });
 
     it('should restore the last setup', (done) => {
@@ -100,4 +116,11 @@ describe('AuthorPage', () => {
         authorPage.showCorpora(author);
         expect(authorPage.corpusService.currentAuthor).toBe(author);
     });
+
+    it('should toggle treebank authors', () => {
+        authorPage.showOnlyTreebanks = false;
+        authorPage.corpusService.availableAuthors = [{corpora: [], name: ''}];
+        authorPage.toggleTreebankAuthors();
+        expect(authorPage.baseAuthorList.length).toBe(1);
+    });
 });
diff --git a/mc_frontend/src/app/author/author.page.ts b/mc_frontend/src/app/author/author.page.ts
index 46def0c..9e94ece 100644
--- a/mc_frontend/src/app/author/author.page.ts
+++ b/mc_frontend/src/app/author/author.page.ts
@@ -9,6 +9,8 @@ import {ExerciseService} from '../exercise.service';
 import {ApplicationState} from '../models/applicationState';
 import {take} from 'rxjs/operators';
 import configMC from '../../configMC';
+import {UpdateInfo} from '../models/updateInfo';
+import {Storage} from '@ionic/storage';
 
 /**
  * Generated class for the AuthorPage page.
@@ -28,7 +30,8 @@ export class AuthorPage implements OnInit {
                 public corpusService: CorpusService,
                 public http: HttpClient,
                 public exerciseService: ExerciseService,
-                public helperService: HelperService) {
+                public helperService: HelperService,
+                public storage: Storage) {
     }
 
     public authorsDisplayed: Author[];
@@ -55,14 +58,22 @@ export class AuthorPage implements OnInit {
         return this.corpusService.availableAuthors.filter(author => author.corpora.some(corpus => this.corpusService.isTreebank(corpus)));
     }
 
-    ngOnInit(): void {
-        if (!this.corpusService.availableAuthors.length) {
-            this.corpusService.loadCorporaFromLocalStorage().then(() => {
+    ngOnInit(): Promise<void> {
+        return new Promise<void>(resolve => {
+            if (!this.corpusService.availableAuthors.length) {
+                this.storage.get(configMC.localStorageKeyUpdateInfo).then((jsonString: string) => {
+                    // check local storage for necessary updates
+                    const updateInfo: UpdateInfo = JSON.parse(jsonString) as UpdateInfo;
+                    this.corpusService.getCorpora(updateInfo ? updateInfo.corpora : 0).then(() => {
+                        this.toggleTreebankAuthors();
+                        return resolve();
+                    });
+                });
+            } else {
                 this.toggleTreebankAuthors();
-            });
-        } else {
-            this.toggleTreebankAuthors();
-        }
+                return resolve();
+            }
+        });
     }
 
     restoreLastSetup(): Promise<void> {
diff --git a/mc_frontend/src/app/corpus.service.spec.ts b/mc_frontend/src/app/corpus.service.spec.ts
index 538e546..9b7597d 100644
--- a/mc_frontend/src/app/corpus.service.spec.ts
+++ b/mc_frontend/src/app/corpus.service.spec.ts
@@ -238,7 +238,7 @@ describe('CorpusService', () => {
     it('should initialize the corpus service', (done) => {
         const annisRespSpy: Spy = spyOn(corpusService, 'checkAnnisResponse').and.callFake(() => Promise.reject());
         const restoreSpy: Spy = spyOn(corpusService, 'restoreLastCorpus').and.returnValue(Promise.resolve());
-        const updateInfoSpy: Spy = spyOn(corpusService, 'initUpdateInfo').and.callFake(() => Promise.reject());
+        const updateInfoSpy: Spy = spyOn(corpusService.helperService, 'initUpdateInfo').and.callFake(() => Promise.reject());
         helperService.applicationState.next(helperService.deepCopy(MockMC.applicationState) as ApplicationState);
         corpusService.initCorpusService().then(() => {
         }, () => {
@@ -286,23 +286,6 @@ describe('CorpusService', () => {
         expect(corpus).toBeTruthy();
     }));
 
-    it('should initialize the update information', (done) => {
-        const updateInfoSpy: Spy = spyOn(corpusService.storage, 'get').withArgs(configMC.localStorageKeyUpdateInfo);
-        updateInfoSpy.and.returnValue(Promise.resolve(''));
-        corpusService.initUpdateInfo().then(() => {
-            updateInfoSpy.and.callThrough();
-            corpusService.storage.get(configMC.localStorageKeyUpdateInfo).then((jsonString: string) => {
-                const updateInfo: UpdateInfo = JSON.parse(jsonString) as UpdateInfo;
-                expect(updateInfo.corpora).toBe(1);
-                const setSpy: Spy = spyOn(corpusService.storage, 'set').and.returnValue(Promise.resolve());
-                corpusService.initUpdateInfo().then(() => {
-                    expect(setSpy).toHaveBeenCalledTimes(0);
-                    done();
-                });
-            });
-        });
-    });
-
     it('should load corpora from local storage', (done) => {
         corpusService.availableCorpora = [];
         spyOn(corpusService.storage, 'get').withArgs(configMC.localStorageKeyCorpora).and.returnValue(
diff --git a/mc_frontend/src/app/corpus.service.ts b/mc_frontend/src/app/corpus.service.ts
index 124f4d3..633eee6 100644
--- a/mc_frontend/src/app/corpus.service.ts
+++ b/mc_frontend/src/app/corpus.service.ts
@@ -66,6 +66,7 @@ export class CorpusService {
     public invalidQueryCorpusString: string;
     public invalidSentenceCountString: string;
     public invalidTextRangeString: string;
+    public isInitialized: ReplaySubject<boolean>;
     public isTextRangeCorrect = false;
     public phenomenonMap: PhenomenonMap = new PhenomenonMap({
         dependency: new PhenomenonMapContent({translationObject: DependencyTranslation}),
@@ -305,9 +306,10 @@ export class CorpusService {
 
     initCorpusService(): Promise<void> {
         return new Promise<void>((resolve, reject) => {
+            this.isInitialized = new ReplaySubject<boolean>(1);
             this.initCurrentCorpus().then();
             this.initCurrentTextRange();
-            this.initUpdateInfo().then(() => {
+            this.helperService.initUpdateInfo().then(() => {
                 this.checkForUpdates().finally(() => {
                     this.checkAnnisResponse().then(() => {
                         this.restoreLastCorpus().then(() => {
@@ -370,23 +372,6 @@ export class CorpusService {
         });
     }
 
-    initUpdateInfo(): Promise<void> {
-        return new Promise<void>(resolve => {
-            this.storage.get(configMC.localStorageKeyUpdateInfo).then((jsonString: string) => {
-                if (jsonString) {
-                    return resolve();
-                }
-                const ui: UpdateInfo = new UpdateInfo({
-                    corpora: 1,
-                    exerciseList: 1
-                });
-                this.storage.set(configMC.localStorageKeyUpdateInfo, JSON.stringify(ui)).then(() => {
-                    return resolve();
-                });
-            });
-        });
-    }
-
     isTreebank(corpus: CorpusMC): boolean {
         return corpus.source_urn.includes('proiel');
     }
diff --git a/mc_frontend/src/app/exercise-list/exercise-list.page.spec.ts b/mc_frontend/src/app/exercise-list/exercise-list.page.spec.ts
index 9ab66da..b4526d7 100644
--- a/mc_frontend/src/app/exercise-list/exercise-list.page.spec.ts
+++ b/mc_frontend/src/app/exercise-list/exercise-list.page.spec.ts
@@ -17,6 +17,7 @@ import Spy = jasmine.Spy;
 import configMC from '../../configMC';
 import {UpdateInfo} from '../models/updateInfo';
 import {VocabularyMC} from '../../../openapi';
+import {ReplaySubject} from 'rxjs';
 
 describe('ExerciseListPage', () => {
     let exerciseListPage: ExerciseListPage;
@@ -46,6 +47,8 @@ describe('ExerciseListPage', () => {
         fixture = TestBed.createComponent(ExerciseListPage);
         exerciseListPage = fixture.componentInstance;
         getExerciseListSpy = spyOn(exerciseListPage, 'getExerciseList').and.returnValue(Promise.resolve());
+        exerciseListPage.corpusService.isInitialized = new ReplaySubject<boolean>(1);
+        exerciseListPage.corpusService.isInitialized.next(true);
         fixture.detectChanges();
     });
 
diff --git a/mc_frontend/src/app/exercise-list/exercise-list.page.ts b/mc_frontend/src/app/exercise-list/exercise-list.page.ts
index a3f2ae7..6287ce0 100644
--- a/mc_frontend/src/app/exercise-list/exercise-list.page.ts
+++ b/mc_frontend/src/app/exercise-list/exercise-list.page.ts
@@ -124,10 +124,12 @@ export class ExerciseListPage implements OnInit {
     ngOnInit(): Promise<void> {
         return new Promise<void>(((resolve, reject) => {
             this.vocService.currentReferenceVocabulary = null;
-            this.getExerciseList().then(() => {
-                return resolve();
-            }, () => {
-                return resolve();
+            this.corpusService.isInitialized.pipe(take(1)).subscribe(() => {
+                this.getExerciseList().then(() => {
+                    return resolve();
+                }, () => {
+                    return resolve();
+                });
             });
         }));
     }
diff --git a/mc_frontend/src/app/helper.service.spec.ts b/mc_frontend/src/app/helper.service.spec.ts
index 6ff054b..960644e 100644
--- a/mc_frontend/src/app/helper.service.spec.ts
+++ b/mc_frontend/src/app/helper.service.spec.ts
@@ -16,6 +16,7 @@ import {ApplicationState} from './models/applicationState';
 import {take} from 'rxjs/operators';
 import {HttpErrorResponse, HttpParams} from '@angular/common/http';
 import MockMC from './models/mockMC';
+import {UpdateInfo} from './models/updateInfo';
 
 describe('HelperService', () => {
     let helperService: HelperService;
@@ -157,6 +158,22 @@ describe('HelperService', () => {
             });
         });
     });
+    it('should initialize the update information', (done) => {
+        const updateInfoSpy: Spy = spyOn(helperService.storage, 'get').withArgs(configMC.localStorageKeyUpdateInfo);
+        updateInfoSpy.and.returnValue(Promise.resolve(''));
+        helperService.initUpdateInfo().then(() => {
+            updateInfoSpy.and.callThrough();
+            helperService.storage.get(configMC.localStorageKeyUpdateInfo).then((jsonString: string) => {
+                const updateInfo: UpdateInfo = JSON.parse(jsonString) as UpdateInfo;
+                expect(updateInfo.corpora).toBe(1);
+                const setSpy: Spy = spyOn(helperService.storage, 'set').and.returnValue(Promise.resolve());
+                helperService.initUpdateInfo().then(() => {
+                    expect(setSpy).toHaveBeenCalledTimes(0);
+                    done();
+                });
+            });
+        });
+    });
 
     it('should make a get request', (done) => {
         const toastCtrl: ToastController = TestBed.inject(ToastController);
diff --git a/mc_frontend/src/app/helper.service.ts b/mc_frontend/src/app/helper.service.ts
index 84a9cd3..444014b 100644
--- a/mc_frontend/src/app/helper.service.ts
+++ b/mc_frontend/src/app/helper.service.ts
@@ -12,6 +12,7 @@ import {ReplaySubject} from 'rxjs';
 import {TextData} from './models/textData';
 import configMC from '../configMC';
 import EventRegistry from './models/eventRegistry';
+import {UpdateInfo} from './models/updateInfo';
 
 declare var H5P: any;
 // dirty hack to prevent H5P access errors after resize events
@@ -271,6 +272,23 @@ export class HelperService {
         });
     }
 
+    initUpdateInfo(): Promise<void> {
+        return new Promise<void>(resolve => {
+            this.storage.get(configMC.localStorageKeyUpdateInfo).then((jsonString: string) => {
+                if (jsonString) {
+                    return resolve();
+                }
+                const ui: UpdateInfo = new UpdateInfo({
+                    corpora: 1,
+                    exerciseList: 1
+                });
+                this.storage.set(configMC.localStorageKeyUpdateInfo, JSON.stringify(ui)).then(() => {
+                    return resolve();
+                });
+            });
+        });
+    }
+
     loadTranslations(translate: TranslateService): void {
         // dirty hack to wait until the translation loader is initialized in IE11
         this.getDelayedTranslation(translate, 'CORPUS_UPDATE_COMPLETED').then((value: string) => {
-- 
GitLab