Skip to content

Commit

Permalink
Merge branch 'main' into 1303-order-page-isnt-polling-and-can-be-out-…
Browse files Browse the repository at this point in the history
…of-sync
  • Loading branch information
hardyjosh authored Feb 20, 2025
2 parents c9ae58d + ee0bc6b commit 9b19c7c
Show file tree
Hide file tree
Showing 5 changed files with 189 additions and 98 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,20 +28,8 @@
import type { DisclaimerModalProps, DeployModalProps } from '../../types/modal';
import { getDeploymentTransactionArgs } from './getDeploymentTransactionArgs';
import type { HandleAddOrderResult } from './getDeploymentTransactionArgs';
enum DeploymentStepErrors {
NO_GUI = 'Error loading GUI',
NO_STRATEGY = 'No valid strategy exists at this URL',
NO_SELECT_TOKENS = 'Error loading tokens',
NO_TOKEN_INFO = 'Error loading token information',
NO_FIELD_DEFINITIONS = 'Error loading field definitions',
NO_DEPOSITS = 'Error loading deposits',
NO_TOKEN_INPUTS = 'Error loading token inputs',
NO_TOKEN_OUTPUTS = 'Error loading token outputs',
NO_GUI_DETAILS = 'Error getting GUI details',
NO_CHAIN = 'Unsupported chain ID',
SERIALIZE_ERROR = 'Error serializing state',
ADD_ORDER_FAILED = 'Failed to add order'
}
import { DeploymentStepsError, DeploymentStepsErrorCode } from '$lib/errors';
export let settings: Writable<ConfigSource>;
export let dotrain: string;
export let deployment: GuiDeployment;
Expand All @@ -59,11 +47,11 @@
let showAdvancedOptions: boolean = false;
let gui: DotrainOrderGui | null = null;
let checkingDeployment: boolean = false;
let error: DeploymentStepErrors | null = null;
let errorDetails: string | null = null;
let networkKey: string | null = null;
let subgraphUrl: string = '';
let deploymentStepsError = DeploymentStepsError.error;
export let wagmiConfig: Writable<Config | undefined>;
export let wagmiConnected: Writable<boolean>;
export let appKitModal: Writable<AppKit>;
Expand All @@ -74,8 +62,7 @@
async function handleDeploymentChange(deployment: string) {
if (!deployment || !dotrain) return;
error = null;
errorDetails = null;
DeploymentStepsError.clear();
try {
gui = await DotrainOrderGui.chooseDeployment(dotrain, deployment, pushGuiStateToUrlHistory);
Expand All @@ -87,13 +74,11 @@
selectTokens = gui.getSelectTokens();
return selectTokens;
} catch (e) {
error = DeploymentStepErrors.NO_SELECT_TOKENS;
return (errorDetails = e instanceof Error ? e.message : 'Unknown error');
DeploymentStepsError.catch(e, DeploymentStepsErrorCode.NO_SELECT_TOKENS);
}
}
} catch (e) {
error = DeploymentStepErrors.NO_GUI;
return (errorDetails = e instanceof Error ? e.message : 'Unknown error');
DeploymentStepsError.catch(e, DeploymentStepsErrorCode.NO_GUI);
}
}
Expand All @@ -102,8 +87,7 @@
try {
allFieldDefinitions = gui.getAllFieldDefinitions();
} catch (e) {
error = DeploymentStepErrors.NO_FIELD_DEFINITIONS;
errorDetails = e instanceof Error ? e.message : 'Unknown error';
DeploymentStepsError.catch(e, DeploymentStepsErrorCode.NO_FIELD_DEFINITIONS);
}
}
Expand All @@ -115,8 +99,7 @@
allDepositFields = depositFields;
} catch (e) {
error = DeploymentStepErrors.NO_DEPOSITS;
errorDetails = e instanceof Error ? e.message : 'Unknown error';
DeploymentStepsError.catch(e, DeploymentStepsErrorCode.NO_DEPOSITS);
}
}
Expand All @@ -127,8 +110,7 @@
try {
allTokenInputs = gui.getCurrentDeployment().deployment.order.inputs;
} catch (e) {
error = DeploymentStepErrors.NO_TOKEN_INPUTS;
errorDetails = e instanceof Error ? e.message : 'Unknown error';
DeploymentStepsError.catch(e, DeploymentStepsErrorCode.NO_TOKEN_INPUTS);
}
}
Expand All @@ -137,8 +119,7 @@
try {
allTokenOutputs = gui.getCurrentDeployment().deployment.order.outputs;
} catch (e) {
error = DeploymentStepErrors.NO_TOKEN_OUTPUTS;
errorDetails = e instanceof Error ? e.message : 'Unknown error';
DeploymentStepsError.catch(e, DeploymentStepsErrorCode.NO_TOKEN_OUTPUTS);
}
}
Expand All @@ -148,15 +129,14 @@
async function updateFields() {
try {
error = null;
errorDetails = null;
DeploymentStepsError.clear();
getAllDepositFields();
getAllFieldDefinitions();
getAllTokenInputs();
getAllTokenOutputs();
} catch (e) {
error = DeploymentStepErrors.NO_GUI;
errorDetails = e instanceof Error ? e.message : 'Unknown error';
DeploymentStepsError.catch(e, DeploymentStepsErrorCode.NO_GUI);
}
}
Expand Down Expand Up @@ -184,24 +164,23 @@
}
async function handleDeployButtonClick() {
error = null;
errorDetails = null;
DeploymentStepsError.clear();
if (!gui) {
error = DeploymentStepErrors.NO_GUI;
DeploymentStepsError.catch(null, DeploymentStepsErrorCode.NO_GUI);
return;
}
if (!allTokenOutputs) {
error = DeploymentStepErrors.NO_TOKEN_OUTPUTS;
DeploymentStepsError.catch(null, DeploymentStepsErrorCode.NO_TOKEN_OUTPUTS);
return;
}
if (!wagmiConfig) {
error = DeploymentStepErrors.NO_CHAIN;
DeploymentStepsError.catch(null, DeploymentStepsErrorCode.NO_CHAIN);
return;
}
if (!networkKey) {
error = DeploymentStepErrors.NO_CHAIN;
DeploymentStepsError.catch(null, DeploymentStepsErrorCode.NO_CHAIN);
return;
}
Expand All @@ -213,21 +192,20 @@
result = await getDeploymentTransactionArgs(gui, $wagmiConfig);
} catch (e) {
checkingDeployment = false;
error = DeploymentStepErrors.ADD_ORDER_FAILED;
errorDetails = e instanceof Error ? e.message : 'Unknown error';
DeploymentStepsError.catch(e, DeploymentStepsErrorCode.ADD_ORDER_FAILED);
}
if (!result) {
checkingDeployment = false;
error = DeploymentStepErrors.ADD_ORDER_FAILED;
DeploymentStepsError.catch(null, DeploymentStepsErrorCode.ADD_ORDER_FAILED);
return;
}
checkingDeployment = false;
const onAccept = () => {
if (!networkKey) {
error = DeploymentStepErrors.NO_CHAIN;
DeploymentStepsError.catch(null, DeploymentStepsErrorCode.NO_CHAIN);
return;
}
Expand Down Expand Up @@ -260,21 +238,18 @@
showAdvancedOptions = true;
}
} catch (e) {
error = DeploymentStepErrors.NO_SELECT_TOKENS;
return (errorDetails = e instanceof Error ? e.message : 'Unknown error');
DeploymentStepsError.catch(e, DeploymentStepsErrorCode.NO_SELECT_TOKENS);
}
}
};
</script>

