diff --git a/Cargo.lock b/Cargo.lock index d69c5c1ce..a299e17d2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6457,6 +6457,7 @@ dependencies = [ "cynic", "cynic-codegen", "cynic-introspection", + "futures", "httpmock", "insta", "js-sys", diff --git a/crates/subgraph/Cargo.toml b/crates/subgraph/Cargo.toml index fa652dc7a..7762358c0 100644 --- a/crates/subgraph/Cargo.toml +++ b/crates/subgraph/Cargo.toml @@ -19,6 +19,7 @@ alloy = { workspace = true, features = ["rand"] } rain_orderbook_bindings = { workspace = true } chrono = { workspace = true } cynic-introspection = "3.7.3" +futures = "0.3.17" tsify = { version = "0.4.5", default-features = false, features = ["js", "wasm-bindgen"] } wasm-bindgen = { version = "0.2.92" } js-sys = { version = "0.3.69" } diff --git a/crates/subgraph/src/lib.rs b/crates/subgraph/src/lib.rs index 2ca4cc6f5..12685fe86 100644 --- a/crates/subgraph/src/lib.rs +++ b/crates/subgraph/src/lib.rs @@ -1,4 +1,5 @@ mod cynic_client; +mod multi_orderbook_client; mod orderbook_client; mod pagination; pub mod types; @@ -10,5 +11,6 @@ pub mod vol; #[cynic::schema("orderbook")] pub mod schema {} +pub use multi_orderbook_client::{MultiOrderbookSubgraphClient, MultiSubgraphArgs}; pub use orderbook_client::{OrderbookSubgraphClient, OrderbookSubgraphClientError}; pub use pagination::{PageQueryClient, PaginationArgs}; diff --git a/crates/subgraph/src/multi_orderbook_client.rs b/crates/subgraph/src/multi_orderbook_client.rs new file mode 100644 index 000000000..ee00be6ce --- /dev/null +++ b/crates/subgraph/src/multi_orderbook_client.rs @@ -0,0 +1,67 @@ +use futures::future::join_all; +use reqwest::Url; +use serde::Deserialize; + +use crate::{ + types::common::{OrderWithSubgraphName, OrdersListFilterArgs}, + OrderbookSubgraphClient, OrderbookSubgraphClientError, PaginationArgs, +}; + +#[derive(Debug, Clone, Deserialize)] +pub struct MultiSubgraphArgs { + url: Url, + name: String, +} + +pub struct MultiOrderbookSubgraphClient { + subgraphs: Vec, +} +impl MultiOrderbookSubgraphClient { + pub fn new(subgraphs: Vec) -> Self { + Self { subgraphs } + } + + fn get_orderbook_subgraph_client(&self, url: Url) -> OrderbookSubgraphClient { + OrderbookSubgraphClient::new(url) + } + + pub async fn orders_list( + &self, + filter_args: OrdersListFilterArgs, + pagination_args: PaginationArgs, + ) -> Result, OrderbookSubgraphClientError> { + let futures = self.subgraphs.iter().map(|subgraph| { + let url = subgraph.url.clone(); + let filter_args = filter_args.clone(); + let pagination_args = pagination_args.clone(); + async move { + let client = self.get_orderbook_subgraph_client(url); + let orders = client.orders_list(filter_args, pagination_args).await?; + let wrapped_orders: Vec = orders + .into_iter() + .map(|order| OrderWithSubgraphName { + order, + subgraph_name: subgraph.name.clone(), + }) + .collect(); + Ok::<_, OrderbookSubgraphClientError>(wrapped_orders) + } + }); + + let results = join_all(futures).await; + + let mut all_orders: Vec = results + .into_iter() + .filter_map(Result::ok) + .flatten() + .collect(); + + all_orders.sort_by(|a, b| { + let a_timestamp = a.order.timestamp_added.0.parse::().unwrap_or(0); + let b_timestamp = b.order.timestamp_added.0.parse::().unwrap_or(0); + b_timestamp.cmp(&a_timestamp) + }); + + Ok(all_orders) + } +} diff --git a/crates/subgraph/src/types/common.rs b/crates/subgraph/src/types/common.rs index fbf287e21..7c0936cdd 100644 --- a/crates/subgraph/src/types/common.rs +++ b/crates/subgraph/src/types/common.rs @@ -94,6 +94,14 @@ pub struct Order { pub trades: Vec, } +#[derive(Debug, Serialize, Clone)] +#[typeshare] +#[serde(rename_all = "camelCase")] +pub struct OrderWithSubgraphName { + pub order: Order, + pub subgraph_name: String, +} + #[derive(cynic::QueryFragment, Debug, Serialize, Clone)] #[cynic(graphql_type = "Order")] #[serde(rename_all = "camelCase")] diff --git a/package-lock.json b/package-lock.json index 41820f38f..a19cd5e64 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@rainlanguage/orderbook", - "version": "0.0.1-alpha.4", + "version": "0.0.1-alpha.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@rainlanguage/orderbook", - "version": "0.0.1-alpha.4", + "version": "0.0.1-alpha.5", "license": "CAL-1.0", "dependencies": { "buffer": "^6.0.3" diff --git a/package.json b/package.json index 5261b910c..493ab3f40 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@rainlanguage/orderbook", "description": "Provides RainLanguage Orderbook rust crates' functionalities in typescript through wasm bindgen", - "version": "0.0.1-alpha.4", + "version": "0.0.1-alpha.5", "author": "Rain Language", "license": "CAL-1.0", "repository": { diff --git a/tauri-app/src-tauri/Cargo.lock b/tauri-app/src-tauri/Cargo.lock index 49665c07d..f98176982 100644 --- a/tauri-app/src-tauri/Cargo.lock +++ b/tauri-app/src-tauri/Cargo.lock @@ -8371,6 +8371,7 @@ dependencies = [ "cynic", "cynic-codegen", "cynic-introspection", + "futures", "js-sys", "rain_orderbook_bindings", "reqwest 0.12.5", diff --git a/tauri-app/src-tauri/src/commands/order.rs b/tauri-app/src-tauri/src/commands/order.rs index 0a121b535..c7b93ffd5 100644 --- a/tauri-app/src-tauri/src/commands/order.rs +++ b/tauri-app/src-tauri/src/commands/order.rs @@ -8,22 +8,20 @@ use rain_orderbook_common::{ types::FlattenError, types::OrderDetailExtended, types::OrderFlattened, }; use rain_orderbook_subgraph_client::{types::common::*, PaginationArgs}; +use rain_orderbook_subgraph_client::{MultiOrderbookSubgraphClient, MultiSubgraphArgs}; use std::fs; use std::path::PathBuf; use tauri::AppHandle; #[tauri::command] pub async fn orders_list( - subgraph_args: SubgraphArgs, + multi_subgraph_args: Vec, filter_args: OrdersListFilterArgs, pagination_args: PaginationArgs, -) -> CommandResult> { - let orders = subgraph_args - .to_subgraph_client() - .await? - .orders_list(filter_args, pagination_args) - .await?; - Ok(orders) +) -> CommandResult> { + let client = MultiOrderbookSubgraphClient::new(multi_subgraph_args); + let all_orders = client.orders_list(filter_args, pagination_args).await?; + Ok(all_orders) } #[tauri::command] diff --git a/tauri-app/src/lib/components/ListViewOrderbookSelector.svelte b/tauri-app/src/lib/components/ListViewOrderbookSelector.svelte index 1b66d003e..8c55deff5 100644 --- a/tauri-app/src/lib/components/ListViewOrderbookSelector.svelte +++ b/tauri-app/src/lib/components/ListViewOrderbookSelector.svelte @@ -1,7 +1,5 @@ + + diff --git a/tauri-app/src/lib/components/dropdown/DropdownActiveSubgraphs.test.ts b/tauri-app/src/lib/components/dropdown/DropdownActiveSubgraphs.test.ts new file mode 100644 index 000000000..262bd3bf4 --- /dev/null +++ b/tauri-app/src/lib/components/dropdown/DropdownActiveSubgraphs.test.ts @@ -0,0 +1,75 @@ +import { render, fireEvent, screen, waitFor } from '@testing-library/svelte'; +import { get, writable } from 'svelte/store'; +import { activeSubgraphs, settings } from '$lib/stores/settings'; +import { beforeEach, expect, test, vi, describe } from 'vitest'; +import DropdownActiveSubgraphs from './DropdownActiveSubgraphs.svelte'; + +vi.mock('$lib/stores/settings', async (importOriginal) => { + const { mockConfigSource } = await import('$lib/mocks/settings'); + return { + ...((await importOriginal()) as object), + settings: writable({ + ...mockConfigSource, + subgraphs: { + mainnet: 'mainnet', + testnet: 'testnet', + local: 'local', + }, + }), + activeSubgraphs: writable({}), + }; +}); + +describe('DropdownActiveSubgraphs', () => { + beforeEach(() => { + settings.set({ + ...get(settings), + subgraphs: { + mainnet: 'mainnet', + testnet: 'testnet', + local: 'local', + }, + }); + activeSubgraphs.set({}); + }); + + test('renders correctly', () => { + render(DropdownActiveSubgraphs); + expect(screen.getByText('Networks')).toBeInTheDocument(); + }); + + test('displays the correct number of options', async () => { + render(DropdownActiveSubgraphs); + + await fireEvent.click(screen.getByTestId('dropdown-checkbox-button')); + + await waitFor(() => { + const options = screen.getAllByTestId('dropdown-checkbox-option'); + expect(options).toHaveLength(3); + }); + }); + + test('updates active subgraphs when an option is selected', async () => { + render(DropdownActiveSubgraphs); + + await fireEvent.click(screen.getByTestId('dropdown-checkbox-button')); + await fireEvent.click(screen.getByText('mainnet')); + await waitFor(() => { + expect(get(activeSubgraphs)).toEqual({ mainnet: 'mainnet' }); + }); + + await fireEvent.click(screen.getByText('testnet')); + await waitFor(() => { + expect(get(activeSubgraphs)).toEqual({ mainnet: 'mainnet', testnet: 'testnet' }); + }); + + await fireEvent.click(screen.getByText('local')); + await waitFor(() => { + expect(get(activeSubgraphs)).toEqual({ + mainnet: 'mainnet', + testnet: 'testnet', + local: 'local', + }); + }); + }); +}); diff --git a/tauri-app/src/lib/components/input/InputOrderHash.svelte b/tauri-app/src/lib/components/input/InputOrderHash.svelte index e58c99919..ea2d14ac1 100644 --- a/tauri-app/src/lib/components/input/InputOrderHash.svelte +++ b/tauri-app/src/lib/components/input/InputOrderHash.svelte @@ -11,7 +11,7 @@ } -
+
{ return ordersList( - $subgraphUrl, + $activeSubgraphs, Object.values(get(activeAccounts)), $activeOrderStatus, $orderHash, @@ -41,7 +41,7 @@ return lastPage.length === DEFAULT_PAGE_SIZE ? lastPageParam + 1 : undefined; }, refetchInterval: DEFAULT_REFRESH_INTERVAL, - enabled: !!$subgraphUrl, + enabled: Object.keys($activeSubgraphs).length > 0, }); @@ -50,7 +50,9 @@ {query} emptyMessage="No Orders Found" on:clickRow={(e) => { - goto(`/orders/${e.detail.item.id}`); + activeNetworkRef.set(e.detail.item.subgraphName); + activeOrderbookRef.set(e.detail.item.subgraphName); + goto(`/orders/${e.detail.item.order.id}`); }} > @@ -61,6 +63,7 @@ + Network Active Order Owner @@ -78,41 +81,44 @@ + + {item.subgraphName} + - {#if item.active} + {#if item.order.active} Active {:else} Inactive {/if} - + - + - + - {formatTimestampSecondsAsLocal(BigInt(item.timestampAdded))} + {formatTimestampSecondsAsLocal(BigInt(item.order.timestampAdded))} - {item.inputs?.map((t) => t.token.symbol)} + {item.order.inputs?.map((t) => t.token.symbol)} - {item.outputs?.map((t) => t.token.symbol)} + {item.order.outputs?.map((t) => t.token.symbol)} {item.trades.length > 99 ? '>99' : item.trades.length}{item.order.trades.length > 99 ? '>99' : item.order.trades.length} - {#if $walletAddressMatchesOrBlank(item.owner) && item.active} + {#if $walletAddressMatchesOrBlank(item.order.owner) && item.order.active} {/if} - {#if $walletAddressMatchesOrBlank(item.owner) && item.active} - + {#if $walletAddressMatchesOrBlank(item.order.owner) && item.order.active} + { e.stopPropagation(); - handleOrderRemoveModal(item, $query.refetch); + handleOrderRemoveModal(item.order, $query.refetch); }}>Remove diff --git a/tauri-app/src/lib/components/tables/OrdersListTable.test.ts b/tauri-app/src/lib/components/tables/OrdersListTable.test.ts index 2592ff098..314b40fa2 100644 --- a/tauri-app/src/lib/components/tables/OrdersListTable.test.ts +++ b/tauri-app/src/lib/components/tables/OrdersListTable.test.ts @@ -6,11 +6,17 @@ import { mockIPC } from '@tauri-apps/api/mocks'; import { goto } from '$app/navigation'; import { handleOrderRemoveModal } from '$lib/services/modal'; import { formatTimestampSecondsAsLocal } from '$lib/utils/time'; -import type { Order } from '$lib/typeshare/subgraphTypes'; +import type { OrderWithSubgraphName } from '$lib/typeshare/subgraphTypes'; const { mockWalletAddressMatchesOrBlankStore } = await vi.hoisted( () => import('$lib/mocks/wallets'), ); +const { activeNetworkRefSetMock, activeOrderbookRefSetMock } = vi.hoisted(() => { + return { + activeNetworkRefSetMock: vi.fn(), + activeOrderbookRefSetMock: vi.fn(), + }; +}); vi.mock('$lib/stores/wallets', async () => { return { @@ -23,6 +29,8 @@ vi.mock('$lib/stores/settings', async (importOriginal) => { const { mockSettingsStore } = await import('$lib/mocks/settings'); const _activeOrderbook = writable(); + const _activeOrderbookRef = writable(); + const _activeNetworkRef = writable(); return { ...((await importOriginal()) as object), @@ -32,6 +40,18 @@ vi.mock('$lib/stores/settings', async (importOriginal) => { ..._activeOrderbook, load: vi.fn(() => _activeOrderbook.set(true)), }, + activeOrderbookRef: { + ..._activeOrderbookRef, + set: activeOrderbookRefSetMock, + }, + activeNetworkRef: { + ..._activeNetworkRef, + set: activeNetworkRefSetMock, + }, + activeSubgraphs: writable({ + 'network-one': 'https://network-one.com', + 'network-two': 'https://network-two.com', + }), }; }); @@ -52,80 +72,86 @@ vi.mock('$app/stores', async () => { }; }); -const mockOrders: Order[] = [ +const mockOrders: OrderWithSubgraphName[] = [ { - id: 'order1', - orderHash: 'order1', - orderBytes: '0x00', - addEvents: [], - active: false, - owner: '0xOwner1', - timestampAdded: '1625247300', - inputs: [ - { - id: '0x00', - owner: '0x00', - vaultId: '0x00', - balance: '100', - orderbook: { id: '0x00' }, - ordersAsInput: [], - ordersAsOutput: [], - balanceChanges: [], - token: { id: '0x00', address: '0x00', symbol: 'ETH' }, - }, - ], - outputs: [ - { - id: '0x00', - owner: '0x00', - vaultId: '0x00', - balance: '100', - orderbook: { id: '0x00' }, - ordersAsInput: [], - ordersAsOutput: [], - balanceChanges: [], - token: { id: '0x00', address: '0x00', symbol: 'USDC' }, - }, - ], - orderbook: { id: '0x00' }, - trades: [], + order: { + id: 'order1', + orderHash: 'order1', + orderBytes: '0x00', + addEvents: [], + active: false, + owner: '0xOwner1', + timestampAdded: '1625247300', + inputs: [ + { + id: '0x00', + owner: '0x00', + vaultId: '0x00', + balance: '100', + orderbook: { id: '0x00' }, + ordersAsInput: [], + ordersAsOutput: [], + balanceChanges: [], + token: { id: '0x00', address: '0x00', symbol: 'ETH' }, + }, + ], + outputs: [ + { + id: '0x00', + owner: '0x00', + vaultId: '0x00', + balance: '100', + orderbook: { id: '0x00' }, + ordersAsInput: [], + ordersAsOutput: [], + balanceChanges: [], + token: { id: '0x00', address: '0x00', symbol: 'USDC' }, + }, + ], + orderbook: { id: '0x00' }, + trades: [], + }, + subgraphName: 'network-one', }, { - id: 'order2', - orderHash: 'order2', - orderBytes: '0x00', - addEvents: [], - active: true, - owner: '0xOwner2', - timestampAdded: '1625247600', - inputs: [ - { - id: '0x00', - owner: '0x00', - vaultId: '0x00', - balance: '100', - orderbook: { id: '0x00' }, - ordersAsInput: [], - ordersAsOutput: [], - balanceChanges: [], - token: { id: '0x00', address: '0x00', symbol: 'USDT' }, - }, - ], - outputs: [ - { - id: '0x00', - owner: '0x00', - vaultId: '0x00', - balance: '100', - orderbook: { id: '0x00' }, - ordersAsInput: [], - ordersAsOutput: [], - balanceChanges: [], - token: { id: '0x00', address: '0x00', symbol: 'DAI' }, - }, - ], - orderbook: { id: '0x00' }, - trades: Array.from({ length: 100 }, (_, i) => ({ id: `trade${i}` })), + order: { + id: 'order2', + orderHash: 'order2', + orderBytes: '0x00', + addEvents: [], + active: true, + owner: '0xOwner2', + timestampAdded: '1625247600', + inputs: [ + { + id: '0x00', + owner: '0x00', + vaultId: '0x00', + balance: '100', + orderbook: { id: '0x00' }, + ordersAsInput: [], + ordersAsOutput: [], + balanceChanges: [], + token: { id: '0x00', address: '0x00', symbol: 'USDT' }, + }, + ], + outputs: [ + { + id: '0x00', + owner: '0x00', + vaultId: '0x00', + balance: '100', + orderbook: { id: '0x00' }, + ordersAsInput: [], + ordersAsOutput: [], + balanceChanges: [], + token: { id: '0x00', address: '0x00', symbol: 'DAI' }, + }, + ], + orderbook: { id: '0x00' }, + trades: Array.from({ length: 100 }, (_, i) => ({ id: `trade${i}` })), + }, + subgraphName: 'network-two', }, ]; @@ -141,6 +167,7 @@ test('renders the orders list table with correct data', async () => { render(OrdersListTable, { context: new Map([['$$_queryClient', queryClient]]) }); await waitFor(async () => { + expect(screen.getByTestId('orderListHeadingNetwork')).toHaveTextContent('Network'); expect(screen.getByTestId('orderListHeadingActive')).toHaveTextContent('Active'); expect(screen.getByTestId('orderListHeadingID')).toHaveTextContent('Order'); expect(screen.getByTestId('orderListHeadingOwner')).toHaveTextContent('Owner'); @@ -161,6 +188,12 @@ test('renders the orders list table with correct data', async () => { expect(await screen.findAllByTestId('orderListRowOutputs')).toHaveLength(2); expect(await screen.findAllByTestId('orderListRowTrades')).toHaveLength(2); + expect((await screen.findAllByTestId('orderListRowNetwork'))[0]).toHaveTextContent( + 'network-one', + ); + expect((await screen.findAllByTestId('orderListRowNetwork'))[1]).toHaveTextContent( + 'network-two', + ); expect((await screen.findAllByTestId('orderListRowActive'))[0]).toHaveTextContent('Inactive'); expect((await screen.findAllByTestId('orderListRowActive'))[1]).toHaveTextContent('Active'); expect((await screen.findAllByTestId('orderListRowID'))[0]).toHaveTextContent('order...rder1'); @@ -172,10 +205,10 @@ test('renders the orders list table with correct data', async () => { '0xOwn...wner2', ); expect((await screen.findAllByTestId('orderListRowLastAdded'))[0]).toHaveTextContent( - formatTimestampSecondsAsLocal(BigInt(mockOrders[0].timestampAdded)), + formatTimestampSecondsAsLocal(BigInt(mockOrders[0].order.timestampAdded)), ); expect((await screen.findAllByTestId('orderListRowLastAdded'))[1]).toHaveTextContent( - formatTimestampSecondsAsLocal(BigInt(mockOrders[1].timestampAdded)), + formatTimestampSecondsAsLocal(BigInt(mockOrders[1].order.timestampAdded)), ); expect((await screen.findAllByTestId('orderListRowInputs'))[0]).toHaveTextContent('ETH'); expect((await screen.findAllByTestId('orderListRowInputs'))[1]).toHaveTextContent('USDT'); @@ -222,11 +255,32 @@ test('clicking a row links to the order detail page', async () => { expect(goto).toHaveBeenCalledWith('/orders/order1'); }); +test('clicking a row updates the active network and orderbook', async () => { + const queryClient = new QueryClient(); + + mockIPC((cmd) => { + if (cmd === 'orders_list') { + return [mockOrders[0]]; + } + }); + + render(OrdersListTable, { context: new Map([['$$_queryClient', queryClient]]) }); + + await waitFor(async () => { + expect(screen.getByTestId('bodyRow')).toBeInTheDocument(); + }); + + await fireEvent.click(await screen.findByTestId('bodyRow')); + + expect(activeNetworkRefSetMock).toHaveBeenCalledWith('network-one'); + expect(activeOrderbookRefSetMock).toHaveBeenCalledWith('network-one'); +}); + test('does not show the dropdown menu if the wallet address does not match', async () => { const queryClient = new QueryClient(); const modifiedMockOrders = [...mockOrders]; - modifiedMockOrders[0].active = true; + modifiedMockOrders[0].order.active = true; mockIPC((cmd) => { if (cmd === 'orders_list') { @@ -253,7 +307,7 @@ test('clicking the remove option in the dropdown menu opens the remove modal', a mockWalletAddressMatchesOrBlankStore.set(() => true); const modifiedMockOrders = [...mockOrders]; - modifiedMockOrders[0].active = true; + modifiedMockOrders[0].order.active = true; mockIPC((cmd) => { if (cmd === 'orders_list') { @@ -272,6 +326,6 @@ test('clicking the remove option in the dropdown menu opens the remove modal', a }); await waitFor(() => { - expect(handleOrderRemoveModal).toHaveBeenCalledWith(mockOrders[0], expect.any(Function)); + expect(handleOrderRemoveModal).toHaveBeenCalledWith(mockOrders[0].order, expect.any(Function)); }); }); diff --git a/tauri-app/src/lib/queries/ordersList.ts b/tauri-app/src/lib/queries/ordersList.ts index b9c5d3761..cfedfd54c 100644 --- a/tauri-app/src/lib/queries/ordersList.ts +++ b/tauri-app/src/lib/queries/ordersList.ts @@ -1,12 +1,13 @@ import { invoke } from '@tauri-apps/api'; import { DEFAULT_PAGE_SIZE } from './constants'; import { mockIPC } from '@tauri-apps/api/mocks'; -import type { Order } from '$lib/typeshare/subgraphTypes'; +import type { OrderWithSubgraphName } from '$lib/typeshare/subgraphTypes'; export type OrdersListArgs = { - subgraphArgs: { + multiSubgraphArgs: { url: string; - }; + name: string; + }[]; filterArgs: { owners: string[]; active: boolean | undefined; @@ -19,18 +20,21 @@ export type OrdersListArgs = { }; export const ordersList = async ( - url: string | undefined, + activeSubgraphs: Record, owners: string[] = [], active: boolean | undefined = undefined, orderHash: string = '', pageParam: number, pageSize: number = DEFAULT_PAGE_SIZE, ) => { - if (!url) { + if (!Object.keys(activeSubgraphs).length) { return []; } - return await invoke('orders_list', { - subgraphArgs: { url }, + return await invoke('orders_list', { + multiSubgraphArgs: Object.entries(activeSubgraphs).map(([name, url]) => ({ + name, + url, + })), filterArgs: { owners, active, @@ -63,10 +67,10 @@ if (import.meta.vitest) { }); // check for a result with no URL - expect(await ordersList(undefined, [], undefined, undefined, 0)).toEqual([]); + expect(await ordersList({}, [], undefined, undefined, 0)).toEqual([]); // check for a result with a URL - expect(await ordersList('http://localhost:8000', [], undefined, undefined, 0)).toEqual([ + expect(await ordersList({ network: 'url' }, [], undefined, undefined, 0)).toEqual([ { id: '1', order_bytes: '0x123', diff --git a/tauri-app/src/lib/stores/settings.test.ts b/tauri-app/src/lib/stores/settings.test.ts index 0ae6b5949..6ae187bc5 100644 --- a/tauri-app/src/lib/stores/settings.test.ts +++ b/tauri-app/src/lib/stores/settings.test.ts @@ -1,10 +1,13 @@ import { expect, test, beforeEach, describe } from 'vitest'; -import { settings, activeAccountsItems } from './settings'; +import { settings, activeAccountsItems, activeSubgraphs } from './settings'; import { mockConfigSource } from '$lib/mocks/settings'; import { get } from 'svelte/store'; describe('Settings active accounts items', async () => { beforeEach(() => { + activeSubgraphs.set({ + mainnet: 'mainnet', + }); activeAccountsItems.set(mockConfigSource.accounts as Record); expect(get(activeAccountsItems)).toEqual(mockConfigSource.accounts); }); @@ -39,4 +42,38 @@ describe('Settings active accounts items', async () => { name_one: mockConfigSource.accounts?.name_one as string, }); }); + + test('should update active subgraphs when subgraph value change', () => { + const newSettings = { + ...mockConfigSource, + subgraphs: { + mainnet: 'new value', + }, + }; + + settings.set(newSettings); + + expect(get(activeSubgraphs)).toEqual({}); + }); + + test('should update active subgraphs when subgraph removed', () => { + const newSettings = { + ...mockConfigSource, + subgraphs: { + testnet: 'testnet', + }, + }; + + settings.set(newSettings); + + expect(get(activeSubgraphs)).toEqual({}); + }); + + test('should reset active subgraphs when subgraphs are undefined', () => { + settings.set({ + ...mockConfigSource, + subgraphs: undefined, + }); + expect(get(activeSubgraphs)).toEqual({}); + }); }); diff --git a/tauri-app/src/lib/stores/settings.ts b/tauri-app/src/lib/stores/settings.ts index 32d1b4fd3..01e788eae 100644 --- a/tauri-app/src/lib/stores/settings.ts +++ b/tauri-app/src/lib/stores/settings.ts @@ -101,7 +101,6 @@ export const hasRequiredSettings = derived( // accounts export const accounts = derived(settings, ($settings) => $settings?.accounts ?? {}); - export const activeAccountsItems = cachedWritableStore>( 'settings.activeAccountsItems', {}, @@ -114,7 +113,6 @@ export const activeAccountsItems = cachedWritableStore>( } }, ); - export const activeAccounts = derived( [accounts, activeAccountsItems], ([$accounts, $activeAccountsItems]) => @@ -125,6 +123,23 @@ export const activeAccounts = derived( ), ); +// subgraphs +export const subgraph = derived(settings, ($settings) => + $settings?.subgraphs !== undefined ? Object.entries($settings.subgraphs) : [], +); +export const activeSubgraphs = cachedWritableStore>( + 'settings.activeSubgraphs', + {}, + JSON.stringify, + (s) => { + try { + return JSON.parse(s); + } catch { + return {}; + } + }, +); + // When networks / orderbooks settings updated, reset active network / orderbook settings.subscribe(async () => { const $settings = get(settings); @@ -166,6 +181,22 @@ settings.subscribe(async () => { ); activeAccountsItems.set(updatedActiveAccounts); } + + // Reset active subgraphs if subgraphs have changed + if ($settings?.subgraphs === undefined) { + activeSubgraphs.set({}); + } else { + const currentActiveSubgraphs = get(activeSubgraphs); + const updatedActiveSubgraphs = Object.fromEntries( + Object.entries($settings.subgraphs).filter(([key, value]) => { + if (key in currentActiveSubgraphs) { + return currentActiveSubgraphs[key] === value; + } + return false; + }), + ); + activeSubgraphs.set(updatedActiveSubgraphs); + } }); // When active network is updated to undefined, reset active orderbook