diff --git a/redisinsight/ui/src/pages/vector-search/hooks/useStartWizard.spec.ts b/redisinsight/ui/src/pages/vector-search/hooks/useStartWizard.spec.ts
new file mode 100644
index 0000000000..25ac9d25db
--- /dev/null
+++ b/redisinsight/ui/src/pages/vector-search/hooks/useStartWizard.spec.ts
@@ -0,0 +1,71 @@
+import { renderHook, act } from '@testing-library/react-hooks'
+import { Pages } from 'uiSrc/constants'
+import useStartWizard from './useStartWizard'
+
+describe('useStartWizard', () => {
+ let mockPush: jest.Mock
+ let useHistoryMock: jest.SpyInstance
+ let useParamsMock: jest.SpyInstance
+
+ beforeEach(() => {
+ jest.clearAllMocks()
+ mockPush = jest.fn()
+
+ useHistoryMock = jest.spyOn(require('react-router-dom'), 'useHistory')
+ useHistoryMock.mockImplementation(() => ({
+ push: mockPush,
+ }))
+
+ useParamsMock = jest.spyOn(require('react-router-dom'), 'useParams')
+ useParamsMock.mockImplementation(() => ({
+ instanceId: 'test-instance-id',
+ }))
+ })
+
+ afterEach(() => {
+ useHistoryMock.mockRestore()
+ useParamsMock.mockRestore()
+ })
+
+ it('should navigate to vector search create index page when start is called', () => {
+ const { result } = renderHook(() => useStartWizard())
+
+ act(() => {
+ result.current()
+ })
+
+ expect(mockPush).toHaveBeenCalledWith(
+ Pages.vectorSearchCreateIndex('test-instance-id'),
+ )
+ expect(mockPush).toHaveBeenCalledTimes(1)
+ })
+
+ it('should use instanceId from useParams in navigation', () => {
+ const customInstanceId = 'custom-instance-123'
+ useParamsMock.mockImplementation(() => ({ instanceId: customInstanceId }))
+
+ const { result } = renderHook(() => useStartWizard())
+
+ act(() => {
+ result.current()
+ })
+
+ expect(mockPush).toHaveBeenCalledWith(
+ Pages.vectorSearchCreateIndex(customInstanceId),
+ )
+ })
+
+ it('should handle missing instanceId gracefully', () => {
+ useParamsMock.mockImplementation(() => ({ instanceId: undefined }))
+
+ const { result } = renderHook(() => useStartWizard())
+
+ act(() => {
+ result.current()
+ })
+
+ expect(mockPush).toHaveBeenCalledWith(
+ Pages.vectorSearchCreateIndex(undefined as any),
+ )
+ })
+})
diff --git a/redisinsight/ui/src/pages/vector-search/hooks/useStartWizard.ts b/redisinsight/ui/src/pages/vector-search/hooks/useStartWizard.ts
new file mode 100644
index 0000000000..44e572fae9
--- /dev/null
+++ b/redisinsight/ui/src/pages/vector-search/hooks/useStartWizard.ts
@@ -0,0 +1,14 @@
+import { useCallback } from 'react'
+import { useHistory, useParams } from 'react-router-dom'
+import { Pages } from 'uiSrc/constants'
+
+const useStartWizard = () => {
+ const history = useHistory()
+ const { instanceId } = useParams<{ instanceId: string }>()
+
+ return useCallback(() => {
+ history.push(Pages.vectorSearchCreateIndex(instanceId))
+ }, [history, instanceId])
+}
+
+export default useStartWizard
diff --git a/redisinsight/ui/src/pages/vector-search/manage-indexes/ManageIndexesList.spec.tsx b/redisinsight/ui/src/pages/vector-search/manage-indexes/ManageIndexesList.spec.tsx
index 85794c08d1..06cc38aadf 100644
--- a/redisinsight/ui/src/pages/vector-search/manage-indexes/ManageIndexesList.spec.tsx
+++ b/redisinsight/ui/src/pages/vector-search/manage-indexes/ManageIndexesList.spec.tsx
@@ -32,18 +32,21 @@ jest.mock('uiSrc/slices/instances/instances', () => ({
const renderComponent = () => render()
+const mockedRedisearchListSelector = redisearchListSelector as jest.Mock
+const mockedConnectedInstanceSelector = connectedInstanceSelector as jest.Mock
+
describe('ManageIndexesList', () => {
beforeEach(() => {
cleanup()
jest.clearAllMocks()
// Reset mocks to default state before each test
- ;(redisearchListSelector as jest.Mock).mockReturnValue({
+ mockedRedisearchListSelector.mockReturnValue({
data: [],
loading: false,
error: '',
})
- ;(connectedInstanceSelector as jest.Mock).mockReturnValue({
+ mockedConnectedInstanceSelector.mockReturnValue({
id: 'test-instance-123',
connectionType: 'STANDALONE',
host: 'localhost',
@@ -62,7 +65,7 @@ describe('ManageIndexesList', () => {
})
it('should render Loader spinner while fetching data', async () => {
- ;(redisearchListSelector as jest.Mock).mockReturnValue({
+ mockedRedisearchListSelector.mockReturnValue({
data: [],
loading: true,
error: '',
@@ -74,13 +77,28 @@ describe('ManageIndexesList', () => {
expect(loader).toBeInTheDocument()
})
+ it('should render no indexes message when there are no indexes', async () => {
+ mockedRedisearchListSelector.mockReturnValue({
+ data: [],
+ loading: false,
+ error: '',
+ })
+
+ renderComponent()
+
+ const noIndexesMessage = await screen.getByText(
+ 'No indexes to display yet.',
+ )
+ expect(noIndexesMessage).toBeInTheDocument()
+ })
+
it('should render indexes boxes when data is available', () => {
const mockIndexes = [
Buffer.from('test-index-1'),
Buffer.from('test-index-2'),
]
- ;(redisearchListSelector as jest.Mock).mockReturnValue({
+ mockedRedisearchListSelector.mockReturnValue({
data: mockIndexes,
loading: false,
error: '',
@@ -109,7 +127,7 @@ describe('ManageIndexesList', () => {
it('should not render indexes boxes when there is no instanceHost', () => {
// Mock connectedInstanceSelector to return no host
// TODO: Potential candidate for a factory function to create mock instances
- ;(connectedInstanceSelector as jest.Mock).mockReturnValue({
+ mockedConnectedInstanceSelector.mockReturnValue({
id: 'test-instance-123',
connectionType: 'STANDALONE',
host: null, // No host means no instanceId
@@ -136,7 +154,7 @@ describe('ManageIndexesList', () => {
it('should not render indexes boxes when redisearch module is not available', () => {
// Mock connectedInstanceSelector to return modules without redisearch
// TODO: Potential candidate for a factory function to create mock instances
- ;(connectedInstanceSelector as jest.Mock).mockReturnValue({
+ mockedConnectedInstanceSelector.mockReturnValue({
id: 'test-instance-123',
connectionType: 'STANDALONE',
host: 'localhost',
diff --git a/redisinsight/ui/src/pages/vector-search/manage-indexes/ManageIndexesList.tsx b/redisinsight/ui/src/pages/vector-search/manage-indexes/ManageIndexesList.tsx
index 13038a4185..15d568d7f7 100644
--- a/redisinsight/ui/src/pages/vector-search/manage-indexes/ManageIndexesList.tsx
+++ b/redisinsight/ui/src/pages/vector-search/manage-indexes/ManageIndexesList.tsx
@@ -5,14 +5,18 @@ import { bufferToString } from 'uiSrc/utils'
import { StyledManageIndexesListAction } from './ManageIndexesList.styles'
import { IndexSection } from './IndexSection'
import { useRedisearchListData } from '../useRedisearchListData'
+import NoIndexesMessage from './NoIndexesMessage'
export const ManageIndexesList = () => {
const { data, loading } = useRedisearchListData()
+ const hasIndexes = !!data?.length
return (
{loading && }
+ {!loading && !hasIndexes && }
+
{data.map((index) => (
))}
diff --git a/redisinsight/ui/src/pages/vector-search/manage-indexes/NoIndexesMessage.tsx b/redisinsight/ui/src/pages/vector-search/manage-indexes/NoIndexesMessage.tsx
new file mode 100644
index 0000000000..d73a404fd5
--- /dev/null
+++ b/redisinsight/ui/src/pages/vector-search/manage-indexes/NoIndexesMessage.tsx
@@ -0,0 +1,24 @@
+import React from 'react'
+import { Button } from 'uiSrc/components/base/forms/buttons'
+import { Col } from 'uiSrc/components/base/layout/flex'
+import { Text } from 'uiSrc/components/base/text'
+import useStartWizard from '../hooks/useStartWizard'
+
+const NoIndexesMessage = () => {
+ const start = useStartWizard()
+
+ return (
+
+ No indexes to display yet.
+
+
+ Complete vector search onboarding to create your first index with sample
+ data, or create one manually.
+
+
+
+
+ )
+}
+
+export default NoIndexesMessage
diff --git a/redisinsight/ui/src/pages/vector-search/query/StartWizardButton.spec.tsx b/redisinsight/ui/src/pages/vector-search/query/StartWizardButton.spec.tsx
index 5804bb92d2..05183b52fc 100644
--- a/redisinsight/ui/src/pages/vector-search/query/StartWizardButton.spec.tsx
+++ b/redisinsight/ui/src/pages/vector-search/query/StartWizardButton.spec.tsx
@@ -1,10 +1,15 @@
import React from 'react'
import { cleanup, render, screen, userEvent } from 'uiSrc/utils/test-utils'
-import { Pages } from 'uiSrc/constants'
-
import { StartWizardButton } from './StartWizardButton'
+import useStartWizard from '../hooks/useStartWizard'
+
+jest.mock('../hooks/useStartWizard', () => ({
+ __esModule: true,
+ default: jest.fn(),
+}))
const renderComponent = () => render()
+const mockedUseStartWizard = useStartWizard as jest.Mock
describe('StartWizardButton', () => {
beforeEach(() => {
@@ -12,38 +17,22 @@ describe('StartWizardButton', () => {
jest.clearAllMocks()
})
- it('should navigate to vector search create index page when "Get started" is clicked', async () => {
- const mockPush = jest.fn()
-
- const useHistoryMock = jest.spyOn(require('react-router-dom'), 'useHistory')
- useHistoryMock.mockImplementation(() => ({
- push: mockPush,
- }))
+ it('renders the CTA and calls the start function on click', async () => {
+ const startMock = jest.fn()
+ mockedUseStartWizard.mockReturnValue(startMock)
renderComponent()
- const getStartedButton = screen.getByText('Get started')
- await userEvent.click(getStartedButton)
-
- expect(mockPush).toHaveBeenCalledWith(
- Pages.vectorSearchCreateIndex('instanceId'),
- )
- expect(mockPush).toHaveBeenCalledTimes(1)
-
- useHistoryMock.mockRestore()
- })
-
- it('should maintain callback reference stability with useCallback', () => {
- const { rerender } = renderComponent()
-
- const firstRenderButton = screen.getByText('Get started')
-
- rerender()
+ expect(
+ screen.getByText(
+ 'Power fast, real-time semantic AI search with vector search.',
+ ),
+ ).toBeInTheDocument()
- const secondRenderButton = screen.getByText('Get started')
+ const btn = screen.getByRole('button', { name: /get started/i })
+ expect(btn).toBeInTheDocument()
- // Both buttons should exist (they're the same element after rerender)
- expect(firstRenderButton).toBeInTheDocument()
- expect(secondRenderButton).toBeInTheDocument()
+ await userEvent.click(btn)
+ expect(startMock).toHaveBeenCalledTimes(1)
})
})
diff --git a/redisinsight/ui/src/pages/vector-search/query/StartWizardButton.tsx b/redisinsight/ui/src/pages/vector-search/query/StartWizardButton.tsx
index d787c8d172..ea8501587f 100644
--- a/redisinsight/ui/src/pages/vector-search/query/StartWizardButton.tsx
+++ b/redisinsight/ui/src/pages/vector-search/query/StartWizardButton.tsx
@@ -1,15 +1,9 @@
-import React, { useCallback } from 'react'
-import { useHistory, useParams } from 'react-router-dom'
+import React from 'react'
import { CallOut } from 'uiSrc/components/base/display/call-out/CallOut'
-import { Pages } from 'uiSrc/constants'
+import useStartWizard from '../hooks/useStartWizard'
export const StartWizardButton = () => {
- const history = useHistory()
- const { instanceId } = useParams<{ instanceId: string }>()
-
- const startCreateIndexWizard = useCallback(() => {
- history.push(Pages.vectorSearchCreateIndex(instanceId))
- }, [history, instanceId])
+ const start = useStartWizard()
return (
{
actions={{
primary: {
label: 'Get started',
- onClick: startCreateIndexWizard,
+ onClick: start,
},
}}
>