From 683f79964001dc0fe8c37994006b04ee39ff7992 Mon Sep 17 00:00:00 2001
From: Xiao Gui <xgui3783@gmail.com>
Date: Wed, 25 Mar 2020 15:57:54 +0100
Subject: [PATCH] bugfix: some preview cannot be viewed

---
 .../advanced/browsingForDatasets.e2e-spec.js  |  67 +++++++++++-
 e2e/src/advanced/favDatasets.e2e-spec.js      |  17 ++-
 e2e/src/util.js                               | 101 ++++++++++++------
 src/atlasViewer/atlasViewer.component.ts      |  32 +-----
 src/services/state/uiState.store.ts           |  53 ++++++---
 .../databrowser.useEffect.ts                  |   3 +-
 .../kgSingleDatasetService.service.ts         |   3 +
 7 files changed, 185 insertions(+), 91 deletions(-)

diff --git a/e2e/src/advanced/browsingForDatasets.e2e-spec.js b/e2e/src/advanced/browsingForDatasets.e2e-spec.js
index d6ec2e810..b5cb270a7 100644
--- a/e2e/src/advanced/browsingForDatasets.e2e-spec.js
+++ b/e2e/src/advanced/browsingForDatasets.e2e-spec.js
@@ -24,7 +24,7 @@ const areasShouldHaveRecptor = [
   'Area FG2 (FusG)'
 ]
 