<div>
{#if error || errorDetails}
{#if $deploymentStepsError}
<Alert color="red">
{#if error}
<p class="text-red-500">{error}</p>
{/if}
{#if errorDetails}
<p class="text-red-500">{errorDetails}</p>
<p class="text-red-500">{$deploymentStepsError.code}</p>
{#if $deploymentStepsError.details}
<p class="text-red-500">{$deploymentStepsError.details}</p>
{/if}
</Alert>
{/if}
Expand Down Expand Up @@ -311,13 +286,11 @@
<TokenIOSection bind:allTokenInputs bind:allTokenOutputs {gui} />
{/if}

{#if error || errorDetails}
{#if $deploymentStepsError}
<Alert color="red">
{#if error}
<p class="text-red-500">{error}</p>
{/if}
{#if errorDetails}
<p class="text-red-500">{errorDetails}</p>
<p class="text-red-500">{$deploymentStepsError.code}</p>
{#if $deploymentStepsError.details}
<p class="text-red-500">{$deploymentStepsError.details}</p>
{/if}
</Alert>
{/if}
Expand Down
51 changes: 51 additions & 0 deletions packages/ui-components/src/lib/errors/DeploymentStepsError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { writable } from 'svelte/store';

export enum DeploymentStepsErrorCode {
NO_GUI = 'Error loading GUI',
NO_STRATEGY = 'No valid strategy exists at this URL',
NO_SELECT_TOKENS = 'Error loading tokens',
NO_TOKEN_INFO = 'Error loading token information',
NO_FIELD_DEFINITIONS = 'Error loading field definitions',
NO_DEPOSITS = 'Error loading deposits',
NO_TOKEN_INPUTS = 'Error loading token inputs',
NO_TOKEN_OUTPUTS = 'Error loading token outputs',
NO_GUI_DETAILS = 'Error getting GUI details',
NO_CHAIN = 'Unsupported chain ID',
SERIALIZE_ERROR = 'Error serializing state',
ADD_ORDER_FAILED = 'Failed to add order'
}

export class DeploymentStepsError extends Error {
private static errorStore = writable<DeploymentStepsError | null>(null);

constructor(
public code: DeploymentStepsErrorCode,
public details?: string
) {
super(code);
this.name = 'DeploymentStepsError';
}

static get error() {
return this.errorStore;
}

static throwIfNull<T>(value: T | null | undefined, code: DeploymentStepsErrorCode): T {
if (value === null || value === undefined) {
throw new DeploymentStepsError(code);
}
return value;
}

static catch(e: unknown, code: DeploymentStepsErrorCode) {
const error =
e instanceof DeploymentStepsError
? e
: new DeploymentStepsError(code, e instanceof Error ? e.message : 'Unknown error');
this.errorStore.set(error);
}

static clear() {
this.errorStore.set(null);
}
}
1 change: 1 addition & 0 deletions packages/ui-components/src/lib/errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './DeploymentStepsError';
55 changes: 54 additions & 1 deletion packages/webapp/src/__tests__/DepositOrWithdrawModal.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { signerAddress } from '$lib/stores/wagmi';
import { readContract, switchChain } from '@wagmi/core';

import type { ComponentProps } from 'svelte';
import { getVaultApprovalCalldata } from '@rainlanguage/orderbook/js_api';
import { getVaultDepositCalldata } from '@rainlanguage/orderbook/js_api';

export type ModalProps = ComponentProps<DepositOrWithdrawModal>;

Expand Down Expand Up @@ -147,7 +149,7 @@ describe('DepositOrWithdrawModal', () => {
render(DepositOrWithdrawModal, defaultProps);

await waitFor(() => {
expect(screen.getByTestId('chain-error')).toHaveTextContent(
expect(screen.getByTestId('error-message')).toHaveTextContent(
'Switch to Ethereum to check your balance.'
);
});
Expand All @@ -173,4 +175,55 @@ describe('DepositOrWithdrawModal', () => {
const continueButton = screen.getByText('Deposit');
expect(continueButton).toBeDisabled();
});

it('shows loading state while checking calldata', async () => {
render(DepositOrWithdrawModal, defaultProps);

const input = screen.getByRole('textbox');
await fireEvent.input(input, { target: { value: '1' } });

const depositButton = screen.getByText('Deposit');
await fireEvent.click(depositButton);

expect(screen.getByText('Checking...')).toBeInTheDocument();
});

it('handles failed calldata fetch', async () => {
vi.mocked(getVaultDepositCalldata).mockRejectedValueOnce(new Error('Failed to fetch'));

render(DepositOrWithdrawModal, defaultProps);

const input = screen.getByRole('textbox');
await fireEvent.input(input, { target: { value: '1' } });

const depositButton = screen.getByText('Deposit');
await fireEvent.click(depositButton);

await waitFor(() => {
expect(screen.getByTestId('error-message')).toHaveTextContent('Failed to get calldata.');
});
});

it('handles deposit without approval when approval fails', async () => {
const handleTransactionSpy = vi.spyOn(transactionStore, 'handleDepositOrWithdrawTransaction');
vi.mocked(getVaultApprovalCalldata).mockRejectedValueOnce(new Error('Approval not needed'));

render(DepositOrWithdrawModal, defaultProps);

const input = screen.getByRole('textbox');
await fireEvent.input(input, { target: { value: '1' } });

const depositButton = screen.getByText('Deposit');
await fireEvent.click(depositButton);

expect(handleTransactionSpy).toHaveBeenCalledWith({
action: 'deposit',
chainId: 1,
vault: mockVault,
config: undefined,
subgraphUrl: undefined,
approvalCalldata: undefined,
transactionCalldata: { to: '0x123', data: '0x456' }
});
});
});
Loading

0 comments on commit 9b19c7c

Please sign in to comment.