Skip to content

Commit

Permalink
Merge pull request #74 from akhuoa/feature/usability-improvements
Browse files Browse the repository at this point in the history
Display connectivity info without obstructing current map view
  • Loading branch information
alan-wu authored Jul 10, 2024
2 parents 00e3b31 + 16a4a33 commit 7430411
Show file tree
Hide file tree
Showing 22 changed files with 3,569 additions and 63 deletions.
4 changes: 2 additions & 2 deletions .env
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
VITE_APP_API_LOCATION=http://localhost:5000/
VITE_FLATMAPAPI_LOCATION=''
VITE_APP_ALGOLIA_KEY=
VITE_APP_ALGOLIA_ID=
VITE_APP_PENNSIEVE_API_LOCATION=https://api.pennsieve.io
VITE_APP_ALGOLIA_INDEX=k-core_dev_published_time_desc
VITE_APP_BL_SERVER_URL=
VITE_APP_NL_LINK_PREFIX=
VITE_APP_ROOT_URL=
VITE_APP_ROOT_URL=
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
.DS_Store
dist
node_modules
cypress/videos/*
cypress/screenshots/*
cypress/results/*
cypress/reports/*
.env.local
docs/.vitepress/dist
docs/.vitepress/cache
docs/components
.vitepress
23 changes: 23 additions & 0 deletions cypress.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { defineConfig } from "cypress";

export default defineConfig({
defaultCommandTimeout: 10000,
reporter: "junit",
experimentalMemoryManagement: true,
numTestsKeptInMemory: 0,
reporter: "cypress-multi-reporters",
reporterOptions: {
configFile: "reporter-config.json",
},
component: {
viewportWidth: 1440,
viewportHeight: 900,
specPattern: "cypress/component/*.cy.js",
devServer: {
framework: "vue",
bundler: "vite",
},
},
video: true,
videoCompression: true,
});
172 changes: 172 additions & 0 deletions cypress/component/SideBar.cy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
import SideBar from "../../src/components/SideBar.vue"

const searchHistory = [
{ filters: [], search: "heart" },
{ filters: [], search: "lung" },
{ filters: [], search: "scaffold" },
{ filters: [], search: "pig" },
{ filters: [], search: "heart" },
]

describe("<SideBar />", () => {
before(() => {
cy.intercept('**/dataset_info/**').as('dataset_info')
cy.intercept('**/discover/**').as('discover')
cy.intercept('**/image_search/**').as('image_search')
})

it("Should test the SideBar browser functionality", () => {
// Mock search history
cy.clearLocalStorage().then(() => {
window.localStorage.setItem(
"sparc.science-sidebar-search-history",
JSON.stringify(searchHistory)
)
})

// see: https://on.cypress.io/mounting-vue
cy.mount(SideBar, {
props: {
envVars: {
API_LOCATION: Cypress.env("API_LOCATION"),
ALGOLIA_KEY: Cypress.env("ALGOLIA_KEY"),
ALGOLIA_ID: Cypress.env("ALGOLIA_ID"),
ALGOLIA_INDEX: Cypress.env("ALGOLIA_INDEX"),
PENNSIEVE_API_LOCATION: Cypress.env("PENNSIEVE_API_LOCATION"),
BL_SERVER_URL: Cypress.env("BL_SERVER_URL"),
NL_LINK_PREFIX: Cypress.env("NL_LINK_PREFIX"),
ROOT_URL: Cypress.env("ROOT_URL"),
},
},
})

cy.get(".open-tab").should("exist").click()

cy.wait(['@dataset_info', '@discover', '@image_search'], { timeout: 20000 })

// Sidebar should be visible and have close tab
cy.get('.tab-container').should('not.exist')
cy.get(".close-tab").should("exist")
cy.get(".sidebar-container").should("be.visible")

// Default 10 dataset cards should be loaded
cy.get(".dataset-card-container").should("have.length", 10)

// Search history should be loaded
cy.get(".history-container > .search-tag").should("have.length", 3)

// Search history should be clickable and filter the dataset cards
cy.get(".history-container > .search-tag").contains("heart").click()
cy.get('.header > .el-input > .el-input__wrapper > .el-input__inner').invoke('val').then(($input) => {
expect($input).to.equal('heart')
})
cy.get('.history-container > .el-select > .el-select__wrapper').click()
cy.get('.history-container > .search-select > transition-stub > .sidebar-search-select-popper').as('fullHistory')
cy.get('@fullHistory').should('be.visible')
cy.get('@fullHistory').contains('scaffold').click()
cy.get('.header > .el-input > .el-input__wrapper > .el-input__inner').invoke('val').then(($input) => {
expect($input).to.equal('scaffold')
})

// Filter tags should be added and removed
cy.get('.ml-2 > .el-tag').should('not.exist')
cy.get('.el-cascader > .el-input > .el-input__wrapper').click()
cy.get(':nth-child(1) > .el-cascader-menu__wrap > .el-scrollbar__view').contains('Sex').click()
cy.get(':nth-child(2) > .el-cascader-menu__wrap > .el-scrollbar__view').contains('Male').click()
cy.get('.ml-2 > .el-tag').should('exist').and('contain', 'Male')
cy.get('.ml-2 > .el-tag > .el-tag__close').click()
cy.get('.ml-2 > .el-tag').should('not.exist')

// Dataset card content should be loaded
cy.get(':nth-child(1) > .dataset-card-container > .dataset-card > :nth-child(2) > .card-left > .full-size > .gallery > .gallery-strip > .card-line > .key-image-span > .el-card > .el-card__body > :nth-child(1) > .cursor-pointer > img').should('exist')
cy.get(':nth-child(1) > .dataset-card-container > .dataset-card > :nth-child(2) > .card-left > .full-size > .gallery > .gallery-strip > .card-line > .key-image-span > .el-card > .el-card__body > :nth-child(1) > .details > .el-button').should('exist')
cy.get(':nth-child(1) > .dataset-card-container > .dataset-card > :nth-child(2) > .card-right > .title').should('exist')
cy.get(':nth-child(1) > .dataset-card-container > .dataset-card > :nth-child(2) > .card-right > :nth-child(2)').should('exist')
})

it("Should test the Provenance Card functionality", () => {
cy.fixture('neuronInfo.json').as('neuronInfo');

// Pass provenance card data to SideBar
cy.get('@neuronInfo').then((neuronInfo) => {
cy.mount(SideBar, {
props: {
envVars: {
API_LOCATION: Cypress.env("API_LOCATION"),
ALGOLIA_KEY: Cypress.env("ALGOLIA_KEY"),
ALGOLIA_ID: Cypress.env("ALGOLIA_ID"),
ALGOLIA_INDEX: Cypress.env("ALGOLIA_INDEX"),
PENNSIEVE_API_LOCATION: Cypress.env("PENNSIEVE_API_LOCATION"),
BL_SERVER_URL: Cypress.env("BL_SERVER_URL"),
NL_LINK_PREFIX: Cypress.env("NL_LINK_PREFIX"),
ROOT_URL: Cypress.env("ROOT_URL"),
},
connectivityInfo: neuronInfo,
openAtStart: true,
activeTabId: 2,
},
})
}).as('wrapper')

// Sidebar Connectivity tab should be activated when neuron is clicked
cy.get('.tab-container').should('exist')
cy.get(':nth-child(1) > .title-text-table > .title-text').should('exist').and('contain', 'Search').as('Search')
cy.get('.active-tab > .title-text-table > .title-text').should('exist').and('contain', 'Connectivity').as('Connectivity')
cy.get('.active-tab > .el-button > :nth-child(1)').should('exist').as('CloseConnectivity')

cy.get('@neuronInfo').then((neuronInfo) => {
cy.get('.block > .title').contains(new RegExp(neuronInfo.name, 'i')).should('exist')
if ('origins' in neuronInfo) {
cy.get('.attribute-title-container').contains(/ORIGIN/i).should('exist').as('origins')
cy.get('@origins').parent().siblings('.attribute-content').should('have.length', neuronInfo.origins.length)
}
if ('components' in neuronInfo) {
cy.get('.attribute-title-container').contains(/COMPONENTS/i).should('exist').as('components')
cy.get('@components').parent().siblings('.attribute-content').should('have.length', neuronInfo.components.length)
}
if ('destinations' in neuronInfo) {
cy.get('.attribute-title-container').contains(/DESTINATION/i).should('exist').as('destinations')
cy.get('@destinations').parent().siblings('.attribute-content').should('have.length', neuronInfo.destinations.length)
}
})

// Click event in Provenance card should behave correctly
cy.get('@wrapper').then(({ wrapper, component }) => {
// Click on tabs
cy.get('@Search').click().then(() => {
expect(wrapper.emitted()['tabClicked'][0]).to.deep.equal([{ id: 1, type: 'search' }]) // Switch to Search
})
cy.get('@Connectivity').click().then(() => {
expect(wrapper.emitted()['tabClicked'][1]).to.deep.equal([{ id: 2, type: 'connectivity' }]) // Switch to Connectivity
})
cy.get('@CloseConnectivity').click().then(() => {
expect(wrapper.emitted()['connectivity-info-close']).to.exist // Close Connectivity
})

// Click on buttons
cy.window().then((window) => {
cy.stub(window, 'open').as('Open')
})
cy.get('#open-pubmed-button').should('exist').click()
cy.get('@neuronInfo').then((neuronInfo) => {
cy.get('@Open').should('have.been.calledOnceWithExactly', Cypress.sinon.match(neuronInfo.hyperlinks[0].url), '_blank')
})

cy.get('.connectivity-info-title > :nth-child(2) > .el-button').click()
cy.get('#open-dendrites-button').should('exist').click()
cy.get('.el-button').contains('Explore destination data').should('exist').click()
cy.get('.el-button').contains('Search for data on components').should('exist').click()

cy.then(() => {
// The emit order will follow the clicked order
cy.get('@neuronInfo').then((neuronInfo) => {
expect(wrapper.emitted()['show-connectivity'][0][0]).to.deep.equal(neuronInfo.featureId)
const getName = (entries) => entries.map((entry) => entry.name.toLowerCase())
expect(wrapper.emitted()['actionClick'][0][0]['labels']).to.deep.equal(getName(neuronInfo.originsWithDatasets))
expect(wrapper.emitted()['actionClick'][1][0]['labels']).to.deep.equal(getName(neuronInfo.destinationsWithDatasets))
expect(wrapper.emitted()['actionClick'][2][0]['labels']).to.deep.equal(getName(neuronInfo.componentsWithDatasets))
})
})
})
})
})
5 changes: 5 additions & 0 deletions cypress/fixtures/example.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "[email protected]",
"body": "Fixtures are a great way to mock data for responses to routes"
}
55 changes: 55 additions & 0 deletions cypress/fixtures/neuronInfo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
{
"destinations": [
"uterus",
"uterine cervix",
"nucleus of solitary tract"
],
"origins": [
"inferior vagus X ganglion"
],
"components": [
"vagus nerve"
],
"destinationsWithDatasets": [
{
"id": "UBERON:0000002",
"name": "uterine cervix"
},
{
"id": "UBERON:0000995",
"name": "uterus"
},
{
"id": "UBERON:0009050",
"name": "nucleus of solitary tract"
}
],
"originsWithDatasets": [
{
"id": "UBERON:0005363",
"name": "inferior vagus X ganglion"
}
],
"componentsWithDatasets": [
{
"id": "UBERON:0001759",
"name": "vagus nerve"
}
],
"title": "nodose ganglion with axon sensory terminal in uterus or uterine cervix via abdominal vagal nerve with axon terminal in nucleus of the tractus solitarius via vagus nerve",
"featureId": [
"ilxtr:sparc-nlp/femrep/64"
],
"hyperlinks": [
{
"url": "https://pubmed.ncbi.nlm.nih.gov/?term=%2F%2Fdoi.org%2F10.1016%2F0196-9781%2885%2990306-7%2C%2F%2Fdx.doi.org%2F10.1007%2Fs004410051211%2C%2F%2Fdoi.org%2F10.1016%2F0361-9230%2890%2990221-K%2C%2F%2Fdoi.org%2F10.1016%2Fj.autneu.2014.11.009%2C%2F%2Fdoi.org%2F10.1016%2F0006-8993%2895%2901312-1",
"id": "pubmed"
}
],
"provenanceTaxonomy": [
"NCBITaxon:10116"
],
"provenanceTaxonomyLabel": [
"Rattus norvegicus"
]
}
37 changes: 37 additions & 0 deletions cypress/support/commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }
12 changes: 12 additions & 0 deletions cypress/support/component-index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<title>Components App</title>
</head>
<body>
<div data-cy-root></div>
</body>
</html>
31 changes: 31 additions & 0 deletions cypress/support/component.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// ***********************************************************
// This example support/component.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************

// Import commands.js using ES2015 syntax:
import './commands'

// Alternatively you can use CommonJS syntax:
// require('./commands')

import { mount } from 'cypress/vue'

// Augment the Cypress namespace to include type definitions for
// your custom command.
// with a <reference path="./component" /> at the top of your spec.

Cypress.Commands.add('mount', mount)

// Example use:
// cy.mount(MyComponent)
Loading

0 comments on commit 7430411

Please sign in to comment.