-describe('dataset browser', () => {
+describe('> dataset browser', () => {
   let iavPage
   beforeAll(async () => {
     iavPage = new AtlasPage()
@@ -32,7 +32,7 @@ describe('dataset browser', () => {
   })
 
   for (const template of templates) {
-    describe(`in template: ${template}`, () => {
+    describe(`> in template: ${template}`, () => {
       beforeAll(async () => {
         await iavPage.goto()
         await iavPage.selectTitleCard(template)
@@ -45,7 +45,7 @@ describe('dataset browser', () => {
         await iavPage.clearAllSelectedRegions()
       })
       for (const area of areasShouldHaveRecptor) {
-        it(`receptor data ${area} should be able to be found`, async () => {
+        it(`> receptor data ${area} should be able to be found`, async () => {
           await iavPage.searchRegionWithText(area)
           await iavPage.wait(2000)
           await iavPage.selectSearchRegionAutocompleteWithText()
@@ -60,4 +60,65 @@ describe('dataset browser', () => {
       }
     })
   }
+})
+
+const template = 'ICBM 2009c Nonlinear Asymmetric'
+const area = 'Area hOc1 (V1, 17, CalcS)'
+
+const receptorName = `Density measurements of different receptors for Area hOc1 (V1, 17, CalcS) [human, v1.0]`
+
+describe('> dataset previews', () => {
+  let iavPage
+  beforeEach(async () => {
+    iavPage = new AtlasPage()
+    await iavPage.init()
+    await iavPage.goto()
+    await iavPage.selectTitleCard(template)
+    await iavPage.wait(500)
+    await iavPage.waitUntilAllChunksLoaded()
+
+    await iavPage.searchRegionWithText(area)
+    await iavPage.wait(2000)
+    await iavPage.selectSearchRegionAutocompleteWithText()
+    await iavPage.dismissModal()
+    await iavPage.searchRegionWithText('')
+
+    const datasets = await iavPage.getVisibleDatasets()
+    const receptorIndex = datasets.indexOf(receptorName)
+
+    await iavPage.clickNthDataset(receptorIndex)
+    await iavPage.waitFor(true, true)
+    await iavPage.clickModalBtnByText(/preview/i)
+    await iavPage.waitFor(true, true)
+  })
+
+  describe('> can display graph', () => {
+
+    it('> can display radar graph', async () => {
+      const files = await iavPage.getBottomSheetList()
+      const fingerprintIndex = files.findIndex(file => /fingerprint/i.test(file))
+      await iavPage.clickNthItemFromBottomSheetList(fingerprintIndex)
+      await iavPage.waitFor(true, true)
+      const modalHasCanvas = await iavPage.modalHasChild('canvas')
+      expect(modalHasCanvas).toEqual(true)
+    })
+
+    it('> can display profile', async () => {
+
+      const files = await iavPage.getBottomSheetList()
+      const profileIndex = files.findIndex(file => /profile/i.test(file))
+      await iavPage.clickNthItemFromBottomSheetList(profileIndex)
+      await iavPage.waitFor(true, true)
+      const modalHasCanvas = await iavPage.modalHasChild('canvas')
+      expect(modalHasCanvas).toEqual(true)
+    })
+  })
+  it('> can display image', async () => {
+    const files = await iavPage.getBottomSheetList()
+    const imageIndex = files.findIndex(file => /image\//i.test(file))
+    await iavPage.clickNthItemFromBottomSheetList(imageIndex)
+    await iavPage.waitFor(true, true)
+    const modalHasImage = await iavPage.modalHasChild('div[data-img-src]')
+    expect(modalHasImage).toEqual(true)
+  })
 })
\ No newline at end of file
diff --git a/e2e/src/advanced/favDatasets.e2e-spec.js b/e2e/src/advanced/favDatasets.e2e-spec.js
index 9d71c8f87..5e35cabf7 100644
--- a/e2e/src/advanced/favDatasets.e2e-spec.js
+++ b/e2e/src/advanced/favDatasets.e2e-spec.js
@@ -27,19 +27,19 @@ describe(`fav'ing dataset`, () => {
       await iavPage.wait(500)
       await iavPage.clearAllSelectedRegions()
     }catch(e) {
-
+      console.warn(`#afterEach error: clearing text field`, e)
     }
 
     try {
       await iavPage.showPinnedDatasetPanel()
-      const textsArr = await iavPage.getPinnedDatasetsFromOpenedPanel()
+      const textsArr = await iavPage.getBottomSheetList()
       let length = textsArr.length
       while(length > 0){
-        await iavPage.unpinNthDatasetFromOpenedPanel(0)
+        await iavPage.clickNthItemFromBottomSheetList(0, `[aria-label="Toggle pinning this dataset"]`)
         length --
       }
     }catch(e){
-
+      console.warn(`#afterEach error: unfav all`, e)
     }
 
     await iavPage.wait(500)
@@ -71,7 +71,6 @@ describe(`fav'ing dataset`, () => {
     expect(txt2).toEqual(`Pinned dataset: ${pmapName}`)
   })
 
-
   describe('> fav dataset list', () => {
     beforeEach(async () => {
 
@@ -98,7 +97,7 @@ describe(`fav'ing dataset`, () => {
     it('> clicking pin shows pinned datasets', async () => {
       await iavPage.showPinnedDatasetPanel()
       await iavPage.wait(500)
-      const textsArr = await iavPage.getPinnedDatasetsFromOpenedPanel()
+      const textsArr = await iavPage.getBottomSheetList()
       
       expect(textsArr.length).toEqual(2)
       expect(textsArr).toContain(receptorName)
@@ -108,10 +107,10 @@ describe(`fav'ing dataset`, () => {
     it('> click unpin in fav data panel unpins, but also allow user to undo', async () => {
       await iavPage.showPinnedDatasetPanel()
       await iavPage.wait(1000)
-      const textsArr = await iavPage.getPinnedDatasetsFromOpenedPanel()
+      const textsArr = await iavPage.getBottomSheetList()
       const idx = textsArr.indexOf(receptorName)
       if (idx < 0) throw new Error(`index of receptor name not found: ${receptorName}: ${textsArr}`)
-      await iavPage.unpinNthDatasetFromOpenedPanel(idx)
+      await iavPage.clickNthItemFromBottomSheetList(idx, `[aria-label="Toggle pinning this dataset"]`)
       await iavPage.wait(500)
   
       const txt = await iavPage.getSnackbarMessage()
@@ -123,7 +122,7 @@ describe(`fav'ing dataset`, () => {
       await iavPage.clickSnackbarAction()
       await iavPage.wait(500)
 
-      const textsArr3 = await iavPage.getPinnedDatasetsFromOpenedPanel()
+      const textsArr3 = await iavPage.getBottomSheetList()
       const number2 = await iavPage.getNumberOfFavDataset()
       expect(number2).toEqual(2)
       expect(
diff --git a/e2e/src/util.js b/e2e/src/util.js
index d1128db02..e8f4ece07 100644
--- a/e2e/src/util.js
+++ b/e2e/src/util.js
@@ -61,6 +61,23 @@ class WdBase{
       })
   }
 
+  async waitForAsync(){
+
+    const checkReady = async () => {
+      const el = await this._browser.findElements(
+        By.css('.spinnerAnimationCircle')
+      )
+      return !el.length
+    }
+
+    do {
+      // Do nothing, until ready
+    } while (
+      await this.wait(100),
+      !(await checkReady())
+    )
+  }
+
   async cursorMoveTo({ position }) {
     if (!position) throw new Error(`cursorGoto: position must be defined!`)
     const x = Array.isArray(position) ? position[0] : position.x
@@ -128,6 +145,11 @@ class WdBase{
     await this._browser.sleep(ms)
   }
 
+  async waitFor(animation = false, async = false){
+    if (animation) await this.wait(500)
+    if (async) await this.waitForAsync()
+  }
+
   async getSnackbarMessage(){
     const txt = await this._driver
       .findElement( By.tagName('simple-snack-bar') )
@@ -143,6 +165,40 @@ class WdBase{
       .click()
   }
 
+  _getBottomSheet() {
+    return this._driver.findElement( By.tagName('mat-bottom-sheet-container') )
+  }
+
+  _getBottomSheetList(){
+    return this._getBottomSheet().findElements( By.tagName('mat-list-item') )
+  }
+
+  async getBottomSheetList(){
+    const listItems = await this._getBottomSheetList()
+    const output = []
+    for (const item of listItems) {
+      output.push(
+        await _getTextFromWebElement(item)
+      )
+    }
+    return output
+  }
+
+  async clickNthItemFromBottomSheetList(index, cssSelector){
+
+    const list = await this._getBottomSheetList()
+
+    if (!list[index]) throw new Error(`index out of bound: ${index} in list with size ${list.length}`)
+
+    if (cssSelector) {
+      await list[index]
+        .findElement( By.css(cssSelector) )
+        .click()
+    } else {
+      await list[index].click()
+    }
+  }
+
   async clearAlerts() {
     await this._driver
       .actions()
@@ -193,6 +249,18 @@ class WdLayoutPage extends WdBase{
     return arr
   }
 
+  async modalHasChild(cssSelector){
+    try {
+      const isDisplayed = await this._getModal()
+        .findElement( By.css( cssSelector ) )
+        .isDisplayed()
+      return isDisplayed
+    } catch (e) {
+      console.warn(`modalhaschild thrown error`, e)
+      return false
+    }
+  }
+
   // text can be instance of regex or string
   async clickModalBtnByText(text){
     const btns = await this._getModalBtns()
@@ -355,39 +423,6 @@ class WdLayoutPage extends WdBase{
     await this._getFavDatasetIcon().click()
     await this.wait(500)
   }
-
-  _getPinnedDatasetPanel(){
-    return this._driver
-      .findElement(
-        By.css('[aria-label="Pinned datasets panel"]')
-      )
-  }
-
-  async getPinnedDatasetsFromOpenedPanel(){
-    const list = await this._getPinnedDatasetPanel()
-      .findElements(
-        By.tagName('mat-list-item')
-      )
-
-    const returnArr = []
-    for (const el of list) {
-      const text = await _getTextFromWebElement(el)
-      returnArr.push(text)
-    }
-    return returnArr
-  }
-
-  async unpinNthDatasetFromOpenedPanel(index){
-    const list = await this._getPinnedDatasetPanel()
-      .findElements(
-        By.tagName('mat-list-item')
-      )
-
-    if (!list[index]) throw new Error(`index out of bound: ${index} in list with size ${list.length}`)
-    await list[index]
-      .findElement( By.css('[aria-label="Toggle pinning this dataset"]') )
-      .click()
-  }
 }
 
 class WdIavPage extends WdLayoutPage{
diff --git a/src/atlasViewer/atlasViewer.component.ts b/src/atlasViewer/atlasViewer.component.ts
index 94b0ced53..94466205b 100644
--- a/src/atlasViewer/atlasViewer.component.ts
+++ b/src/atlasViewer/atlasViewer.component.ts
@@ -29,7 +29,7 @@ import { AtlasViewerConstantsServices, UNSUPPORTED_INTERVAL, UNSUPPORTED_PREVIEW
 import { WidgetServices } from "./widgetUnit/widgetService.service";
 
 import { LocalFileService } from "src/services/localFile.service";
-import { AGREE_COOKIE, AGREE_KG_TOS, SHOW_BOTTOM_SHEET, SHOW_KG_TOS } from "src/services/state/uiState.store";
+import { AGREE_COOKIE, AGREE_KG_TOS, SHOW_KG_TOS } from "src/services/state/uiState.store";
 import {
   CLOSE_SIDE_PANEL,
   OPEN_SIDE_PANEL,
@@ -39,7 +39,6 @@ import { getViewer, isSame } from "src/util/fn";
 import { NehubaContainer } from "../ui/nehubaContainer/nehubaContainer.component";
 import { colorAnimation } from "./atlasViewer.animation"
 import { MouseHoverDirective } from "src/util/directives/mouseOver.directive";
-import {MatBottomSheet, MatBottomSheetRef} from "@angular/material/bottom-sheet";
 import {MatSnackBar, MatSnackBarRef} from "@angular/material/snack-bar";
 import {MatDialog, MatDialogRef} from "@angular/material/dialog";
 
@@ -95,8 +94,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
 
   private snackbarRef: MatSnackBarRef<any>
   public snackbarMessage$: Observable<string>
-  private bottomSheetRef: MatBottomSheetRef
-  private bottomSheet$: Observable<TemplateRef<any>>
 
   public dedicatedView$: Observable<string | null>
   public onhoverSegments$: Observable<string[]>
@@ -138,8 +135,7 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
     private dispatcher$: ActionsSubject,
     private rd: Renderer2,
     public localFileService: LocalFileService,
-    private snackbar: MatSnackBar,
-    private bottomSheet: MatBottomSheet
+    private snackbar: MatSnackBar
   ) {
 
     this.snackbarMessage$ = this.store.pipe(
@@ -158,12 +154,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
       distinctUntilChanged(),
     )
 
-    this.bottomSheet$ = this.store.pipe(
-      select('uiState'),
-      select('bottomSheetTemplate'),
-      distinctUntilChanged(),
-    )
-
     /**
      * TODO deprecated
      */
@@ -255,24 +245,6 @@ export class AtlasViewer implements OnDestroy, OnInit, AfterViewInit {
 
     )
 
-    this.subscriptions.push(
-      this.bottomSheet$.subscribe(templateRef => {
-        if (!templateRef) {
-          if (this.bottomSheetRef) {
-            this.bottomSheetRef.dismiss()
-          }
-        } else {
-          this.bottomSheetRef = this.bottomSheet.open(templateRef)
-          this.bottomSheetRef.afterDismissed().subscribe(() => {
-            this.store.dispatch({
-              type: SHOW_BOTTOM_SHEET,
-              bottomSheetTemplate: null,
-            })
-            this.bottomSheetRef = null
-          })
-        }
-      }),
-    )
     this.subscriptions.push(
       this.onhoverSegments$.subscribe(hr => {
         this.hoveringRegions = hr
diff --git a/src/services/state/uiState.store.ts b/src/services/state/uiState.store.ts
index 2340438b7..f02d62672 100644
--- a/src/services/state/uiState.store.ts
+++ b/src/services/state/uiState.store.ts
@@ -1,11 +1,12 @@
-import { Injectable, TemplateRef } from '@angular/core';
+import { Injectable, TemplateRef, OnDestroy } from '@angular/core';
 import { Action, select, Store } from '@ngrx/store'
 
-import { Effect } from "@ngrx/effects";
-import { Observable } from "rxjs";
+import { Effect, Actions, ofType } from "@ngrx/effects";
+import { Observable, Subscription } from "rxjs";
 import { filter, map, mapTo, scan, startWith } from "rxjs/operators";
 import { COOKIE_VERSION, KG_TOS_VERSION, LOCAL_STORAGE_CONST } from 'src/util/constants'
 import { IavRootStoreInterface } from '../stateStore.service'
+import { MatBottomSheetRef, MatBottomSheet } from '@angular/material/bottom-sheet';
 
 export const defaultState: StateInterface = {
   mouseOverSegments: [],
@@ -20,7 +21,6 @@ export const defaultState: StateInterface = {
   sidePanelExploreCurrentViewIsOpen: false,
 
   snackbarMessage: null,
-  bottomSheetTemplate: null,
 
   pluginRegionSelectionEnabled: false,
   persistentStateNotifierMessage: null,
@@ -143,13 +143,6 @@ export const getStateStore = ({ state = defaultState } = {}) => (prevState: Stat
       agreedKgTos: true,
     }
   }
-  case SHOW_BOTTOM_SHEET: {
-    const { bottomSheetTemplate } = action
-    return {
-      ...prevState,
-      bottomSheetTemplate,
-    }
-  }
   default: return prevState
   }
 }
@@ -191,8 +184,6 @@ export interface StateInterface {
 
   agreedCookies: boolean
   agreedKgTos: boolean
-
-  bottomSheetTemplate: TemplateRef<any>
 }
 
 export interface ActionInterface extends Action {
@@ -216,7 +207,9 @@ export interface ActionInterface extends Action {
   providedIn: 'root',
 })
 
-export class UiStateUseEffect {
+export class UiStateUseEffect implements OnDestroy{
+
+  private subscriptions: Subscription[] = []
 
   private numRegionSelectedWithHistory$: Observable<any[]>
 
@@ -226,7 +219,13 @@ export class UiStateUseEffect {
   @Effect()
   public viewCurrentOpen$: Observable<any>
 
-  constructor(store$: Store<IavRootStoreInterface>) {
+  private bottomSheetRef: MatBottomSheetRef
+
+  constructor(
+    store$: Store<IavRootStoreInterface>,
+    actions$: Actions,
+    bottomSheet: MatBottomSheet
+  ) {
     this.numRegionSelectedWithHistory$ = store$.pipe(
       select('viewerState'),
       select('regionsSelected'),
@@ -248,6 +247,30 @@ export class UiStateUseEffect {
         type: EXPAND_SIDE_PANEL_CURRENT_VIEW,
       }),
     )
+    
+    this.subscriptions.push(
+      actions$.pipe(
+        ofType(SHOW_BOTTOM_SHEET)
+      ).subscribe(({ bottomSheetTemplate, config }) => {
+        if (!bottomSheetTemplate) {
+          if (this.bottomSheetRef) {
+            this.bottomSheetRef.dismiss()
+            this.bottomSheetRef = null
+          }
+        } else {
+          this.bottomSheetRef = bottomSheet.open(bottomSheetTemplate, config)
+          this.bottomSheetRef.afterDismissed().subscribe(() => {
+            this.bottomSheetRef = null
+          })
+        }
+      })
+    )
+  }
+
+  ngOnDestroy(){
+    while(this.subscriptions.length > 0) {
+      this.subscriptions.pop().unsubscribe()
+    }
   }
 }
 
diff --git a/src/ui/databrowserModule/databrowser.useEffect.ts b/src/ui/databrowserModule/databrowser.useEffect.ts
index f202ab20c..b30394864 100644
--- a/src/ui/databrowserModule/databrowser.useEffect.ts
+++ b/src/ui/databrowserModule/databrowser.useEffect.ts
@@ -63,7 +63,8 @@ export class DataBrowserUseEffect implements OnDestroy {
         map((datasetPreviews) => datasetPreviews[datasetPreviews.length - 1]),
         switchMap(({ datasetId, filename }) =>{
           const re = getKgSchemaIdFromFullId(datasetId)
-          return this.http.get(`${DATASET_PREVIEW_URL}/${re[1]}/${filename}`).pipe(
+          const url = `${DATASET_PREVIEW_URL}/${re[1]}/${encodeURIComponent(filename)}`
+          return this.http.get(url).pipe(
             filter((file: any) => PREVIEW_FILE_TYPES_NO_UI.indexOf( determinePreviewFileType(file) ) < 0),
             mapTo({
               datasetId,
diff --git a/src/ui/databrowserModule/kgSingleDatasetService.service.ts b/src/ui/databrowserModule/kgSingleDatasetService.service.ts
index 0ad31491a..82d26d408 100644
--- a/src/ui/databrowserModule/kgSingleDatasetService.service.ts
+++ b/src/ui/databrowserModule/kgSingleDatasetService.service.ts
@@ -70,6 +70,9 @@ export class KgSingleDatasetService implements OnDestroy {
     this.store$.dispatch({
       type: SHOW_BOTTOM_SHEET,
       bottomSheetTemplate: template,
+      config: {
+        ariaLabel: `List of preview files`
+      }
     })
   }
 
-- 
GitLab