Repository: langgenius/dify
File: web/app/components/apps/__tests__/list.spec.tsx (TypeScript)

Code before:
  default: () => {
    return React.createElement('footer', { 'data-testid': 'footer', 'role': 'contentinfo' }, 'Footer')
  },
}))

let intersectionCallback: IntersectionObserverCallback | null = null
const mockObserve = vi.fn()
const mockDisconnect = vi.fn()

beforeAll(() => {
  globalThis.IntersectionObserver = class MockIntersectionObserver {
    constructor(callback: IntersectionObserverCallback) {
      intersectionCallback = callback
    }

    observe = mockObserve
    disconnect = mockDisconnect
    unobserve = vi.fn()
    root = null
    rootMargin = ''
    thresholds = []
    takeRecords = () => []
  } as unknown as typeof IntersectionObserver
})

// Render helper wrapping with NuqsTestingAdapter
const onUrlUpdate = vi.fn<(event: UrlUpdateEvent) => void>()
const renderList = (searchParams = '') => {
  const wrapper = ({ children }: { children: ReactNode }) => (
    <NuqsTestingAdapter searchParams={searchParams} onUrlUpdate={onUrlUpdate}>
      {children}
    </NuqsTestingAdapter>
  )
  return render(<List />, { wrapper })
}

describe('List', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    onUrlUpdate.mockClear()
    useTagStore.setState({
      tagList: [{ id: 'tag-1', name: 'Test Tag', type: 'app', binding_count: 0 }],
      showTagManagementModal: false,
    })
    mockIsCurrentWorkspaceEditor.mockReturnValue(true)
    mockIsCurrentWorkspaceDatasetOperator.mockReturnValue(false)
    mockDragging = false
    mockOnDSLFileDropped = null
    mockServiceState.error = null
    mockServiceState.hasNextPage = false
    mockServiceState.isLoading = false

Review comment (suggestion):
onUrlUpdate is created at the top level of the module and shared across all tests. However, there is no beforeEach(() => onUrlUpdate.mockClear()) to reset it, which could lead to state leakage between tests.

Code after:
    return React.createElement('footer', { 'data-testid': 'footer', 'role': 'contentinfo' }, 'Footer')
  },
}))

let intersectionCallback: IntersectionObserverCallback | null = null
const mockObserve = vi.fn()
const mockDisconnect = vi.fn()

beforeAll(() => {
  globalThis.IntersectionObserver = class MockIntersectionObserver {
    constructor(callback: IntersectionObserverCallback) {
      intersectionCallback = callback
    }

    observe = mockObserve
    disconnect = mockDisconnect
    unobserve = vi.fn()
    root = null
    rootMargin = ''
    thresholds = []
    takeRecords = () => []
  } as unknown as typeof IntersectionObserver
})

// Render helper wrapping with shared nuqs testing helper.
const onUrlUpdate = vi.fn<(event: UrlUpdateEvent) => void>()
const renderList = (searchParams = '') => {
  return renderWithNuqs(
    <List />,
    { searchParams, onUrlUpdate },
  )
}

describe('List', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    onUrlUpdate.mockClear()
    useTagStore.setState({
      tagList: [{ id: 'tag-1', name: 'Test Tag', type: 'app', binding_count: 0 }],
      showTagManagementModal: false,
    })
    mockIsCurrentWorkspaceEditor.mockReturnValue(true)
    mockIsCurrentWorkspaceDatasetOperator.mockReturnValue(false)
    mockDragging = false
    mockOnDSLFileDropped = null
    mockServiceState.error = null
    mockServiceState.hasNextPage = false
    mockServiceState.isLoading = false
    mockServiceState.isFetchingNextPage = false
    mockQueryState.tagIDs = []
    mockQueryState.keywords = ''