Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: add missing dependencies in hooks #286

Open
wants to merge 2 commits into
base: 2.3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 54 additions & 43 deletions src/ui/components/orchestrator/tools/useQueenNavigation.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'

import type {
CollectedValues,
Expand Down Expand Up @@ -105,7 +105,7 @@ export function useQueenNavigation({
}
}

const handleData = () => {
const handleData = useCallback(() => {
const changedData = getChangedData(true) as SurveyUnitData
const hasDataChanged = getHasDataChanged(changedData)

Expand All @@ -119,51 +119,54 @@ export function useQueenNavigation({
hasDataChanged: hasDataChanged,
data: newData,
}
}
}, [getChangedData, surveyUnitData])

// updates the surveyUnitState
const updateState = (newState: QuestionnaireState) => {
onChangeSurveyUnitState({
surveyUnitId: initialSurveyUnit.id,
newState: newState,
})
setSurveyUnitState(newState)
}
const updateState = useCallback(
(newState: QuestionnaireState) => {
onChangeSurveyUnitState({
surveyUnitId: initialSurveyUnit.id,
newState: newState,
})
setSurveyUnitState(newState)
},
[initialSurveyUnit.id, onChangeSurveyUnitState],
)

const handleState = (
hasDataChanged: boolean,
forcedState?: QuestionnaireState,
) => {
// forcedState is used for definitiveQuit which forces the validation
if (forcedState) {
updateState(forcedState)
return forcedState
}
// calculates the new state : currently the only (calculable) possible change is into INIT if data changed
const newState = hasDataChanged ? 'INIT' : surveyUnitState
// updates state only if necessary : prevents for calling onChangeSurveyUnitState
if (newState !== surveyUnitState) {
updateState(newState)
}
return newState
}
const handleState = useCallback(
(hasDataChanged: boolean, forcedState?: QuestionnaireState) => {
// forcedState is used for definitiveQuit which forces the validation
if (forcedState) {
updateState(forcedState)
return forcedState
}
// calculates the new state : currently the only (calculable) possible change is into INIT if data changed
const newState = hasDataChanged ? 'INIT' : surveyUnitState
// updates state only if necessary : prevents for calling onChangeSurveyUnitState
if (newState !== surveyUnitState) {
updateState(newState)
}
return newState
},
[surveyUnitState, updateState],
)

// get the updated SurveyUnit
const getUpdatedSurveyUnit = (
state: QuestionnaireState,
data: SurveyUnitData,
): SurveyUnit => {
const surveyUnit = {
...initialSurveyUnit,
data,
stateData: {
state: state,
date: new Date().getTime(),
currentPage: pageTag ?? '1',
},
}
return surveyUnit
}
const getUpdatedSurveyUnit = useCallback(
(state: QuestionnaireState, data: SurveyUnitData): SurveyUnit => {
const surveyUnit = {
...initialSurveyUnit,
data,
stateData: {
state: state,
date: new Date().getTime(),
currentPage: pageTag ?? '1',
},
}
return surveyUnit
},
[initialSurveyUnit, pageTag],
)

// handle updated surveyUnit when page changes
useEffect(() => {
Expand All @@ -182,7 +185,15 @@ export function useQueenNavigation({

const surveyUnit = getUpdatedSurveyUnit(state, data)
return onChangePage(surveyUnit)
}, [pageTag, lastReachedPage, isWelcomeModalOpen])
}, [
pageTag,
lastReachedPage,
isWelcomeModalOpen,
handleData,
handleState,
getUpdatedSurveyUnit,
onChangePage,
])

const orchestratorQuit = () => {
// get updated data
Expand Down
2 changes: 1 addition & 1 deletion src/ui/pages/synchronize/SynchronizeData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export function SynchronizeData() {

useEffect(() => {
synchronizeData.upload()
}, [])
}, [synchronizeData])

const { evtSynchronizeData } = useCore().evts

Expand Down
145 changes: 145 additions & 0 deletions src/ui/pages/synchronize/synchronizeData.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import { render, waitFor } from '@testing-library/react'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'

import { useCore, useCoreState } from '@/core'

import { SynchronizeData } from './SynchronizeData'

vi.mock('@/i18n', () => ({
useTranslation: () => ({ t: (keyMessage: string) => keyMessage }),
getTranslation: () => ({ t: (keyMessage: string) => keyMessage }),
}))

vi.mock('@/core') // Global mock of `useCore` and `useCoreState`

const mockUpload = vi.fn()
const mockAttach = vi.fn()

describe('SynchronizeData', () => {
beforeEach(() => {
vi.resetAllMocks()

// Default Mock Implementations
vi.mocked(useCoreState).mockReturnValue({
hideProgress: false,
isUploading: false,
isDownloading: false,
uploadProgress: 0,
})

vi.mocked(useCore).mockReturnValue({
functions: {
synchronizeData: {
upload: mockUpload,
},
},
evts: {
evtSynchronizeData: {
$attach: mockAttach,
},
},
} as any) // type issues
})

afterEach(() => vi.clearAllMocks())

it('should call synchronizeData.upload on mount', async () => {
render(<SynchronizeData />)

await waitFor(() => {
expect(mockUpload).toHaveBeenCalled()
})
})

it('should render the loading display with upload progress', async () => {
vi.mocked(useCoreState).mockReturnValue({
hideProgress: false,
isUploading: true,
isDownloading: false,
uploadProgress: 50,
})

const { getByText, getByRole } = render(<SynchronizeData />)

// Check that loading display is rendered with upload progress
expect(getByText('synchronizationInProgress')).toBeInTheDocument()
expect(getByText('uploadingData')).toBeInTheDocument()

const uploadProgressBar = getByRole('progressbar')
expect(uploadProgressBar).toHaveAttribute('aria-valuenow', '50')
})

it('should render the loading display with download progress', () => {
vi.mocked(useCoreState).mockReturnValue({
hideProgress: false,
isUploading: false,
isDownloading: true,
surveyProgress: 10,
nomenclatureProgress: 20,
surveyUnitProgress: 30,
externalResourcesProgress: 15,
externalResourcesProgressCount: {
totalExternalResources: 10,
externalResourcesCompleted: 2,
},
})

const { getByText, getAllByRole } = render(<SynchronizeData />)

// Check that loading display is rendered with download progress
expect(getByText('synchronizationInProgress')).toBeInTheDocument()
expect(getByText('downloadingData')).toBeInTheDocument()

// Check that all progress bars are rendered
const progressBars = getAllByRole('progressbar')
expect(progressBars).toHaveLength(4)

// Check that extra title for external resources progress is displayed
expect(getByText('externalResourcesProgress: 2 / 10')).toBeInTheDocument()
})

it('should not render external resources progress bar only if externalResourcesProgress and externalResourcesProgressCount are undefined', () => {
vi.mocked(useCoreState).mockReturnValue({
hideProgress: false,
isUploading: false,
isDownloading: true,
surveyProgress: 10,
nomenclatureProgress: 20,
surveyUnitProgress: 30,
externalResourcesProgress: undefined, // No external resources
externalResourcesProgressCount: undefined,
} as any)

const { getAllByRole, queryByText } = render(<SynchronizeData />)

// Check that all progress bars are rendered except external resources
const progressBars = getAllByRole('progressbar')
expect(progressBars).toHaveLength(3)

// Check that the external resources progress is not displayed
expect(queryByText('externalResourcesProgress')).not.toBeInTheDocument()
})

it('should not render anything if hideProgress is true', () => {
vi.mocked(useCoreState).mockReturnValue({
hideProgress: true,
})

const { queryByText } = render(<SynchronizeData />)

expect(queryByText('synchronizationInProgress')).toBeNull()
})

it('should handle redirect event correctly', async () => {
render(<SynchronizeData />)

// Check that window.location is updated
await waitFor(() => {
// Normalize both URLs before comparison. No better idea since the redirect adds a "/" at the end of url
const actualLocation = window.location.toString().replace(/\/$/, '')
const expectedLocation = window.location.origin.replace(/\/$/, '')

expect(actualLocation).toBe(expectedLocation)
})
})
})
2 changes: 1 addition & 1 deletion src/ui/routing/NavigationManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export function NavigationManager({ children }: PropsWithChildren) {
return () => {
window.removeEventListener('[Pearl] navigated', shellNavigationHandler)
}
}, [location])
}, [location, navigate])

useEffect(() => {
window.dispatchEvent(
Expand Down
Loading