fix(studio): preserve health advisor hook order

This commit is contained in:
Jordi Enric committed 2026-09-10 13:57:55 +02:00
1 parent 557c84a300
commit 1134871b8b
2 files changed
+110 -15

No files matched your search

@@ -20,24 +20,10 @@ import type { NextPageWithLayout } from '@/types'
const ProjectHealthLints: NextPageWithLayout = () => {
const router = useRouter()
const { preset, id, ref } = useParams()
const { data: project } = useSelectedProjectQuery()
const { ref } = useParams()
const { hasLoaded: flagsLoaded } = useFeatureFlags()
const isHealthAdvisorEnabled = useFlag('healthAdvisor') === true
const [filters, setFilters] = useState<{ level: LINTER_LEVELS; filters: string[] }[]>([
{ level: LINTER_LEVELS.ERROR, filters: [] },
{ level: LINTER_LEVELS.WARN, filters: [] },
{ level: LINTER_LEVELS.INFO, filters: [] },
])
const [currentTab, setCurrentTab] = useState<LINTER_LEVELS>(
parseLinterLevel(preset) ?? LINTER_LEVELS.ERROR
)
const { data, isPending, isRefetching, refetch } = useProjectHealthLintsQuery(
{ projectRef: project?.ref },
{ enabled: isHealthAdvisorEnabled }
)
const isFlagLoading = IS_PLATFORM && !flagsLoaded
useEffect(() => {
@@ -48,6 +34,25 @@ const ProjectHealthLints: NextPageWithLayout = () => {
if (isFlagLoading || !isHealthAdvisorEnabled) return null
return <ProjectHealthLintsContent />
}
const ProjectHealthLintsContent = () => {
const { preset, id } = useParams()
const { data: project } = useSelectedProjectQuery()
const [filters, setFilters] = useState<{ level: LINTER_LEVELS; filters: string[] }[]>([
{ level: LINTER_LEVELS.ERROR, filters: [] },
{ level: LINTER_LEVELS.WARN, filters: [] },
{ level: LINTER_LEVELS.INFO, filters: [] },
])
const [currentTab, setCurrentTab] = useState<LINTER_LEVELS>(
parseLinterLevel(preset) ?? LINTER_LEVELS.ERROR
)
const { data, isPending, isRefetching, refetch } = useProjectHealthLintsQuery({
projectRef: project?.ref,
})
// Health checks are platform-only. If this page is opened self-hosted the query stays
// disabled, and `isPending` would otherwise spin forever.
const isLoading = IS_PLATFORM && isPending
@@ -0,0 +1,90 @@
import { render, screen } from '@testing-library/react'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import ProjectHealthLints from '@/pages/project/[ref]/advisors/health'
const mocks = vi.hoisted(() => ({
flags: { hasLoaded: false, enabled: false },
router: { replace: vi.fn() },
query: vi.fn(),
shortcuts: vi.fn(),
}))
vi.mock('common', () => ({
useFeatureFlags: () => ({ hasLoaded: mocks.flags.hasLoaded }),
useFlag: () => mocks.flags.enabled,
useParams: () => ({ ref: 'test-project' }),
}))
vi.mock('next/router', () => ({ useRouter: () => mocks.router }))
vi.mock('@/lib/constants', () => ({ IS_PLATFORM: true }))
vi.mock('@/hooks/misc/useSelectedProject', () => ({
useSelectedProjectQuery: () => ({ data: { ref: 'test-project' } }),
}))
vi.mock('@/data/lint/health-lints-query', () => ({
useProjectHealthLintsQuery: mocks.query,
}))
vi.mock('@/components/interfaces/Linter/useAdvisorPageShortcuts', () => ({
useAdvisorPageShortcuts: mocks.shortcuts,
}))
vi.mock('@/components/interfaces/Linter/Linter.utils', () => ({
lintInfoMap: [],
parseLinterLevel: () => undefined,
}))
vi.mock('@/components/interfaces/Linter/LinterDataGrid', () => ({
LinterDataGrid: () => null,
}))
vi.mock('@/components/interfaces/Linter/LinterFilters', () => ({ default: () => null }))
vi.mock('@/components/interfaces/Linter/LintPageTabs', () => ({ default: () => null }))
vi.mock('@/components/layouts/AdvisorsLayout/AdvisorsLayout', () => ({ default: () => null }))
vi.mock('@/components/layouts/DefaultLayout', () => ({ DefaultLayout: () => null }))
vi.mock('@/components/ui/Forms/FormHeader', () => ({
FormHeader: ({ title }: { title: string }) => <h1>{title}</h1>,
}))
vi.mock('ui', () => ({ LoadingLine: () => null }))
describe('Health Advisor feature flag', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.flags.hasLoaded = false
mocks.flags.enabled = false
mocks.query.mockReturnValue({
data: [],
isPending: false,
isRefetching: false,
refetch: vi.fn(),
})
})
it('renders after the flag loads without changing hook order', () => {
const { rerender } = render(<ProjectHealthLints />)
expect(screen.queryByRole('heading', { name: 'Health Advisor' })).not.toBeInTheDocument()
expect(mocks.router.replace).not.toHaveBeenCalled()
expect(mocks.query).not.toHaveBeenCalled()
expect(mocks.shortcuts).not.toHaveBeenCalled()
mocks.flags.hasLoaded = true
mocks.flags.enabled = true
rerender(<ProjectHealthLints />)
expect(screen.getByRole('heading', { name: 'Health Advisor' })).toBeInTheDocument()
expect(mocks.query).toHaveBeenCalled()
expect(mocks.shortcuts).toHaveBeenCalled()
expect(mocks.router.replace).not.toHaveBeenCalled()
mocks.flags.enabled = false
rerender(<ProjectHealthLints />)
expect(screen.queryByRole('heading', { name: 'Health Advisor' })).not.toBeInTheDocument()
expect(mocks.router.replace).toHaveBeenCalledWith('/project/test-project/advisors/security')
})
it('redirects when the loaded flag is disabled without mounting health functionality', () => {
mocks.flags.hasLoaded = true
render(<ProjectHealthLints />)
expect(mocks.router.replace).toHaveBeenCalledWith('/project/test-project/advisors/security')
expect(mocks.query).not.toHaveBeenCalled()
expect(mocks.shortcuts).not.toHaveBeenCalled()
})
})