diff --git a/src/electron/frontend/core/components/Neurosift.js b/src/electron/frontend/core/components/Neurosift.ts similarity index 68% rename from src/electron/frontend/core/components/Neurosift.js rename to src/electron/frontend/core/components/Neurosift.ts index 381e6dbca..3214396f1 100644 --- a/src/electron/frontend/core/components/Neurosift.js +++ b/src/electron/frontend/core/components/Neurosift.ts @@ -1,99 +1,110 @@ -import { LitElement, css, html } from "lit"; - -import { Loader } from "./Loader"; -import { FullScreenToggle } from "./FullScreenToggle"; -import { baseUrl } from "../server/globals"; - -export function getURLFromFilePath(file, projectName) { - const regexp = new RegExp(`.+(${projectName}.+)`); - return `${baseUrl}/preview/${file.match(regexp)[1]}`; -} - -export class Neurosift extends LitElement { - static get styles() { - return css` - :host { - background: white; - width: 100%; - height: 100%; - display: grid; - grid-template-rows: 100%; - grid-template-columns: 100%; - position: relative; - --loader-color: hsl(200, 80%, 50%); - } - - iframe, - .loader-container { - width: 100%; - height: 100%; - } - - .loader-container { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - top: 0; - left: 0; - } - - .fullscreen-toggle { - display: flex; - position: absolute; - top: 10px; - right: 10px; - padding: 10px; - color: white; - background-color: gainsboro; - border: 1px solid gray; - border-radius: 10px; - cursor: pointer; - } - - span { - font-size: 14px; - } - - small { - padding-left: 10px; - } - - iframe { - border: 0; - } - `; - } - - static get properties() { - return { - url: { type: String, reflect: true }, - }; - } - - constructor({ url, fullscreen = true } = {}) { - super(); - this.url = url; - this.fullscreen = fullscreen; - } - - render() { - return this.url - ? html`
- ${new Loader({ - message: `Loading Neurosift view...
${this.url}`, - })} -
- ${this.fullscreen ? new FullScreenToggle({ target: this }) : ""} - ` - : ``; - } -} - -customElements.get("neurosift-iframe") || customElements.define("neurosift-iframe", Neurosift); +import { LitElement, css, html, CSSResult, TemplateResult } from "lit"; +import { Loader } from "./Loader"; +import { FullScreenToggle } from "./FullScreenToggle"; +import { baseUrl } from "../server/globals"; + +export function getURLFromFilePath(file: string, projectName: string): string { + const regexp = new RegExp(`.+(${projectName}.+)`); + const match = file.match(regexp); + if (!match) throw new Error(`File path ${file} does not contain project name ${projectName}`); + return `${baseUrl}/preview/${match[1]}`; +} + +export class Neurosift extends LitElement { + static get styles(): CSSResult { + return css` + :host { + background: white; + width: 100%; + height: 100%; + display: grid; + grid-template-rows: 100%; + grid-template-columns: 100%; + position: relative; + --loader-color: hsl(200, 80%, 50%); + } + + iframe, + .loader-container { + width: 100%; + height: 100%; + } + + .loader-container { + display: flex; + align-items: center; + justify-content: center; + position: absolute; + top: 0; + left: 0; + } + + .fullscreen-toggle { + display: flex; + position: absolute; + top: 10px; + right: 10px; + padding: 10px; + color: white; + background-color: gainsboro; + border: 1px solid gray; + border-radius: 10px; + cursor: pointer; + } + + span { + font-size: 14px; + } + + small { + padding-left: 10px; + } + + iframe { + border: 0; + } + `; + } + + static get properties() { + return { + url: { type: String, reflect: true }, + fullscreen: { type: Boolean } + }; + } + + declare url?: string; + declare fullscreen: boolean; + + constructor(options: { url?: string; fullscreen?: boolean } = {}) { + super(); + this.url = options.url; + this.fullscreen = options.fullscreen ?? true; + } + + render(): TemplateResult | string { + // Clear neurosift cross-session storage database + // see https://github.com/NeurodataWithoutBorders/nwb-guide/issues/974 + if (this.url) { + indexedDB.deleteDatabase("neurosift-hdf5-cache"); + } + + return this.url + ? html`
+ ${new Loader({ + message: `Loading Neurosift view...
${this.url}`, + })} +
+ ${this.fullscreen ? new FullScreenToggle({ target: this }) : ""} + ` + : ""; + } +} + +customElements.get("neurosift-iframe") || customElements.define("neurosift-iframe", Neurosift); diff --git a/src/electron/frontend/core/components/pages/preview/PreviewPage.js b/src/electron/frontend/core/components/pages/preview/PreviewPage.js index 8dfe7b4d6..0b4c44eca 100644 --- a/src/electron/frontend/core/components/pages/preview/PreviewPage.js +++ b/src/electron/frontend/core/components/pages/preview/PreviewPage.js @@ -2,7 +2,7 @@ import { html } from "lit"; import { Page } from "../Page.js"; import { onThrow } from "../../../errors"; import { JSONSchemaInput } from "../../JSONSchemaInput.js"; -import { Neurosift } from "../../Neurosift.js"; +import { Neurosift } from "../../Neurosift"; import { baseUrl } from "../../../server/globals"; export class PreviewPage extends Page { diff --git a/tests/components/Neurosift.test.ts b/tests/components/Neurosift.test.ts new file mode 100644 index 000000000..d9efe93fc --- /dev/null +++ b/tests/components/Neurosift.test.ts @@ -0,0 +1,74 @@ +// Test that the indexed database is not cleared on initial render and cleared +// when a url is provided and the component is rendered again as in PreviewPage.js +import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest'; +import { Neurosift } from '../../src/electron/frontend/core/components/Neurosift'; + +describe('Neurosift', () => { + let originalIndexedDB: IDBFactory; + let mockDeleteDatabase: ReturnType; + + beforeEach(() => { + // Mock indexedDB.deleteDatabase so that we can test if it is called + mockDeleteDatabase = vi.fn(); + originalIndexedDB = window.indexedDB; + window.indexedDB = { + deleteDatabase: mockDeleteDatabase, + open: vi.fn(), + databases: vi.fn(), + cmp: vi.fn() + } as unknown as IDBFactory; + }); + + afterEach(() => { + // Restore original indexedDB + window.indexedDB = originalIndexedDB; + }); + + test('if neurosift storage is not cleared on initial render', async () => { + const neurosift = new Neurosift(); + + // Add the component to the DOM + document.body.appendChild(neurosift); + + // Wait for the component to render + neurosift.requestUpdate(); + + // Verify deleteDatabase was not called + expect(mockDeleteDatabase).not.toHaveBeenCalled(); + + // Remove the component from the DOM + document.body.removeChild(neurosift); + }); + + test('if neurosift storage is cleared when url is provided and component is rendered again', async () => { + const neurosift = new Neurosift(); + + // Add the component to the DOM + document.body.appendChild(neurosift); + + // Wait for the component to render + neurosift.requestUpdate(); + + // Set the URL to a test URL + neurosift.url = 'http://test.url/files/test.nwb'; + + // Wait for the component to render again + neurosift.requestUpdate(); + await neurosift.updateComplete; + + // Verify deleteDatabase was called + expect(mockDeleteDatabase).toHaveBeenCalledWith('neurosift-hdf5-cache'); + expect(mockDeleteDatabase).toHaveBeenCalledTimes(1); + + // Wait for the component to render again + neurosift.requestUpdate(); + await neurosift.updateComplete; + + // Verify deleteDatabase was called + expect(mockDeleteDatabase).toHaveBeenCalledWith('neurosift-hdf5-cache'); + expect(mockDeleteDatabase).toHaveBeenCalledTimes(2); + + // Remove the component from the DOM + document.body.removeChild(neurosift); + }); +});