Skip to content

Commit

Permalink
feat: Create Acmg Criteria component (#70) (#73)
Browse files Browse the repository at this point in the history
  • Loading branch information
gromdimon authored Sep 25, 2023
1 parent 607af69 commit 7c463f1
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 97 deletions.
105 changes: 105 additions & 0 deletions frontend/src/components/AcmgCriteriaCard.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
<script setup lang="ts">
/** The component for the Acmg Criteria Card. */
import {
Presence,
StateSource,
AcmgCriteria,
type CriteriaState,
AcmgEvidenceLevel,
ACMG_CRITERIA_DEFS,
ACMG_EVIDENCE_LEVELS_PATHOGENIC,
ACMG_EVIDENCE_LEVELS_BENIGN
} from '@/lib/acmgSeqVar'
interface Props {
/** The acmg rating. */
acmgRating: any
/** The acmg criteria. */
criteria: AcmgCriteria
/** The acmg criteria state. */
criteriaState: CriteriaState
}
const props = withDefaults(defineProps<Props>(), {
acmgRating: undefined,
criteria: undefined,
criteriaState: undefined
})
const findSwitchColor = (): string => {
const evidence = props.criteriaState.evidenceLevel
if (evidence === AcmgEvidenceLevel.PathogenicVeryStrong) {
return 'red-accent-4'
} else if (evidence === AcmgEvidenceLevel.PathogenicStrong) {
return 'orange-darken-4'
} else if (evidence === AcmgEvidenceLevel.PathogenicModerate) {
return 'amber-darken-4'
} else if (evidence === AcmgEvidenceLevel.PathogenicSupporting) {
return 'yellow-darken-3'
} else if (evidence === AcmgEvidenceLevel.BenignStandalone) {
return 'green-darken-4'
} else if (evidence === AcmgEvidenceLevel.BenignStrong) {
return 'light-green'
} else if (evidence === AcmgEvidenceLevel.BenignSupporting) {
return 'lime'
} else {
return 'primary'
}
}
const switchCriteria = (criteria: AcmgCriteria, presence: Presence) => {
if (presence === Presence.Present) {
props.acmgRating.setPresence(StateSource.User, criteria, Presence.Absent)
} else {
props.acmgRating.setPresence(StateSource.User, criteria, Presence.Present)
}
}
</script>

<template>
<v-card class="mx-auto compact-form" width="150" style="margin: 10px">
<div class="d-flex justify-content-between">
<v-switch
:color="findSwitchColor()"
:label="props.criteria"
:model-value="props.criteriaState.presence === Presence.Present"
@update:model-value="switchCriteria(props.criteria, props.criteriaState.presence)"
hide-details="auto"
density="compact"
class="switch"
/>
<v-tooltip :text="ACMG_CRITERIA_DEFS.get(props.criteria)?.hint">
<template v-slot:activator="{ props }">
<v-icon style="margin: 10px" v-bind="props">mdi-information</v-icon>
</template>
</v-tooltip>
</div>
<v-select
:model-value="props.criteriaState.evidenceLevel"
@update:model-value="props.acmgRating.setEvidenceLevel(StateSource.User, criteria, $event)"
:items="
ACMG_EVIDENCE_LEVELS_PATHOGENIC.includes(props.criteriaState.evidenceLevel)
? ACMG_EVIDENCE_LEVELS_PATHOGENIC
: ACMG_EVIDENCE_LEVELS_BENIGN
"
hide-details="auto"
density="compact"
class="select"
/>
</v-card>
</template>

<style scoped>
.switch {
margin-left: 10px;
padding: 0px;
}
.select {
margin: 0px;
}
.compact-form {
transform: scale(0.9);
transform-origin: left;
}
</style>
112 changes: 15 additions & 97 deletions frontend/src/components/VariantDetails/AcmgRating.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@ import { computed, onMounted, ref, watch } from 'vue'
import { StoreState } from '@/stores/misc'
import { useVariantAcmgRatingStore } from '@/stores/variantAcmgRating'
import { type SmallVariant } from '@/stores/variantInfo'
import AcmgCriteriaCard from '@/components/AcmgCriteriaCard.vue'
import {
AcmgCriteria,
StateSource,
Presence,
ALL_ACMG_CRITERIA,
ACMG_EVIDENCE_LEVELS_PATHOGENIC,
ACMG_EVIDENCE_LEVELS_BENIGN,
ACMG_CRITERIA_DEFS,
AcmgEvidenceLevel
ACMG_CRITERIA_DEFS
} from '@/lib/acmgSeqVar'
const props = defineProps({
Expand Down Expand Up @@ -48,35 +46,6 @@ const calculateAcmgRating = computed((): string => {
return acmgClass
})
const findSwitchColor = (criteria: AcmgCriteria): string => {
const evidence = acmgRatingStore.acmgRating.getCriteriaState(criteria).evidenceLevel
if (evidence === AcmgEvidenceLevel.PathogenicVeryStrong) {
return 'red-accent-4'
} else if (evidence === AcmgEvidenceLevel.PathogenicStrong) {
return 'orange-darken-4'
} else if (evidence === AcmgEvidenceLevel.PathogenicModerate) {
return 'amber-darken-4'
} else if (evidence === AcmgEvidenceLevel.PathogenicSupporting) {
return 'yellow-darken-3'
} else if (evidence === AcmgEvidenceLevel.BenignStandalone) {
return 'green-darken-4'
} else if (evidence === AcmgEvidenceLevel.BenignStrong) {
return 'light-green'
} else if (evidence === AcmgEvidenceLevel.BenignSupporting) {
return 'lime'
} else {
return 'primary'
}
}
const switchCriteria = (criteria: AcmgCriteria, presence: Presence) => {
if (presence === Presence.Present) {
acmgRatingStore.acmgRating.setPresence(StateSource.User, criteria, Presence.Absent)
} else {
acmgRatingStore.acmgRating.setPresence(StateSource.User, criteria, Presence.Present)
}
}
watch(
() => [props.smallVariant, acmgRatingStore.storeState],
async () => {
Expand Down Expand Up @@ -153,19 +122,10 @@ onMounted(async () => {
)
"
>
<v-switch
:color="findSwitchColor(criteria)"
:label="criteria"
:model-value="
acmgRatingStore.acmgRating.getCriteriaState(criteria).presence === Presence.Present
"
@update:model-value="
switchCriteria(
criteria,
acmgRatingStore.acmgRating.getCriteriaState(criteria).presence
)
"
style="margin-right: 20px"
<AcmgCriteriaCard
:acmg-rating="acmgRatingStore.acmgRating"
:criteria="criteria"
:criteria-state="acmgRatingStore.acmgRating.getCriteriaState(criteria)"
/>
</div>
</div>
Expand All @@ -183,19 +143,10 @@ onMounted(async () => {
)
"
>
<v-switch
:color="findSwitchColor(criteria)"
:label="criteria"
:model-value="
acmgRatingStore.acmgRating.getCriteriaState(criteria).presence === Presence.Present
"
@update:model-value="
switchCriteria(
criteria,
acmgRatingStore.acmgRating.getCriteriaState(criteria).presence
)
"
style="margin-right: 20px"
<AcmgCriteriaCard
:acmg-rating="acmgRatingStore.acmgRating"
:criteria="criteria"
:criteria-state="acmgRatingStore.acmgRating.getCriteriaState(criteria)"
/>
</div>
</div>
Expand All @@ -221,44 +172,11 @@ onMounted(async () => {
Presence.Present || showFailed
"
>
<v-card class="mx-auto" width="200" style="margin: 10px">
<div class="d-flex justify-content-between">
<v-switch
:color="findSwitchColor(criteria)"
:label="criteria"
:model-value="
acmgRatingStore.acmgRating.getCriteriaState(criteria).presence ===
Presence.Present
"
@update:model-value="
switchCriteria(
criteria,
acmgRatingStore.acmgRating.getCriteriaState(criteria).presence
)
"
style="margin-right: 20px; margin-left: 10px"
/>
<v-tooltip :text="ACMG_CRITERIA_DEFS.get(criteria)?.hint">
<template v-slot:activator="{ props }">
<v-icon style="margin: 10px" v-bind="props">mdi-information</v-icon>
</template>
</v-tooltip>
</div>
<v-divider />
<v-select
:model-value="acmgRatingStore.acmgRating.getCriteriaState(criteria).evidenceLevel"
@update:model-value="
acmgRatingStore.acmgRating.setEvidenceLevel(StateSource.User, criteria, $event)
"
:items="
ACMG_EVIDENCE_LEVELS_PATHOGENIC.includes(
acmgRatingStore.acmgRating.getCriteriaState(criteria).evidenceLevel
)
? ACMG_EVIDENCE_LEVELS_PATHOGENIC
: ACMG_EVIDENCE_LEVELS_BENIGN
"
></v-select>
</v-card>
<AcmgCriteriaCard
:acmg-rating="acmgRatingStore.acmgRating"
:criteria="criteria"
:criteria-state="acmgRatingStore.acmgRating.getCriteriaState(criteria)"
/>
</td>
<td
v-if="
Expand Down
67 changes: 67 additions & 0 deletions frontend/src/components/__tests__/AcmgCriteriaCard.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { describe, expect, it, vi } from 'vitest'
import { mount } from '@vue/test-utils'
import { createRouter, createWebHistory } from 'vue-router'
import { routes } from '@/router'

import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'

import { MultiSourceAcmgCriteriaState, StateSource, AcmgCriteria, Presence } from '@/lib/acmgSeqVar'
import AcmgCriteriaCard from '@/components/AcmgCriteriaCard.vue'

const vuetify = createVuetify({
components,
directives
})

const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: routes
})
// Mock router push
router.push = vi.fn()

const makeWrapper = () => {
const acmgRating = new MultiSourceAcmgCriteriaState()
acmgRating.setPresence(StateSource.InterVar, AcmgCriteria.Pvs1, Presence.Present)
const criteria = AcmgCriteria.Pvs1
const criteriaState = acmgRating.getCriteriaState(criteria)

return mount(AcmgCriteriaCard, {
props: {
acmgRating: acmgRating,
criteria: criteria,
criteriaState: criteriaState
},
global: {
plugins: [vuetify, router],
components: {
AcmgCriteriaCard
}
}
})
}

describe.concurrent('AcmgCriteriaCard', async () => {
it('renders the AcmgRating info', async () => {
const wrapper = makeWrapper()
expect(wrapper.text()).toContain('Pathogenic')
expect(wrapper.text()).toContain('Pvs1')

const switcher = wrapper.find('.v-switch')
expect(switcher.text()).toContain('Pvs1')

const selection = wrapper.find('.v-select')
expect(selection.text()).toContain('Pathogenic')
})

it('should correctly update the AcmgCriteriaCard info', async () => {
const wrapper = makeWrapper()
const switcher = wrapper.find('.v-switch')
await switcher.trigger('click')

const selection = wrapper.find('.v-select')
await selection.trigger('click')
})
})

0 comments on commit 7c463f1

Please sign in to comment.