mirror of
https://github.com/lordmathis/llamactl.git
synced 2025-11-06 00:54:23 +00:00
Add AuthProvider to test components
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||||
import { render, screen, waitFor } from '@testing-library/react'
|
import { render, screen, waitFor } from '@testing-library/react'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import App from '@/App'
|
import App from '@/App'
|
||||||
import { InstancesProvider } from '@/contexts/InstancesContext'
|
import { InstancesProvider } from '@/contexts/InstancesContext'
|
||||||
import { instancesApi } from '@/lib/api'
|
import { instancesApi } from '@/lib/api'
|
||||||
import type { Instance } from '@/types/instance'
|
import type { Instance } from '@/types/instance'
|
||||||
|
import { AuthProvider } from '@/contexts/AuthContext'
|
||||||
|
|
||||||
// Mock the API
|
// Mock the API
|
||||||
vi.mock('@/lib/api', () => ({
|
vi.mock('@/lib/api', () => ({
|
||||||
@@ -35,9 +36,11 @@ vi.mock('@/lib/healthService', () => ({
|
|||||||
|
|
||||||
function renderApp() {
|
function renderApp() {
|
||||||
return render(
|
return render(
|
||||||
<InstancesProvider>
|
<AuthProvider>
|
||||||
<App />
|
<InstancesProvider>
|
||||||
</InstancesProvider>
|
<App />
|
||||||
|
</InstancesProvider>
|
||||||
|
</AuthProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +53,12 @@ describe('App Component - Critical Business Logic Only', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
vi.mocked(instancesApi.list).mockResolvedValue(mockInstances)
|
vi.mocked(instancesApi.list).mockResolvedValue(mockInstances)
|
||||||
|
window.sessionStorage.setItem('llamactl_management_key', 'test-api-key-123')
|
||||||
|
global.fetch = vi.fn(() => Promise.resolve(new Response(null, { status: 200 })))
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('End-to-End Instance Management', () => {
|
describe('End-to-End Instance Management', () => {
|
||||||
@@ -167,7 +176,6 @@ describe('App Component - Critical Business Logic Only', () => {
|
|||||||
renderApp()
|
renderApp()
|
||||||
|
|
||||||
// App should still render and show error
|
// App should still render and show error
|
||||||
expect(screen.getByText('Llamactl Dashboard')).toBeInTheDocument()
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByText('Error loading instances')).toBeInTheDocument()
|
expect(screen.getByText('Error loading instances')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -62,12 +62,6 @@ const LoginDialog: React.FC<LoginDialogProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleCancel = () => {
|
|
||||||
setApiKey('')
|
|
||||||
clearError()
|
|
||||||
onOpenChange?.(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
if (e.key === 'Enter' && !isSubmitDisabled) {
|
if (e.key === 'Enter' && !isSubmitDisabled) {
|
||||||
// Create a synthetic FormEvent to satisfy handleSubmit's type
|
// Create a synthetic FormEvent to satisfy handleSubmit's type
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import InstanceCard from '@/components/InstanceCard'
|
import InstanceCard from '@/components/InstanceCard'
|
||||||
@@ -27,9 +27,15 @@ describe('InstanceCard - Instance Actions and State', () => {
|
|||||||
options: { model: 'running-model.gguf' }
|
options: { model: 'running-model.gguf' }
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
})
|
window.sessionStorage.setItem('llamactl_management_key', 'test-api-key-123')
|
||||||
|
global.fetch = vi.fn(() => Promise.resolve(new Response(null, { status: 200 })))
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
describe('Instance Action Buttons', () => {
|
describe('Instance Action Buttons', () => {
|
||||||
it('calls startInstance when start button clicked on stopped instance', async () => {
|
it('calls startInstance when start button clicked on stopped instance', async () => {
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import InstanceList from '@/components/InstanceList'
|
import InstanceList from '@/components/InstanceList'
|
||||||
import { InstancesProvider } from '@/contexts/InstancesContext'
|
import { InstancesProvider } from '@/contexts/InstancesContext'
|
||||||
import { instancesApi } from '@/lib/api'
|
import { instancesApi } from '@/lib/api'
|
||||||
import type { Instance } from '@/types/instance'
|
import type { Instance } from '@/types/instance'
|
||||||
|
import { AuthProvider } from '@/contexts/AuthContext'
|
||||||
|
|
||||||
// Mock the API
|
// Mock the API
|
||||||
vi.mock('@/lib/api', () => ({
|
vi.mock('@/lib/api', () => ({
|
||||||
@@ -30,13 +31,16 @@ vi.mock('@/lib/healthService', () => ({
|
|||||||
|
|
||||||
function renderInstanceList(editInstance = vi.fn()) {
|
function renderInstanceList(editInstance = vi.fn()) {
|
||||||
return render(
|
return render(
|
||||||
<InstancesProvider>
|
<AuthProvider>
|
||||||
<InstanceList editInstance={editInstance} />
|
<InstancesProvider>
|
||||||
</InstancesProvider>
|
<InstanceList editInstance={editInstance} />
|
||||||
|
</InstancesProvider>
|
||||||
|
</AuthProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('InstanceList - State Management and UI Logic', () => {
|
describe('InstanceList - State Management and UI Logic', () => {
|
||||||
|
|
||||||
const mockEditInstance = vi.fn()
|
const mockEditInstance = vi.fn()
|
||||||
|
|
||||||
const mockInstances: Instance[] = [
|
const mockInstances: Instance[] = [
|
||||||
@@ -45,12 +49,20 @@ describe('InstanceList - State Management and UI Logic', () => {
|
|||||||
{ name: 'instance-3', running: false, options: { model: 'model3.gguf' } }
|
{ name: 'instance-3', running: false, options: { model: 'model3.gguf' } }
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const DUMMY_API_KEY = 'test-api-key-123'
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
|
window.sessionStorage.setItem('llamactl_management_key', DUMMY_API_KEY)
|
||||||
|
global.fetch = vi.fn(() => Promise.resolve(new Response(null, { status: 200 })))
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks()
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Loading State', () => {
|
describe('Loading State', () => {
|
||||||
it('shows loading spinner while instances are being fetched', async () => {
|
it('shows loading spinner while instances are being fetched', () => {
|
||||||
// Mock a delayed response to test loading state
|
// Mock a delayed response to test loading state
|
||||||
vi.mocked(instancesApi.list).mockImplementation(() =>
|
vi.mocked(instancesApi.list).mockImplementation(() =>
|
||||||
new Promise(resolve => setTimeout(() => resolve(mockInstances), 100))
|
new Promise(resolve => setTimeout(() => resolve(mockInstances), 100))
|
||||||
@@ -220,27 +232,5 @@ describe('InstanceList - State Management and UI Logic', () => {
|
|||||||
expect(await screen.findByText('Instances (3)')).toBeInTheDocument()
|
expect(await screen.findByText('Instances (3)')).toBeInTheDocument()
|
||||||
expect(screen.queryByText('Loading instances...')).not.toBeInTheDocument()
|
expect(screen.queryByText('Loading instances...')).not.toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('handles transition from error back to loaded state', async () => {
|
|
||||||
// Start with error
|
|
||||||
vi.mocked(instancesApi.list).mockRejectedValue(new Error('Network error'))
|
|
||||||
|
|
||||||
const { rerender } = renderInstanceList(mockEditInstance)
|
|
||||||
|
|
||||||
expect(await screen.findByText('Error loading instances')).toBeInTheDocument()
|
|
||||||
|
|
||||||
// Simulate recovery (e.g., retry after network recovery)
|
|
||||||
vi.mocked(instancesApi.list).mockResolvedValue(mockInstances)
|
|
||||||
|
|
||||||
rerender(
|
|
||||||
<InstancesProvider>
|
|
||||||
<InstanceList editInstance={mockEditInstance} />
|
|
||||||
</InstancesProvider>
|
|
||||||
)
|
|
||||||
|
|
||||||
// Should eventually show instances
|
|
||||||
// Note: This test is somewhat artificial since the context handles retries
|
|
||||||
expect(screen.getByText('Error loading instances')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
||||||
import { render, screen, waitFor } from '@testing-library/react'
|
import { render, screen, waitFor } from '@testing-library/react'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import InstanceDialog from '@/components/InstanceDialog'
|
import InstanceDialog from '@/components/InstanceDialog'
|
||||||
@@ -8,9 +8,15 @@ describe('InstanceModal - Form Logic and Validation', () => {
|
|||||||
const mockOnSave = vi.fn()
|
const mockOnSave = vi.fn()
|
||||||
const mockOnOpenChange = vi.fn()
|
const mockOnOpenChange = vi.fn()
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks()
|
||||||
})
|
window.sessionStorage.setItem('llamactl_management_key', 'test-api-key-123')
|
||||||
|
global.fetch = vi.fn(() => Promise.resolve(new Response(null, { status: 200 })))
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
vi.restoreAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
describe('Create Mode', () => {
|
describe('Create Mode', () => {
|
||||||
it('validates instance name is required', async () => {
|
it('validates instance name is required', async () => {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
|
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
|
||||||
import { render, screen, waitFor } from '@testing-library/react'
|
import { render, screen, waitFor } from "@testing-library/react";
|
||||||
import type { ReactNode } from 'react'
|
import type { ReactNode } from "react";
|
||||||
import { InstancesProvider, useInstances } from '@/contexts/InstancesContext'
|
import { InstancesProvider, useInstances } from "@/contexts/InstancesContext";
|
||||||
import { instancesApi } from '@/lib/api'
|
import { instancesApi } from "@/lib/api";
|
||||||
import type { Instance } from '@/types/instance'
|
import type { Instance } from "@/types/instance";
|
||||||
|
import { AuthProvider } from "../AuthContext";
|
||||||
|
|
||||||
// Mock the API module
|
// Mock the API module
|
||||||
vi.mock('@/lib/api', () => ({
|
vi.mock("@/lib/api", () => ({
|
||||||
instancesApi: {
|
instancesApi: {
|
||||||
list: vi.fn(),
|
list: vi.fn(),
|
||||||
create: vi.fn(),
|
create: vi.fn(),
|
||||||
@@ -15,8 +16,8 @@ vi.mock('@/lib/api', () => ({
|
|||||||
stop: vi.fn(),
|
stop: vi.fn(),
|
||||||
restart: vi.fn(),
|
restart: vi.fn(),
|
||||||
delete: vi.fn(),
|
delete: vi.fn(),
|
||||||
}
|
},
|
||||||
}))
|
}));
|
||||||
|
|
||||||
// Test component to access context
|
// Test component to access context
|
||||||
function TestComponent() {
|
function TestComponent() {
|
||||||
@@ -30,366 +31,389 @@ function TestComponent() {
|
|||||||
stopInstance,
|
stopInstance,
|
||||||
restartInstance,
|
restartInstance,
|
||||||
deleteInstance,
|
deleteInstance,
|
||||||
clearError
|
clearError,
|
||||||
} = useInstances()
|
} = useInstances();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div data-testid="loading">{loading.toString()}</div>
|
<div data-testid="loading">{loading.toString()}</div>
|
||||||
<div data-testid="error">{error || 'no-error'}</div>
|
<div data-testid="error">{error || "no-error"}</div>
|
||||||
<div data-testid="instances-count">{instances.length}</div>
|
<div data-testid="instances-count">{instances.length}</div>
|
||||||
{instances.map(instance => (
|
{instances.map((instance) => (
|
||||||
<div key={instance.name} data-testid={`instance-${instance.name}`}>
|
<div key={instance.name} data-testid={`instance-${instance.name}`}>
|
||||||
{instance.name}:{instance.running.toString()}
|
{instance.name}:{instance.running.toString()}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* Action buttons for testing with specific instances */}
|
{/* Action buttons for testing with specific instances */}
|
||||||
<button
|
<button
|
||||||
onClick={() => createInstance('new-instance', { model: 'test.gguf' })}
|
onClick={() => createInstance("new-instance", { model: "test.gguf" })}
|
||||||
data-testid="create-instance"
|
data-testid="create-instance"
|
||||||
>
|
>
|
||||||
Create Instance
|
Create Instance
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => updateInstance('instance1', { model: 'updated.gguf' })}
|
onClick={() => updateInstance("instance1", { model: "updated.gguf" })}
|
||||||
data-testid="update-instance"
|
data-testid="update-instance"
|
||||||
>
|
>
|
||||||
Update Instance
|
Update Instance
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => startInstance('instance2')}
|
onClick={() => startInstance("instance2")}
|
||||||
data-testid="start-instance"
|
data-testid="start-instance"
|
||||||
>
|
>
|
||||||
Start Instance2
|
Start Instance2
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => stopInstance('instance1')}
|
onClick={() => stopInstance("instance1")}
|
||||||
data-testid="stop-instance"
|
data-testid="stop-instance"
|
||||||
>
|
>
|
||||||
Stop Instance1
|
Stop Instance1
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => restartInstance('instance1')}
|
onClick={() => restartInstance("instance1")}
|
||||||
data-testid="restart-instance"
|
data-testid="restart-instance"
|
||||||
>
|
>
|
||||||
Restart Instance1
|
Restart Instance1
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => deleteInstance('instance2')}
|
onClick={() => deleteInstance("instance2")}
|
||||||
data-testid="delete-instance"
|
data-testid="delete-instance"
|
||||||
>
|
>
|
||||||
Delete Instance2
|
Delete Instance2
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button onClick={clearError} data-testid="clear-error">
|
||||||
onClick={clearError}
|
|
||||||
data-testid="clear-error"
|
|
||||||
>
|
|
||||||
Clear Error
|
Clear Error
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderWithProvider(children: ReactNode) {
|
function renderWithProvider(children: ReactNode) {
|
||||||
return render(
|
return render(
|
||||||
<InstancesProvider>
|
<AuthProvider>
|
||||||
{children}
|
<InstancesProvider>{children}</InstancesProvider>
|
||||||
</InstancesProvider>
|
</AuthProvider>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('InstancesContext', () => {
|
describe("InstancesContext", () => {
|
||||||
const mockInstances: Instance[] = [
|
const mockInstances: Instance[] = [
|
||||||
{ name: 'instance1', running: true, options: { model: 'model1.gguf' } },
|
{ name: "instance1", running: true, options: { model: "model1.gguf" } },
|
||||||
{ name: 'instance2', running: false, options: { model: 'model2.gguf' } }
|
{ name: "instance2", running: false, options: { model: "model2.gguf" } },
|
||||||
]
|
];
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.clearAllMocks();
|
||||||
|
window.sessionStorage.setItem('llamactl_management_key', 'test-api-key-123');
|
||||||
|
global.fetch = vi.fn(() => Promise.resolve(new Response(null, { status: 200 })));
|
||||||
// Default successful API responses
|
// Default successful API responses
|
||||||
vi.mocked(instancesApi.list).mockResolvedValue(mockInstances)
|
vi.mocked(instancesApi.list).mockResolvedValue(mockInstances);
|
||||||
})
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
vi.clearAllMocks()
|
vi.restoreAllMocks();
|
||||||
})
|
});
|
||||||
|
|
||||||
describe('Initial Loading', () => {
|
describe("Initial Loading", () => {
|
||||||
it('loads instances on mount', async () => {
|
it("loads instances on mount", async () => {
|
||||||
renderWithProvider(<TestComponent />)
|
renderWithProvider(<TestComponent />);
|
||||||
|
|
||||||
// Should start loading
|
// Should start loading
|
||||||
expect(screen.getByTestId('loading')).toHaveTextContent('true')
|
expect(screen.getByTestId("loading")).toHaveTextContent("true");
|
||||||
|
|
||||||
// Should fetch instances
|
// Should fetch instances
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(instancesApi.list).toHaveBeenCalledOnce()
|
expect(instancesApi.list).toHaveBeenCalledOnce();
|
||||||
})
|
});
|
||||||
|
|
||||||
// Should display loaded instances
|
// Should display loaded instances
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('loading')).toHaveTextContent('false')
|
expect(screen.getByTestId("loading")).toHaveTextContent("false");
|
||||||
expect(screen.getByTestId('instances-count')).toHaveTextContent('2')
|
expect(screen.getByTestId("instances-count")).toHaveTextContent("2");
|
||||||
expect(screen.getByTestId('instance-instance1')).toHaveTextContent('instance1:true')
|
expect(screen.getByTestId("instance-instance1")).toHaveTextContent(
|
||||||
expect(screen.getByTestId('instance-instance2')).toHaveTextContent('instance2:false')
|
"instance1:true"
|
||||||
})
|
);
|
||||||
})
|
expect(screen.getByTestId("instance-instance2")).toHaveTextContent(
|
||||||
|
"instance2:false"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('handles API error during initial load', async () => {
|
it("handles API error during initial load", async () => {
|
||||||
const errorMessage = 'Network error'
|
const errorMessage = "Network error";
|
||||||
vi.mocked(instancesApi.list).mockRejectedValue(new Error(errorMessage))
|
vi.mocked(instancesApi.list).mockRejectedValue(new Error(errorMessage));
|
||||||
|
|
||||||
renderWithProvider(<TestComponent />)
|
renderWithProvider(<TestComponent />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('loading')).toHaveTextContent('false')
|
expect(screen.getByTestId("loading")).toHaveTextContent("false");
|
||||||
expect(screen.getByTestId('error')).toHaveTextContent(errorMessage)
|
expect(screen.getByTestId("error")).toHaveTextContent(errorMessage);
|
||||||
expect(screen.getByTestId('instances-count')).toHaveTextContent('0')
|
expect(screen.getByTestId("instances-count")).toHaveTextContent("0");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe('Create Instance', () => {
|
describe("Create Instance", () => {
|
||||||
it('creates instance and adds it to state', async () => {
|
it("creates instance and adds it to state", async () => {
|
||||||
const newInstance: Instance = {
|
const newInstance: Instance = {
|
||||||
name: 'new-instance',
|
name: "new-instance",
|
||||||
running: false,
|
running: false,
|
||||||
options: { model: 'test.gguf' }
|
options: { model: "test.gguf" },
|
||||||
}
|
};
|
||||||
vi.mocked(instancesApi.create).mockResolvedValue(newInstance)
|
vi.mocked(instancesApi.create).mockResolvedValue(newInstance);
|
||||||
|
|
||||||
renderWithProvider(<TestComponent />)
|
renderWithProvider(<TestComponent />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('loading')).toHaveTextContent('false')
|
expect(screen.getByTestId("loading")).toHaveTextContent("false");
|
||||||
expect(screen.getByTestId('instances-count')).toHaveTextContent('2')
|
expect(screen.getByTestId("instances-count")).toHaveTextContent("2");
|
||||||
})
|
});
|
||||||
|
|
||||||
screen.getByTestId('create-instance').click()
|
screen.getByTestId("create-instance").click();
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(instancesApi.create).toHaveBeenCalledWith('new-instance', { model: 'test.gguf' })
|
expect(instancesApi.create).toHaveBeenCalledWith("new-instance", {
|
||||||
})
|
model: "test.gguf",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('instances-count')).toHaveTextContent('3')
|
expect(screen.getByTestId("instances-count")).toHaveTextContent("3");
|
||||||
expect(screen.getByTestId('instance-new-instance')).toHaveTextContent('new-instance:false')
|
expect(screen.getByTestId("instance-new-instance")).toHaveTextContent(
|
||||||
})
|
"new-instance:false"
|
||||||
})
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('handles create instance error without changing state', async () => {
|
it("handles create instance error without changing state", async () => {
|
||||||
const errorMessage = 'Instance already exists'
|
const errorMessage = "Instance already exists";
|
||||||
vi.mocked(instancesApi.create).mockRejectedValue(new Error(errorMessage))
|
vi.mocked(instancesApi.create).mockRejectedValue(new Error(errorMessage));
|
||||||
|
|
||||||
renderWithProvider(<TestComponent />)
|
renderWithProvider(<TestComponent />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('loading')).toHaveTextContent('false')
|
expect(screen.getByTestId("loading")).toHaveTextContent("false");
|
||||||
expect(screen.getByTestId('instances-count')).toHaveTextContent('2')
|
expect(screen.getByTestId("instances-count")).toHaveTextContent("2");
|
||||||
})
|
});
|
||||||
|
|
||||||
screen.getByTestId('create-instance').click()
|
screen.getByTestId("create-instance").click();
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('error')).toHaveTextContent(errorMessage)
|
expect(screen.getByTestId("error")).toHaveTextContent(errorMessage);
|
||||||
})
|
});
|
||||||
|
|
||||||
expect(screen.getByTestId('instances-count')).toHaveTextContent('2')
|
expect(screen.getByTestId("instances-count")).toHaveTextContent("2");
|
||||||
expect(screen.queryByTestId('instance-new-instance')).not.toBeInTheDocument()
|
expect(
|
||||||
})
|
screen.queryByTestId("instance-new-instance")
|
||||||
})
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Update Instance', () => {
|
describe("Update Instance", () => {
|
||||||
it('updates instance and maintains it in state', async () => {
|
it("updates instance and maintains it in state", async () => {
|
||||||
const updatedInstance: Instance = {
|
const updatedInstance: Instance = {
|
||||||
name: 'instance1',
|
name: "instance1",
|
||||||
running: true,
|
running: true,
|
||||||
options: { model: 'updated.gguf' }
|
options: { model: "updated.gguf" },
|
||||||
}
|
};
|
||||||
vi.mocked(instancesApi.update).mockResolvedValue(updatedInstance)
|
vi.mocked(instancesApi.update).mockResolvedValue(updatedInstance);
|
||||||
|
|
||||||
renderWithProvider(<TestComponent />)
|
renderWithProvider(<TestComponent />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('loading')).toHaveTextContent('false')
|
expect(screen.getByTestId("loading")).toHaveTextContent("false");
|
||||||
expect(screen.getByTestId('instances-count')).toHaveTextContent('2')
|
expect(screen.getByTestId("instances-count")).toHaveTextContent("2");
|
||||||
})
|
});
|
||||||
|
|
||||||
screen.getByTestId('update-instance').click()
|
screen.getByTestId("update-instance").click();
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(instancesApi.update).toHaveBeenCalledWith('instance1', { model: 'updated.gguf' })
|
expect(instancesApi.update).toHaveBeenCalledWith("instance1", {
|
||||||
})
|
model: "updated.gguf",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('instances-count')).toHaveTextContent('2')
|
expect(screen.getByTestId("instances-count")).toHaveTextContent("2");
|
||||||
expect(screen.getByTestId('instance-instance1')).toBeInTheDocument()
|
expect(screen.getByTestId("instance-instance1")).toBeInTheDocument();
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe('Start/Stop Instance', () => {
|
describe("Start/Stop Instance", () => {
|
||||||
it('starts existing instance and updates its running state', async () => {
|
it("starts existing instance and updates its running state", async () => {
|
||||||
vi.mocked(instancesApi.start).mockResolvedValue({} as Instance)
|
vi.mocked(instancesApi.start).mockResolvedValue({} as Instance);
|
||||||
|
|
||||||
renderWithProvider(<TestComponent />)
|
renderWithProvider(<TestComponent />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('loading')).toHaveTextContent('false')
|
expect(screen.getByTestId("loading")).toHaveTextContent("false");
|
||||||
// instance2 starts as not running
|
// instance2 starts as not running
|
||||||
expect(screen.getByTestId('instance-instance2')).toHaveTextContent('instance2:false')
|
expect(screen.getByTestId("instance-instance2")).toHaveTextContent(
|
||||||
})
|
"instance2:false"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// Start instance2 (button already configured to start instance2)
|
// Start instance2 (button already configured to start instance2)
|
||||||
screen.getByTestId('start-instance').click()
|
screen.getByTestId("start-instance").click();
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(instancesApi.start).toHaveBeenCalledWith('instance2')
|
expect(instancesApi.start).toHaveBeenCalledWith("instance2");
|
||||||
// The running state should be updated to true
|
// The running state should be updated to true
|
||||||
expect(screen.getByTestId('instance-instance2')).toHaveTextContent('instance2:true')
|
expect(screen.getByTestId("instance-instance2")).toHaveTextContent(
|
||||||
})
|
"instance2:true"
|
||||||
})
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('stops instance and updates running state to false', async () => {
|
it("stops instance and updates running state to false", async () => {
|
||||||
vi.mocked(instancesApi.stop).mockResolvedValue({} as Instance)
|
vi.mocked(instancesApi.stop).mockResolvedValue({} as Instance);
|
||||||
|
|
||||||
renderWithProvider(<TestComponent />)
|
renderWithProvider(<TestComponent />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('loading')).toHaveTextContent('false')
|
expect(screen.getByTestId("loading")).toHaveTextContent("false");
|
||||||
// instance1 starts as running
|
// instance1 starts as running
|
||||||
expect(screen.getByTestId('instance-instance1')).toHaveTextContent('instance1:true')
|
expect(screen.getByTestId("instance-instance1")).toHaveTextContent(
|
||||||
})
|
"instance1:true"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
// Stop instance1 (button already configured to stop instance1)
|
// Stop instance1 (button already configured to stop instance1)
|
||||||
screen.getByTestId('stop-instance').click()
|
screen.getByTestId("stop-instance").click();
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(instancesApi.stop).toHaveBeenCalledWith('instance1')
|
expect(instancesApi.stop).toHaveBeenCalledWith("instance1");
|
||||||
// The running state should be updated to false
|
// The running state should be updated to false
|
||||||
expect(screen.getByTestId('instance-instance1')).toHaveTextContent('instance1:false')
|
expect(screen.getByTestId("instance-instance1")).toHaveTextContent(
|
||||||
})
|
"instance1:false"
|
||||||
})
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('handles start instance error', async () => {
|
it("handles start instance error", async () => {
|
||||||
const errorMessage = 'Failed to start instance'
|
const errorMessage = "Failed to start instance";
|
||||||
vi.mocked(instancesApi.start).mockRejectedValue(new Error(errorMessage))
|
vi.mocked(instancesApi.start).mockRejectedValue(new Error(errorMessage));
|
||||||
|
|
||||||
renderWithProvider(<TestComponent />)
|
renderWithProvider(<TestComponent />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('loading')).toHaveTextContent('false')
|
expect(screen.getByTestId("loading")).toHaveTextContent("false");
|
||||||
})
|
});
|
||||||
|
|
||||||
screen.getByTestId('start-instance').click()
|
screen.getByTestId("start-instance").click();
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('error')).toHaveTextContent(errorMessage)
|
expect(screen.getByTestId("error")).toHaveTextContent(errorMessage);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe('Delete Instance', () => {
|
describe("Delete Instance", () => {
|
||||||
it('deletes instance and removes it from state', async () => {
|
it("deletes instance and removes it from state", async () => {
|
||||||
vi.mocked(instancesApi.delete).mockResolvedValue(undefined)
|
vi.mocked(instancesApi.delete).mockResolvedValue(undefined);
|
||||||
|
|
||||||
renderWithProvider(<TestComponent />)
|
renderWithProvider(<TestComponent />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('loading')).toHaveTextContent('false')
|
expect(screen.getByTestId("loading")).toHaveTextContent("false");
|
||||||
expect(screen.getByTestId('instances-count')).toHaveTextContent('2')
|
expect(screen.getByTestId("instances-count")).toHaveTextContent("2");
|
||||||
expect(screen.getByTestId('instance-instance2')).toBeInTheDocument()
|
expect(screen.getByTestId("instance-instance2")).toBeInTheDocument();
|
||||||
})
|
});
|
||||||
|
|
||||||
screen.getByTestId('delete-instance').click()
|
screen.getByTestId("delete-instance").click();
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(instancesApi.delete).toHaveBeenCalledWith('instance2')
|
expect(instancesApi.delete).toHaveBeenCalledWith("instance2");
|
||||||
})
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('instances-count')).toHaveTextContent('1')
|
expect(screen.getByTestId("instances-count")).toHaveTextContent("1");
|
||||||
expect(screen.queryByTestId('instance-instance2')).not.toBeInTheDocument()
|
expect(
|
||||||
expect(screen.getByTestId('instance-instance1')).toBeInTheDocument() // instance1 should still exist
|
screen.queryByTestId("instance-instance2")
|
||||||
})
|
).not.toBeInTheDocument();
|
||||||
})
|
expect(screen.getByTestId("instance-instance1")).toBeInTheDocument(); // instance1 should still exist
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('handles delete instance error without changing state', async () => {
|
it("handles delete instance error without changing state", async () => {
|
||||||
const errorMessage = 'Instance is running'
|
const errorMessage = "Instance is running";
|
||||||
vi.mocked(instancesApi.delete).mockRejectedValue(new Error(errorMessage))
|
vi.mocked(instancesApi.delete).mockRejectedValue(new Error(errorMessage));
|
||||||
|
|
||||||
renderWithProvider(<TestComponent />)
|
renderWithProvider(<TestComponent />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('loading')).toHaveTextContent('false')
|
expect(screen.getByTestId("loading")).toHaveTextContent("false");
|
||||||
expect(screen.getByTestId('instances-count')).toHaveTextContent('2')
|
expect(screen.getByTestId("instances-count")).toHaveTextContent("2");
|
||||||
})
|
});
|
||||||
|
|
||||||
screen.getByTestId('delete-instance').click()
|
screen.getByTestId("delete-instance").click();
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('error')).toHaveTextContent(errorMessage)
|
expect(screen.getByTestId("error")).toHaveTextContent(errorMessage);
|
||||||
})
|
});
|
||||||
|
|
||||||
expect(screen.getByTestId('instances-count')).toHaveTextContent('2')
|
expect(screen.getByTestId("instances-count")).toHaveTextContent("2");
|
||||||
expect(screen.getByTestId('instance-instance2')).toBeInTheDocument()
|
expect(screen.getByTestId("instance-instance2")).toBeInTheDocument();
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe('Error Management', () => {
|
describe("Error Management", () => {
|
||||||
it('clears error when clearError is called', async () => {
|
it("clears error when clearError is called", async () => {
|
||||||
const errorMessage = 'Test error'
|
const errorMessage = "Test error";
|
||||||
vi.mocked(instancesApi.list).mockRejectedValue(new Error(errorMessage))
|
vi.mocked(instancesApi.list).mockRejectedValue(new Error(errorMessage));
|
||||||
|
|
||||||
renderWithProvider(<TestComponent />)
|
renderWithProvider(<TestComponent />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('error')).toHaveTextContent(errorMessage)
|
expect(screen.getByTestId("error")).toHaveTextContent(errorMessage);
|
||||||
})
|
});
|
||||||
|
|
||||||
screen.getByTestId('clear-error').click()
|
screen.getByTestId("clear-error").click();
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('error')).toHaveTextContent('no-error')
|
expect(screen.getByTestId("error")).toHaveTextContent("no-error");
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
|
||||||
describe('State Consistency', () => {
|
describe("State Consistency", () => {
|
||||||
it('maintains consistent state during multiple operations', async () => {
|
it("maintains consistent state during multiple operations", async () => {
|
||||||
// Test that operations don't interfere with each other
|
// Test that operations don't interfere with each other
|
||||||
const newInstance: Instance = {
|
const newInstance: Instance = {
|
||||||
name: 'new-instance',
|
name: "new-instance",
|
||||||
running: false,
|
running: false,
|
||||||
options: {}
|
options: {},
|
||||||
}
|
};
|
||||||
vi.mocked(instancesApi.create).mockResolvedValue(newInstance)
|
vi.mocked(instancesApi.create).mockResolvedValue(newInstance);
|
||||||
vi.mocked(instancesApi.start).mockResolvedValue({} as Instance)
|
vi.mocked(instancesApi.start).mockResolvedValue({} as Instance);
|
||||||
|
|
||||||
renderWithProvider(<TestComponent />)
|
renderWithProvider(<TestComponent />);
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('loading')).toHaveTextContent('false')
|
expect(screen.getByTestId("loading")).toHaveTextContent("false");
|
||||||
expect(screen.getByTestId('instances-count')).toHaveTextContent('2')
|
expect(screen.getByTestId("instances-count")).toHaveTextContent("2");
|
||||||
})
|
});
|
||||||
|
|
||||||
// Create new instance
|
// Create new instance
|
||||||
screen.getByTestId('create-instance').click()
|
screen.getByTestId("create-instance").click();
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(screen.getByTestId('instances-count')).toHaveTextContent('3')
|
expect(screen.getByTestId("instances-count")).toHaveTextContent("3");
|
||||||
})
|
});
|
||||||
|
|
||||||
// Start an instance (this should not affect the count)
|
// Start an instance (this should not affect the count)
|
||||||
screen.getByTestId('start-instance').click()
|
screen.getByTestId("start-instance").click();
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(instancesApi.start).toHaveBeenCalled()
|
expect(instancesApi.start).toHaveBeenCalled();
|
||||||
expect(screen.getByTestId('instances-count')).toHaveTextContent('3') // Still 3
|
expect(screen.getByTestId("instances-count")).toHaveTextContent("3"); // Still 3
|
||||||
// But the running state should change
|
// But the running state should change
|
||||||
expect(screen.getByTestId('instance-instance2')).toHaveTextContent('instance2:true')
|
expect(screen.getByTestId("instance-instance2")).toHaveTextContent(
|
||||||
})
|
"instance2:true"
|
||||||
})
|
);
|
||||||
})
|
});
|
||||||
})
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user