Skip to content

Commit

Permalink
Merge branch 'main' into 2025-01-23-state-management
Browse files Browse the repository at this point in the history
  • Loading branch information
hardingjam committed Feb 4, 2025
2 parents 99c0ea6 + 6faccfe commit 5dbbfef
Show file tree
Hide file tree
Showing 33 changed files with 1,664 additions and 353 deletions.
205 changes: 122 additions & 83 deletions packages/ui-components/src/__tests__/DeploymentSteps.test.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { describe, it, expect, vi, beforeEach, type Mock } from 'vitest';
import { render, screen, fireEvent, waitFor } from '@testing-library/svelte';
import { render, screen, waitFor } from '@testing-library/svelte';
import DeploymentSteps from '../lib/components/deployment/DeploymentSteps.svelte';
import { DotrainOrderGui } from '@rainlanguage/orderbook/js_api';
import userEvent from '@testing-library/user-event';

import type { ComponentProps } from 'svelte';
import { writable } from 'svelte/store';
import type { AppKit } from '@reown/appkit';
const { mockWagmiConfigStore, mockConnectedStore } = await vi.hoisted(
() => import('../lib/__mocks__/stores')
);

export type DeploymentStepsProps = ComponentProps<DeploymentSteps>;

vi.mock('@rainlanguage/orderbook/js_api', () => ({
DotrainOrderGui: {
getDeploymentKeys: vi.fn(),
chooseDeployment: vi.fn()
}
}));
Expand Down Expand Up @@ -566,115 +573,147 @@ describe('DeploymentSteps', () => {
vi.clearAllMocks();
});

