/**
* Tests for Resource component - resource.tsx
*
* TDD RED phase: Write failing tests first.
*
* This includes tests for the stale registration bug (ui-n710):
* Resource component uses useEffect with only [name] dependency,
* so changing view components after mount doesn't update the registry.
*/
import { describe, it, expect } from 'vitest'
import { render, screen, act } from '@testing-library/react'
import { useState, type FC } from 'react'
import { Resource } from '../../components/resource'
import { ResourcesProvider, useResources, type ResourcesContextValue } from '../../context/resource-context'
import type { Identifier } from '../../types'
// Mock view components
const MockListComponent: FC = () =>
// Helper to access resources context
function ResourcesInspector({ onResources }: { onResources: (ctx: ResourcesContextValue) => void }) {
const ctx = useResources()
onResources(ctx)
return null
}
describe('Resource Component', () => {
describe('basic registration', () => {
it('should register resource with ResourcesProvider on mount', () => {
let ctx: ResourcesContextValue | undefined
render(
{ ctx = c }} />
)
const resource = ctx!.getResource('users')
expect(resource).toBeDefined()
expect(resource?.name).toBe('users')
expect(resource?.label).toBe('Users')
expect(resource?.list).toBe(MockListComponent)
})
it('should unregister resource on unmount', () => {
let ctx: ResourcesContextValue | undefined
const { unmount } = render(
{ ctx = c }} />
)
expect(ctx!.getResource('users')).toBeDefined()
unmount()
// After unmount, the resource should be unregistered
// Note: We need to re-render the inspector to see the change
// This is tricky to test because unmount removes everything
})
it('should re-register when name changes', () => {
let ctx: ResourcesContextValue | undefined
function DynamicResource() {
const [name, setName] = useState('users')
return (
{ ctx = c }} />
)
}
render()
expect(ctx!.getResource('users')).toBeDefined()
expect(ctx!.getResource('products')).toBeUndefined()
act(() => {
screen.getByTestId('change-name').click()
})
// Old resource should be unregistered, new one registered
expect(ctx!.getResource('users')).toBeUndefined()
expect(ctx!.getResource('products')).toBeDefined()
})
})
describe('stale registration bug (ui-n710)', () => {
/**
* This test demonstrates the stale registration bug:
*
* When view components (list, edit, etc.) change but name stays the same,
* the registry doesn't get updated because useEffect only depends on [name].
*
* Current behavior: The registry keeps the OLD definition
* Expected behavior: The registry should have the UPDATED definition
*/
it('should update registry when view components change (currently FAILING due to stale registration)', () => {
let ctx: ResourcesContextValue | undefined
function DynamicResource() {
const [editComponent, setEditComponent] = useState | undefined>(undefined)
return (
{ ctx = c }} />
)
}
render()
// Initially no edit component
expect(ctx!.getResource('users')?.edit).toBeUndefined()
expect(ctx!.getResource('users')?.list).toBe(MockListComponent)
// Add edit component - the Resource's props change but name stays the same
act(() => {
screen.getByTestId('add-edit').click()
})
// BUG: This assertion will FAIL because useEffect only runs when name changes
// The registry still has the OLD definition without the edit component
//
// Expected: UpdatedEditComponent
// Actual: undefined (stale definition)
expect(ctx!.getResource('users')?.edit).toBe(UpdatedEditComponent)
})
it('should update registry when label changes', () => {
let ctx: ResourcesContextValue | undefined
function DynamicResource() {
const [label, setLabel] = useState('Users')
return (
{ ctx = c }} />
)
}
render()
expect(ctx!.getResource('users')?.label).toBe('Users')
act(() => {
screen.getByTestId('change-label').click()
})
// BUG: This will also fail due to stale registration
expect(ctx!.getResource('users')?.label).toBe('Updated Users')
})
})
describe('children rendering', () => {
it('should render children wrapped in ResourceProvider when provided', () => {
render(
Child Content
)
expect(screen.getByTestId('child-content').textContent).toBe('Child Content')
})
it('should not render anything when no children provided', () => {
const { container } = render(
)
// Resource returns null when no children
expect(container.querySelector('[data-testid]')).toBeNull()
})
})
})