Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move URL update function to gui as a callback #1361

Merged
merged 12 commits into from
Feb 20, 2025
8 changes: 6 additions & 2 deletions crates/js_api/src/gui/deposits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ impl DotrainOrderGui {
.ok_or(GuiError::DepositTokenNotFound(token.clone()))?;

if amount.is_empty() {
self.remove_deposit(token);
self.remove_deposit(token)?;
return Ok(());
}

Expand All @@ -84,12 +84,16 @@ impl DotrainOrderGui {
};

self.deposits.insert(token, value);

self.execute_state_update_callback()?;
Ok(())
}

#[wasm_bindgen(js_name = "removeDeposit")]
pub fn remove_deposit(&mut self, token: String) {
pub fn remove_deposit(&mut self, token: String) -> Result<(), GuiError> {
self.deposits.remove(&token);
self.execute_state_update_callback()?;
Ok(())
}

#[wasm_bindgen(js_name = "getDepositPresets")]
Expand Down
6 changes: 5 additions & 1 deletion crates/js_api/src/gui/field_values.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ impl DotrainOrderGui {
}
}
self.field_values.insert(binding, value);

self.execute_state_update_callback()?;
Ok(())
}

Expand All @@ -54,8 +56,10 @@ impl DotrainOrderGui {
}

#[wasm_bindgen(js_name = "removeFieldValue")]
pub fn remove_field_value(&mut self, binding: String) {
pub fn remove_field_value(&mut self, binding: String) -> Result<(), GuiError> {
self.field_values.remove(&binding);
self.execute_state_update_callback()?;
Ok(())
}

#[wasm_bindgen(js_name = "getFieldValue")]
Expand Down
6 changes: 6 additions & 0 deletions crates/js_api/src/gui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub struct DotrainOrderGui {
selected_deployment: String,
field_values: BTreeMap<String, field_values::PairValue>,
deposits: BTreeMap<String, field_values::PairValue>,
#[serde(skip)]
state_update_callback: Option<js_sys::Function>,
}
#[wasm_bindgen]
impl DotrainOrderGui {
Expand All @@ -67,6 +69,7 @@ impl DotrainOrderGui {
pub async fn choose_deployment(
dotrain: String,
deployment_name: String,
state_update_callback: Option<js_sys::Function>,
) -> Result<DotrainOrderGui, GuiError> {
let dotrain_order = DotrainOrder::new(dotrain, None).await?;

Expand All @@ -80,6 +83,7 @@ impl DotrainOrderGui {
selected_deployment: deployment_name.clone(),
field_values: BTreeMap::new(),
deposits: BTreeMap::new(),
state_update_callback,
})
}

Expand Down Expand Up @@ -235,6 +239,8 @@ pub enum GuiError {
BindingHasNoPresets(String),
#[error("Token not in select tokens: {0}")]
TokenNotInSelectTokens(String),
#[error("JavaScript error: {0}")]
JsError(String),
#[error(transparent)]
DotrainOrderError(#[from] DotrainOrderError),
#[error(transparent)]
Expand Down
2 changes: 2 additions & 0 deletions crates/js_api/src/gui/order_operations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,8 @@ impl DotrainOrderGui {
.dotrain_yaml()
.get_order(&deployment.deployment.order.key)?
.update_vault_id(is_input, index, vault_id)?;

self.execute_state_update_callback()?;
Ok(())
}

Expand Down
4 changes: 4 additions & 0 deletions crates/js_api/src/gui/select_tokens.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ impl DotrainOrderGui {
Some(&token_info.name),
Some(&token_info.symbol),
)?;

self.execute_state_update_callback()?;
Ok(())
}

Expand All @@ -122,6 +124,8 @@ impl DotrainOrderGui {
}

Token::remove_record_from_yaml(self.dotrain_order.orderbook_yaml().documents, &key)?;

self.execute_state_update_callback()?;
Ok(())
}

Expand Down
13 changes: 13 additions & 0 deletions crates/js_api/src/gui/state_management.rs
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ impl DotrainOrderGui {
pub async fn deserialize_state(
dotrain: String,
serialized: String,
state_update_callback: Option<js_sys::Function>,
) -> Result<DotrainOrderGui, GuiError> {
let compressed = URL_SAFE.decode(serialized)?;

Expand Down Expand Up @@ -185,6 +186,7 @@ impl DotrainOrderGui {
field_values,
deposits,
selected_deployment: state.selected_deployment.clone(),
state_update_callback,
};

let deployment_select_tokens = Gui::parse_select_tokens(
Expand Down Expand Up @@ -247,4 +249,15 @@ impl DotrainOrderGui {
let value = self.deposits.get(&token);
value.map(|v| v.is_preset)
}

#[wasm_bindgen(js_name = "executeStateUpdateCallback")]
pub fn execute_state_update_callback(&self) -> Result<(), GuiError> {
if let Some(callback) = &self.state_update_callback {
let state = to_value(&self.serialize_state()?)?;
callback.call1(&JsValue::UNDEFINED, &state).map_err(|e| {
GuiError::JsError(format!("Failed to execute state update callback: {:?}", e))
})?;
}
Ok(())
}
}
88 changes: 76 additions & 12 deletions packages/orderbook/test/js_api/gui.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import assert from 'assert';
import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';
import { afterAll, beforeAll, beforeEach, describe, expect, it, Mock, vi } from 'vitest';
import { DotrainOrderGui } from '../../dist/cjs/js_api.js';
import {
AddOrderCalldataResult,
Expand Down Expand Up @@ -369,6 +369,18 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function ()
assert.equal(guiConfig.description, 'Fixed limit order strategy');
});

it('should initialize gui object with state update callback', async () => {
const stateUpdateCallback = vi.fn();
const gui = await DotrainOrderGui.chooseDeployment(
dotrainWithGui,
'some-deployment',
stateUpdateCallback
);

gui.executeStateUpdateCallback();
assert.equal(stateUpdateCallback.mock.calls.length, 1);
});

it('should get strategy details', async () => {
const strategyDetails: NameAndDescription =
await DotrainOrderGui.getStrategyDetails(dotrainWithGui);
Expand Down Expand Up @@ -434,14 +446,20 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function ()

describe('deposit tests', async () => {
let gui: DotrainOrderGui;
beforeAll(async () => {
let stateUpdateCallback: Mock;
beforeEach(async () => {
stateUpdateCallback = vi.fn();
mockServer
.forPost('/rpc-url')
.withBodyIncluding('0x82ad56cb')
.thenSendJsonRpcResult(
'0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007546f6b656e203100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025431000000000000000000000000000000000000000000000000000000000000'
);
gui = await DotrainOrderGui.chooseDeployment(dotrainWithGui, 'some-deployment');
gui = await DotrainOrderGui.chooseDeployment(
dotrainWithGui,
'some-deployment',
stateUpdateCallback
);
});

it('should add deposit', async () => {
Expand All @@ -452,6 +470,9 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function ()
assert.equal(deposits.length, 1);

assert.equal(gui.hasAnyDeposit(), true);

assert.equal(stateUpdateCallback.mock.calls.length, 1);
expect(stateUpdateCallback).toHaveBeenCalledWith(gui.serializeState());
});

it('should update deposit', async () => {
Expand All @@ -460,6 +481,9 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function ()
const deposits: TokenDeposit[] = gui.getDeposits();
assert.equal(deposits.length, 1);
assert.equal(deposits[0].amount, '100.6');

assert.equal(stateUpdateCallback.mock.calls.length, 2);
expect(stateUpdateCallback).toHaveBeenCalledWith(gui.serializeState());
});

it('should throw error if deposit token is not found in gui config', () => {
Expand All @@ -481,6 +505,9 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function ()
assert.equal(gui.getDeposits().length, 1);
gui.saveDeposit('token1', '');
assert.equal(gui.getDeposits().length, 0);

assert.equal(stateUpdateCallback.mock.calls.length, 4);
expect(stateUpdateCallback).toHaveBeenCalledWith(gui.serializeState());
});

it('should get deposit presets', async () => {
Expand All @@ -502,14 +529,20 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function ()

describe('field value tests', async () => {
let gui: DotrainOrderGui;
beforeAll(async () => {
let stateUpdateCallback: Mock;
beforeEach(async () => {
stateUpdateCallback = vi.fn();
mockServer
.forPost('/rpc-url')
.withBodyIncluding('0x82ad56cb')
.thenSendJsonRpcResult(
'0x00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000001a0000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000007546f6b656e203100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000025431000000000000000000000000000000000000000000000000000000000000'
);
gui = await DotrainOrderGui.chooseDeployment(dotrainWithGui, 'some-deployment');
gui = await DotrainOrderGui.chooseDeployment(
dotrainWithGui,
'some-deployment',
stateUpdateCallback
);
});

it('should save the field value as presets', async () => {
Expand All @@ -529,6 +562,9 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function ()
value: allFieldDefinitions[0].presets[2].id
});
assert.deepEqual(gui.getFieldValue('binding-1'), allFieldDefinitions[0].presets[2]);

assert.equal(stateUpdateCallback.mock.calls.length, 3);
expect(stateUpdateCallback).toHaveBeenCalledWith(gui.serializeState());
});

it('should save field value as custom values', async () => {
Expand Down Expand Up @@ -582,6 +618,9 @@ describe('Rain Orderbook JS API Package Bindgen Tests - Gui', async function ()
value: 'true'
}
});

assert.equal(stateUpdateCallback.mock.calls.length, 4);
expect(stateUpdateCallback).toHaveBeenCalledWith(gui.serializeState());
});

it('should throw error during save if preset is not found in field definition', () => {
Expand Down Expand Up @@ -1162,6 +1201,7 @@ ${dotrainWithoutVaultIds}`;
});

it('should set vault ids', async () => {
let stateUpdateCallback = vi.fn();
mockServer
.forPost('/rpc-url')
.withBodyIncluding('0x82ad56cb')
Expand All @@ -1174,7 +1214,11 @@ ${dotrainWithoutVaultIds}`;

${dotrainWithoutVaultIds}
`;
gui = await DotrainOrderGui.chooseDeployment(testDotrain, 'other-deployment');
gui = await DotrainOrderGui.chooseDeployment(
testDotrain,
'other-deployment',
stateUpdateCallback
);

let currentDeployment: GuiDeployment = gui.getCurrentDeployment();
assert.equal(currentDeployment.deployment.order.inputs[0].vaultId, undefined);
Expand Down Expand Up @@ -1210,6 +1254,9 @@ ${dotrainWithoutVaultIds}`;
expect(() => gui.setVaultId(true, 0, 'test')).toThrow(
"Invalid value for field 'vault-id': Failed to parse vault id in index '0' of inputs in order 'some-order'"
);

assert.equal(stateUpdateCallback.mock.calls.length, 4);
expect(stateUpdateCallback).toHaveBeenCalledWith(gui.serializeState());
});

it('should skip deposits with zero amount for deposit calldata', async () => {
Expand Down Expand Up @@ -1239,13 +1286,19 @@ ${dotrainWithoutVaultIds}`;

describe('select tokens tests', async () => {
let gui: DotrainOrderGui;
beforeAll(async () => {
let stateUpdateCallback: Mock;
beforeEach(async () => {
stateUpdateCallback = vi.fn();
let dotrain3 = `
${guiConfig3}

${dotrainWithoutTokens}
`;
gui = await DotrainOrderGui.chooseDeployment(dotrain3, 'other-deployment');
gui = await DotrainOrderGui.chooseDeployment(
dotrain3,
'other-deployment',
stateUpdateCallback
);
});

it('should get select tokens', async () => {
Expand Down Expand Up @@ -1321,12 +1374,12 @@ ${dotrainWithoutVaultIds}`;
assert.equal(tokenInfo.name, 'Teken 2');
assert.equal(tokenInfo.symbol, 'T2');
assert.equal(tokenInfo.decimals, 18);

assert.equal(stateUpdateCallback.mock.calls.length, 2);
expect(stateUpdateCallback).toHaveBeenCalledWith(gui.serializeState());
});

it('should replace select token', async () => {
gui.removeSelectToken('token1');
gui.removeSelectToken('token2');

mockServer
.forPost('/rpc-url')
.once()
Expand Down Expand Up @@ -1355,15 +1408,23 @@ ${dotrainWithoutVaultIds}`;
assert.equal(tokenInfo.name, 'Teken 2');
assert.equal(tokenInfo.symbol, 'T2');
assert.equal(tokenInfo.decimals, 18);

assert.equal(stateUpdateCallback.mock.calls.length, 3);
expect(stateUpdateCallback).toHaveBeenCalledWith(gui.serializeState());
});

it('should remove select token', async () => {
stateUpdateCallback = vi.fn();
let dotrain3 = `
${guiConfig3}

${dotrainWithoutTokens}
`;
gui = await DotrainOrderGui.chooseDeployment(dotrain3, 'other-deployment');
gui = await DotrainOrderGui.chooseDeployment(
dotrain3,
'other-deployment',
stateUpdateCallback
);

mockServer
.forPost('/rpc-url')
Expand Down Expand Up @@ -1392,6 +1453,9 @@ ${dotrainWithoutVaultIds}`;
await expect(async () => await gui.getTokenInfo('token1')).rejects.toThrow(
"Missing required field 'tokens' in root"
);

assert.equal(stateUpdateCallback.mock.calls.length, 2);
expect(stateUpdateCallback).toHaveBeenCalledWith(gui.serializeState());
});

it('should get network key', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -630,7 +630,7 @@ const defaultProps: DeploymentStepsProps = {
handleDeployModal: vi.fn() as unknown as (args: DeployModalProps) => void,
handleDisclaimerModal: vi.fn() as unknown as (args: DisclaimerModalProps) => void,
settings: writable({} as ConfigSource),
handleUpdateGuiState: vi.fn()
pushGuiStateToUrlHistory: vi.fn()
};

describe('DeploymentSteps', () => {
Expand Down
Loading
Loading