Skip to content

Commit

Permalink
Merge pull request #114 from bcgov/feature/frontend-model-param-unit-…
Browse files Browse the repository at this point in the history
…test

Frontend: Unit Test #3 - modelParameterService and modelParameterStore
  • Loading branch information
vividroyjeong authored Jan 28, 2025
2 parents 94b4c88 + 2ea1f13 commit fa370bc
Show file tree
Hide file tree
Showing 4 changed files with 419 additions and 3 deletions.
168 changes: 168 additions & 0 deletions frontend/cypress/e2e/unit/modelParameterService.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/// <reference types="cypress" />

import { createCSVFiles, runModel } from '@/services/modelParameterService'
import { SelectedExecutionOptionsEnum } from '@/services/vdyp-api'
import * as apiActions from '@/services/apiActions'
import { CONSTANTS, DEFAULTS, OPTIONS } from '@/constants'
import sinon from 'sinon'

describe('Model Parameter Service Unit Tests', () => {
const mockModelParameterStore = {
derivedBy: DEFAULTS.DEFAULT_VALUES.DERIVED_BY,
speciesList: [
{ species: 'PL', percent: '30.0' },
{ species: 'AC', percent: '30.0' },
{ species: 'H', percent: '30.0' },
{ species: 'S', percent: '10.0' },
{ species: null, percent: '0.0' },
{ species: null, percent: '0.0' },
],
becZone: DEFAULTS.DEFAULT_VALUES.BEC_ZONE,
ecoZone: OPTIONS.ecoZoneOptions[0].value,
incSecondaryHeight: false,
highestPercentSpecies: 'PL',
bha50SiteIndex: DEFAULTS.DEFAULT_VALUES.BHA50_SITE_INDEX,
percentStockableArea: DEFAULTS.DEFAULT_VALUES.PERCENT_STOCKABLE_AREA,
startingAge: DEFAULTS.DEFAULT_VALUES.STARTING_AGE,
finishingAge: DEFAULTS.DEFAULT_VALUES.FINISHING_AGE,
ageIncrement: DEFAULTS.DEFAULT_VALUES.AGE_INCREMENT,
}

let projectionStub: sinon.SinonStub

beforeEach(() => {
projectionStub = sinon
.stub(apiActions, 'projectionHcsvPost')
.resolves(new Blob(['mock response'], { type: 'application/json' }))
})

afterEach(() => {
projectionStub.restore()
})

it('should call projectionHcsvPost once', async () => {
await runModel(mockModelParameterStore)
expect(projectionStub.calledOnce).to.be.true
})

it('should create CSV files correctly', () => {
const { blobPolygon, blobLayer } = createCSVFiles(mockModelParameterStore)

cy.wrap(blobPolygon).should('be.instanceOf', Blob)
cy.wrap(blobLayer).should('be.instanceOf', Blob)

const derivedByCode =
mockModelParameterStore.derivedBy === CONSTANTS.DERIVED_BY.VOLUME
? CONSTANTS.INVENTORY_CODES.FIP
: mockModelParameterStore.derivedBy === CONSTANTS.DERIVED_BY.BASAL_AREA
? CONSTANTS.INVENTORY_CODES.VRI
: ''

cy.wrap(blobPolygon.text()).then((text) => {
expect(text).to.include(derivedByCode)
expect(text).to.include(mockModelParameterStore.becZone)
expect(text).to.include(mockModelParameterStore.ecoZone)
expect(text).to.include(mockModelParameterStore.percentStockableArea)
})

cy.wrap(blobLayer.text()).then((text) => {
expect(text).to.include(mockModelParameterStore.highestPercentSpecies)
expect(text).to.include(mockModelParameterStore.bha50SiteIndex)
expect(text).to.include('PL,30.0')
expect(text).to.include('AC,30.0')
expect(text).to.include('H,30.0')
expect(text).to.include('S,10.0')
})
})

it('should reject with error for invalid model parameters', async () => {
const invalidModelParameterStore = {
...mockModelParameterStore,
startingAge: null,
}

try {
await runModel(invalidModelParameterStore)
} catch (error) {
expect(error).to.be.an('error')
}
})

it('should call projectionHcsvPost with correct form data', async () => {
await runModel(mockModelParameterStore)

expect(projectionStub.calledOnce).to.be.true
const formDataArg = projectionStub.getCall(0).args[0] as FormData

expect(formDataArg.has('polygonInputData')).to.be.true
expect(formDataArg.has('layersInputData')).to.be.true
expect(formDataArg.has('projectionParameters')).to.be.true
})

it('should include additional options when secondary height is enabled', async () => {
const updatedModelParameterStore = {
...mockModelParameterStore,
incSecondaryHeight: true,
}

await runModel(updatedModelParameterStore)

const formDataArg = projectionStub.getCall(0).args[0] as FormData
const projectionParamsBlob = formDataArg.get('projectionParameters') as Blob

const projectionParamsText = await projectionParamsBlob.text()
const projectionParams = JSON.parse(projectionParamsText)

expect(projectionParams.selectedExecutionOptions).to.include(
SelectedExecutionOptionsEnum.DoIncludeSecondarySpeciesDominantHeightInYieldTable,
)
})

it('should contain expected execution options', async () => {
await runModel(mockModelParameterStore)

const formDataArg = projectionStub.getCall(0).args[0] as FormData
const projectionParamsBlob = formDataArg.get('projectionParameters') as Blob

const projectionParamsText = await projectionParamsBlob.text()
const projectionParams = JSON.parse(projectionParamsText)

expect(projectionParams.selectedExecutionOptions).to.include(
SelectedExecutionOptionsEnum.DoEnableProgressLogging,
SelectedExecutionOptionsEnum.DoEnableErrorLogging,
)
})

it('should handle empty species list without errors', async () => {
const emptySpeciesStore = {
...mockModelParameterStore,
speciesList: new Array(6).fill({ species: null, percent: '0.0' }),
}

await runModel(emptySpeciesStore)
expect(projectionStub.calledOnce).to.be.true
})

it('should handle missing fields in model parameter store', () => {
const invalidStore = {
...mockModelParameterStore,
becZone: undefined,
}

const { blobPolygon } = createCSVFiles(invalidStore)
cy.wrap(blobPolygon.text()).then((text) => {
expect(text).not.to.include(DEFAULTS.DEFAULT_VALUES.BEC_ZONE)
})
})

it('should handle projectionHcsvPost failure', async () => {
projectionStub.rejects(new Error('API call failed'))

try {
await runModel(mockModelParameterStore)
} catch (error) {
expect(error).to.be.an('error')
expect(error.message).to.equal('API call failed')
}
})
})
120 changes: 120 additions & 0 deletions frontend/cypress/e2e/unit/modelParameterStore.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/// <reference types="cypress" />