it('renders strategy URL input and load button initially', () => {
render(DeploymentSteps);
expect(screen.getByPlaceholderText('Enter URL to .rain file')).toBeInTheDocument();
const loadButton = screen.getByText('Load Strategy');
expect(loadButton).toBeInTheDocument();
expect(loadButton).toBeDisabled();
});
it('shows deployment details when provided', async () => {
(DotrainOrderGui.chooseDeployment as Mock).mockResolvedValue({
getSelectTokens: () => []
});

it('enables load button when URL is entered', async () => {
render(DeploymentSteps);
const urlInput = screen.getByPlaceholderText('Enter URL to .rain file');
const loadButton = screen.getByText('Load Strategy');
const deploymentDetails = {
name: 'SFLR<>WFLR on Flare',
description: 'Rotate sFLR (Sceptre staked FLR) and WFLR on Flare.'
};

render(DeploymentSteps, {
props: {
dotrain,
deployment: 'flare-sflr-wflr',
deploymentDetails,
wagmiConfig: mockWagmiConfigStore,
wagmiConnected: mockConnectedStore,
appKitModal: writable({} as AppKit),
handleDeployModal: vi.fn()
}
});

await userEvent.type(urlInput, 'https://example.com/strategy.rain');
await waitFor(() => {
expect(loadButton).not.toBeDisabled();
expect(screen.getByText('SFLR<>WFLR on Flare')).toBeInTheDocument();
expect(
screen.getByText('Rotate sFLR (Sceptre staked FLR) and WFLR on Flare.')
).toBeInTheDocument();
});
});

it('loads strategy from URL when button is clicked', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
text: () => Promise.resolve(JSON.stringify(dotrain))
it('shows select tokens section when tokens need to be selected', async () => {
const mockSelectTokens = ['token1', 'token2'];
(DotrainOrderGui.chooseDeployment as Mock).mockResolvedValue({
getSelectTokens: () => mockSelectTokens
});

render(DeploymentSteps);
const urlInput = screen.getByPlaceholderText('Enter URL to .rain file');
const loadButton = screen.getByText('Load Strategy');
await userEvent.clear(urlInput);
await fireEvent.input(urlInput, { target: { value: 'https://example.com/strategy.rain' } });
await userEvent.click(loadButton);
render(DeploymentSteps, {
props: {
dotrain,
deployment: 'flare-sflr-wflr',
deploymentDetails: { name: 'Deployment 1', description: 'Description 1' },
wagmiConfig: mockWagmiConfigStore,
wagmiConnected: mockConnectedStore,
appKitModal: writable({} as AppKit),
handleDeployModal: vi.fn()
}
});

await waitFor(() => {
expect(global.fetch).toHaveBeenCalledWith('https://example.com/strategy.rain');
expect(screen.getByText('Select Tokens')).toBeInTheDocument();
expect(
screen.getByText('Select the tokens that you want to use in your order.')
).toBeInTheDocument();
});
});

it('shows deployments dropdown after strategy is loaded', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
text: () => Promise.resolve(JSON.stringify(dotrain))
it('shows error message when GUI initialization fails', async () => {
(DotrainOrderGui.chooseDeployment as Mock).mockRejectedValue(
new Error('Failed to initialize GUI')
);

render(DeploymentSteps, {
props: {
dotrain,
deployment: 'flare-sflr-wflr',
deploymentDetails: { name: 'Deployment 1', description: 'Description 1' },
wagmiConfig: mockWagmiConfigStore,
wagmiConnected: mockConnectedStore,
appKitModal: writable({} as AppKit),
handleDeployModal: vi.fn()
}
});

const mockDeployments = [
{ key: 'deployment1', label: 'Deployment 1' },
{ key: 'deployment2', label: 'Deployment 2' }
];

(DotrainOrderGui.getDeploymentKeys as Mock).mockResolvedValue(mockDeployments);

render(DeploymentSteps);
const urlInput = screen.getByPlaceholderText('Enter URL to .rain file');
const loadButton = screen.getByText('Load Strategy');

await userEvent.type(urlInput, 'https://example.com/strategy.rain');
await userEvent.click(loadButton);

await waitFor(() => {
expect(screen.getByText('Select Deployment')).toBeInTheDocument();
expect(screen.getByText('Select a deployment')).toBeInTheDocument();
expect(screen.getByText('Error loading GUI')).toBeInTheDocument();
expect(screen.getByText('Failed to initialize GUI')).toBeInTheDocument();
});
});

it('handles URL fetch errors', async () => {
global.fetch = vi.fn().mockRejectedValue(new Error('Failed to fetch'));

render(DeploymentSteps);
const urlInput = screen.getByPlaceholderText('Enter URL to .rain file');
const loadButton = screen.getByText('Load Strategy');
it('shows deploy strategy button when all required fields are filled', async () => {
mockConnectedStore.mockSetSubscribeValue(true);
(DotrainOrderGui.chooseDeployment as Mock).mockResolvedValue({
getSelectTokens: () => [],
getCurrentDeployment: () => ({
deployment: {
order: {
inputs: [],
outputs: []
}
},
deposits: []
}),
getAllFieldDefinitions: () => []
});

await userEvent.type(urlInput, 'https://example.com/strategy.rain');
await userEvent.click(loadButton);
render(DeploymentSteps, {
props: {
dotrain,
deployment: 'flare-sflr-wflr',
deploymentDetails: { name: 'Deployment 1', description: 'Description 1' },
wagmiConfig: mockWagmiConfigStore,
wagmiConnected: mockConnectedStore,
appKitModal: writable({} as AppKit),
handleDeployModal: vi.fn()
}
});

await waitFor(() => {
expect(screen.getByText('No valid strategy exists at this URL')).toBeInTheDocument();
expect(screen.getByText('Deploy Strategy')).toBeInTheDocument();
});
});

it('initializes GUI when deployment is selected', async () => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
text: () => Promise.resolve(JSON.stringify(dotrain))
it('shows connect wallet button when not connected', async () => {
mockConnectedStore.mockSetSubscribeValue(false);
(DotrainOrderGui.chooseDeployment as Mock).mockResolvedValue({
getSelectTokens: () => [],
getCurrentDeployment: () => ({
deployment: {
order: {
inputs: [],
outputs: []
}
},
deposits: []
}),
getAllFieldDefinitions: () => []
});

const mockDeployments: string[] = ['deployment1', 'deployment2'];

(DotrainOrderGui.getDeploymentKeys as Mock).mockResolvedValue(mockDeployments);

render(DeploymentSteps);
const urlInput = screen.getByPlaceholderText('Enter URL to .rain file');
const loadButton = screen.getByText('Load Strategy');

await userEvent.type(urlInput, 'https://example.com/strategy.rain');
await userEvent.click(loadButton);

await waitFor(() => {
expect(screen.getByText('Select Deployment')).toBeInTheDocument();
expect(screen.getByText('Select a deployment')).toBeInTheDocument();
render(DeploymentSteps, {
props: {
dotrain,
deployment: 'flare-sflr-wflr',
deploymentDetails: { name: 'Deployment 1', description: 'Description 1' },
wagmiConfig: mockWagmiConfigStore,
wagmiConnected: mockConnectedStore,
appKitModal: writable({} as AppKit),
handleDeployModal: vi.fn()
}
});

const dropdownButton = screen.getByTestId('dropdown-button');
await userEvent.click(dropdownButton);
const dropdown = screen.getByTestId('dropdown');
await userEvent.click(dropdown);
const deploymentOption = screen.getByText('deployment1');
await userEvent.click(deploymentOption);

await waitFor(() => {
expect(DotrainOrderGui.chooseDeployment).toHaveBeenCalled();
expect(screen.getByText('Connect Wallet')).toBeInTheDocument();
});
});
});
34 changes: 34 additions & 0 deletions packages/ui-components/src/__tests__/DeploymentTile.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { render, screen } from '@testing-library/svelte';
import { describe, it, expect, vi } from 'vitest';
import { goto } from '$app/navigation';
import DeploymentTile from '../lib/components/deployment/DeploymentTile.svelte';

// Mock the goto function
vi.mock('$app/navigation', () => ({
goto: vi.fn()
}));

describe('DeploymentTile', () => {
const mockProps = {
strategyName: 'test-strategy',
key: 'test-key',
name: 'Test Deployment',
description: 'This is a test deployment description'
};

it('renders the deployment name and description', () => {
render(DeploymentTile, mockProps);

expect(screen.getByText('Test Deployment')).toBeInTheDocument();
expect(screen.getByText('This is a test deployment description')).toBeInTheDocument();
});

it('navigates to the correct URL when clicked', async () => {
const { getByRole } = render(DeploymentTile, mockProps);

const button = getByRole('button');
await button.click();

expect(goto).toHaveBeenCalledWith('/deploy/test-strategy/test-key');
});
});
71 changes: 71 additions & 0 deletions packages/ui-components/src/__tests__/DeploymentsSection.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { render, screen } from '@testing-library/svelte';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import DeploymentsSection from '../lib/components/deployment/DeploymentsSection.svelte';
import { DotrainOrderGui } from '@rainlanguage/orderbook/js_api';

// Mock the DotrainOrderGui
vi.mock('@rainlanguage/orderbook/js_api', () => ({
DotrainOrderGui: {
getDeploymentDetails: vi.fn()
}
}));

describe('DeploymentsSection', () => {
beforeEach(() => {
vi.clearAllMocks();
});

it('should render deployments when data is available', async () => {
const mockDeployments = new Map([
['key1', { name: 'Deployment 1', description: 'Description 1' }],
['key2', { name: 'Deployment 2', description: 'Description 2' }]
]);

vi.mocked(DotrainOrderGui.getDeploymentDetails).mockResolvedValue(mockDeployments);

render(DeploymentsSection, {
props: {
dotrain: 'test-dotrain',
strategyName: 'Test Strategy'
}
});

// Wait for deployments to load
const deployment1 = await screen.findByText('Deployment 1');
const deployment2 = await screen.findByText('Deployment 2');

expect(deployment1).toBeInTheDocument();
expect(deployment2).toBeInTheDocument();
});

it('should handle error when fetching deployments fails', async () => {
vi.mocked(DotrainOrderGui.getDeploymentDetails).mockRejectedValue(new Error('API Error'));

render(DeploymentsSection, {
props: {
dotrain: 'test-dotrain',
strategyName: 'Test Strategy'
}
});

const errorMessage = await screen.findByText(
'Error loading deployments: Error getting deployments.'
);
expect(errorMessage).toBeInTheDocument();
});

it('should fetch deployments when dotrain prop changes', async () => {
const { rerender } = render(DeploymentsSection, {
props: {
dotrain: '',
strategyName: 'Test Strategy'
}
});

expect(DotrainOrderGui.getDeploymentDetails).not.toHaveBeenCalled();

await rerender({ dotrain: 'new-dotrain', strategyName: 'Test Strategy' });

expect(DotrainOrderGui.getDeploymentDetails).toHaveBeenCalledWith('new-dotrain');
});
});
Loading

0 comments on commit 5dbbfef

Please sign in to comment.