From 900726960404a782a2961fc3350f1e1fa8608bdd Mon Sep 17 00:00:00 2001 From: Manuel Holtgrewe Date: Mon, 29 Jan 2024 13:00:30 +0100 Subject: [PATCH] feat: adding GeneOverviewCard (#31) --- .eslintrc.cjs | 1 - package-lock.json | 112 ++++----- .../AlternativeIdentifiers.vue | 93 ++++++++ .../GeneOverviewCard/ExternalResources.vue | 119 ++++++++++ .../GeneOverviewCard/GeneOverviewCard.spec.ts | 94 ++++++++ .../GeneOverviewCard.stories.ts | 48 ++++ .../GeneOverviewCard/GeneOverviewCard.vue | 220 ++++++++++++++++++ src/components/GeneOverviewCard/GeneRifs.vue | 76 ++++++ .../GeneOverviewCard/LocusDatabases.vue | 39 ++++ 9 files changed, 745 insertions(+), 57 deletions(-) create mode 100644 src/components/GeneOverviewCard/AlternativeIdentifiers.vue create mode 100644 src/components/GeneOverviewCard/ExternalResources.vue create mode 100644 src/components/GeneOverviewCard/GeneOverviewCard.spec.ts create mode 100644 src/components/GeneOverviewCard/GeneOverviewCard.stories.ts create mode 100644 src/components/GeneOverviewCard/GeneOverviewCard.vue create mode 100644 src/components/GeneOverviewCard/GeneRifs.vue create mode 100644 src/components/GeneOverviewCard/LocusDatabases.vue diff --git a/.eslintrc.cjs b/.eslintrc.cjs index d64c3a1..ff09f05 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -13,7 +13,6 @@ module.exports = { rules: { '@typescript-eslint/no-explicit-any': 'off', '@typescript-eslint/ban-ts-comment': 'off', - 'vue/multi-word-component-names': 'off', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': [ 'warn', // or "error" diff --git a/package-lock.json b/package-lock.json index 0202cd2..87f0c1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2680,9 +2680,9 @@ } }, "node_modules/@floating-ui/dom": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.0.tgz", - "integrity": "sha512-SZ0BEXzsaaS6THZfZJUcAobbZTD+MvfGM42bxgeg0Tnkp4/an/avqwAXiVLsFtIBZtfsx3Ymvwx0+KnnhdA/9g==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.1.tgz", + "integrity": "sha512-iA8qE43/H5iGozC3W0YSnVSW42Vh522yyM1gj+BqRwVsTNOyr231PsXDaV04yT39PsO0QL2QpbI/M0ZaLUQgRQ==", "dev": true, "dependencies": { "@floating-ui/core": "^1.6.0", @@ -2690,12 +2690,12 @@ } }, "node_modules/@floating-ui/react-dom": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.7.tgz", - "integrity": "sha512-B5GJxKUyPcGsvE1vua+Abvw0t6zVMyTbtG+Jk7BoI4hfc5Ahv50dstRIAn0nS0274kR9gnKwxIXyGA8EzBZJrA==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", "dev": true, "dependencies": { - "@floating-ui/dom": "^1.6.0" + "@floating-ui/dom": "^1.6.1" }, "peerDependencies": { "react": ">=16.8.0", @@ -3231,6 +3231,15 @@ "node": ">=8" } }, + "node_modules/@npmcli/config/node_modules/ini": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", + "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", + "dev": true, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, "node_modules/@npmcli/config/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -4806,6 +4815,15 @@ "url": "https://opencollective.com/storybook" } }, + "node_modules/@storybook/cli/node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/@storybook/cli/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -5975,9 +5993,9 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.7", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.7.tgz", - "integrity": "sha512-GPmeN1C3XAyV5uybAf4cMLWT9fDWcmQhZVtMFu7OR32WjrqGG+Wnk2V1d0bmtUyE/Zy1QJ9BxyiTih9z8Oks8A==", + "version": "20.11.10", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.10.tgz", + "integrity": "sha512-rZEfe/hJSGYmdfX9tvcPMYeYPW2sNl50nsw4jZmRcaG0HIAb0WYEpsB05GOb53vjqpyE9GUhlDQ4jLSoB5q9kg==", "devOptional": true, "dependencies": { "undici-types": "~5.26.4" @@ -7674,9 +7692,9 @@ } }, "node_modules/browserslist": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.2.tgz", - "integrity": "sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==", + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", + "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", "dev": true, "funding": [ { @@ -7693,8 +7711,8 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001565", - "electron-to-chromium": "^1.4.601", + "caniuse-lite": "^1.0.30001580", + "electron-to-chromium": "^1.4.648", "node-releases": "^2.0.14", "update-browserslist-db": "^1.0.13" }, @@ -7858,9 +7876,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001580", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001580.tgz", - "integrity": "sha512-mtj5ur2FFPZcCEpXFy8ADXbDACuNFXg6mxVDqp7tqooX6l3zwm+d8EPoeOSIFRDvHs8qu7/SLFOGniULkcH2iA==", + "version": "1.0.30001581", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001581.tgz", + "integrity": "sha512-whlTkwhqV2tUmP3oYhtNfaWGYHDdS3JYFQBKXxcUR9qqPWsRhFHhoISO2Xnl/g0xyKzht9mI1LZpiNWfMzHixQ==", "dev": true, "funding": [ { @@ -8192,12 +8210,12 @@ } }, "node_modules/commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, "engines": { - "node": ">= 6" + "node": ">=14" } }, "node_modules/commondir": { @@ -8318,12 +8336,6 @@ "proto-list": "~1.2.1" } }, - "node_modules/config-chain/node_modules/ini": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", - "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true - }, "node_modules/consola": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", @@ -9171,15 +9183,6 @@ "node": ">=14" } }, - "node_modules/editorconfig/node_modules/commander": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", - "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", - "dev": true, - "engines": { - "node": ">=14" - } - }, "node_modules/editorconfig/node_modules/lru-cache": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", @@ -9250,9 +9253,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.4.647", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.647.tgz", - "integrity": "sha512-Z/fTNGwc45WrYQhPaEcz5tAJuZZ8G7S/DBnhS6Kgp4BxnS40Z/HqlJ0hHg3Z79IGVzuVartIlTcjw/cQbPLgOw==", + "version": "1.4.648", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.648.tgz", + "integrity": "sha512-EmFMarXeqJp9cUKu/QEciEApn0S/xRcpZWuAm32U7NgoZCimjsilKXHRO9saeEW55eHZagIDg6XTUOv32w9pjg==", "dev": true }, "node_modules/emoji-regex": { @@ -10668,9 +10671,9 @@ "dev": true }, "node_modules/fastq": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz", - "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.0.tgz", + "integrity": "sha512-zGygtijUMT7jnk3h26kUms3BkSDp4IfIKjmnqI2tvx6nuBfiF1UqOxbnLfzdv+apBy+53oaImsKtMw/xYbW+1w==", "dependencies": { "reusify": "^1.0.4" } @@ -11603,9 +11606,9 @@ } }, "node_modules/immutable": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", - "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==", + "version": "4.3.5", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz", + "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==", "devOptional": true }, "node_modules/import-fresh": { @@ -11687,13 +11690,10 @@ "dev": true }, "node_modules/ini": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ini/-/ini-4.1.1.tgz", - "integrity": "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g==", - "dev": true, - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true }, "node_modules/internal-slot": { "version": "1.0.6", @@ -14202,9 +14202,9 @@ } }, "node_modules/micromark-util-character": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.0.1.tgz", - "integrity": "sha512-3wgnrmEAJ4T+mGXAUfMvMAbxU9RDG43XmGce4j6CwPtVxB3vfwXSZ6KhFwDzZ3mZHhmPimMAXg71veiBGzeAZw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.0.tgz", + "integrity": "sha512-KvOVV+X1yLBfs9dCBSopq/+G1PcgT3lAK07mC4BzXi5E7ahzMAF8oIupDDJ6mievI6F+lAATkbQQlQixJfT3aQ==", "dev": true, "funding": [ { diff --git a/src/components/GeneOverviewCard/AlternativeIdentifiers.vue b/src/components/GeneOverviewCard/AlternativeIdentifiers.vue new file mode 100644 index 0000000..2842616 --- /dev/null +++ b/src/components/GeneOverviewCard/AlternativeIdentifiers.vue @@ -0,0 +1,93 @@ + + + diff --git a/src/components/GeneOverviewCard/ExternalResources.vue b/src/components/GeneOverviewCard/ExternalResources.vue new file mode 100644 index 0000000..437c697 --- /dev/null +++ b/src/components/GeneOverviewCard/ExternalResources.vue @@ -0,0 +1,119 @@ + + + diff --git a/src/components/GeneOverviewCard/GeneOverviewCard.spec.ts b/src/components/GeneOverviewCard/GeneOverviewCard.spec.ts new file mode 100644 index 0000000..34e8cf5 --- /dev/null +++ b/src/components/GeneOverviewCard/GeneOverviewCard.spec.ts @@ -0,0 +1,94 @@ +import fs from 'fs' +import path from 'path' +import { describe, expect, it, test } from 'vitest' + +import { setupMountedComponents } from '../../lib/testUtils' +import { Record as GeneInfoRecord } from '../../pbs/annonars/genes/base' +import GeneOverviewCard from './GeneOverviewCard.vue' + +// Load fixture data for gene TGDS (little data) and BRCA1 (lots of data). +const geneInfoTgds = GeneInfoRecord.fromJsonString( + fs.readFileSync( + path.resolve(__dirname, '../GenePathogenicityCard/fixture.geneInfo.TGDS.json'), + 'utf8' + ) +) +const geneInfoBrca1 = GeneInfoRecord.fromJsonString( + fs.readFileSync( + path.resolve(__dirname, '../GenePathogenicityCard/fixture.geneInfo.BRCA1.json'), + 'utf8' + ) +) + +describe.concurrent('OverviewCard', async () => { + test.each([ + ['TGDS', geneInfoTgds], + ['BRCA1', geneInfoBrca1] + ])( + 'renders the OverviewCard information for %s', + async (geneSymbol: string, geneInfo: GeneInfoRecord) => { + // arrange: + const { wrapper } = await setupMountedComponents( + { component: GeneOverviewCard }, + { + props: { + geneInfo + } + } + ) + + // act: nothing, only test rendering + + // assert: + expect(wrapper.text()).toContain(geneSymbol) + } + ) + + it('expands the OverviewCard information (BRCA1)', async () => { + // arrange: + const { wrapper } = await setupMountedComponents( + { component: GeneOverviewCard }, + { + props: { + geneInfo: geneInfoBrca1 + } + } + ) + + // act: + expect(wrapper.text()).not.toContain('Alternative Identifiers') // guard + const expandButton = wrapper.find('#overview-card-expand-button') + expect(expandButton.exists()).toBe(true) + await expandButton.trigger('click') + + // assert: + expect(wrapper.text()).toContain('Alternative Identifiers') + expect(wrapper.text()).toContain('External Resources') + expect(wrapper.text()).toContain('Locus-Specific Databases') + expect(wrapper.text()).toContain('NCBI References Into Function') + }) + + it('expands the OverviewCard information (TGDS)', async () => { + // arrange: + const { wrapper } = await setupMountedComponents( + { component: GeneOverviewCard }, + { + props: { + geneInfo: geneInfoTgds + } + } + ) + + // act: + expect(wrapper.text()).not.toContain('Alternative Identifiers') // guard + const expandButton = wrapper.find('#overview-card-expand-button') + expect(expandButton.exists()).toBe(true) + await expandButton.trigger('click') + + // assert: + expect(wrapper.text()).toContain('Alternative Identifiers') + expect(wrapper.text()).toContain('External Resources') + expect(wrapper.text()).toContain('Locus-Specific Databases') + expect(wrapper.text()).toContain('NCBI References Into Function') + }) +}) diff --git a/src/components/GeneOverviewCard/GeneOverviewCard.stories.ts b/src/components/GeneOverviewCard/GeneOverviewCard.stories.ts new file mode 100644 index 0000000..9f113b3 --- /dev/null +++ b/src/components/GeneOverviewCard/GeneOverviewCard.stories.ts @@ -0,0 +1,48 @@ +import { JsonValue } from '@protobuf-ts/runtime' +import type { Meta, StoryObj } from '@storybook/vue3' + +import { Record as GeneInfoRecord } from '../../pbs/annonars/genes/base' +import geneInfoBrca1Json from '../GenePathogenicityCard/fixture.geneInfo.BRCA1.json' +import geneInfoTgdsJson from '../GenePathogenicityCard/fixture.geneInfo.TGDS.json' +import GeneOverviewCard from './GeneOverviewCard.vue' + +// Here, fixture data is loaded via `import` from JSON file. Reading the file +// as in the tests fails with "process is not defined" error in the browser. + +// @ts-ignore +const geneInfoTgds = GeneInfoRecord.fromJson(geneInfoTgdsJson as JsonValue) +// @ts-ignore +const geneInfoBrca1 = GeneInfoRecord.fromJson(geneInfoBrca1Json as JsonValue) + +const meta = { + title: 'Gene/GeneOverviewCard', + component: GeneOverviewCard, + tags: ['autodocs'], + argTypes: { + geneInfo: { control: { type: 'object' } } + }, + args: { + geneInfo: geneInfoTgds + } +} satisfies Meta + +export default meta +type Story = StoryObj + +export const TGDS: Story = { + args: { + geneInfo: geneInfoTgds + } +} + +export const BRCA1: Story = { + args: { + geneInfo: geneInfoBrca1 + } +} + +export const Undefined: Story = { + args: { + geneInfo: undefined + } +} diff --git a/src/components/GeneOverviewCard/GeneOverviewCard.vue b/src/components/GeneOverviewCard/GeneOverviewCard.vue new file mode 100644 index 0000000..6b55f6a --- /dev/null +++ b/src/components/GeneOverviewCard/GeneOverviewCard.vue @@ -0,0 +1,220 @@ + + + + diff --git a/src/components/GeneOverviewCard/GeneRifs.vue b/src/components/GeneOverviewCard/GeneRifs.vue new file mode 100644 index 0000000..d0d0b10 --- /dev/null +++ b/src/components/GeneOverviewCard/GeneRifs.vue @@ -0,0 +1,76 @@ + + + diff --git a/src/components/GeneOverviewCard/LocusDatabases.vue b/src/components/GeneOverviewCard/LocusDatabases.vue new file mode 100644 index 0000000..0aea1da --- /dev/null +++ b/src/components/GeneOverviewCard/LocusDatabases.vue @@ -0,0 +1,39 @@ + + +