import { setActivePinia, createPinia } from 'pinia'
import { useModelParameterStore } from '@/stores/modelParameterStore'
import { CONSTANTS, DEFAULTS } from '@/constants'

describe('ModelParameterStore Unit Tests', () => {
let store: ReturnType<typeof useModelParameterStore>

beforeEach(() => {
setActivePinia(createPinia())
store = useModelParameterStore()
})

it('should initialize with default values', () => {
expect(store.panelOpenStates.speciesInfo).to.equal(CONSTANTS.PANEL.OPEN)
expect(store.panelOpenStates.siteInfo).to.equal(CONSTANTS.PANEL.CLOSE)
expect(store.panelOpenStates.standDensity).to.equal(CONSTANTS.PANEL.CLOSE)
expect(store.panelOpenStates.reportInfo).to.equal(CONSTANTS.PANEL.CLOSE)

expect(store.panelState.speciesInfo.confirmed).to.be.false
expect(store.panelState.speciesInfo.editable).to.be.true

expect(store.panelState.siteInfo.confirmed).to.be.false
expect(store.panelState.siteInfo.editable).to.be.false

expect(store.panelState.standDensity.confirmed).to.be.false
expect(store.panelState.standDensity.editable).to.be.false

expect(store.panelState.reportInfo.confirmed).to.be.false
expect(store.panelState.reportInfo.editable).to.be.false

expect(store.runModelEnabled).to.be.false
})

it('should confirm panel and enable the next panel', () => {
store.confirmPanel(CONSTANTS.MODEL_PARAMETER_PANEL.SPECIES_INFO)

expect(store.panelState.speciesInfo.confirmed).to.be.true
expect(store.panelState.speciesInfo.editable).to.be.false
expect(store.panelOpenStates.siteInfo).to.equal(CONSTANTS.PANEL.OPEN)
expect(store.panelState.siteInfo.editable).to.be.true
})

it('should edit panel and disable subsequent panels', () => {
store.confirmPanel(CONSTANTS.MODEL_PARAMETER_PANEL.SPECIES_INFO)
store.editPanel(CONSTANTS.MODEL_PARAMETER_PANEL.SPECIES_INFO)

expect(store.panelOpenStates.speciesInfo).to.equal(CONSTANTS.PANEL.OPEN)
expect(store.panelState.speciesInfo.confirmed).to.be.false
expect(store.panelState.speciesInfo.editable).to.be.true

expect(store.panelOpenStates.siteInfo).to.equal(CONSTANTS.PANEL.CLOSE)
expect(store.panelState.siteInfo.confirmed).to.be.false
expect(store.panelState.siteInfo.editable).to.be.false

expect(store.panelOpenStates.standDensity).to.equal(CONSTANTS.PANEL.CLOSE)
expect(store.panelState.standDensity.confirmed).to.be.false
expect(store.panelState.standDensity.editable).to.be.false

expect(store.panelOpenStates.reportInfo).to.equal(CONSTANTS.PANEL.CLOSE)
expect(store.panelState.reportInfo.confirmed).to.be.false
expect(store.panelState.reportInfo.editable).to.be.false
})

it('should calculate total species percent correctly', () => {
store.speciesList = [
{ species: 'PL', percent: '40.0' },
{ species: 'AC', percent: '35.5' },
{ species: 'H', percent: '24.5' },
{ species: 'S', percent: null },
{ species: null, percent: null },
{ species: null, percent: null },
]
expect(store.totalSpeciesPercent).to.equal('100.0')
})

it('should update species groups correctly', () => {
store.speciesList = [
{ species: 'PL', percent: '50.0' },
{ species: 'PL', percent: '30.0' },
{ species: 'AC', percent: '20.0' },
{ species: null, percent: null },
]
store.updateSpeciesGroup()

expect(store.speciesGroups.length).to.equal(2)
expect(store.speciesGroups[0].group).to.equal('PL')
expect(store.speciesGroups[0].percent).to.equal('80.0')
expect(store.speciesGroups[1].group).to.equal('AC')
expect(store.speciesGroups[1].percent).to.equal('20.0')
})

it('should set default values correctly', () => {
store.setDefaultValues()

expect(store.derivedBy).to.equal(DEFAULTS.DEFAULT_VALUES.DERIVED_BY)
expect(store.speciesList[0].species).to.equal('PL')
expect(store.speciesList[0].percent).to.equal('30.0')
expect(store.becZone).to.equal(DEFAULTS.DEFAULT_VALUES.BEC_ZONE)
expect(store.startingAge).to.equal(DEFAULTS.DEFAULT_VALUES.STARTING_AGE)
})

it('should handle empty species list without errors', () => {
store.speciesList = []
store.updateSpeciesGroup()

expect(store.speciesGroups.length).to.equal(0)
expect(store.highestPercentSpecies).to.be.null
})

it('should enable run model button when all panels are confirmed', () => {
store.confirmPanel(CONSTANTS.MODEL_PARAMETER_PANEL.SPECIES_INFO)
store.confirmPanel(CONSTANTS.MODEL_PARAMETER_PANEL.SITE_INFO)
store.confirmPanel(CONSTANTS.MODEL_PARAMETER_PANEL.STAND_DENSITY)
store.confirmPanel(CONSTANTS.MODEL_PARAMETER_PANEL.REPORT_INFO)

expect(store.runModelEnabled).to.be.true
})
})
Loading

0 comments on commit fa370bc

Please sign in to comment.