Compare commits

..
Author SHA1 Message Date
Bartłomiej Głownia cfe083cbee feat(trading): margin mode selector 2024-01-05 13:54:53 +01:00
Ben 462066959d chore(trading): update vega (#5563) 2024-01-03 12:00:00 +00:00
daro-maj f12f5ab961 chore(trading): fix volume issue for market selector tests (#5564) 2024-01-03 12:04:49 +01:00
98b2460d8b fix(trading): mobile friendly referral and fees views (#5530)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2024-01-02 15:50:04 +00:00
Matthew Russell 67c182f3de chore(trading-view): minor tv chart tweaks (#5545) 2024-01-02 13:20:01 +00:00
68e0009ab0 feat(trading,markets,proposals): market banners (#5494)
Co-authored-by: bwallacee <ben@vega.xyz>
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
2024-01-02 11:04:43 +00:00
Bartłomiej Głownia ad74b12908 feat(trading): do not show error message if there is no faucet tx error (#5539) 2024-01-02 10:19:10 +01:00
Matthew Russell 7fff33e493 feat(trading): competition feature flag and team page (#5549) 2023-12-29 15:50:51 +00:00
daro-maj e7cb2c8276 chore(trading): fix fees tests (#5548) 2023-12-29 15:25:33 +00:00
4adaeea40a feat(trading): pane settings view (#5509)
Co-authored-by: Dariusz Majcherczyk <dariusz.majcherczyk@gmail.com>
Co-authored-by: Matthew Russell <mattrussell36@gmail.com>
2023-12-28 12:33:18 +00:00
Matthew Russell d47d894147 fix(deal-ticket): stray curly in stop order deal ticket (#5547) 2023-12-28 12:27:56 +00:00
Bartłomiej Głownia 515932d401 fix(trading): use correct interpolation prefix in market info label (#5537) 2023-12-27 18:47:19 +00:00
daro-maj 723ff2805d chore(trading): check e2e tests issue (#5544) 2023-12-27 14:07:27 +01:00
Ben 424cced4be chore(trading): vegaservice import (#5536) 2023-12-27 08:17:20 +00:00
Daniel b2c7ee82c5 feat(ci): validators testnet deployment (#5541) 2023-12-22 18:05:31 +00:00
m.rayandDaniel 4cc63be4f9 chore(trading): update validator testnet pipelines (#5543)
Co-authored-by: Daniel <daniel.1302@gmail.com>
2023-12-22 18:03:45 +00:00
157 changed files with 2809 additions and 1756 deletions
@@ -24,7 +24,7 @@ export const AssetsTable = ({ data }: AssetsTableProps) => {
const navigate = useNavigate(); const navigate = useNavigate();
const ref = useRef<AgGridReact>(null); const ref = useRef<AgGridReact>(null);
const showColumnsOnDesktop = () => { const showColumnsOnDesktop = () => {
ref.current?.columnApi.setColumnsVisible( ref.current?.api.setColumnsVisible(
['id', 'type', 'status'], ['id', 'type', 'status'],
window.innerWidth > BREAKPOINT_MD window.innerWidth > BREAKPOINT_MD
); );
@@ -29,7 +29,7 @@ export const MarketsTable = ({ data }: MarketsTableProps) => {
const gridRef = useRef<AgGridReact>(null); const gridRef = useRef<AgGridReact>(null);
useLayoutEffect(() => { useLayoutEffect(() => {
const showColumnsOnDesktop = () => { const showColumnsOnDesktop = () => {
gridRef.current?.columnApi.setColumnsVisible( gridRef.current?.api.setColumnsVisible(
['id', 'state', 'asset'], ['id', 'state', 'asset'],
window.innerWidth > BREAKPOINT_MD window.innerWidth > BREAKPOINT_MD
); );
@@ -44,11 +44,11 @@ export const ProposalsTable = ({ data }: ProposalsTableProps) => {
const gridRef = useRef<AgGridReact>(null); const gridRef = useRef<AgGridReact>(null);
useLayoutEffect(() => { useLayoutEffect(() => {
const showColumnsOnDesktop = () => { const showColumnsOnDesktop = () => {
gridRef.current?.columnApi.setColumnsVisible( gridRef.current?.api.setColumnsVisible(
['voting', 'cDate', 'eDate', 'type'], ['voting', 'cDate', 'eDate', 'type'],
window.innerWidth > BREAKPOINT_MD window.innerWidth > BREAKPOINT_MD
); );
gridRef.current?.columnApi.setColumnWidth( gridRef.current?.api.setColumnWidth(
'actions', 'actions',
window.innerWidth > BREAKPOINT_MD ? 221 : 80 window.innerWidth > BREAKPOINT_MD ? 221 : 80
); );
+1
View File
@@ -25,3 +25,4 @@ NX_ICEBERG_ORDERS=true
# NX_PRODUCT_PERPETUALS # NX_PRODUCT_PERPETUALS
NX_METAMASK_SNAPS=true NX_METAMASK_SNAPS=true
NX_REFERRALS=true NX_REFERRALS=true
NX_TEAM_COMPETITION=true
+8 -13
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useMemo } from 'react'; import { useEffect } from 'react';
import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils'; import { addDecimalsFormatNumber, titlefy } from '@vegaprotocol/utils';
import { useScreenDimensions } from '@vegaprotocol/react-helpers'; import { useScreenDimensions } from '@vegaprotocol/react-helpers';
import { useThrottledDataProvider } from '@vegaprotocol/data-provider'; import { useThrottledDataProvider } from '@vegaprotocol/data-provider';
@@ -72,17 +72,6 @@ export const MarketPage = () => {
} }
}, [update, lastMarketId, data?.id]); }, [update, lastMarketId, data?.id]);
const pinnedAsset = data && getAsset(data);
const tradeView = useMemo(() => {
if (pinnedAsset) {
if (largeScreen) {
return <TradeGrid market={data} pinnedAsset={pinnedAsset} />;
}
return <TradePanels market={data} pinnedAsset={pinnedAsset} />;
}
}, [largeScreen, data, pinnedAsset]);
if (loading) { if (loading) {
return ( return (
<Splash> <Splash>
@@ -118,6 +107,8 @@ export const MarketPage = () => {
); );
} }
const pinnedAsset = data && getAsset(data);
return ( return (
<> <>
<TitleUpdater <TitleUpdater
@@ -125,7 +116,11 @@ export const MarketPage = () => {
marketName={data?.tradableInstrument.instrument.name} marketName={data?.tradableInstrument.instrument.name}
decimalPlaces={data?.decimalPlaces} decimalPlaces={data?.decimalPlaces}
/> />
{tradeView} {largeScreen ? (
<TradeGrid market={data} pinnedAsset={pinnedAsset} />
) : (
<TradePanels market={data} pinnedAsset={pinnedAsset} />
)}
</> </>
); );
}; };
+51 -44
View File
@@ -1,11 +1,10 @@
import { memo } from 'react'; import { memo, type ReactNode } from 'react';
import type { ReactNode } from 'react';
import { LayoutPriority } from 'allotment'; import { LayoutPriority } from 'allotment';
import classNames from 'classnames'; import classNames from 'classnames';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import type { PinnedAsset } from '@vegaprotocol/accounts'; import { useFeatureFlags } from '@vegaprotocol/environment';
import { OracleBanner, useMarket } from '@vegaprotocol/markets'; import { type PinnedAsset } from '@vegaprotocol/accounts';
import type { Market } from '@vegaprotocol/markets'; import { type Market } from '@vegaprotocol/markets';
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit'; import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
import { import {
ResizableGrid, ResizableGrid,
@@ -13,31 +12,19 @@ import {
usePaneLayout, usePaneLayout,
} from '../../components/resizable-grid'; } from '../../components/resizable-grid';
import { TradingViews } from './trade-views'; import { TradingViews } from './trade-views';
import {
MarketSuccessorBanner,
MarketSuccessorProposalBanner,
MarketTerminationBanner,
} from '../../components/market-banner';
import { useFeatureFlags } from '@vegaprotocol/environment';
import { useT } from '../../lib/use-t'; import { useT } from '../../lib/use-t';
import { ErrorBoundary } from '../../components/error-boundary'; import { ErrorBoundary } from '../../components/error-boundary';
import { MarketBanner } from '../../components/market-banner';
interface TradeGridProps { interface TradeGridProps {
market: Market | null; market: Market;
pinnedAsset?: PinnedAsset; pinnedAsset?: PinnedAsset | undefined;
} }
const MainGrid = memo( const MainGrid = memo(
({ ({ market, pinnedAsset }: { market: Market; pinnedAsset?: PinnedAsset }) => {
marketId,
pinnedAsset,
}: {
marketId: string;
pinnedAsset?: PinnedAsset;
}) => {
const featureFlags = useFeatureFlags((state) => state.flags); const featureFlags = useFeatureFlags((state) => state.flags);
const t = useT(); const t = useT();
const { data: market } = useMarket(marketId);
const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'top' }); const [sizes, handleOnLayoutChange] = usePaneLayout({ id: 'top' });
const [sizesMiddle, handleOnMiddleLayoutChange] = usePaneLayout({ const [sizesMiddle, handleOnMiddleLayoutChange] = usePaneLayout({
id: 'middle-1', id: 'middle-1',
@@ -65,17 +52,17 @@ const MainGrid = memo(
menu={<TradingViews.chart.menu />} menu={<TradingViews.chart.menu />}
> >
<ErrorBoundary feature="chart"> <ErrorBoundary feature="chart">
<TradingViews.chart.component marketId={marketId} /> <TradingViews.chart.component marketId={market.id} />
</ErrorBoundary> </ErrorBoundary>
</Tab> </Tab>
<Tab id="depth" name={t('Depth')}> <Tab id="depth" name={t('Depth')}>
<ErrorBoundary feature="depth"> <ErrorBoundary feature="depth">
<TradingViews.depth.component marketId={marketId} /> <TradingViews.depth.component marketId={market.id} />
</ErrorBoundary> </ErrorBoundary>
</Tab> </Tab>
<Tab id="liquidity" name={t('Liquidity')}> <Tab id="liquidity" name={t('Liquidity')}>
<ErrorBoundary feature="liquidity"> <ErrorBoundary feature="liquidity">
<TradingViews.liquidity.component marketId={marketId} /> <TradingViews.liquidity.component marketId={market.id} />
</ErrorBoundary> </ErrorBoundary>
</Tab> </Tab>
{market && {market &&
@@ -83,17 +70,21 @@ const MainGrid = memo(
'Perpetual' ? ( 'Perpetual' ? (
<Tab id="funding-history" name={t('Funding history')}> <Tab id="funding-history" name={t('Funding history')}>
<ErrorBoundary feature="funding-history"> <ErrorBoundary feature="funding-history">
<TradingViews.funding.component marketId={marketId} /> <TradingViews.funding.component marketId={market.id} />
</ErrorBoundary> </ErrorBoundary>
</Tab> </Tab>
) : null} ) : null}
{market && {market &&
market.tradableInstrument.instrument.product.__typename === market.tradableInstrument.instrument.product.__typename ===
'Perpetual' ? ( 'Perpetual' ? (
<Tab id="funding-payments" name={t('Funding payments')}> <Tab
id="funding-payments"
name={t('Funding payments')}
settings={<TradingViews.fundingPayments.settings />}
>
<ErrorBoundary feature="funding-payments"> <ErrorBoundary feature="funding-payments">
<TradingViews.fundingPayments.component <TradingViews.fundingPayments.component
marketId={marketId} marketId={market.id}
/> />
</ErrorBoundary> </ErrorBoundary>
</Tab> </Tab>
@@ -109,12 +100,16 @@ const MainGrid = memo(
<Tabs storageKey="console-trade-grid-main-right"> <Tabs storageKey="console-trade-grid-main-right">
<Tab id="orderbook" name={t('Orderbook')}> <Tab id="orderbook" name={t('Orderbook')}>
<ErrorBoundary feature="orderbook"> <ErrorBoundary feature="orderbook">
<TradingViews.orderbook.component marketId={marketId} /> <TradingViews.orderbook.component marketId={market.id} />
</ErrorBoundary> </ErrorBoundary>
</Tab> </Tab>
<Tab id="trades" name={t('Trades')}> <Tab
id="trades"
name={t('Trades')}
settings={<TradingViews.trades.settings />}
>
<ErrorBoundary feature="trades"> <ErrorBoundary feature="trades">
<TradingViews.trades.component marketId={marketId} /> <TradingViews.trades.component marketId={market.id} />
</ErrorBoundary> </ErrorBoundary>
</Tab> </Tab>
</Tabs> </Tabs>
@@ -133,6 +128,7 @@ const MainGrid = memo(
id="positions" id="positions"
name={t('Positions')} name={t('Positions')}
menu={<TradingViews.positions.menu />} menu={<TradingViews.positions.menu />}
settings={<TradingViews.positions.settings />}
> >
<ErrorBoundary feature="positions"> <ErrorBoundary feature="positions">
<TradingViews.positions.component /> <TradingViews.positions.component />
@@ -142,17 +138,26 @@ const MainGrid = memo(
id="open-orders" id="open-orders"
name={t('Open')} name={t('Open')}
menu={<TradingViews.activeOrders.menu />} menu={<TradingViews.activeOrders.menu />}
settings={<TradingViews.activeOrders.settings />}
> >
<ErrorBoundary feature="activeOrders"> <ErrorBoundary feature="activeOrders">
<TradingViews.activeOrders.component /> <TradingViews.activeOrders.component />
</ErrorBoundary> </ErrorBoundary>
</Tab> </Tab>
<Tab id="closed-orders" name={t('Closed')}> <Tab
id="closed-orders"
name={t('Closed')}
settings={<TradingViews.closedOrders.settings />}
>
<ErrorBoundary feature="closedOrders"> <ErrorBoundary feature="closedOrders">
<TradingViews.closedOrders.component /> <TradingViews.closedOrders.component />
</ErrorBoundary> </ErrorBoundary>
</Tab> </Tab>
<Tab id="rejected-orders" name={t('Rejected')}> <Tab
id="rejected-orders"
name={t('Rejected')}
settings={<TradingViews.rejectedOrders.settings />}
>
<ErrorBoundary feature="rejectedOrders"> <ErrorBoundary feature="rejectedOrders">
<TradingViews.rejectedOrders.component /> <TradingViews.rejectedOrders.component />
</ErrorBoundary> </ErrorBoundary>
@@ -161,25 +166,35 @@ const MainGrid = memo(
id="orders" id="orders"
name={t('All')} name={t('All')}
menu={<TradingViews.orders.menu />} menu={<TradingViews.orders.menu />}
settings={<TradingViews.orders.settings />}
> >
<ErrorBoundary feature="orders"> <ErrorBoundary feature="orders">
<TradingViews.orders.component /> <TradingViews.orders.component />
</ErrorBoundary> </ErrorBoundary>
</Tab> </Tab>
{featureFlags.STOP_ORDERS ? ( {featureFlags.STOP_ORDERS ? (
<Tab id="stop-orders" name={t('Stop orders')}> <Tab
id="stop-orders"
name={t('Stop orders')}
settings={<TradingViews.stopOrders.settings />}
>
<ErrorBoundary feature="stop-orders"> <ErrorBoundary feature="stop-orders">
<TradingViews.stopOrders.component /> <TradingViews.stopOrders.component />
</ErrorBoundary> </ErrorBoundary>
</Tab> </Tab>
) : null} ) : null}
<Tab id="fills" name={t('Fills')}> <Tab
id="fills"
name={t('Fills')}
settings={<TradingViews.fills.settings />}
>
<TradingViews.fills.component /> <TradingViews.fills.component />
</Tab> </Tab>
<Tab <Tab
id="accounts" id="accounts"
name={t('Collateral')} name={t('Collateral')}
menu={<TradingViews.collateral.menu />} menu={<TradingViews.collateral.menu />}
settings={<TradingViews.collateral.settings />}
> >
<ErrorBoundary feature="collateral"> <ErrorBoundary feature="collateral">
<TradingViews.collateral.component <TradingViews.collateral.component
@@ -197,7 +212,6 @@ const MainGrid = memo(
MainGrid.displayName = 'MainGrid'; MainGrid.displayName = 'MainGrid';
export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => { export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => {
const featureFlags = useFeatureFlags((state) => state.flags);
const wrapperClasses = classNames( const wrapperClasses = classNames(
'h-full grid', 'h-full grid',
'grid-rows-[min-content_1fr]' 'grid-rows-[min-content_1fr]'
@@ -206,17 +220,10 @@ export const TradeGrid = ({ market, pinnedAsset }: TradeGridProps) => {
return ( return (
<div className={wrapperClasses}> <div className={wrapperClasses}>
<div> <div>
{featureFlags.SUCCESSOR_MARKETS && ( <MarketBanner market={market} />
<>
<MarketSuccessorBanner market={market} />
<MarketSuccessorProposalBanner marketId={market?.id} />
</>
)}
<MarketTerminationBanner market={market} />
<OracleBanner marketId={market?.id || ''} />
</div> </div>
<div className="min-h-0 p-0.5"> <div className="min-h-0 p-0.5">
<MainGrid marketId={market?.id || ''} pinnedAsset={pinnedAsset} /> <MainGrid market={market} pinnedAsset={pinnedAsset} />
</div> </div>
</div> </div>
); );
@@ -1,29 +1,30 @@
import { type PinnedAsset } from '@vegaprotocol/accounts'; import { type PinnedAsset } from '@vegaprotocol/accounts';
import { type Market } from '@vegaprotocol/markets'; import { type Market } from '@vegaprotocol/markets';
import { OracleBanner } from '@vegaprotocol/markets'; // TODO: handle oracle banner
// import { OracleBanner } from '@vegaprotocol/markets';
import { useState } from 'react'; import { useState } from 'react';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import classNames from 'classnames'; import classNames from 'classnames';
import { Splash } from '@vegaprotocol/ui-toolkit';
import { useT } from '../../lib/use-t';
import { import {
MarketSuccessorBanner, Popover,
MarketSuccessorProposalBanner, Splash,
MarketTerminationBanner, VegaIcon,
} from '../../components/market-banner'; VegaIconNames,
} from '@vegaprotocol/ui-toolkit';
import { useT } from '../../lib/use-t';
import { MarketBanner } from '../../components/market-banner';
import { ErrorBoundary } from '../../components/error-boundary'; import { ErrorBoundary } from '../../components/error-boundary';
import { type TradingView } from './trade-views'; import { type TradingView } from './trade-views';
import { TradingViews } from './trade-views'; import { TradingViews } from './trade-views';
import { useFeatureFlags } from '@vegaprotocol/environment';
interface TradePanelsProps { interface TradePanelsProps {
market: Market | null; market: Market;
pinnedAsset?: PinnedAsset; pinnedAsset?: PinnedAsset;
} }
export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => { export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
const featureFlags = useFeatureFlags((state) => state.flags);
const [view, setView] = useState<TradingView>('chart'); const [view, setView] = useState<TradingView>('chart');
const viewCfg = TradingViews[view];
const renderView = () => { const renderView = () => {
const Component = TradingViews[view].component; const Component = TradingViews[view].component;
@@ -44,35 +45,36 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
}; };
const renderMenu = () => { const renderMenu = () => {
const viewCfg = TradingViews[view]; if ('menu' in viewCfg || 'settings' in viewCfg) {
if ('menu' in viewCfg) {
const Menu = viewCfg.menu;
return ( return (
<div className="flex items-center justify-end gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default"> <div className="flex items-center justify-end gap-1 p-1 bg-vega-clight-800 dark:bg-vega-cdark-800 border-b border-default">
<Menu /> {'menu' in viewCfg ? <viewCfg.menu /> : null}
{'settings' in viewCfg ? (
<Popover
align="end"
trigger={
<span className="ml-1 flex items-center justify-center h-6 w-6">
<VegaIcon name={VegaIconNames.COG} size={16} />
</span>
}
>
<div className="p-4 flex justify-end">
<viewCfg.settings />
</div>
</Popover>
) : null}
</div> </div>
); );
} }
return null;
}; };
return ( return (
<div className="h-full grid grid-rows-[min-content_min-content_1fr_min-content]"> <div className="h-full grid grid-rows-[min-content_min-content_1fr_min-content]">
<div> <div>
{featureFlags.SUCCESSOR_MARKETS && ( <MarketBanner market={market} />
<>
<MarketSuccessorBanner market={market} />
<MarketSuccessorProposalBanner marketId={market?.id} />
</>
)}
<MarketTerminationBanner market={market} />
<OracleBanner marketId={market?.id || ''} />
</div> </div>
<div>{renderMenu()}</div> <div>{renderMenu()}</div>
<div className="h-full"> <div className="h-full relative">
<AutoSizer> <AutoSizer>
{({ width, height }) => ( {({ width, height }) => (
<div style={{ width, height }} className="overflow-auto"> <div style={{ width, height }} className="overflow-auto">
@@ -110,7 +112,9 @@ export const TradePanels = ({ market, pinnedAsset }: TradePanelsProps) => {
key={key} key={key}
view={key} view={key}
isActive={isActive} isActive={isActive}
onClick={() => setView(key)} onClick={() => {
setView(key);
}}
/> />
); );
})} })}
@@ -1,15 +1,36 @@
import { DepthChartContainer } from '@vegaprotocol/market-depth'; import { DepthChartContainer } from '@vegaprotocol/market-depth';
import { Filter, OpenOrdersMenu } from '@vegaprotocol/orders'; import { Filter, OpenOrdersMenu } from '@vegaprotocol/orders';
import { TradesContainer } from '../../components/trades-container'; import {
TradesContainer,
TradesSettings,
} from '../../components/trades-container';
import { OrderbookContainer } from '../../components/orderbook-container'; import { OrderbookContainer } from '../../components/orderbook-container';
import { FillsContainer } from '../../components/fills-container'; import {
import { PositionsContainer } from '../../components/positions-container'; FillsContainer,
import { AccountsContainer } from '../../components/accounts-container'; FillsSettings,
} from '../../components/fills-container';
import {
PositionsContainer,
PositionsSettings,
} from '../../components/positions-container';
import {
AccountsContainer,
AccountsSettings,
} from '../../components/accounts-container';
import { LiquidityContainer } from '../../components/liquidity-container'; import { LiquidityContainer } from '../../components/liquidity-container';
import { FundingContainer } from '../../components/funding-container'; import { FundingContainer } from '../../components/funding-container';
import { FundingPaymentsContainer } from '../../components/funding-payments-container'; import {
import { OrdersContainer } from '../../components/orders-container'; FundingPaymentsContainer,
import { StopOrdersContainer } from '../../components/stop-orders-container'; FundingPaymentsSettings,
} from '../../components/funding-payments-container';
import {
OrdersContainer,
OrdersSettings,
} from '../../components/orders-container';
import {
StopOrdersContainer,
StopOrdersSettings,
} from '../../components/stop-orders-container';
import { AccountsMenu } from '../../components/accounts-menu'; import { AccountsMenu } from '../../components/accounts-menu';
import { PositionsMenu } from '../../components/positions-menu'; import { PositionsMenu } from '../../components/positions-menu';
import { ChartContainer, ChartMenu } from '../../components/chart-container'; import { ChartContainer, ChartMenu } from '../../components/chart-container';
@@ -32,37 +53,46 @@ export const TradingViews = {
}, },
fundingPayments: { fundingPayments: {
component: FundingPaymentsContainer, component: FundingPaymentsContainer,
settings: FundingPaymentsSettings,
}, },
orderbook: { orderbook: {
component: OrderbookContainer, component: OrderbookContainer,
}, },
trades: { trades: {
component: TradesContainer, component: TradesContainer,
settings: TradesSettings,
}, },
positions: { positions: {
component: PositionsContainer, component: PositionsContainer,
menu: PositionsMenu, menu: PositionsMenu,
settings: PositionsSettings,
}, },
activeOrders: { activeOrders: {
component: () => <OrdersContainer filter={Filter.Open} />, component: () => <OrdersContainer filter={Filter.Open} />,
menu: OpenOrdersMenu, menu: OpenOrdersMenu,
settings: () => <OrdersSettings filter={Filter.Open} />,
}, },
closedOrders: { closedOrders: {
component: () => <OrdersContainer filter={Filter.Closed} />, component: () => <OrdersContainer filter={Filter.Closed} />,
settings: () => <OrdersSettings filter={Filter.Closed} />,
}, },
rejectedOrders: { rejectedOrders: {
component: () => <OrdersContainer filter={Filter.Rejected} />, component: () => <OrdersContainer filter={Filter.Rejected} />,
settings: () => <OrdersSettings filter={Filter.Rejected} />,
}, },
orders: { orders: {
component: OrdersContainer, component: OrdersContainer,
menu: OpenOrdersMenu, menu: OpenOrdersMenu,
settings: OrdersSettings,
}, },
stopOrders: { stopOrders: {
component: StopOrdersContainer, component: StopOrdersContainer,
settings: StopOrdersSettings,
}, },
collateral: { collateral: {
component: AccountsContainer, component: AccountsContainer,
menu: AccountsMenu, menu: AccountsMenu,
settings: AccountsSettings,
}, },
fills: { component: FillsContainer }, fills: { component: FillsContainer, settings: FillsSettings },
} as const; } as const;
@@ -42,7 +42,7 @@ export const createDataGridSlice: StateCreator<DataGridSlice> = (set) => ({
}, },
}); });
const useMarketsStore = create<DataGridSlice>()( export const useMarketsStore = create<DataGridSlice>()(
persist(createDataGridSlice, { persist(createDataGridSlice, {
name: 'vega_market_list_store', name: 'vega_market_list_store',
}) })
@@ -16,6 +16,7 @@ import {
} from '@vegaprotocol/environment'; } from '@vegaprotocol/environment';
import { useT } from '../../lib/use-t'; import { useT } from '../../lib/use-t';
import { ErrorBoundary } from '../../components/error-boundary'; import { ErrorBoundary } from '../../components/error-boundary';
import { MarketsSettings } from './markets-settings';
export const MarketsPage = () => { export const MarketsPage = () => {
const t = useT(); const t = useT();
@@ -34,7 +35,11 @@ export const MarketsPage = () => {
<div className="h-full pt-0.5 pb-3 px-1.5"> <div className="h-full pt-0.5 pb-3 px-1.5">
<div className="h-full my-1 border rounded-sm border-default"> <div className="h-full my-1 border rounded-sm border-default">
<Tabs storageKey="console-markets"> <Tabs storageKey="console-markets">
<Tab id="open-markets" name={t('Open markets')}> <Tab
id="open-markets"
name={t('Open markets')}
settings={<MarketsSettings />}
>
<ErrorBoundary feature="markets-open"> <ErrorBoundary feature="markets-open">
<OpenMarkets /> <OpenMarkets />
</ErrorBoundary> </ErrorBoundary>
@@ -42,6 +47,7 @@ export const MarketsPage = () => {
<Tab <Tab
id="proposed-markets" id="proposed-markets"
name={t('Proposed markets')} name={t('Proposed markets')}
settings={<MarketsSettings />}
menu={ menu={
<TradingAnchorButton <TradingAnchorButton
size="extra-small" size="extra-small"
@@ -56,7 +62,11 @@ export const MarketsPage = () => {
<Proposed /> <Proposed />
</ErrorBoundary> </ErrorBoundary>
</Tab> </Tab>
<Tab id="closed-markets" name={t('Closed markets')}> <Tab
id="closed-markets"
name={t('Closed markets')}
settings={<MarketsSettings />}
>
<ErrorBoundary feature="markets-closed"> <ErrorBoundary feature="markets-closed">
<Closed /> <Closed />
</ErrorBoundary> </ErrorBoundary>
@@ -0,0 +1,8 @@
import { GridSettings } from '../../components/grid-settings/grid-settings';
import { useMarketsStore } from './market-list-table';
export const MarketsSettings = () => (
<GridSettings
updateGridStore={useMarketsStore((store) => store.updateGridStore)}
/>
);
@@ -5,14 +5,29 @@ import { titlefy } from '@vegaprotocol/utils';
import { useIncompleteWithdrawals } from '@vegaprotocol/withdraws'; import { useIncompleteWithdrawals } from '@vegaprotocol/withdraws';
import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit'; import { Tab, LocalStoragePersistTabs as Tabs } from '@vegaprotocol/ui-toolkit';
import { usePageTitleStore } from '../../stores'; import { usePageTitleStore } from '../../stores';
import { AccountsContainer } from '../../components/accounts-container'; import {
AccountsContainer,
AccountsSettings,
} from '../../components/accounts-container';
import { DepositsContainer } from '../../components/deposits-container'; import { DepositsContainer } from '../../components/deposits-container';
import { FillsContainer } from '../../components/fills-container'; import {
import { FundingPaymentsContainer } from '../../components/funding-payments-container'; FillsContainer,
import { PositionsContainer } from '../../components/positions-container'; FillsSettings,
} from '../../components/fills-container';
import {
FundingPaymentsContainer,
FundingPaymentsSettings,
} from '../../components/funding-payments-container';
import {
PositionsContainer,
PositionsSettings,
} from '../../components/positions-container';
import { PositionsMenu } from '../../components/positions-menu'; import { PositionsMenu } from '../../components/positions-menu';
import { WithdrawalsContainer } from '../../components/withdrawals-container'; import { WithdrawalsContainer } from '../../components/withdrawals-container';
import { OrdersContainer } from '../../components/orders-container'; import {
OrdersContainer,
OrdersSettings,
} from '../../components/orders-container';
import { LedgerContainer } from '../../components/ledger-container'; import { LedgerContainer } from '../../components/ledger-container';
import { import {
ResizableGrid, ResizableGrid,
@@ -76,22 +91,27 @@ export const Portfolio = () => {
id="positions" id="positions"
name={t('Positions')} name={t('Positions')}
menu={<PositionsMenu />} menu={<PositionsMenu />}
settings={<PositionsSettings />}
> >
<ErrorBoundary feature="portfolio-positions"> <ErrorBoundary feature="portfolio-positions">
<PositionsContainer allKeys /> <PositionsContainer allKeys />
</ErrorBoundary> </ErrorBoundary>
</Tab> </Tab>
<Tab id="orders" name={t('Orders')}> <Tab id="orders" name={t('Orders')} settings={<OrdersSettings />}>
<ErrorBoundary feature="portfolio-orders"> <ErrorBoundary feature="portfolio-orders">
<OrdersContainer /> <OrdersContainer />
</ErrorBoundary> </ErrorBoundary>
</Tab> </Tab>
<Tab id="fills" name={t('Fills')}> <Tab id="fills" name={t('Fills')} settings={<FillsSettings />}>
<ErrorBoundary feature="portfolio-fills"> <ErrorBoundary feature="portfolio-fills">
<FillsContainer /> <FillsContainer />
</ErrorBoundary> </ErrorBoundary>
</Tab> </Tab>
<Tab id="funding-payments" name={t('Funding payments')}> <Tab
id="funding-payments"
name={t('Funding payments')}
settings={<FundingPaymentsSettings />}
>
<ErrorBoundary feature="portfolio-funding-payments"> <ErrorBoundary feature="portfolio-funding-payments">
<FundingPaymentsContainer /> <FundingPaymentsContainer />
</ErrorBoundary> </ErrorBoundary>
@@ -114,6 +134,7 @@ export const Portfolio = () => {
<Tab <Tab
id="collateral" id="collateral"
name={t('Collateral')} name={t('Collateral')}
settings={<AccountsSettings />}
menu={<AccountsMenu />} menu={<AccountsMenu />}
> >
<ErrorBoundary feature="portfolio-accounts"> <ErrorBoundary feature="portfolio-accounts">
@@ -296,7 +296,7 @@ export const ApplyCodeForm = ({ onSuccess }: { onSuccess?: () => void }) => {
<> <>
<div <div
data-testid="referral-apply-code-form" data-testid="referral-apply-code-form"
className="bg-vega-clight-800 dark:bg-vega-cdark-800 mx-auto w-2/3 max-w-md rounded-lg p-8" className="bg-vega-clight-800 dark:bg-vega-cdark-800 mx-auto md:w-2/3 max-w-md rounded-lg p-8"
> >
<h3 className="calt mb-4 text-center text-2xl"> <h3 className="calt mb-4 text-center text-2xl">
{t('Apply a referral code')} {t('Apply a referral code')}
@@ -3,7 +3,7 @@ export const GRADIENT =
'bg-gradient-to-b from-vega-clight-800 dark:from-vega-cdark-800 to-transparent'; 'bg-gradient-to-b from-vega-clight-800 dark:from-vega-cdark-800 to-transparent';
export const SKY_BACKGROUND = export const SKY_BACKGROUND =
'bg-[url(/sky-light.png)] dark:bg-[url(/sky-dark.png)] bg-[40%_0px] bg-[length:1440px] bg-no-repeat bg-local'; 'bg-[url(/sky-light.png)] dark:bg-[url(/sky-dark.png)] bg-[37%_0px] bg-[length:1440px] bg-no-repeat bg-local';
// TODO: Update the links to use the correct referral related pages // TODO: Update the links to use the correct referral related pages
export const REFERRAL_DOCS_LINK = export const REFERRAL_DOCS_LINK =
@@ -49,7 +49,7 @@ export const CreateCodeForm = () => {
return ( return (
<div <div
data-testid="referral-create-code-form" data-testid="referral-create-code-form"
className="w-2/3 max-w-md mx-auto bg-vega-clight-800 dark:bg-vega-cdark-800 p-8 rounded-lg" className="md:w-2/3 max-w-md mx-auto bg-vega-clight-800 dark:bg-vega-cdark-800 p-8 rounded-lg"
> >
<h3 className="mb-4 text-2xl text-center calt"> <h3 className="mb-4 text-2xl text-center calt">
{t('Create a referral code')} {t('Create a referral code')}
@@ -1,5 +1,5 @@
import { useT } from '../../lib/use-t'; import { useT } from '../../lib/use-t';
import { Table } from './table'; import { Table } from '../../components/table';
export const HowItWorksTable = () => { export const HowItWorksTable = () => {
const t = useT(); const t = useT();
@@ -5,16 +5,16 @@ import { useT } from '../../lib/use-t';
export const LandingBanner = () => { export const LandingBanner = () => {
const t = useT(); const t = useT();
return ( return (
<div className={classNames('relative mb-20')}> <div className={classNames('relative mb-10 lg:mb-20')}>
<div className=""> <div className="">
<div <div
aria-hidden aria-hidden
className="absolute top-20 right-[120px] md:right-[240px] max-sm:hidden" className="absolute top-20 right-[220px] md:right-[240px] max-sm:hidden"
> >
<AnimatedDudeWithWire /> <AnimatedDudeWithWire />
</div> </div>
<div className="pt-20 sm:w-[50%]"> <div className="pt-10 lg:pt-20 sm:w-[50%]">
<h1 className="text-6xl font-alpha calt mb-10"> <h1 className="text-3xl _text-[6vw] lg:!text-6xl leading-[1em] font-alpha calt mb-10">
{t('Vega community referrals')} {t('Vega community referrals')}
</h1> </h1>
<p className="text-lg mb-1"> <p className="text-lg mb-1">
@@ -22,7 +22,7 @@ export const LandingBanner = () => {
'Referral programs can be proposed and created via community governance.' 'Referral programs can be proposed and created via community governance.'
)} )}
</p> </p>
<p className="text-lg mb-10"> <p className="text-lg mb-1">
{t( {t(
'Once live, users can generate referral codes to share with their friends and earn commission on their trades, while referred traders can access fee discounts based on the running volume of the group.' 'Once live, users can generate referral codes to share with their friends and earn commission on their trades, while referred traders can access fee discounts based on the running volume of the group.'
)} )}
@@ -13,7 +13,7 @@ export const Layout = ({
<div <div
className={classNames( className={classNames(
'max-w-[1440px]', 'max-w-[1440px]',
'mx-auto px-16 md:px-32 pb-32', 'mx-auto px-4 lg:px-32 pb-32',
'relative z-0', 'relative z-0',
className className
)} )}
@@ -14,7 +14,7 @@ import {
useUpdateReferees, useUpdateReferees,
} from './hooks/use-referral'; } from './hooks/use-referral';
import classNames from 'classnames'; import classNames from 'classnames';
import { Table } from './table'; import { Table } from '../../components/table';
import { import {
addDecimalsFormatNumber, addDecimalsFormatNumber,
getDateFormat, getDateFormat,
@@ -72,7 +72,7 @@ export const Referrals = () => {
{showNav && <Nav />} {showNav && <Nav />}
<div <div
className={classNames({ className={classNames({
'py-16': showNav, 'py-8 lg:py-16': showNav,
'h-[300px] relative': loading || error, 'h-[300px] relative': loading || error,
})} })}
> >
+24 -41
View File
@@ -3,11 +3,12 @@ import {
getDateTimeFormat, getDateTimeFormat,
} from '@vegaprotocol/utils'; } from '@vegaprotocol/utils';
import { useReferralProgram } from './hooks/use-referral-program'; import { useReferralProgram } from './hooks/use-referral-program';
import { Table } from './table'; import { Table } from '../../components/table';
import classNames from 'classnames'; import classNames from 'classnames';
import { BORDER_COLOR, GRADIENT } from './constants'; import { BORDER_COLOR, GRADIENT } from './constants';
import { Tag } from './tag'; import { Tag } from '../../components/helpers/tag';
import type { ComponentProps, ReactNode } from 'react'; import { getTierColor, getTierGradient } from '../../components/helpers/tiers';
import type { ReactNode } from 'react';
import { ExternalLink, truncateMiddle } from '@vegaprotocol/ui-toolkit'; import { ExternalLink, truncateMiddle } from '@vegaprotocol/ui-toolkit';
import { import {
DApp, DApp,
@@ -19,25 +20,6 @@ import {
import { useT, ns } from '../../lib/use-t'; import { useT, ns } from '../../lib/use-t';
import { Trans } from 'react-i18next'; import { Trans } from 'react-i18next';
// rainbow-ish order
const TIER_COLORS: Array<ComponentProps<typeof Tag>['color']> = [
'pink',
'orange',
'yellow',
'green',
'blue',
'purple',
];
const getTierColor = (tier: number) => {
const tiers = Object.keys(TIER_COLORS).length;
let index = Math.abs(tier - 1);
if (tier >= tiers) {
index = index % tiers;
}
return TIER_COLORS[index];
};
const Loading = ({ variant }: { variant: 'large' | 'inline' }) => ( const Loading = ({ variant }: { variant: 'large' | 'inline' }) => (
<div <div
className={classNames( className={classNames(
@@ -53,10 +35,12 @@ const StakingTier = ({
tier, tier,
referralRewardMultiplier, referralRewardMultiplier,
minimumStakedTokens, minimumStakedTokens,
max,
}: { }: {
tier: number; tier: number;
referralRewardMultiplier: string; referralRewardMultiplier: string;
minimumStakedTokens: string; minimumStakedTokens: string;
max?: number;
}) => { }) => {
const t = useT(); const t = useT();
const minimum = addDecimalsFormatNumber(minimumStakedTokens, 18); const minimum = addDecimalsFormatNumber(minimumStakedTokens, 18);
@@ -97,7 +81,7 @@ const StakingTier = ({
)} )}
> >
<div> <div>
<Tag color={getTierColor(tier)}> <Tag color={getTierColor(tier, max)}>
{t('Multiplier')} {referralRewardMultiplier}x {t('Multiplier')} {referralRewardMultiplier}x
</Tag> </Tag>
<p className="mt-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100"> <p className="mt-1 text-sm text-vega-clight-100 dark:text-vega-cdark-100">
@@ -159,7 +143,9 @@ export const TiersContainer = () => {
return ( return (
<> <>
<h2 className="text-3xl mt-10">{t('Current program details')}</h2> <h2 className="text-3xl mt-10 font-alpha calt">
{t('Current program details')}
</h2>
{details?.id && ( {details?.id && (
<p> <p>
<Trans <Trans
@@ -198,7 +184,18 @@ export const TiersContainer = () => {
</div> </div>
{/* Container */} {/* Container */}
<div className="bg-vega-clight-800 dark:bg-vega-cdark-800 text-black dark:text-white rounded-lg p-6 mt-1 mb-20"> <div
className={classNames(
'md:bg-vega-clight-800',
'md:dark:bg-vega-cdark-800',
'md:text-black',
'md:dark:text-white',
'md:rounded-lg',
'md:p-6',
'mt-1',
'mb-20'
)}
>
{/* Benefit tiers */} {/* Benefit tiers */}
<div className="flex flex-col mb-5"> <div className="flex flex-col mb-5">
<h3 className="text-2xl calt">{t('Benefit tiers')}</h3> <h3 className="text-2xl calt">{t('Benefit tiers')}</h3>
@@ -261,6 +258,7 @@ const StakingTiers = ({
<StakingTier <StakingTier
key={i} key={i}
tier={tier} tier={tier}
max={data.length}
referralRewardMultiplier={referralRewardMultiplier} referralRewardMultiplier={referralRewardMultiplier}
minimumStakedTokens={minimumStakedTokens} minimumStakedTokens={minimumStakedTokens}
/> />
@@ -322,22 +320,7 @@ const TiersTable = ({
className="bg-white dark:bg-vega-cdark-900" className="bg-white dark:bg-vega-cdark-900"
data={data.map((d) => ({ data={data.map((d) => ({
...d, ...d,
className: classNames({ className: classNames(getTierGradient(d.tier, data.length)),
'from-vega-yellow-400 dark:from-vega-yellow-600 to-20% bg-highlight':
'yellow' === getTierColor(d.tier),
'from-vega-green-400 dark:from-vega-green-600 to-20% bg-highlight':
'green' === getTierColor(d.tier),
'from-vega-blue-400 dark:from-vega-blue-600 to-20% bg-highlight':
'blue' === getTierColor(d.tier),
'from-vega-purple-400 dark:from-vega-purple-600 to-20% bg-highlight':
'purple' === getTierColor(d.tier),
'from-vega-pink-400 dark:from-vega-pink-600 to-20% bg-highlight':
'pink' === getTierColor(d.tier),
'from-vega-orange-400 dark:from-vega-orange-600 to-20% bg-highlight':
'orange' === getTierColor(d.tier),
'from-vega-clight-200 dark:from-vega-cdark-200 to-20% bg-highlight':
'none' === getTierColor(d.tier),
}),
}))} }))}
/> />
); );
+10 -5
View File
@@ -19,9 +19,11 @@ export const Tile = ({
return ( return (
<div <div
className={classNames( className={classNames(
'rounded-lg overflow-hidden relative', 'text-black dark:text-white',
'bg-vega-clight-800 dark:bg-vega-cdark-800 text-black dark:text-white', 'overflow-hidden relative',
'p-6', 'p-3 md:p-6',
'rounded-lg',
'bg-vega-clight-800 dark:bg-vega-cdark-800',
className className
)} )}
> >
@@ -55,7 +57,10 @@ export const StatTile = ({
> >
{title} {title}
</h3> </h3>
<div data-testid={`${testId}-value`} className="text-5xl text-left"> <div
data-testid={`${testId}-value`}
className="text-2xl lg:text-5xl text-left"
>
{children} {children}
</div> </div>
{description && ( {description && (
@@ -119,7 +124,7 @@ export const CodeTile = ({
> >
<div <div
className={classNames( className={classNames(
'relative bg-rainbow bg-clip-text text-transparent text-5xl overflow-hidden', 'relative bg-rainbow bg-clip-text text-transparent text-2xl lg:text-5xl overflow-hidden',
FADE_OUT_STYLE FADE_OUT_STYLE
)} )}
> >
+1
View File
@@ -0,0 +1 @@
export { Teams } from './teams';
@@ -0,0 +1,7 @@
export const Teams = () => {
return (
<div>
<h1>Teams</h1>
</div>
);
};
@@ -73,7 +73,7 @@ export const AccountsContainer = ({
); );
}; };
const useAccountStore = create<DataGridSlice>()( export const useAccountStore = create<DataGridSlice>()(
persist(createDataGridSlice, { persist(createDataGridSlice, {
name: 'vega_accounts_store', name: 'vega_accounts_store',
}) })
@@ -0,0 +1,8 @@
import { GridSettings } from '../grid-settings/grid-settings';
import { useAccountStore } from './accounts-container';
export const AccountsSettings = () => (
<GridSettings
updateGridStore={useAccountStore((store) => store.updateGridStore)}
/>
);
@@ -1 +1,2 @@
export * from './accounts-container'; export * from './accounts-container';
export * from './accounts-settings';
+17 -3
View File
@@ -9,6 +9,7 @@ export const Card = ({
loading = false, loading = false,
highlight = false, highlight = false,
testId, testId,
noBackgroundOnMobile = false,
}: { }: {
children: ReactNode; children: ReactNode;
title: string; title: string;
@@ -16,20 +17,33 @@ export const Card = ({
loading?: boolean; loading?: boolean;
highlight?: boolean; highlight?: boolean;
testId?: string; testId?: string;
noBackgroundOnMobile?: boolean;
}) => { }) => {
return ( return (
<div <div
data-testid={testId} data-testid={testId}
className={classNames( className={classNames(
'bg-vega-clight-800 dark:bg-vega-cdark-800 col-span-full p-0.5 lg:col-auto', 'col-span-full lg:col-auto',
'rounded-lg', {
'rounded-lg bg-vega-clight-800 dark:bg-vega-cdark-800 p-0.5':
!noBackgroundOnMobile,
'mt-3 md:mt-0 md:rounded-lg md:bg-vega-clight-800 md:dark:bg-vega-cdark-800 md:p-0.5':
noBackgroundOnMobile,
},
{ {
'bg-rainbow': highlight, 'bg-rainbow': highlight,
}, },
className className
)} )}
> >
<div className="bg-vega-clight-800 dark:bg-vega-cdark-800 h-full w-full rounded p-4"> <div
className={classNames('h-full w-full', {
'bg-vega-clight-800 dark:bg-vega-cdark-800 rounded p-4':
!noBackgroundOnMobile,
'md:bg-vega-clight-800 md:dark:bg-vega-cdark-800 md:rounded md:p-4':
noBackgroundOnMobile,
})}
>
<h2 className="mb-3">{title}</h2> <h2 className="mb-3">{title}</h2>
{loading ? <CardLoader /> : children} {loading ? <CardLoader /> : children}
</div> </div>
@@ -13,7 +13,7 @@ import { MarketFees } from './market-fees';
import { useVolumeStats } from './use-volume-stats'; import { useVolumeStats } from './use-volume-stats';
import { useReferralStats } from './use-referral-stats'; import { useReferralStats } from './use-referral-stats';
import { formatPercentage, getAdjustedFee } from './utils'; import { formatPercentage, getAdjustedFee } from './utils';
import { Table, Td, Th, THead, Tr } from './table'; import { Table as SimpleTable } from '../../components/table';
import BigNumber from 'bignumber.js'; import BigNumber from 'bignumber.js';
import { Links } from '../../lib/links'; import { Links } from '../../lib/links';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@@ -24,6 +24,8 @@ import {
truncateMiddle, truncateMiddle,
} from '@vegaprotocol/ui-toolkit'; } from '@vegaprotocol/ui-toolkit';
import { useT } from '../../lib/use-t'; import { useT } from '../../lib/use-t';
import classNames from 'classnames';
import { getTierGradient } from '../helpers/tiers';
export const FeesContainer = () => { export const FeesContainer = () => {
const t = useT(); const t = useT();
@@ -166,6 +168,7 @@ export const FeesContainer = () => {
className="lg:col-span-full xl:col-span-2" className="lg:col-span-full xl:col-span-2"
loading={loading} loading={loading}
data-testid="volume-discount-card" data-testid="volume-discount-card"
noBackgroundOnMobile={true}
> >
<VolumeTiers <VolumeTiers
tiers={volumeTiers} tiers={volumeTiers}
@@ -179,12 +182,14 @@ export const FeesContainer = () => {
className="lg:col-span-full xl:col-span-2" className="lg:col-span-full xl:col-span-2"
loading={loading} loading={loading}
data-testid="referral-discount-card" data-testid="referral-discount-card"
noBackgroundOnMobile={true}
> >
<ReferralTiers <ReferralTiers
tiers={referralTiers} tiers={referralTiers}
tierIndex={referralTierIndex} tierIndex={referralTierIndex}
epochsInSet={epochsInSet} epochsInSet={epochsInSet}
referralVolumeInWindow={referralVolumeInWindow} referralVolumeInWindow={referralVolumeInWindow}
referralDiscountWindowLength={referralDiscountWindowLength}
/> />
</Card> </Card>
<Card <Card
@@ -192,6 +197,7 @@ export const FeesContainer = () => {
className="lg:col-span-full" className="lg:col-span-full"
loading={marketsLoading} loading={marketsLoading}
data-testid="fees-by-market-card" data-testid="fees-by-market-card"
noBackgroundOnMobile={true}
> >
<MarketFees <MarketFees
markets={markets} markets={markets}
@@ -477,44 +483,65 @@ const VolumeTiers = ({
return ( return (
<div> <div>
<Table> <SimpleTable
<THead> className="bg-white dark:bg-vega-cdark-900"
<Tr> columns={[
<Th data-testid="tier-header">{t('Tier')}</Th> { name: 'tier', displayName: t('Tier'), testId: 'col-tier-value' },
<Th data-testid="discount-header">{t('Discount')}</Th> {
<Th data-testid="min-volume-header">{t('Min. trading volume')}</Th> name: 'discount',
<Th data-testid="my-volume-header"> displayName: t('Discount'),
{t('myVolume', 'My volume (last {{count}} epochs)', { testId: 'discount-value',
count: windowLength, },
})} {
</Th> name: 'minTradingVolume',
<Th data-testid="actions-header" /> displayName: t('Min. trading volume'),
</Tr> testId: 'min-volume-value',
</THead> },
<tbody> {
{Array.from(tiers).map((tier, i) => { name: 'myVolume',
const isUserTier = tierIndex === i; displayName: t('myVolume', 'My volume (last {{count}} epochs)', {
count: windowLength,
return ( }),
<Tr key={i} data-testid={`tier-row-${i}`}> testId: 'my-volume-value',
<Td data-testid={`tier-value-${i}`}>{i + 1}</Td> },
<Td data-testid={`discount-value-${i}`}> {
{formatPercentage(Number(tier.volumeDiscountFactor))}% name: 'indicator',
</Td> className: 'max-md:hidden',
<Td data-testid={`min-volume-value-${i}`}> testId: 'your-tier',
{formatNumber(tier.minimumRunningNotionalTakerVolume)} },
</Td> ]}
<Td data-testid={`my-volume-value-${i}`}> data={Array.from(tiers).map((tier, i) => {
{isUserTier ? formatNumber(lastEpochVolume) : ''} const isUserTier = tierIndex === i;
</Td> const indicator = isUserTier ? (
<Td data-testid={`your-tier-${i}`}> <YourTier testId={`your-volume-tier-${i}`} />
{isUserTier ? <YourTier /> : null} ) : null;
</Td> const tierIndicator = (
</Tr> <div className="flex justify-between">
); <span data-testid={`tier-value-${i}`}>{i + 1}</span>
})} <span className="md:hidden">{indicator}</span>
</tbody> </div>
</Table> );
return {
tier: tierIndicator,
discount: (
<>{formatPercentage(Number(tier.volumeDiscountFactor))}%</>
),
minTradingVolume: (
<>{formatNumber(tier.minimumRunningNotionalTakerVolume)}</>
),
myVolume: isUserTier ? (
formatNumber(lastEpochVolume)
) : (
<span className="md:hidden">-</span>
),
indicator: indicator,
className: classNames(
getTierGradient(i + 1, tiers.length),
'text-xs'
),
};
})}
/>
</div> </div>
); );
}; };
@@ -524,6 +551,7 @@ const ReferralTiers = ({
tierIndex, tierIndex,
epochsInSet, epochsInSet,
referralVolumeInWindow, referralVolumeInWindow,
referralDiscountWindowLength,
}: { }: {
tiers: Array<{ tiers: Array<{
referralDiscountFactor: string; referralDiscountFactor: string;
@@ -533,6 +561,7 @@ const ReferralTiers = ({
tierIndex: number; tierIndex: number;
epochsInSet: number; epochsInSet: number;
referralVolumeInWindow: number; referralVolumeInWindow: number;
referralDiscountWindowLength: number;
}) => { }) => {
const t = useT(); const t = useT();
@@ -544,60 +573,81 @@ const ReferralTiers = ({
return ( return (
<div> <div>
<Table> <SimpleTable
<THead> className="bg-white dark:bg-vega-cdark-900"
<Tr> columns={[
<Th data-testid="tier-header">{t('Tier')}</Th> { name: 'tier', displayName: t('Tier'), testId: 'col-tier-value' },
<Th data-testid="discount-header">{t('Discount')}</Th> {
<Th data-testid="min-volume-header">{t('Min. trading volume')}</Th> name: 'discount',
<Th data-testid="required-epochs-header">{t('Required epochs')}</Th> displayName: t('Discount'),
<Th data-testid="extra-header" /> tooltip: t(
</Tr> "The proportion of the referee's taker fees to be discounted"
</THead> ),
<tbody> testId: 'discount-value',
{Array.from(tiers).map((tier, i) => { },
const isUserTier = tierIndex === i; {
name: 'volume',
displayName: t(
'minTradingVolume',
'Min. trading volume (last {{count}} epochs)',
{
count: referralDiscountWindowLength,
}
),
tooltip: t(
'The minimum running notional for the given benefit tier'
),
testId: 'min-volume-value',
},
{
name: 'epochs',
displayName: t('Min. epochs'),
tooltip: t(
'The minimum number of epochs the party needs to be in the referral set to be eligible for the benefit'
),
testId: 'required-epochs-value',
},
{
name: 'indicator',
className: 'max-md:hidden',
testId: 'user-tier-or-unlocks',
},
]}
data={Array.from(tiers).map((tier, i) => {
const isUserTier = tierIndex === i;
const requiredVolume = Number(tier.minimumRunningNotionalTakerVolume);
const requiredVolume = Number( const indicator = isUserTier ? (
tier.minimumRunningNotionalTakerVolume <YourTier testId={`your-referral-tier-${i}`} />
); ) : referralVolumeInWindow >= requiredVolume &&
let unlocksIn = null; epochsInSet < tier.minimumEpochs ? (
<span className="text-muted text-xs">
Unlocks in {tier.minimumEpochs - epochsInSet} epochs
</span>
) : null;
if ( const tierIndicator = (
referralVolumeInWindow >= requiredVolume && <div className="flex justify-between">
epochsInSet < tier.minimumEpochs <span data-testid={`tier-value-${i}`}>{i + 1}</span>
) { <span className="md:hidden">{indicator}</span>
unlocksIn = ( </div>
<span className="text-muted"> );
Unlocks in {tier.minimumEpochs - epochsInSet} epochs
</span>
);
}
return ( return {
<Tr key={i} data-testid={`tier-row-${i}`}> tier: tierIndicator,
<Td data-testid={`tier-value-${i}`}>{i + 1}</Td> discount: (
<Td data-testid={`discount-value-${i}`}> <>{formatPercentage(Number(tier.referralDiscountFactor))}%</>
{formatPercentage(Number(tier.referralDiscountFactor))}% ),
</Td> volume: formatNumber(tier.minimumRunningNotionalTakerVolume),
<Td data-testid={`min-volume-value-${i}`}> epochs: tier.minimumEpochs,
{formatNumber(tier.minimumRunningNotionalTakerVolume)} indicator,
</Td> className: classNames(
<Td data-testid={`required-epochs-value-${i}`}> getTierGradient(i + 1, tiers.length),
{tier.minimumEpochs} 'text-xs'
</Td> ),
<Td data-testid={`user-tier-or-unlocks-${i}`}> };
{isUserTier ? ( })}
<YourTier testId={`your-tier-${i}`} /> />
) : (
unlocksIn
)}
</Td>
</Tr>
);
})}
</tbody>
</Table>
</div> </div>
); );
}; };
@@ -611,7 +661,7 @@ const YourTier = ({ testId }: YourTierProps) => {
return ( return (
<span <span
className="bg-rainbow whitespace-nowrap rounded-xl px-4 py-1.5 text-white" className="bg-rainbow whitespace-nowrap rounded-xl px-4 py-1.5 text-white text-xs"
data-testid={testId} data-testid={testId}
> >
{t('Your tier')} {t('Your tier')}
@@ -8,43 +8,53 @@ import { useNavigateWithMeta } from '../../lib/hooks/use-market-click-handler';
import { Links } from '../../lib/links'; import { Links } from '../../lib/links';
import { useT } from '../../lib/use-t'; import { useT } from '../../lib/use-t';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { type ColDef } from 'ag-grid-community/dist/lib/entities/colDef';
const useFeesTableColumnDefs = () => { const useFeesTableColumnDefs = (): ColDef[] => {
const t = useT(); const t = useT();
return useMemo( return useMemo(
() => [ () =>
{ field: 'code', cellRenderer: 'MarketCodeCell' }, [
{ {
field: 'feeAfterDiscount', field: 'code',
headerName: t('Total fee after discount'), cellRenderer: 'MarketCodeCell',
valueFormatter: ({ value }: { value: number }) => value + '%', pinned: 'left',
}, width: 150,
{ },
field: 'infraFee', {
valueFormatter: ({ value }: { value: number }) => value + '%', field: 'liquidityFee',
}, valueFormatter: ({ value }: { value: number }) => value + '%',
{ },
field: 'makerFee', {
valueFormatter: ({ value }: { value: number }) => value + '%', field: 'feeAfterDiscount',
}, headerName: t('Total fee after discount'),
{ valueFormatter: ({ value }: { value: number }) => value + '%',
field: 'liquidityFee', },
valueFormatter: ({ value }: { value: number }) => value + '%', {
}, field: 'totalFee',
{ headerName: t('Total fee before discount'),
field: 'totalFee', valueFormatter: ({ value }: { value: number }) => value + '%',
headerName: t('Total fee before discount'), },
valueFormatter: ({ value }: { value: number }) => value + '%', {
}, field: 'infraFee',
], valueFormatter: ({ value }: { value: number }) => value + '%',
},
{
field: 'makerFee',
valueFormatter: ({ value }: { value: number }) => value + '%',
},
] as ColDef[],
[t] [t]
); );
}; };
const feesTableDefaultColDef = { const feesTableDefaultColDef = {
flex: 1, flex: 1,
minWidth: 62,
resizable: true, resizable: true,
sortable: true, sortable: true,
suppressMovable: true,
pinned: false,
}; };
const components = { const components = {
@@ -62,6 +72,8 @@ export const MarketFees = ({
}) => { }) => {
const navigateWithMeta = useNavigateWithMeta(); const navigateWithMeta = useNavigateWithMeta();
const colDef = useFeesTableColumnDefs();
const rows = compact(markets || []).map((m) => { const rows = compact(markets || []).map((m) => {
const infraFee = new BigNumber(m.fees.factors.infrastructureFee); const infraFee = new BigNumber(m.fees.factors.infrastructureFee);
const makerFee = new BigNumber(m.fees.factors.makerFee); const makerFee = new BigNumber(m.fees.factors.makerFee);
@@ -88,9 +100,9 @@ export const MarketFees = ({
}); });
return ( return (
<div className="border rounded-sm border-default"> <div className="border rounded-lg md:rounded-sm overflow-hidden border-default">
<AgGrid <AgGrid
columnDefs={useFeesTableColumnDefs()} columnDefs={colDef}
rowData={rows} rowData={rows}
getRowId={({ data }) => data.id} getRowId={({ data }) => data.id}
defaultColDef={feesTableDefaultColDef} defaultColDef={feesTableDefaultColDef}
@@ -38,7 +38,7 @@ export const FillsContainer = () => {
); );
}; };
const useFillsStore = create<DataGridSlice>()( export const useFillsStore = create<DataGridSlice>()(
persist(createDataGridSlice, { persist(createDataGridSlice, {
name: 'vega_fills_store', name: 'vega_fills_store',
}) })
@@ -0,0 +1,8 @@
import { GridSettings } from '../grid-settings/grid-settings';
import { useFillsStore } from './fills-container';
export const FillsSettings = () => (
<GridSettings
updateGridStore={useFillsStore((store) => store.updateGridStore)}
/>
);
@@ -1 +1,2 @@
export * from './fills-container'; export * from './fills-container';
export * from './fills-settings';
@@ -45,7 +45,7 @@ export const FundingPaymentsContainer = ({
); );
}; };
const useFundingPaymentsStore = create<DataGridSlice>()( export const useFundingPaymentsStore = create<DataGridSlice>()(
persist(createDataGridSlice, { persist(createDataGridSlice, {
name: 'vega_funding_payments_store', name: 'vega_funding_payments_store',
}) })
@@ -0,0 +1,8 @@
import { GridSettings } from '../grid-settings/grid-settings';
import { useFundingPaymentsStore } from './funding-payments-container';
export const FundingPaymentsSettings = () => (
<GridSettings
updateGridStore={useFundingPaymentsStore((store) => store.updateGridStore)}
/>
);
@@ -1 +1,2 @@
export * from './funding-payments-container'; export * from './funding-payments-container';
export * from './funding-payments-settings';
@@ -0,0 +1,24 @@
import { useT } from '../../lib/use-t';
import type { DataGridStore } from '../../stores/datagrid-store-slice';
import { TradingButton as Button } from '@vegaprotocol/ui-toolkit';
export const GridSettings = ({
updateGridStore,
}: {
updateGridStore: (gridStore: DataGridStore) => void;
}) => {
const t = useT();
return (
<Button
onClick={() =>
updateGridStore({
columnState: undefined,
filterModel: undefined,
})
}
size="extra-small"
>
{t('Reset Columns')}
</Button>
);
};
@@ -2,7 +2,15 @@ import type { HTMLAttributes } from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
type TagProps = { type TagProps = {
color?: 'yellow' | 'green' | 'blue' | 'purple' | 'pink' | 'orange' | 'none'; color?:
| 'yellow'
| 'green'
| 'blue'
| 'purple'
| 'pink'
| 'orange'
| 'red'
| 'none';
}; };
export const Tag = ({ export const Tag = ({
color = 'none', color = 'none',
+48
View File
@@ -0,0 +1,48 @@
import type { ComponentProps } from 'react';
import type { Tag } from './tag';
import classNames from 'classnames';
// rainbow-ish order
export const TIER_COLORS: Array<ComponentProps<typeof Tag>['color']> = [
'none', // worst tier if 8 tiers, otherwise relative to the best tier
'red',
'pink',
'orange',
'yellow',
'green',
'blue',
'purple', // best tier
];
export const getTierColor = (tier: number, max = TIER_COLORS.length) => {
const available =
max < TIER_COLORS.length
? TIER_COLORS.slice(TIER_COLORS.length - max)
: TIER_COLORS;
const tiers = Object.keys(available).length;
let index = Math.abs(tier - 1);
if (tier >= tiers) {
index = index % tiers;
}
return available[index];
};
export const getTierGradient = (tier: number, max = TIER_COLORS.length) =>
classNames({
'from-vega-yellow-400 dark:from-vega-yellow-600 to-20% bg-highlight':
'yellow' === getTierColor(tier, max),
'from-vega-green-400 dark:from-vega-green-600 to-20% bg-highlight':
'green' === getTierColor(tier, max),
'from-vega-blue-400 dark:from-vega-blue-600 to-20% bg-highlight':
'blue' === getTierColor(tier, max),
'from-vega-purple-400 dark:from-vega-purple-600 to-20% bg-highlight':
'purple' === getTierColor(tier, max),
'from-vega-pink-400 dark:from-vega-pink-600 to-20% bg-highlight':
'pink' === getTierColor(tier, max),
'from-vega-orange-400 dark:from-vega-orange-600 to-20% bg-highlight':
'orange' === getTierColor(tier, max),
'from-vega-red-400 dark:from-vega-red-600 to-20% bg-highlight':
'red' === getTierColor(tier, max),
'from-vega-clight-600 dark:from-vega-cdark-600 to-20% bg-highlight':
'none' === getTierColor(tier, max),
});
@@ -1,3 +1 @@
export * from './market-successor-banner'; export { MarketBanner } from './market-banner';
export * from './market-successor-proposal-banner';
export * from './market-termination-banner';
@@ -0,0 +1,205 @@
import compact from 'lodash/compact';
import { MarketState } from '@vegaprotocol/types';
import { Intent, NotificationBanner } from '@vegaprotocol/ui-toolkit';
import {
useMarketState,
type Market,
useMaliciousOracle,
} from '@vegaprotocol/markets';
import { useState } from 'react';
import { type MarketViewProposalFieldsFragment } from '@vegaprotocol/proposals';
import { MarketSuspendedBanner } from './market-suspended-banner';
import { MarketUpdateBanner } from './market-update-banner';
import { MarketUpdateStateBanner } from './market-update-state-banner';
import { MarketSettledBanner } from './market-settled-banner';
import { MarketSuccessorProposalBanner } from './market-successor-proposal-banner';
import { MarketOracleBanner, type Oracle } from './market-oracle-banner';
import {
useSuccessorMarketProposals,
useUpdateMarketProposals,
useUpdateMarketStateProposals,
} from './use-market-proposals';
type UpdateMarketBanner = {
kind: 'UpdateMarket';
proposals: MarketViewProposalFieldsFragment[];
};
type UpdateMarketStateBanner = {
kind: 'UpdateMarketState';
proposals: MarketViewProposalFieldsFragment[];
};
type NewMarketBanner = {
kind: 'NewMarket'; // aka a proposal of NewMarket which succeeds the current market
proposals: MarketViewProposalFieldsFragment[];
};
type SettledBanner = {
kind: 'Settled';
market: Market;
};
type SuspendedBanner = {
kind: 'Suspended';
market: Market;
};
type OracleBanner = {
kind: 'Oracle';
oracle: Oracle;
};
type Banner =
| UpdateMarketBanner
| UpdateMarketStateBanner
| NewMarketBanner
| SettledBanner
| SuspendedBanner
| OracleBanner;
export const MarketBanner = ({ market }: { market: Market }) => {
const { data: marketState } = useMarketState(market.id);
const { proposals: successorProposals, loading: successorLoading } =
useSuccessorMarketProposals(market.id);
const { proposals: updateMarketProposals, loading: updateMarketLoading } =
useUpdateMarketProposals(market.id);
const {
proposals: updateMarketStateProposals,
loading: updateMarketStateLoading,
} = useUpdateMarketStateProposals(market.id);
const { data: maliciousOracle, loading: oracleLoading } = useMaliciousOracle(
market.id
);
const loading =
successorLoading ||
updateMarketLoading ||
updateMarketStateLoading ||
oracleLoading;
if (loading) {
return null;
}
const banners = compact([
updateMarketStateProposals.length
? {
kind: 'UpdateMarketState' as const,
proposals: updateMarketStateProposals,
}
: undefined,
updateMarketProposals.length
? {
kind: 'UpdateMarket' as const,
proposals: updateMarketProposals,
}
: undefined,
successorProposals.length
? {
kind: 'NewMarket' as const,
proposals: successorProposals,
}
: undefined,
marketState === MarketState.STATE_SETTLED
? {
kind: 'Settled' as const,
market,
}
: undefined,
marketState === MarketState.STATE_SUSPENDED_VIA_GOVERNANCE
? {
kind: 'Suspended' as const,
market,
}
: undefined,
maliciousOracle !== undefined
? {
kind: 'Oracle' as const,
oracle: maliciousOracle,
}
: undefined,
]);
return <BannerQueue banners={banners} market={market} />;
};
const BannerQueue = ({
banners,
market,
}: {
banners: Banner[];
market: Market;
}) => {
// for showing banner by index
const [index, setIndex] = useState(0);
const banner = banners[index];
if (!banner) return null;
let content = null;
let intent = Intent.Primary;
switch (banner.kind) {
case 'UpdateMarket': {
content = <MarketUpdateBanner proposals={banner.proposals} />;
break;
}
case 'UpdateMarketState': {
content = (
<MarketUpdateStateBanner proposals={banner.proposals} market={market} />
);
break;
}
case 'NewMarket': {
content = <MarketSuccessorProposalBanner proposals={banner.proposals} />;
intent = Intent.Warning;
break;
}
case 'Settled': {
content = <MarketSettledBanner market={market} />;
break;
}
case 'Suspended': {
content = <MarketSuspendedBanner />;
intent = Intent.Warning;
break;
}
case 'Oracle': {
// @ts-ignore oracle cannot be undefined
content = <MarketOracleBanner oracle={banner.oracle} />;
intent = Intent.Danger;
break;
}
default: {
return null;
}
}
const showCount = banners.length > 1;
const onClose = () => {
setIndex((x) => x + 1);
};
return (
<NotificationBanner
intent={intent}
onClose={onClose}
data-testid="market-banner"
>
<div className="flex items-center justify-between">
{content}
{showCount ? (
<p>
{index + 1}/{banners.length}
</p>
) : null}
</div>
</NotificationBanner>
);
};
@@ -0,0 +1,58 @@
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { MarketOracleBanner } from './market-oracle-banner';
jest.mock('@vegaprotocol/markets', () => ({
...jest.requireActual('@vegaprotocol/markets'),
OracleDialog: ({ open }: { open: boolean }) => {
return <div data-testid="oracle-dialog">{open ? 'open' : 'closed'}</div>;
},
}));
const oracle = {
dataSourceSpecId: 'someId',
provider: {
name: 'Test oracle',
url: 'https://zombo.com',
description_markdown:
'Some markdown describing the oracle provider.\n\nTwitter: @FacesPics2\n',
oracle: {
status: 'COMPROMISED' as const,
status_reason: 'Reason here',
first_verified: '2021-01-01T00:00:00Z',
last_verified: '2022-01-01T00:00:00Z',
type: 'public_key' as const,
public_key: '0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC',
},
proofs: [
{
format: 'url' as const,
available: true,
type: 'web' as const,
url: 'https://proofweb.com',
},
{
format: 'signed_message' as const,
available: true,
type: 'public_key' as const,
public_key: '0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC',
message: 'SOMEHEX',
},
],
github_link:
'https://github.com/vegaprotocol/well-known/blob/main/oracle-providers/eth_address-0xfCEAdAFab14d46e20144F48824d0C09B1a03F2BC.toml',
},
};
describe('MarketOracleBanner', () => {
it('should render successfully', async () => {
render(<MarketOracleBanner oracle={oracle} />);
expect(screen.getByTestId('oracle-dialog')).toHaveTextContent('closed');
expect(screen.getByTestId('oracle-banner-status')).toHaveTextContent(
'COMPROMISED'
);
await userEvent.click(screen.getByRole('button', { name: 'Show more' }));
expect(screen.getByTestId('oracle-dialog')).toHaveTextContent('open');
});
});
@@ -0,0 +1,46 @@
import { useState } from 'react';
import { Trans } from 'react-i18next';
import {
OracleDialog,
useOracleStatuses,
type useMaliciousOracle,
} from '@vegaprotocol/markets';
import { ButtonLink } from '@vegaprotocol/ui-toolkit';
export type Oracle = ReturnType<typeof useMaliciousOracle>['data'];
export const MarketOracleBanner = ({
oracle,
}: {
oracle: NonNullable<Oracle>;
}) => {
const [open, setOpen] = useState(false);
const oracleStatuses = useOracleStatuses();
return (
<>
<OracleDialog open={open} onChange={setOpen} {...oracle} />
<p>
<Trans
defaults="Oracle status for this market is <0>{{status}}</0>. {{description}} <1>Show more</1>"
components={[
<span key="oracle-status" data-testid="oracle-banner-status">
status
</span>,
<ButtonLink
key="oracle-button"
onClick={() => setOpen((x) => !x)}
data-testid="oracle-banner-dialog-trigger"
>
Show more
</ButtonLink>,
]}
values={{
status: oracle.provider.oracle.status,
description: oracleStatuses[oracle.provider.oracle.status],
}}
/>
</p>
</>
);
};
@@ -0,0 +1,219 @@
import { render, screen } from '@testing-library/react';
import { addDays, subDays, subHours } from 'date-fns';
import { MockedProvider, type MockedResponse } from '@apollo/react-testing';
import {
SuccessorMarketDocument,
MarketCandlesDocument,
MarketCandlesUpdateDocument,
SuccessorMarketIdDocument,
type Market,
type SuccessorMarketQuery,
type SuccessorMarketQueryVariables,
type MarketCandlesQuery,
type MarketCandlesQueryVariables,
type MarketCandlesUpdateSubscription,
type MarketCandlesUpdateSubscriptionVariables,
type SuccessorMarketIdQuery,
type SuccessorMarketIdQueryVariables,
} from '@vegaprotocol/markets';
import { MemoryRouter } from 'react-router-dom';
import { createMarketFragment } from '@vegaprotocol/mock';
import { Interval, MarketState, MarketTradingMode } from '@vegaprotocol/types';
import { MarketSettledBanner } from './market-settled-banner';
describe('MarketSettledBanner', () => {
const now = 1701388800000;
const origDateNow = Date.now;
beforeAll(() => {
Date.now = () => now;
});
afterAll(() => {
Date.now = origDateNow;
});
const marketId = 'market-0';
const successorId = 'successor-id';
const successorName = 'successor-name';
const successorMarketIdMock: MockedResponse<
SuccessorMarketIdQuery,
SuccessorMarketIdQueryVariables
> = {
request: {
query: SuccessorMarketIdDocument,
variables: {
marketId: marketId,
},
},
result: {
data: {
market: {
successorMarketID: successorId,
},
},
},
};
const successorMock: MockedResponse<
SuccessorMarketQuery,
SuccessorMarketQueryVariables
> = {
request: {
query: SuccessorMarketDocument,
variables: {
marketId: successorId,
},
},
result: {
data: {
market: {
id: successorId,
state: MarketState.STATE_ACTIVE,
tradingMode: MarketTradingMode.TRADING_MODE_CONTINUOUS,
positionDecimalPlaces: 0,
tradableInstrument: {
instrument: {
name: successorName,
code: 'code',
},
},
proposal: {
id: 'proposal-id',
},
},
},
},
};
const since = subDays(new Date(now), 5).toISOString();
const successorCandlesMock: MockedResponse<
MarketCandlesQuery,
MarketCandlesQueryVariables
> = {
request: {
query: MarketCandlesDocument,
variables: {
marketId: successorId,
interval: Interval.INTERVAL_I1H,
since,
},
},
result: {
data: {
marketsConnection: {
edges: [
{
node: {
candlesConnection: {
edges: [
{
node: {
high: '100',
low: '100',
open: '100',
close: '100',
volume: '100',
periodStart: subHours(new Date(now), 1).toISOString(),
},
},
{
node: {
high: '100',
low: '100',
open: '100',
close: '200',
volume: '100',
periodStart: subHours(new Date(now), 2).toISOString(),
},
},
],
},
},
},
],
},
},
},
};
const successorCandlesUpdateMock: MockedResponse<
MarketCandlesUpdateSubscription,
MarketCandlesUpdateSubscriptionVariables
> = {
request: {
query: MarketCandlesUpdateDocument,
variables: {
marketId: successorId,
interval: Interval.INTERVAL_I1H,
// @ts-ignore this query doesn't need the 'since' variable, but the data-provider
// uses all variables for both the query and the subscription so its needed here
// to match the mock
since,
},
},
result: {
data: {
candles: {
high: '100',
low: '100',
open: '100',
close: '100',
volume: '100',
periodStart: '2020-01-01T00:00:00',
},
},
},
};
const renderComponent = (market: Market, mocks?: MockedResponse[]) => {
return render(
<MemoryRouter>
<MockedProvider mocks={mocks}>
<MarketSettledBanner market={market} />
</MockedProvider>
</MemoryRouter>
);
};
it('shows that the market is settled if there is no successor', () => {
const market = createMarketFragment({
id: marketId,
successorMarketID: undefined,
});
renderComponent(market, [successorMarketIdMock, successorMock]);
expect(
screen.getByText('This market has been settled')
).toBeInTheDocument();
});
it('shows that the market has been succeeded with an active market', async () => {
const market = createMarketFragment({
id: marketId,
successorMarketID: successorId,
marketTimestamps: {
close: addDays(new Date(now), 1).toISOString(),
},
});
renderComponent(market, [
successorMarketIdMock,
successorMock,
successorCandlesMock,
successorCandlesUpdateMock,
]);
expect(
await screen.findByText('This market has been succeeded')
).toBeInTheDocument();
expect(
await screen.findByText(/has a 24h trading volume of 200$/i)
).toBeInTheDocument();
expect(
screen.getByText('This market expires in 1 day.')
).toBeInTheDocument();
expect(screen.getByRole('link', { name: successorName })).toHaveAttribute(
'href',
`/markets/${successorId}`
);
});
});
@@ -0,0 +1,122 @@
import { Link } from 'react-router-dom';
import { Trans } from 'react-i18next';
import { isBefore, formatDuration, intervalToDuration } from 'date-fns';
import {
calcCandleVolume,
useCandles,
useSuccessorMarket,
type Market,
} from '@vegaprotocol/markets';
import {
addDecimalsFormatNumber,
getMarketExpiryDate,
isNumeric,
} from '@vegaprotocol/utils';
import { useT, ns } from '../../lib/use-t';
import { Links } from '../../lib/links';
const getExpiryDate = (tags: string[], close?: string): Date | null => {
const expiryDate = getMarketExpiryDate(tags);
return expiryDate || (close && new Date(close)) || null;
};
export const MarketSettledBanner = ({ market }: { market: Market }) => {
const t = useT();
const { data: successorMarket } = useSuccessorMarket(market.id);
const expiry = market
? getExpiryDate(
market.tradableInstrument.instrument.metadata.tags || [],
market.marketTimestamps.close
)
: null;
const duration =
expiry && isBefore(new Date(Date.now()), expiry)
? intervalToDuration({ start: new Date(Date.now()), end: expiry })
: null;
const { oneDayCandles } = useCandles({
marketId: successorMarket?.id,
});
const candleVolume = oneDayCandles?.length
? calcCandleVolume(oneDayCandles)
: null;
const successorVolume =
candleVolume && isNumeric(successorMarket?.positionDecimalPlaces)
? addDecimalsFormatNumber(
candleVolume,
successorMarket?.positionDecimalPlaces as number
)
: null;
if (successorMarket) {
return (
<div>
<p>{t('This market has been succeeded')}</p>
{duration && (
<div className="mt-1">
<span>
{t('This market expires in {{duration}}.', {
duration: formatDuration(duration, {
format: [
'years',
'months',
'weeks',
'days',
'hours',
'minutes',
],
}),
})}
</span>
<>
{' '}
{successorVolume ? (
<Trans
defaults="The successor market <0>{{instrumentName}}</0> has a 24h trading volume of {{successorVolume}}"
values={{
successorVolume,
instrumentName:
successorMarket?.tradableInstrument.instrument.name,
}}
components={[
<Link
to={Links.MARKET(successorMarket.id)}
key="link"
target="_blank"
>
successor market name
</Link>,
]}
/>
) : (
<Trans
defaults="The successor market is <0>{{instrumentName}}</0>"
values={{
instrumentName:
successorMarket?.tradableInstrument.instrument.name,
}}
components={[
<Link
to={Links.MARKET(successorMarket.id)}
key="link"
target="_blank"
>
successor market name
</Link>,
]}
ns={ns}
/>
)}
</>
</div>
)}
</div>
);
}
return <p>{t('This market has been settled')}</p>;
};
@@ -1,155 +0,0 @@
import { render, screen } from '@testing-library/react';
import { MockedProvider } from '@apollo/react-testing';
import { MarketSuccessorBanner } from './market-successor-banner';
import * as Types from '@vegaprotocol/types';
import * as allUtils from '@vegaprotocol/utils';
import type { Market } from '@vegaprotocol/markets';
import type { PartialDeep } from 'type-fest';
const market = {
id: 'marketId',
tradableInstrument: {
instrument: {
metadata: {
tags: [],
},
},
},
marketTimestamps: {
close: null,
},
} as unknown as Market;
let mockDataSuccessorMarket: PartialDeep<Market> | null = null;
let mockDataMarketState: Market['state'] | null = null;
jest.mock('@vegaprotocol/data-provider', () => ({
...jest.requireActual('@vegaprotocol/data-provider'),
useDataProvider: jest.fn().mockImplementation((args) => {
if (args.skip) {
return {
data: null,
error: null,
};
}
return {
data: mockDataSuccessorMarket,
error: null,
};
}),
}));
jest.mock('@vegaprotocol/utils', () => ({
...jest.requireActual('@vegaprotocol/utils'),
getMarketExpiryDate: jest.fn(),
}));
let mockCandles = {};
jest.mock('@vegaprotocol/markets', () => ({
...jest.requireActual('@vegaprotocol/markets'),
useMarketState: (marketId: string) =>
marketId
? {
data: mockDataMarketState,
}
: { data: undefined },
useSuccessorMarket: (marketId: string) =>
marketId
? {
data: mockDataSuccessorMarket,
}
: { data: undefined },
useCandles: () => mockCandles,
}));
describe('MarketSuccessorBanner', () => {
beforeEach(() => {
jest.clearAllMocks();
mockDataSuccessorMarket = {
id: 'successorMarketID',
state: Types.MarketState.STATE_ACTIVE,
tradingMode: Types.MarketTradingMode.TRADING_MODE_CONTINUOUS,
tradableInstrument: {
instrument: {
name: 'Successor Market Name',
},
},
};
});
describe('should be hidden', () => {
it('when no market', () => {
const { container } = render(<MarketSuccessorBanner market={null} />, {
wrapper: MockedProvider,
});
expect(container).toBeEmptyDOMElement();
});
it('no successor market data', () => {
mockDataSuccessorMarket = null;
const { container } = render(<MarketSuccessorBanner market={market} />, {
wrapper: MockedProvider,
});
expect(container).toBeEmptyDOMElement();
});
});
describe('should be displayed', () => {
it('should be rendered', () => {
render(<MarketSuccessorBanner market={market} />, {
wrapper: MockedProvider,
});
expect(
screen.getByText('This market has been succeeded')
).toBeInTheDocument();
expect(
screen.getByRole('link', { name: 'Successor Market Name' })
).toHaveAttribute('href', '/#/markets/successorMarketID');
});
it('no successor market data, market settled', () => {
mockDataSuccessorMarket = null;
mockDataMarketState = Types.MarketState.STATE_SETTLED;
render(<MarketSuccessorBanner market={market} />, {
wrapper: MockedProvider,
});
expect(
screen.getByText('This market has been settled')
).toBeInTheDocument();
});
it('should display optionally successor volume', () => {
mockDataSuccessorMarket = {
...mockDataSuccessorMarket,
positionDecimalPlaces: 3,
};
mockCandles = {
oneDayCandles: [
{ volume: 123 },
{ volume: 456 },
{ volume: 789 },
{ volume: 99999 },
],
};
render(<MarketSuccessorBanner market={market} />, {
wrapper: MockedProvider,
});
expect(
screen.getByText('has a 24h trading volume of 101.367', {
exact: false,
})
).toBeInTheDocument();
});
it('should display optionally duration', () => {
jest
.spyOn(allUtils, 'getMarketExpiryDate')
.mockReturnValue(
new Date(Date.now() + 24 * 60 * 60 * 1000 + 60 * 1000)
);
render(<MarketSuccessorBanner market={market} />, {
wrapper: MockedProvider,
});
expect(
screen.getByText(/^This market expires in 1 day/)
).toBeInTheDocument();
});
});
});
@@ -1,146 +0,0 @@
import { useState } from 'react';
import { isBefore, formatDuration, intervalToDuration } from 'date-fns';
import type { Market } from '@vegaprotocol/markets';
import {
calcCandleVolume,
useCandles,
useMarketState,
useSuccessorMarket,
} from '@vegaprotocol/markets';
import {
ExternalLink,
Intent,
NotificationBanner,
} from '@vegaprotocol/ui-toolkit';
import {
addDecimalsFormatNumber,
getMarketExpiryDate,
isNumeric,
} from '@vegaprotocol/utils';
import * as Types from '@vegaprotocol/types';
import { useT, ns } from '../../lib/use-t';
import { Trans } from 'react-i18next';
const getExpiryDate = (tags: string[], close?: string): Date | null => {
const expiryDate = getMarketExpiryDate(tags);
return expiryDate || (close && new Date(close)) || null;
};
export const MarketSuccessorBanner = ({
market,
}: {
market: Market | null;
}) => {
const t = useT();
const { data: marketState } = useMarketState(market?.id);
const isSettled = marketState === Types.MarketState.STATE_SETTLED;
const { data: successorData, loading } = useSuccessorMarket(market?.id);
const [visible, setVisible] = useState(true);
const expiry = market
? getExpiryDate(
market.tradableInstrument.instrument.metadata.tags || [],
market.marketTimestamps.close
)
: null;
const duration =
expiry && isBefore(new Date(), expiry)
? intervalToDuration({ start: new Date(), end: expiry })
: null;
const { oneDayCandles } = useCandles({
marketId: successorData?.id,
});
const candleVolume = oneDayCandles?.length
? calcCandleVolume(oneDayCandles)
: null;
const successorVolume =
candleVolume && isNumeric(successorData?.positionDecimalPlaces)
? addDecimalsFormatNumber(
candleVolume,
successorData?.positionDecimalPlaces as number
)
: null;
if (!loading && (isSettled || successorData) && visible) {
return (
<NotificationBanner
intent={Intent.Primary}
onClose={() => {
setVisible(false);
}}
>
<div className="uppercase">
{successorData
? t('This market has been succeeded')
: t('This market has been settled')}
</div>
{(duration || successorData) && (
<div className="mt-1">
{duration && (
<span>
{t('This market expires in {{duration}}.', {
duration: formatDuration(duration, {
format: [
'years',
'months',
'weeks',
'days',
'hours',
'minutes',
],
}),
})}
</span>
)}
{successorData && (
<>
{' '}
{successorVolume ? (
<Trans
defaults="The successor market <0>{{instrumentName}}</0> has a 24h trading volume of {{successorVolume}}"
values={{
successorVolume,
instrumentName:
successorData?.tradableInstrument.instrument.name,
}}
components={[
<ExternalLink
href={`/#/markets/${successorData?.id}`}
key="link"
>
successor market name
</ExternalLink>,
]}
/>
) : (
<Trans
defaults="The successor market is <0>{{instrumentName}}</0>"
values={{
instrumentName:
successorData?.tradableInstrument.instrument.name,
}}
components={[
<ExternalLink
href={`/#/markets/${successorData?.id}`}
key="link"
>
successor market name
</ExternalLink>,
]}
ns={ns}
/>
)}
</>
)}
</div>
)}
</NotificationBanner>
);
}
return null;
};
@@ -1,164 +1,77 @@
import { act, render, screen, waitFor } from '@testing-library/react'; import { render, screen } from '@testing-library/react';
import type { SingleExecutionResult } from '@apollo/client';
import type { MockedResponse } from '@apollo/react-testing';
import { MockedProvider } from '@apollo/react-testing';
import { MarketSuccessorProposalBanner } from './market-successor-proposal-banner'; import { MarketSuccessorProposalBanner } from './market-successor-proposal-banner';
import type { MarketViewProposalsQuery } from '@vegaprotocol/proposals'; import { ProposalState } from '@vegaprotocol/types';
import { MarketViewProposalsDocument } from '@vegaprotocol/proposals';
import * as Types from '@vegaprotocol/types';
const marketProposalMock: MockedResponse<MarketViewProposalsQuery> = { const proposal = {
request: { __typename: 'Proposal' as const,
query: MarketViewProposalsDocument, id: 'proposal-1',
variables: { state: ProposalState.STATE_OPEN,
inState: Types.ProposalState.STATE_OPEN, terms: {
proposalType: Types.ProposalType.TYPE_NEW_MARKET, __typename: 'ProposalTerms' as const,
closingDatetime: '2023-09-27',
enactmentDatetime: '2023-09-28',
change: {
__typename: 'NewMarket' as const,
instrument: {
name: 'New proposal of the market successor',
},
successorConfiguration: {
parentMarketId: 'marketId',
},
}, },
}, },
result: { };
data: { const proposal2 = {
proposalsConnection: { __typename: 'Proposal' as const,
edges: [ id: 'proposal-2',
{ state: ProposalState.STATE_OPEN,
node: { terms: {
__typename: 'Proposal', __typename: 'ProposalTerms' as const,
id: 'proposal-1', closingDatetime: '2023-09-27',
state: Types.ProposalState.STATE_OPEN, enactmentDatetime: '2023-09-28',
terms: { change: {
__typename: 'ProposalTerms', __typename: 'NewMarket' as const,
closingDatetime: '2023-09-27', instrument: {
enactmentDatetime: '2023-09-28', name: 'New second proposal of the market successor',
change: { },
__typename: 'NewMarket', successorConfiguration: {
instrument: { parentMarketId: 'marketId',
name: 'New proposal of the market successor',
},
successorConfiguration: {
parentMarketId: 'marketId',
},
},
},
},
},
],
}, },
}, },
}, },
}; };
describe('MarketSuccessorProposalBanner', () => { describe('MarketSuccessorProposalBanner', () => {
it('should display single proposal', async () => { it('should display single proposal', () => {
render( render(<MarketSuccessorProposalBanner proposals={[proposal]} />);
<MockedProvider mocks={[marketProposalMock]}>
<MarketSuccessorProposalBanner marketId="marketId" />
</MockedProvider>
);
await waitFor(() => {
expect(
screen.getByText('A successors to this market has been proposed')
).toBeInTheDocument();
});
expect( expect(
screen screen.getByText('A successor to this market has been proposed')
.getByRole('link') ).toBeInTheDocument();
.getAttribute('href')
?.endsWith('/proposals/proposal-1') ?? false
).toBe(true);
});
it('should display plural proposals', async () => {
const dualProposalMock = {
...marketProposalMock,
result: {
...marketProposalMock.result,
data: {
proposalsConnection: {
edges: [
...((
marketProposalMock?.result as SingleExecutionResult<MarketViewProposalsQuery>
)?.data?.proposalsConnection?.edges ?? []),
{
node: {
__typename: 'Proposal',
id: 'proposal-2',
state: Types.ProposalState.STATE_OPEN,
terms: {
__typename: 'ProposalTerms',
change: {
__typename: 'NewMarket',
instrument: {
name: 'New second proposal of the market successor',
},
successorConfiguration: {
parentMarketId: 'marketId',
},
},
},
},
},
],
},
},
},
};
render( expect(screen.getByRole('link')).toHaveAttribute(
<MockedProvider mocks={[dualProposalMock]}> 'href',
<MarketSuccessorProposalBanner marketId="marketId" /> expect.stringContaining(proposal.id)
</MockedProvider>
); );
await waitFor(() => { });
expect(
screen.getByText('Successors to this market have been proposed') it('should display plural proposals', () => {
).toBeInTheDocument(); const proposals = [proposal, proposal2];
}); render(<MarketSuccessorProposalBanner proposals={proposals} />);
expect( expect(
screen screen.getByText('Successors to this market have been proposed')
.getAllByRole('link')[0] ).toBeInTheDocument();
.getAttribute('href')
?.endsWith('/proposals/proposal-1') ?? false
).toBe(true);
expect(
screen
.getAllByRole('link')[1]
.getAttribute('href')
?.endsWith('/proposals/proposal-2') ?? false
).toBe(true);
});
it('banner should be hidden because no proposals', () => { const links = screen.getAllByRole('link');
const { container } = render( expect(links).toHaveLength(proposals.length);
<MockedProvider> expect(links[0]).toHaveAttribute(
<MarketSuccessorProposalBanner marketId="marketId" /> 'href',
</MockedProvider> expect.stringContaining(proposal.id)
); );
expect(container).toBeEmptyDOMElement(); expect(links[1]).toHaveAttribute(
}); 'href',
expect.stringContaining(proposal2.id)
it('banner should be hidden because no proposals for the market', () => {
const { container } = render(
<MockedProvider mocks={[marketProposalMock]}>
<MarketSuccessorProposalBanner marketId="otherMarketId" />
</MockedProvider>
); );
expect(container).toBeEmptyDOMElement();
});
it('banner should be hidden after user close click', async () => {
const { container } = render(
<MockedProvider mocks={[marketProposalMock]}>
<MarketSuccessorProposalBanner marketId="marketId" />
</MockedProvider>
);
await waitFor(() => {
expect(
screen.getByText('A successors to this market has been proposed')
).toBeInTheDocument();
});
await act(() => {
screen.getByTestId('notification-banner-close').click();
});
await waitFor(() => {
expect(container).toBeEmptyDOMElement();
});
}); });
}); });
@@ -1,89 +1,51 @@
import { Fragment, useState } from 'react'; import { Fragment } from 'react';
import { import { type MarketViewProposalFieldsFragment } from '@vegaprotocol/proposals';
marketViewProposalsDataProvider, import { ExternalLink } from '@vegaprotocol/ui-toolkit';
type NewMarketSuccessorFieldsFragment,
} from '@vegaprotocol/proposals';
import {
ExternalLink,
Intent,
NotificationBanner,
} from '@vegaprotocol/ui-toolkit';
import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment'; import { DApp, TOKEN_PROPOSAL, useLinks } from '@vegaprotocol/environment';
import * as Types from '@vegaprotocol/types';
import { useDataProvider } from '@vegaprotocol/data-provider';
import { useT } from '../../lib/use-t'; import { useT } from '../../lib/use-t';
export const MarketSuccessorProposalBanner = ({ export const MarketSuccessorProposalBanner = ({
marketId, proposals,
}: { }: {
marketId?: string; proposals: MarketViewProposalFieldsFragment[];
}) => { }) => {
const t = useT(); const t = useT();
const { data: proposals } = useDataProvider({
dataProvider: marketViewProposalsDataProvider,
skip: !marketId,
variables: {
inState: Types.ProposalState.STATE_OPEN,
proposalType: Types.ProposalType.TYPE_NEW_MARKET,
},
});
const successors =
proposals?.filter((item) => {
if (item.terms.change.__typename === 'NewMarket') {
const newMarket = item.terms.change;
if (
newMarket.successorConfiguration?.parentMarketId === marketId &&
item.state === Types.ProposalState.STATE_OPEN
) {
return true;
}
}
return false;
}) ?? [];
const [visible, setVisible] = useState(true);
const tokenLink = useLinks(DApp.Governance); const tokenLink = useLinks(DApp.Governance);
if (visible && successors.length) {
return ( return (
<NotificationBanner <div>
intent={Intent.Primary} <div className="uppercase mb-1">
onClose={() => { {proposals.length === 1
setVisible(false); ? t('A successor to this market has been proposed')
}} : t('Successors to this market have been proposed')}
> </div>
<div className="uppercase mb-1"> <div>
{successors.length === 1 {t(
? t('A successors to this market has been proposed') 'checkOutProposalsAndVote',
: t('Successors to this market have been proposed')} 'Check out the terms of the proposals and vote:',
</div> {
<div> count: proposals.length,
{t( }
'checkOutProposalsAndVote', )}{' '}
'Check out the terms of the proposals and vote:', {proposals.map((item, i) => {
{ if (item.terms.change.__typename !== 'NewMarket') {
count: successors.length, return null;
} }
)}{' '}
{successors.map((item, i) => { const externalLink = tokenLink(
const externalLink = tokenLink( TOKEN_PROPOSAL.replace(':id', item.id || '')
TOKEN_PROPOSAL.replace(':id', item.id || '') );
);
return ( return (
<Fragment key={i}> <Fragment key={i}>
<ExternalLink href={externalLink} key={i}> <ExternalLink href={externalLink} key={i}>
{ {item.terms.change.instrument.name}
(item.terms?.change as NewMarketSuccessorFieldsFragment) </ExternalLink>
?.instrument.name {i < proposals.length - 1 && ', '}
} </Fragment>
</ExternalLink> );
{i < successors.length - 1 && ', '} })}
</Fragment> </div>
); </div>
})} );
</div>
</NotificationBanner>
);
}
return null;
}; };
@@ -0,0 +1,6 @@
import { useT } from '../../lib/use-t';
export const MarketSuspendedBanner = () => {
const t = useT();
return <p>{t('Market was suspended by governance')}</p>;
};
@@ -1,230 +0,0 @@
import { render, screen, waitFor } from '@testing-library/react';
import * as Types from '@vegaprotocol/types';
import type { MockedResponse } from '@apollo/client/testing';
import { MockedProvider } from '@apollo/client/testing';
import type { MarketViewProposalsQuery } from '@vegaprotocol/proposals';
import { MarketViewProposalsDocument } from '@vegaprotocol/proposals';
import type { Market } from '@vegaprotocol/markets';
import { MarketTerminationBanner } from './market-termination-banner';
const marketMock = {
id: 'market-1',
decimalPlaces: 3,
tradableInstrument: {
instrument: {
product: {
__typename: 'Future',
quoteName: 'tDAI',
},
},
},
} as Market;
const passedProposalMock: MockedResponse<MarketViewProposalsQuery> = {
request: {
query: MarketViewProposalsDocument,
variables: {
inState: Types.ProposalState.STATE_PASSED,
proposalType: Types.ProposalType.TYPE_UPDATE_MARKET_STATE,
},
},
result: {
data: {
proposalsConnection: {
edges: [
{
node: {
id: '1',
state: Types.ProposalState.STATE_PASSED,
terms: {
closingDatetime: '2023-09-27T11:48:18Z',
enactmentDatetime: '2023-09-30T11:48:18',
change: {
__typename: 'UpdateMarketState',
updateType:
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
price: '',
market: {
id: 'market-1',
tradableInstrument: {
instrument: {
name: 'Market one name',
code: 'Market one',
},
},
},
},
},
},
},
{
node: {
id: '2',
state: Types.ProposalState.STATE_PASSED,
terms: {
closingDatetime: '2023-09-27T11:48:18Z',
enactmentDatetime: '2023-10-01T11:48:18',
change: {
__typename: 'UpdateMarketState',
updateType:
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
price: '',
market: {
id: 'market-2',
tradableInstrument: {
instrument: {
name: 'Market two name',
code: 'Market two',
},
},
},
},
},
},
},
],
},
},
},
};
const openProposalMock: MockedResponse<MarketViewProposalsQuery> = {
request: {
query: MarketViewProposalsDocument,
variables: {
inState: Types.ProposalState.STATE_OPEN,
proposalType: Types.ProposalType.TYPE_UPDATE_MARKET_STATE,
},
},
result: {
data: {
proposalsConnection: {
edges: [
{
node: {
id: '3',
state: Types.ProposalState.STATE_OPEN,
terms: {
closingDatetime: '2023-09-27T11:48:18Z',
enactmentDatetime: '2023-10-01T11:48:18',
change: {
__typename: 'UpdateMarketState',
updateType:
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
price: '',
market: {
id: 'market-3',
tradableInstrument: {
instrument: {
name: 'Market three name',
code: 'Market three',
},
},
},
},
},
},
},
{
node: {
id: '4',
state: Types.ProposalState.STATE_OPEN,
terms: {
closingDatetime: '2023-09-27T11:48:18Z',
enactmentDatetime: '2023-10-11T11:48:18',
change: {
__typename: 'UpdateMarketState',
updateType:
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
price: '',
market: {
id: 'market-3',
tradableInstrument: {
instrument: {
name: 'Market three name',
code: 'Market three',
},
},
},
},
},
},
},
{
node: {
id: '5',
state: Types.ProposalState.STATE_OPEN,
terms: {
closingDatetime: '2023-09-27T11:48:18Z',
enactmentDatetime: '2023-10-01T11:48:18',
change: {
__typename: 'UpdateMarketState',
updateType:
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE,
price: '',
market: {
id: 'market-4',
tradableInstrument: {
instrument: {
name: 'Market four name',
code: 'Market four',
},
},
},
},
},
},
},
],
},
},
},
};
const mocks: MockedResponse[] = [passedProposalMock, openProposalMock];
describe('MarketTerminationBanner', () => {
beforeAll(() => {
jest.useFakeTimers().setSystemTime(new Date('2023-09-28T10:10:10.000Z'));
});
afterAll(() => {
jest.useRealTimers();
});
it('should be properly rendered', async () => {
const { container } = render(
<MockedProvider mocks={mocks}>
<MarketTerminationBanner market={marketMock} />
</MockedProvider>
);
await waitFor(() => {
expect(container).not.toBeEmptyDOMElement();
});
expect(
screen.getByTestId('termination-warning-banner-market-1')
).toBeInTheDocument();
});
it('should render link to proposals', async () => {
const { container } = render(
<MockedProvider mocks={mocks}>
<MarketTerminationBanner market={{ ...marketMock, id: 'market-3' }} />
</MockedProvider>
);
await waitFor(() => {
expect(container).not.toBeEmptyDOMElement();
});
expect(screen.getByText('View proposals')).toBeInTheDocument();
});
it('should render link to proposal', async () => {
const { container } = render(
<MockedProvider mocks={mocks}>
<MarketTerminationBanner market={{ ...marketMock, id: 'market-4' }} />
</MockedProvider>
);
await waitFor(() => {
expect(container).not.toBeEmptyDOMElement();
});
expect(screen.getByText('View proposal')).toBeInTheDocument();
});
});
@@ -0,0 +1,84 @@
import { render, screen } from '@testing-library/react';
import { ProposalState } from '@vegaprotocol/types';
import { MarketUpdateBanner } from './market-update-banner';
import { type MarketViewProposalFieldsFragment } from '@vegaprotocol/proposals';
describe('MarketUpdateBanner', () => {
const change = {
__typename: 'UpdateMarket' as const,
marketId: 'update-market-id',
};
const openProposal = {
__typename: 'Proposal' as const,
id: 'proposal-1',
state: ProposalState.STATE_OPEN,
terms: {
__typename: 'ProposalTerms' as const,
closingDatetime: '2023-09-27',
enactmentDatetime: '2023-09-28',
change,
},
};
const passedProposal = {
__typename: 'Proposal' as const,
id: 'proposal-2',
state: ProposalState.STATE_PASSED,
terms: {
__typename: 'ProposalTerms' as const,
closingDatetime: '2023-09-27',
enactmentDatetime: '2023-09-28',
change,
},
};
it('renders content for a single open proposal', () => {
render(
<MarketUpdateBanner
proposals={[openProposal as MarketViewProposalFieldsFragment]}
/>
);
expect(
screen.getByText(/^changes have been proposed/i)
).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'View proposal' })).toHaveAttribute(
'href',
expect.stringContaining(openProposal.id as string)
);
});
it('renders content for a single passed proposal', () => {
render(
<MarketUpdateBanner
proposals={[passedProposal as MarketViewProposalFieldsFragment]}
/>
);
expect(
screen.getByText(/^proposal set to change market/i)
).toBeInTheDocument();
expect(screen.getByRole('link', { name: 'View proposal' })).toHaveAttribute(
'href',
expect.stringContaining(passedProposal.id)
);
});
it('renders content for multiple passed proposals', () => {
const proposals = [
openProposal,
openProposal,
] as MarketViewProposalFieldsFragment[];
render(<MarketUpdateBanner proposals={proposals} />);
expect(
screen.getByText(
new RegExp(`^There are ${proposals.length} open proposals`)
)
).toBeInTheDocument();
expect(
screen.getByRole('link', { name: 'View proposals' })
).toHaveAttribute('href', expect.stringContaining('proposals'));
});
});
@@ -0,0 +1,71 @@
import sortBy from 'lodash/sortBy';
import { format } from 'date-fns';
import {
DApp,
TOKEN_PROPOSAL,
TOKEN_PROPOSALS,
useLinks,
} from '@vegaprotocol/environment';
import { type MarketViewProposalFieldsFragment } from '@vegaprotocol/proposals';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
import { ProposalState } from '@vegaprotocol/types';
import { useT } from '../../lib/use-t';
export const MarketUpdateBanner = ({
proposals,
}: {
proposals: MarketViewProposalFieldsFragment[];
}) => {
const governanceLink = useLinks(DApp.Governance);
const t = useT();
const openProposals = sortBy(
proposals.filter((p) => p.state === ProposalState.STATE_OPEN),
(p) => p.terms.enactmentDatetime
);
const passedProposals = sortBy(
proposals.filter((p) => p.state === ProposalState.STATE_PASSED),
(p) => p.terms.enactmentDatetime
);
let content = null;
if (openProposals.length > 1) {
content = (
<p>
{t('There are {{count}} open proposals to change this market', {
count: openProposals.length,
})}
<ExternalLink href={governanceLink(TOKEN_PROPOSALS)}>
{t('View proposals')}
</ExternalLink>
</p>
);
} else if (passedProposals.length) {
const proposal = passedProposals[0];
const proposalLink = governanceLink(
TOKEN_PROPOSAL.replace(':id', proposal?.id || '')
);
content = (
<p>
{t('Proposal set to change market on {{date}}.', {
date: format(new Date(proposal.terms.enactmentDatetime), 'dd MMMM'),
})}
<ExternalLink href={proposalLink}>{t('View proposal')}</ExternalLink>,
</p>
);
} else {
const proposal = openProposals[0];
const proposalLink = governanceLink(
TOKEN_PROPOSAL.replace(':id', proposal?.id || '')
);
content = (
<p>
{t('Changes have been proposed for this market.')}{' '}
<ExternalLink href={proposalLink}>{t('View proposal')}</ExternalLink>
</p>
);
}
return <div data-testid="market-proposal-notification">{content}</div>;
};
@@ -1,44 +1,120 @@
import type { ReactNode } from 'react'; import { type ReactNode } from 'react';
import { useState } from 'react';
import { format, formatDuration, intervalToDuration } from 'date-fns';
import {
ExternalLink,
Intent,
NotificationBanner,
} from '@vegaprotocol/ui-toolkit';
import type { MarketViewProposalFieldsFragment } from '@vegaprotocol/proposals';
import { marketViewProposalsDataProvider } from '@vegaprotocol/proposals';
import * as Types from '@vegaprotocol/types';
import type { Market } from '@vegaprotocol/markets';
import { getQuoteName } from '@vegaprotocol/markets';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import sortBy from 'lodash/sortBy'; import sortBy from 'lodash/sortBy';
import { format, formatDuration, intervalToDuration } from 'date-fns';
import { type MarketViewProposalFieldsFragment } from '@vegaprotocol/proposals';
import { ProposalState } from '@vegaprotocol/types';
import { import {
DApp, DApp,
TOKEN_PROPOSAL, TOKEN_PROPOSAL,
TOKEN_PROPOSALS, TOKEN_PROPOSALS,
useLinks, useLinks,
} from '@vegaprotocol/environment'; } from '@vegaprotocol/environment';
import { useDataProvider } from '@vegaprotocol/data-provider'; import { getQuoteName, type Market } from '@vegaprotocol/markets';
import { addDecimalsFormatNumber } from '@vegaprotocol/utils';
import { useT } from '../../lib/use-t'; import { useT } from '../../lib/use-t';
import { ExternalLink } from '@vegaprotocol/ui-toolkit';
const filterProposals = ( export const MarketUpdateStateBanner = ({
data: MarketViewProposalFieldsFragment[] | null, market,
marketId: string, proposals,
now: number }: {
) => market: Market;
sortBy( proposals: MarketViewProposalFieldsFragment[];
(data || []).filter( }) => {
(item) => const t = useT();
item.terms.change.__typename === 'UpdateMarketState' && const governanceLink = useLinks(DApp.Governance);
item.terms.change.market.id === marketId &&
item.terms.change.updateType === const openProposals = sortBy(
Types.MarketUpdateType.MARKET_STATE_UPDATE_TYPE_TERMINATE && proposals.filter((p) => p.state === ProposalState.STATE_OPEN),
item.terms.enactmentDatetime && (p) => p.terms.enactmentDatetime
new Date(item.terms.enactmentDatetime).getTime() > now
),
(item) => item.terms.enactmentDatetime
); );
const passedProposals = sortBy(
proposals.filter((p) => p.state === ProposalState.STATE_PASSED),
(p) => p.terms.enactmentDatetime
);
if (!passedProposals.length && !openProposals.length) {
return null;
}
const name = market.tradableInstrument.instrument.code;
const assetSymbol = getQuoteName(market);
const proposalLink =
!passedProposals.length && openProposals[0]?.id
? governanceLink(TOKEN_PROPOSAL.replace(':id', openProposals[0]?.id))
: undefined;
const proposalsLink =
openProposals.length > 1 ? governanceLink(TOKEN_PROPOSALS) : undefined;
let content: ReactNode;
if (passedProposals.length) {
const { date, duration, price } = getMessageVariables(passedProposals[0]);
content = (
<>
<p className="uppercase mb-1">
{t('Trading on market {{name}} will stop on {{date}}', {
name,
date,
})}
</p>
<p>
{t(
'You will no longer be able to hold a position on this market when it closes in {{duration}}.',
{ duration }
)}{' '}
{price &&
assetSymbol &&
t('The final price will be {{price}} {{assetSymbol}}.', {
price: addDecimalsFormatNumber(price, market.decimalPlaces),
assetSymbol,
})}
</p>
</>
);
} else if (openProposals.length > 1) {
content = (
<>
<p className="uppercase mb-1">
{t(
'Trading on market {{name}} may stop. There are open proposals to close this market',
{ name }
)}
</p>
<p>
<ExternalLink href={proposalsLink}>
{t('View proposals')}
</ExternalLink>
</p>
</>
);
} else {
const { date, price } = getMessageVariables(openProposals[0]);
content = (
<>
<p className="mb-1">
{t(
'Trading on market {{name}} may stop on {{date}}. There is an open proposal to close this market.',
{ name, date }
)}
</p>
<p>
{price &&
assetSymbol &&
t('Proposed final price is {{price}} {{assetSymbol}}.', {
price: addDecimalsFormatNumber(price, market.decimalPlaces),
assetSymbol,
})}{' '}
<ExternalLink href={proposalLink}>{t('View proposal')}</ExternalLink>
</p>
</>
);
}
return <div data-testid={`update-state-banner-${market.id}`}>{content}</div>;
};
const getMessageVariables = (proposal: MarketViewProposalFieldsFragment) => { const getMessageVariables = (proposal: MarketViewProposalFieldsFragment) => {
const enactmentDatetime = new Date(proposal.terms.enactmentDatetime); const enactmentDatetime = new Date(proposal.terms.enactmentDatetime);
@@ -62,126 +138,3 @@ const getMessageVariables = (proposal: MarketViewProposalFieldsFragment) => {
price, price,
}; };
}; };
export const MarketTerminationBanner = ({
market,
}: {
market: Market | null;
}) => {
const t = useT();
const [visible, setVisible] = useState(true);
const skip = !market || !visible;
const { data: passedProposalsData } = useDataProvider({
dataProvider: marketViewProposalsDataProvider,
skip,
variables: {
inState: Types.ProposalState.STATE_PASSED,
proposalType: Types.ProposalType.TYPE_UPDATE_MARKET_STATE,
},
});
const { data: openProposalsData } = useDataProvider({
dataProvider: marketViewProposalsDataProvider,
skip,
variables: {
inState: Types.ProposalState.STATE_OPEN,
proposalType: Types.ProposalType.TYPE_UPDATE_MARKET_STATE,
},
});
const governanceLink = useLinks(DApp.Governance);
if (!market) return null;
const now = Date.now();
const passedProposals = filterProposals(passedProposalsData, market.id, now);
const openProposals = filterProposals(openProposalsData, market.id, now);
const name = market.tradableInstrument.instrument.code;
if (!passedProposals.length && !openProposals.length) {
return null;
}
const assetSymbol = getQuoteName(market);
const proposalLink =
!passedProposals.length && openProposals[0]?.id
? governanceLink(TOKEN_PROPOSAL.replace(':id', openProposals[0]?.id))
: undefined;
const proposalsLink =
openProposals.length > 1 ? governanceLink(TOKEN_PROPOSALS) : undefined;
let content: ReactNode;
if (passedProposals.length) {
const { date, duration, price } = getMessageVariables(passedProposals[0]);
content = (
<>
<div className="uppercase mb-1">
{t('Trading on Market {{name}} will stop on {{date}}', {
name,
date,
})}
</div>
<div>
{t(
'You will no longer be able to hold a position on this market when it closes in {{duration}}.',
{ duration }
)}{' '}
{price &&
assetSymbol &&
t('The final price will be {{price}} {{assetSymbol}}.', {
price: addDecimalsFormatNumber(price, market.decimalPlaces),
assetSymbol,
})}
</div>
</>
);
} else if (openProposals.length > 1) {
content = (
<>
<div className="uppercase mb-1">
{t(
'Trading on Market {{name}} may stop. There are open proposals to close this market',
{ name }
)}
</div>
<div>
<ExternalLink href={proposalsLink}>
{t('View proposals')}
</ExternalLink>
</div>
</>
);
} else {
const { date, price } = getMessageVariables(openProposals[0]);
content = (
<>
<div className="uppercase mb-1">
{t(
'Trading on Market {{name}} may stop on {{date}}. There is open proposal to close this market.',
{ name, date }
)}
</div>
<div>
{price &&
assetSymbol &&
t('Proposed final price is {{price}} {{assetSymbol}}.', {
price: addDecimalsFormatNumber(price, market.decimalPlaces),
assetSymbol,
})}
</div>
<div>
<ExternalLink href={proposalLink}>{t('View proposal')}</ExternalLink>
</div>
</>
);
}
return (
<NotificationBanner
intent={openProposals.length ? Intent.Warning : Intent.Info}
onClose={() => {
setVisible(false);
}}
data-testid={`termination-warning-banner-${market.id}`}
>
{content}
</NotificationBanner>
);
};
@@ -0,0 +1,88 @@
import {
useMarketProposals,
type MarketViewProposalFieldsFragment,
} from '@vegaprotocol/proposals';
import { ProposalState, ProposalType } from '@vegaprotocol/types';
const isPending = (p: MarketViewProposalFieldsFragment) => {
return [ProposalState.STATE_OPEN, ProposalState.STATE_PASSED].includes(
p.state
);
};
export const useUpdateMarketStateProposals = (
marketId: string,
inState?: ProposalState
) => {
const { data, error, loading } = useMarketProposals({
proposalType: ProposalType.TYPE_UPDATE_MARKET_STATE,
inState,
});
const proposals = data
? data.filter(isPending).filter((p) => {
const change = p.terms.change;
if (
change.__typename === 'UpdateMarketState' &&
change.market.id === marketId
) {
return true;
}
return false;
})
: [];
return { data, error, loading, proposals };
};
export const useUpdateMarketProposals = (
marketId: string,
inState?: ProposalState
) => {
const { data, error, loading } = useMarketProposals({
proposalType: ProposalType.TYPE_UPDATE_MARKET,
inState,
});
const proposals = data
? data.filter(isPending).filter((p) => {
const change = p.terms.change;
if (
change.__typename === 'UpdateMarket' &&
change.marketId === marketId
) {
return true;
}
return false;
})
: [];
return { data, error, loading, proposals };
};
export const useSuccessorMarketProposals = (
marketId: string,
inState?: ProposalState
) => {
const { data, loading, error } = useMarketProposals({
proposalType: ProposalType.TYPE_NEW_MARKET,
inState,
});
const proposals = data
? data.filter(isPending).filter((p) => {
const change = p.terms.change;
if (
change.__typename === 'NewMarket' &&
change.successorConfiguration?.parentMarketId === marketId
) {
return true;
}
return false;
})
: [];
return { data, error, loading, proposals };
};
@@ -27,7 +27,7 @@ export const MarketHeader = () => {
title={ title={
<Popover <Popover
open={open} open={open}
onChange={setOpen} onOpenChange={setOpen}
trigger={ trigger={
<HeaderTitle> <HeaderTitle>
<span> <span>
@@ -1,9 +1,5 @@
import { act, renderHook } from '@testing-library/react'; import { act, renderHook } from '@testing-library/react';
import { import { STORAGE_KEY, useOrderListGridState } from './orders-container';
FilterStatusValue,
STORAGE_KEY,
useOrderListGridState,
} from './orders-container';
import { Filter } from '@vegaprotocol/orders'; import { Filter } from '@vegaprotocol/orders';
import { OrderType } from '@vegaprotocol/types'; import { OrderType } from '@vegaprotocol/types';
@@ -16,31 +12,6 @@ describe('useOrderListGridState', () => {
return renderHook(() => useOrderListGridState(filter)); return renderHook(() => useOrderListGridState(filter));
}; };
it.each(Object.values(Filter))(
'providers correct AgGrid filter for %s',
(filter) => {
const { result } = setup(filter);
expect(typeof result.current.updateGridState).toBe('function');
expect(result.current.gridState).toEqual({
columnState: undefined,
filterModel: {
status: {
value: FilterStatusValue[filter],
},
},
});
}
);
it('provides correct AgGrid filter for all', () => {
const { result } = setup(undefined);
expect(typeof result.current.updateGridState).toBe('function');
expect(result.current.gridState).toEqual({
columnState: undefined,
filterModel: undefined,
});
});
it.each(Object.values(Filter))( it.each(Object.values(Filter))(
'sets and stores column state and filters for %s', 'sets and stores column state and filters for %s',
(filter) => { (filter) => {
@@ -59,12 +30,7 @@ describe('useOrderListGridState', () => {
expect(result.current.gridState).toEqual({ expect(result.current.gridState).toEqual({
columnState: undefined, columnState: undefined,
filterModel: { filterModel,
...filterModel,
status: {
value: FilterStatusValue[filter],
},
},
}); });
const columnState = [{ colId: 'status', width: 200 }]; const columnState = [{ colId: 'status', width: 200 }];
@@ -77,12 +43,7 @@ describe('useOrderListGridState', () => {
expect(result.current.gridState).toEqual({ expect(result.current.gridState).toEqual({
columnState, columnState,
filterModel: { filterModel,
...filterModel,
status: {
value: FilterStatusValue[filter],
},
},
}); });
const storeKeyMap = { const storeKeyMap = {
@@ -9,6 +9,7 @@ import type { DataGridStore } from '../../stores/datagrid-store-slice';
import { OrderStatus } from '@vegaprotocol/types'; import { OrderStatus } from '@vegaprotocol/types';
import { Links } from '../../lib/links'; import { Links } from '../../lib/links';
import { useT } from '../../lib/use-t'; import { useT } from '../../lib/use-t';
import { GridSettings } from '../grid-settings/grid-settings';
const resolveNoRowsMessage = ( const resolveNoRowsMessage = (
filter: Filter | undefined, filter: Filter | undefined,
@@ -38,6 +39,24 @@ export const FilterStatusValue = {
[Filter.Rejected]: [OrderStatus.STATUS_REJECTED], [Filter.Rejected]: [OrderStatus.STATUS_REJECTED],
}; };
export const DefaultFilterModel = {
[Filter.Open]: {
status: {
value: FilterStatusValue[Filter.Open],
},
},
[Filter.Closed]: {
status: {
value: FilterStatusValue[Filter.Closed],
},
},
[Filter.Rejected]: {
status: {
value: FilterStatusValue[Filter.Rejected],
},
},
};
export interface OrderContainerProps { export interface OrderContainerProps {
filter?: Filter; filter?: Filter;
} }
@@ -54,7 +73,8 @@ export const OrdersContainer = ({ filter }: OrderContainerProps) => {
(newState) => { (newState) => {
updateGridState(filter, newState); updateGridState(filter, newState);
}, },
AUTO_SIZE_COLUMNS AUTO_SIZE_COLUMNS,
filter && DefaultFilterModel[filter]
); );
if (!pubKey) { if (!pubKey) {
@@ -80,7 +100,7 @@ export const OrdersContainer = ({ filter }: OrderContainerProps) => {
}; };
export const STORAGE_KEY = 'vega_order_list_store'; export const STORAGE_KEY = 'vega_order_list_store';
const useOrderListStore = create<{ export const useOrderListStore = create<{
open: DataGridStore; open: DataGridStore;
closed: DataGridStore; closed: DataGridStore;
rejected: DataGridStore; rejected: DataGridStore;
@@ -149,34 +169,19 @@ export const useOrderListGridState = (filter: Filter | undefined) => {
case Filter.Open: { case Filter.Open: {
return { return {
columnState: store.open.columnState, columnState: store.open.columnState,
filterModel: { filterModel: store.open.filterModel,
...store.open.filterModel,
status: {
value: FilterStatusValue[Filter.Open],
},
},
}; };
} }
case Filter.Closed: { case Filter.Closed: {
return { return {
columnState: store.closed.columnState, columnState: store.closed.columnState,
filterModel: { filterModel: store.closed.filterModel,
...store.closed.filterModel,
status: {
value: FilterStatusValue[Filter.Closed],
},
},
}; };
} }
case Filter.Rejected: { case Filter.Rejected: {
return { return {
columnState: store.rejected.columnState, columnState: store.rejected.columnState,
filterModel: { filterModel: store.rejected.filterModel,
...store.rejected.filterModel,
status: {
value: FilterStatusValue[Filter.Rejected],
},
},
}; };
} }
default: { default: {
@@ -187,3 +192,14 @@ export const useOrderListGridState = (filter: Filter | undefined) => {
return { gridState, updateGridState }; return { gridState, updateGridState };
}; };
export const OrdersSettings = ({ filter }: { filter?: Filter }) => {
const updateGridState = useOrderListStore((state) => state.update);
return (
<GridSettings
updateGridStore={(gridStore: DataGridStore) =>
updateGridState(filter, gridStore)
}
/>
);
};
@@ -1 +1,2 @@
export * from './positions-container'; export * from './positions-container';
export * from './positions-settings';
@@ -0,0 +1,8 @@
import { GridSettings } from '../grid-settings/grid-settings';
import { usePositionsStore } from './positions-container';
export const PositionsSettings = () => (
<GridSettings
updateGridStore={usePositionsStore((store) => store.updateGridStore)}
/>
);
@@ -231,6 +231,7 @@ export const RewardsContainer = () => {
title={t('Rewards history')} title={t('Rewards history')}
className="lg:col-span-full" className="lg:col-span-full"
loading={rewardsLoading} loading={rewardsLoading}
noBackgroundOnMobile={true}
> >
<RewardsHistoryContainer <RewardsHistoryContainer
epoch={Number(epochData?.epoch.id)} epoch={Number(epochData?.epoch.id)}
@@ -99,16 +99,25 @@ describe('RewardsHistoryTable', () => {
it('renders table with accounts summed up by asset', () => { it('renders table with accounts summed up by asset', () => {
render(<RewardHistoryTable {...props} />); render(<RewardHistoryTable {...props} />);
const container = within( const containerLeft = within(
document.querySelector('.ag-pinned-left-cols-container') as HTMLElement
);
const pinnedRows = containerLeft.getAllByRole('row');
const containerCenter = within(
document.querySelector('.ag-center-cols-container') as HTMLElement document.querySelector('.ag-center-cols-container') as HTMLElement
); );
const rows = container.getAllByRole('row'); const rows = containerCenter.getAllByRole('row');
expect(rows).toHaveLength( expect(rows).toHaveLength(
Object.keys(groupBy(rewardSummaries, 'node.assetId')).length Object.keys(groupBy(rewardSummaries, 'node.assetId')).length
); );
let row = within(rows[0]); let row = within(rows[0]);
let cells = row.getAllByRole('gridcell'); let pinnedRow = within(pinnedRows[0]);
let cells = [
...pinnedRow.getAllByRole('gridcell'),
...row.getAllByRole('gridcell'),
];
let assetCell = getCell(cells, 'asset.symbol'); let assetCell = getCell(cells, 'asset.symbol');
expect(assetCell.getByTestId('stack-cell-primary')).toHaveTextContent( expect(assetCell.getByTestId('stack-cell-primary')).toHaveTextContent(
@@ -140,7 +149,11 @@ describe('RewardsHistoryTable', () => {
// Second row // Second row
row = within(rows[1]); row = within(rows[1]);
cells = row.getAllByRole('gridcell'); pinnedRow = within(pinnedRows[1]);
cells = [
...pinnedRow.getAllByRole('gridcell'),
...row.getAllByRole('gridcell'),
];
assetCell = getCell(cells, 'asset.symbol'); assetCell = getCell(cells, 'asset.symbol');
expect(assetCell.getByTestId('stack-cell-primary')).toHaveTextContent( expect(assetCell.getByTestId('stack-cell-primary')).toHaveTextContent(
@@ -93,6 +93,7 @@ export const RewardsHistoryContainer = ({
const defaultColDef = { const defaultColDef = {
flex: 1, flex: 1,
minWidth: 62,
resizable: true, resizable: true,
sortable: true, sortable: true,
}; };
@@ -195,6 +196,8 @@ export const RewardHistoryTable = ({
return <StackedCell primary={value} secondary={data.asset.name} />; return <StackedCell primary={value} secondary={data.asset.name} />;
}, },
sort: 'desc', sort: 'desc',
pinned: 'left',
width: 150,
}, },
{ {
field: 'infrastructureFees', field: 'infrastructureFees',
@@ -257,7 +260,7 @@ export const RewardHistoryTable = ({
return ( return (
<div> <div>
<div className="mb-2 flex items-center justify-between gap-2"> <div className="mb-2 flex flex-wrap items-center justify-between gap-2">
<h4 className="text-muted flex items-center gap-2 text-sm"> <h4 className="text-muted flex items-center gap-2 text-sm">
<label htmlFor="fromEpoch">{t('From epoch')}</label> <label htmlFor="fromEpoch">{t('From epoch')}</label>
<EpochInput <EpochInput
@@ -329,15 +332,17 @@ export const RewardHistoryTable = ({
</TradingButton> </TradingButton>
</div> </div>
</div> </div>
<AgGrid <div className="border rounded-lg md:rounded-sm overflow-hidden border-default">
columnDefs={columnDefs} <AgGrid
defaultColDef={defaultColDef} columnDefs={columnDefs}
rowData={rowData} defaultColDef={defaultColDef}
rowHeight={45} rowData={rowData}
domLayout="autoHeight" rowHeight={45}
// Show loading message without wiping out the current rows domLayout="autoHeight"
overlayNoRowsTemplate={loading ? t('Loading...') : t('No rows')} // Show loading message without wiping out the current rows
/> overlayNoRowsTemplate={loading ? t('Loading...') : t('No rows')}
/>
</div>
</div> </div>
); );
}; };
@@ -1 +1,3 @@
export * from './stop-orders-container'; export * from './stop-orders-container';
export * from './stop-orders-settings';
@@ -35,7 +35,7 @@ export const StopOrdersContainer = () => {
); );
}; };
const useStopOrdersStore = create<DataGridSlice>()( export const useStopOrdersStore = create<DataGridSlice>()(
persist(createDataGridSlice, { persist(createDataGridSlice, {
name: 'vega_stop_orders_store', name: 'vega_stop_orders_store',
}) })
@@ -0,0 +1,8 @@
import { GridSettings } from '../grid-settings/grid-settings';
import { useStopOrdersStore } from './stop-orders-container';
export const StopOrdersSettings = () => (
<GridSettings
updateGridStore={useStopOrdersStore((store) => store.updateGridStore)}
/>
);
+1
View File
@@ -0,0 +1 @@
export * from './table';
@@ -1,13 +1,17 @@
import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit'; import { Tooltip, VegaIcon, VegaIconNames } from '@vegaprotocol/ui-toolkit';
import classNames from 'classnames'; import classNames from 'classnames';
import { forwardRef, type ReactNode, type HTMLAttributes } from 'react'; import { forwardRef, type ReactNode, type HTMLAttributes } from 'react';
import { BORDER_COLOR, GRADIENT } from './constants';
export const BORDER_COLOR = 'border-vega-clight-500 dark:border-vega-cdark-500';
export const GRADIENT =
'bg-gradient-to-b from-vega-clight-800 dark:from-vega-cdark-800 to-transparent';
type TableColumnDefinition = { type TableColumnDefinition = {
displayName?: ReactNode; displayName?: ReactNode;
name: string; name: string;
tooltip?: string; tooltip?: string;
className?: string; className?: string;
testId?: string;
}; };
type TableProps = { type TableProps = {
@@ -42,7 +46,7 @@ export const Table = forwardRef<
key={name} key={name}
col-id={name} col-id={name}
className={classNames( className={classNames(
'px-5 py-3 text-sm text-vega-clight-100 dark:text-vega-cdark-100 font-normal', 'px-5 py-3 text-xs text-vega-clight-100 dark:text-vega-cdark-100 font-normal',
INNER_BORDER_STYLE INNER_BORDER_STYLE
)} )}
> >
@@ -66,7 +70,7 @@ export const Table = forwardRef<
ref={ref} ref={ref}
className={classNames( className={classNames(
'w-full', 'w-full',
'border-separate border rounded-md border-spacing-0', 'border-separate border rounded-md border-spacing-0 overflow-hidden',
BORDER_COLOR, BORDER_COLOR,
GRADIENT, GRADIENT,
className className
@@ -82,10 +86,10 @@ export const Table = forwardRef<
'max-md:flex flex-col w-full': !noCollapse, 'max-md:flex flex-col w-full': !noCollapse,
})} })}
> >
{columns.map(({ name, displayName, className }, j) => ( {columns.map(({ name, displayName, className, testId }, j) => (
<td <td
className={classNames( className={classNames(
'px-5 py-3 text-base', 'px-5 py-3',
{ {
'max-md:flex max-md:flex-col max-md:justify-between': 'max-md:flex max-md:flex-col max-md:justify-between':
!noCollapse, !noCollapse,
@@ -110,7 +114,7 @@ export const Table = forwardRef<
{displayName} {displayName}
</span> </span>
)} )}
<span>{d[name]}</span> <span data-testid={`${testId || name}-${i}`}>{d[name]}</span>
</td> </td>
))} ))}
</tr> </tr>
@@ -1 +1,2 @@
export * from './trades-container'; export * from './trades-container';
export * from './trades-settings';
@@ -17,7 +17,7 @@ export const TradesContainer = ({ marketId }: TradesContainerProps) => {
return <TradesManager marketId={marketId} gridProps={gridStoreCallbacks} />; return <TradesManager marketId={marketId} gridProps={gridStoreCallbacks} />;
}; };
const useTradesStore = create<DataGridSlice>()( export const useTradesStore = create<DataGridSlice>()(
persist(createDataGridSlice, { persist(createDataGridSlice, {
name: 'vega_trades_store', name: 'vega_trades_store',
}) })
@@ -0,0 +1,8 @@
import { GridSettings } from '../grid-settings/grid-settings';
import { useTradesStore } from './trades-container';
export const TradesSettings = () => (
<GridSettings
updateGridStore={useTradesStore((store) => store.updateGridStore)}
/>
);
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest CONSOLE_IMAGE_NAME=vegaprotocol/trading:latest
VEGA_VERSION=v0.73.9 VEGA_VERSION=v0.73.10
LOCAL_SERVER=false LOCAL_SERVER=false
+2 -1
View File
@@ -1,2 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop CONSOLE_IMAGE_NAME=vegaprotocol/trading:develop
VEGA_VERSION=v0.73.9 VEGA_VERSION=v0.73.10
LOCAL_SERVER=false
+1 -1
View File
@@ -1,3 +1,3 @@
CONSOLE_IMAGE_NAME=vegaprotocol/trading:main CONSOLE_IMAGE_NAME=vegaprotocol/trading:main
VEGA_VERSION=v0.73.8 VEGA_VERSION=v0.73.10
LOCAL_SERVER=false LOCAL_SERVER=false
+8 -1
View File
@@ -8,6 +8,9 @@ import docker
import http.server import http.server
import sys import sys
from dotenv import load_dotenv from dotenv import load_dotenv
from docker.models.containers import Container
from docker.errors import APIError
from contextlib import contextmanager from contextlib import contextmanager
from vega_sim.null_service import VegaServiceNull, Ports from vega_sim.null_service import VegaServiceNull, Ports
from playwright.sync_api import Browser, Page from playwright.sync_api import Browser, Page
@@ -100,12 +103,16 @@ def init_vega(request=None):
container = docker_client.containers.run( container = docker_client.containers.run(
console_image_name, detach=True, ports={"80/tcp": vega.console_port} console_image_name, detach=True, ports={"80/tcp": vega.console_port}
) )
if not isinstance(container, Container):
raise Exception("container instance invalid")
logger.info( logger.info(
f"Container {container.id} started", f"Container {container.id} started",
extra={"worker_id": os.environ.get("PYTEST_XDIST_WORKER")}, extra={"worker_id": os.environ.get("PYTEST_XDIST_WORKER")},
) )
yield vega yield vega
except docker.errors.APIError as e: except APIError as e:
logger.info(f"Container creation failed.") logger.info(f"Container creation failed.")
logger.info(e) logger.info(e)
raise e raise e
@@ -1,6 +1,6 @@
import pytest import pytest
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from datetime import datetime, timedelta from datetime import datetime, timedelta
from conftest import init_vega from conftest import init_vega
from fixtures.market import setup_continuous_market from fixtures.market import setup_continuous_market
@@ -24,7 +24,7 @@ def continuous_market(vega):
return setup_continuous_market(vega) return setup_continuous_market(vega)
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_limit_buy_order_GTT(continuous_market, vega: VegaService, page: Page): def test_limit_buy_order_GTT(continuous_market, vega: VegaServiceNull, page: Page):
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(tif).select_option("Good 'til Time (GTT)") page.get_by_test_id(tif).select_option("Good 'til Time (GTT)")
page.get_by_test_id(order_size).fill("10") page.get_by_test_id(order_size).fill("10")
@@ -52,7 +52,7 @@ def test_limit_buy_order_GTT(continuous_market, vega: VegaService, page: Page):
) )
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_limit_buy_order(continuous_market, vega: VegaService, page: Page): def test_limit_buy_order(continuous_market, vega: VegaServiceNull, page: Page):
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(order_size).fill("10") page.get_by_test_id(order_size).fill("10")
@@ -69,7 +69,7 @@ def test_limit_buy_order(continuous_market, vega: VegaService, page: Page):
) )
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_limit_sell_order(continuous_market, vega: VegaService, page: Page): def test_limit_sell_order(continuous_market, vega: VegaServiceNull, page: Page):
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(order_size).fill("10") page.get_by_test_id(order_size).fill("10")
page.get_by_test_id(order_price).fill("100") page.get_by_test_id(order_price).fill("100")
@@ -93,7 +93,7 @@ def test_limit_sell_order(continuous_market, vega: VegaService, page: Page):
) )
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_market_sell_order(continuous_market, vega: VegaService, page: Page): def test_market_sell_order(continuous_market, vega: VegaServiceNull, page: Page):
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(market_order).click() page.get_by_test_id(market_order).click()
page.get_by_test_id(order_size).fill("10") page.get_by_test_id(order_size).fill("10")
@@ -117,7 +117,7 @@ def test_market_sell_order(continuous_market, vega: VegaService, page: Page):
) )
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_market_buy_order(continuous_market, vega: VegaService, page: Page): def test_market_buy_order(continuous_market, vega: VegaServiceNull, page: Page):
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(market_order).click() page.get_by_test_id(market_order).click()
page.get_by_test_id(order_size).fill("10") page.get_by_test_id(order_size).fill("10")
@@ -1,6 +1,6 @@
import pytest import pytest
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from actions.vega import submit_order from actions.vega import submit_order
from actions.utils import wait_for_toast_confirmation from actions.utils import wait_for_toast_confirmation
@@ -13,7 +13,7 @@ market_trading_mode = "market-trading-mode"
@pytest.mark.skip("tbd") @pytest.mark.skip("tbd")
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_margin_and_fees_estimations(continuous_market, vega: VegaService, page: Page): def test_margin_and_fees_estimations(continuous_market, vega: VegaServiceNull, page: Page):
# setup continuous trading market with one user buy trade # setup continuous trading market with one user buy trade
market_id = continuous_market market_id = continuous_market
page.goto(f"/#/markets/{market_id}") page.goto(f"/#/markets/{market_id}")
@@ -1,6 +1,6 @@
import pytest import pytest
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from actions.vega import submit_order from actions.vega import submit_order
from datetime import datetime, timedelta from datetime import datetime, timedelta
from conftest import init_vega from conftest import init_vega
@@ -38,7 +38,7 @@ timeInForce_col = '[col-id="submission.timeInForce"]'
updatedAt_col = '[col-id="updatedAt"]' updatedAt_col = '[col-id="updatedAt"]'
close_toast = "toast-close" close_toast = "toast-close"
def create_position(vega: VegaService, market_id): def create_position(vega: VegaServiceNull, market_id):
submit_order(vega, "Key 1", market_id, "SIDE_SELL", 100, 110) submit_order(vega, "Key 1", market_id, "SIDE_SELL", 100, 110)
submit_order(vega, "Key 1", market_id, "SIDE_BUY", 100, 110) submit_order(vega, "Key 1", market_id, "SIDE_BUY", 100, 110)
vega.forward("10s") vega.forward("10s")
@@ -69,7 +69,7 @@ def test_stop_order_form_error_validation(continuous_market, page: Page):
@pytest.mark.skip("core issue") @pytest.mark.skip("core issue")
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_submit_stop_order_rejected(continuous_market, vega: VegaService, page: Page): def test_submit_stop_order_rejected(continuous_market, vega: VegaServiceNull, page: Page):
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(stop_orders_tab).click() page.get_by_test_id(stop_orders_tab).click()
page.get_by_test_id(stop_order_btn).click() page.get_by_test_id(stop_order_btn).click()
@@ -108,7 +108,7 @@ def test_submit_stop_order_rejected(continuous_market, vega: VegaService, page:
@pytest.mark.skip("core issue") @pytest.mark.skip("core issue")
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_submit_stop_market_order_triggered( def test_submit_stop_market_order_triggered(
continuous_market, vega: VegaService, page: Page continuous_market, vega: VegaServiceNull, page: Page
): ):
# 7002-SORD-071 # 7002-SORD-071
# 7002-SORD-074 # 7002-SORD-074
@@ -166,7 +166,7 @@ def test_submit_stop_market_order_triggered(
@pytest.mark.skip("core issue") @pytest.mark.skip("core issue")
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_submit_stop_limit_order_pending( def test_submit_stop_limit_order_pending(
continuous_market, vega: VegaService, page: Page continuous_market, vega: VegaServiceNull, page: Page
): ):
# 7002-SORD-071 # 7002-SORD-071
# 7002-SORD-074 # 7002-SORD-074
@@ -227,7 +227,7 @@ def test_submit_stop_limit_order_pending(
@pytest.mark.skip("core issue") @pytest.mark.skip("core issue")
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_submit_stop_limit_order_cancel( def test_submit_stop_limit_order_cancel(
continuous_market, vega: VegaService, page: Page continuous_market, vega: VegaServiceNull, page: Page
): ):
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(stop_orders_tab).click() page.get_by_test_id(stop_orders_tab).click()
@@ -348,7 +348,7 @@ class TestStopOcoValidation:
@pytest.mark.skip("core issue") @pytest.mark.skip("core issue")
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_maximum_number_of_active_stop_orders( def test_maximum_number_of_active_stop_orders(
self, continuous_market, vega: VegaService, page: Page self, continuous_market, vega: VegaServiceNull, page: Page
): ):
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(stop_orders_tab).click() page.get_by_test_id(stop_orders_tab).click()
@@ -1,6 +1,6 @@
import pytest import pytest
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from actions.vega import submit_order from actions.vega import submit_order
from actions.utils import wait_for_toast_confirmation from actions.utils import wait_for_toast_confirmation
@@ -42,7 +42,7 @@ trigger_price_oco = "triggerPrice-oco"
order_size_oco = "order-size-oco" order_size_oco = "order-size-oco"
order_limit_price_oco = "order-price-oco" order_limit_price_oco = "order-price-oco"
def create_position(vega: VegaService, market_id): def create_position(vega: VegaServiceNull, market_id):
submit_order(vega, "Key 1", market_id, "SIDE_SELL", 100, 110) submit_order(vega, "Key 1", market_id, "SIDE_SELL", 100, 110)
submit_order(vega, "Key 1", market_id, "SIDE_BUY", 100, 110) submit_order(vega, "Key 1", market_id, "SIDE_BUY", 100, 110)
vega.wait_fn(1) vega.wait_fn(1)
@@ -51,7 +51,7 @@ def create_position(vega: VegaService, market_id):
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_submit_stop_order_market_oco_rejected( def test_submit_stop_order_market_oco_rejected(
continuous_market, vega: VegaService, page: Page continuous_market, vega: VegaServiceNull, page: Page
): ):
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id(stop_orders_tab).click() page.get_by_test_id(stop_orders_tab).click()
@@ -128,7 +128,7 @@ def test_submit_stop_order_market_oco_rejected(
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_submit_stop_oco_market_order_triggered( def test_submit_stop_oco_market_order_triggered(
continuous_market, vega: VegaService, page: Page continuous_market, vega: VegaServiceNull, page: Page
): ):
create_position(vega, continuous_market) create_position(vega, continuous_market)
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
@@ -205,7 +205,7 @@ def test_submit_stop_oco_market_order_triggered(
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_submit_stop_oco_market_order_pending( def test_submit_stop_oco_market_order_pending(
continuous_market, vega: VegaService, page: Page continuous_market, vega: VegaServiceNull, page: Page
): ):
create_position(vega, continuous_market) create_position(vega, continuous_market)
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
@@ -237,7 +237,7 @@ def test_submit_stop_oco_market_order_pending(
@pytest.mark.usefixtures("page", "continuous_market", "auth", "risk_accepted") @pytest.mark.usefixtures("page", "continuous_market", "auth", "risk_accepted")
def test_submit_stop_oco_limit_order_pending( def test_submit_stop_oco_limit_order_pending(
continuous_market, vega: VegaService, page: Page continuous_market, vega: VegaServiceNull, page: Page
): ):
create_position(vega, continuous_market) create_position(vega, continuous_market)
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
@@ -288,7 +288,7 @@ def test_submit_stop_oco_limit_order_pending(
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_submit_stop_oco_limit_order_cancel( def test_submit_stop_oco_limit_order_cancel(
continuous_market, vega: VegaService, page: Page continuous_market, vega: VegaServiceNull, page: Page
): ):
create_position(vega, continuous_market) create_position(vega, continuous_market)
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
@@ -1,6 +1,6 @@
import pytest import pytest
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from actions.utils import change_keys from actions.utils import change_keys
from conftest import init_vega from conftest import init_vega
from fixtures.market import setup_continuous_market from fixtures.market import setup_continuous_market
@@ -33,7 +33,7 @@ def test_should_display_info_and_button_for_deposit(continuous_market, page: Pag
expect(page.get_by_test_id("sidebar-content")).to_contain_text("DepositFrom") expect(page.get_by_test_id("sidebar-content")).to_contain_text("DepositFrom")
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_should_show_an_error_if_your_balance_is_zero(continuous_market, vega: VegaService, page: Page): def test_should_show_an_error_if_your_balance_is_zero(continuous_market, vega: VegaServiceNull, page: Page):
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
vega.create_key("key_empty") vega.create_key("key_empty")
change_keys(page, vega, "key_empty") change_keys(page, vega, "key_empty")
+17 -13
View File
@@ -1,7 +1,7 @@
import pytest import pytest
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from actions.vega import submit_order from actions.vega import submit_order
from wallet_config import MM_WALLET from wallet_config import MM_WALLET
from conftest import init_vega, init_page, auth_setup from conftest import init_vega, init_page, auth_setup
@@ -27,8 +27,6 @@ MIN_VOLUME_VALUE_0 = "min-volume-value-0"
MIN_VOLUME_VALUE_1 = "min-volume-value-1" MIN_VOLUME_VALUE_1 = "min-volume-value-1"
MY_VOLUME_VALUE_0 = "my-volume-value-0" MY_VOLUME_VALUE_0 = "my-volume-value-0"
MY_VOLUME_VALUE_1 = "my-volume-value-1" MY_VOLUME_VALUE_1 = "my-volume-value-1"
YOUR_TIER_0 = "your-tier-0"
YOUR_TIER_1 = "your-tier-1"
ORDER_SIZE = "order-size" ORDER_SIZE = "order-size"
ORDER_PRICE = "order-price" ORDER_PRICE = "order-price"
DISCOUNT_PILL = "discount-pill" DISCOUNT_PILL = "discount-pill"
@@ -52,6 +50,7 @@ REQUIRED_EPOCHS_VALUE_1 = "required-epochs-value-1"
FILLS = "Fills" FILLS = "Fills"
TAB_FILLS = "tab-fills" TAB_FILLS = "tab-fills"
FEE_BREAKDOWN_TOOLTIP = "fee-breakdown-tooltip" FEE_BREAKDOWN_TOOLTIP = "fee-breakdown-tooltip"
PINNED_ROW_LOCATOR = ".ag-pinned-left-cols-container .ag-row"
ROW_LOCATOR = ".ag-center-cols-container .ag-row" ROW_LOCATOR = ".ag-center-cols-container .ag-row"
# Col-Ids: # Col-Ids:
COL_INSTRUMENT_CODE = '[col-id="market.tradableInstrument.instrument.code"]' COL_INSTRUMENT_CODE = '[col-id="market.tradableInstrument.instrument.code"]'
@@ -143,7 +142,7 @@ def auth(vega_instance, page):
return auth_setup(vega_instance, page) return auth_setup(vega_instance, page)
def setup_market_with_volume_discount_program(vega: VegaService, tier: int): def setup_market_with_volume_discount_program(vega: VegaServiceNull, tier: int):
market = setup_continuous_market(vega, custom_quantum=100000) market = setup_continuous_market(vega, custom_quantum=100000)
vega.update_volume_discount_program( vega.update_volume_discount_program(
proposal_key=MM_WALLET.name, proposal_key=MM_WALLET.name,
@@ -169,7 +168,7 @@ def setup_market_with_volume_discount_program(vega: VegaService, tier: int):
return market return market
def setup_market_with_referral_discount_program(vega: VegaService, tier: int): def setup_market_with_referral_discount_program(vega: VegaServiceNull, tier: int):
market = setup_continuous_market(vega, custom_quantum=100000) market = setup_continuous_market(vega, custom_quantum=100000)
vega.update_referral_program( vega.update_referral_program(
proposal_key=MM_WALLET.name, proposal_key=MM_WALLET.name,
@@ -208,7 +207,7 @@ def setup_market_with_referral_discount_program(vega: VegaService, tier: int):
return market return market
def setup_combined_market(vega: VegaService): def setup_combined_market(vega: VegaServiceNull):
market = setup_continuous_market(vega, custom_quantum=100000) market = setup_continuous_market(vega, custom_quantum=100000)
vega.update_volume_discount_program( vega.update_volume_discount_program(
proposal_key=MM_WALLET.name, proposal_key=MM_WALLET.name,
@@ -405,10 +404,10 @@ def test_fees_page_referral_discount_program_referral_benefits(
@pytest.mark.parametrize( @pytest.mark.parametrize(
"tier, discount_program, my_volume_test_id, my_volume_value, your_tier", "tier, discount_program, my_volume_test_id, my_volume_value, your_tier",
[ [
(1, "volume", "my-volume-value-0", "103", "your-tier-0"), (1, "volume", "my-volume-value-0", "103", "your-volume-tier-0"),
(2, "volume", "my-volume-value-1", "206", "your-tier-1"), (2, "volume", "my-volume-value-1", "206", "your-volume-tier-1"),
(1, "referral", "my-volume-value-0", "103", "your-tier-0"), (1, "referral", "my-volume-value-0", "103", "your-referral-tier-0"),
(2, "referral", "my-volume-value-1", "206", "your-tier-1"), (2, "referral", "my-volume-value-1", "206", "your-referral-tier-1"),
], ],
) )
@pytest.mark.usefixtures("risk_accepted", "auth", "market_ids") @pytest.mark.usefixtures("risk_accepted", "auth", "market_ids")
@@ -439,8 +438,8 @@ def test_fees_page_discount_program_discount(
expect(page.get_by_test_id(REQUIRED_EPOCHS_VALUE_0)).to_have_text("1") expect(page.get_by_test_id(REQUIRED_EPOCHS_VALUE_0)).to_have_text("1")
expect(page.get_by_test_id(REQUIRED_EPOCHS_VALUE_1)).to_have_text("2") expect(page.get_by_test_id(REQUIRED_EPOCHS_VALUE_1)).to_have_text("2")
expect(page.get_by_test_id(your_tier)).to_be_visible() expect(page.get_by_test_id(your_tier).nth(1)).to_be_visible()
expect(page.get_by_test_id(your_tier)).to_have_text("Your tier") expect(page.get_by_test_id(your_tier).nth(1)).to_have_text("Your tier")
@pytest.mark.parametrize( @pytest.mark.parametrize(
@@ -461,8 +460,9 @@ def test_fees_page_discount_program_fees_by_market(
vega_instance, tier, discount_program, market_ids vega_instance, tier, discount_program, market_ids
) )
page.goto("/#/fees") page.goto("/#/fees")
pinned = page.locator(PINNED_ROW_LOCATOR)
row = page.locator(ROW_LOCATOR) row = page.locator(ROW_LOCATOR)
expect(row.locator(COL_CODE)).to_have_text("BTC:DAI_2023Futr") expect(pinned.locator(COL_CODE)).to_have_text("BTC:DAI_2023Futr")
expect(row.locator(COL_FEE_AFTER_DISCOUNT)).to_have_text(fees_after_discount) expect(row.locator(COL_FEE_AFTER_DISCOUNT)).to_have_text(fees_after_discount)
expect(row.locator(COL_INFRA_FEE)).to_have_text("0.05%") expect(row.locator(COL_INFRA_FEE)).to_have_text("0.05%")
expect(row.locator(COL_MAKER_FEE)).to_have_text("10%") expect(row.locator(COL_MAKER_FEE)).to_have_text("10%")
@@ -639,6 +639,8 @@ def test_fills_maker_fee_tooltip_discount_program(
change_keys(page, vega_instance, MM_WALLET.name) change_keys(page, vega_instance, MM_WALLET.name)
page.get_by_test_id(FILLS).click() page.get_by_test_id(FILLS).click()
row = page.get_by_test_id(TAB_FILLS).locator(ROW_LOCATOR).first row = page.get_by_test_id(TAB_FILLS).locator(ROW_LOCATOR).first
# tbd - tooltip is not visible without this wait
page.wait_for_timeout(1000)
row.locator(COL_FEE).hover() row.locator(COL_FEE).hover()
expect(page.get_by_test_id(FEE_BREAKDOWN_TOOLTIP)).to_have_text( expect(page.get_by_test_id(FEE_BREAKDOWN_TOOLTIP)).to_have_text(
f"If the market was activeFee revenue to be received by the maker, takers' fee discounts already applied.During continuous trading the maker pays no infrastructure and liquidity fees.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee-{fee} tDAITotal fees-{fee} tDAI" f"If the market was activeFee revenue to be received by the maker, takers' fee discounts already applied.During continuous trading the maker pays no infrastructure and liquidity fees.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee-{fee} tDAITotal fees-{fee} tDAI"
@@ -676,6 +678,8 @@ def test_fills_taker_fee_tooltip_discount_program(
page.goto(f"/#/markets/{market_id}") page.goto(f"/#/markets/{market_id}")
page.get_by_test_id(FILLS).click() page.get_by_test_id(FILLS).click()
row = page.get_by_test_id(TAB_FILLS).locator(ROW_LOCATOR).first row = page.get_by_test_id(TAB_FILLS).locator(ROW_LOCATOR).first
# tbd - tooltip is not visible without this wait
page.wait_for_timeout(1000)
row.locator(COL_FEE).hover() row.locator(COL_FEE).hover()
expect(page.get_by_test_id(FEE_BREAKDOWN_TOOLTIP)).to_have_text( expect(page.get_by_test_id(FEE_BREAKDOWN_TOOLTIP)).to_have_text(
f"If the market was activeFees to be paid by the taker; discounts are already applied.Infrastructure fee{infra_fee} tDAILiquidity fee0.00 tDAIMaker fee{maker_fee} tDAITotal fees{total_fee} tDAI" f"If the market was activeFees to be paid by the taker; discounts are already applied.Infrastructure fee{infra_fee} tDAILiquidity fee0.00 tDAIMaker fee{maker_fee} tDAITotal fees{total_fee} tDAI"
@@ -1,7 +1,7 @@
import pytest import pytest
from playwright.sync_api import expect, Page from playwright.sync_api import expect, Page
import json import json
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from fixtures.market import setup_simple_market from fixtures.market import setup_simple_market
from conftest import init_vega from conftest import init_vega
from actions.vega import submit_order from actions.vega import submit_order
@@ -16,11 +16,11 @@ def vega():
yield vega yield vega
@pytest.fixture(scope="class") @pytest.fixture(scope="class")
def simple_market(vega: VegaService): def simple_market(vega: VegaServiceNull):
return setup_simple_market(vega) return setup_simple_market(vega)
class TestGetStarted: class TestGetStarted:
def test_get_started_interactive(self, vega: VegaService, page: Page): def test_get_started_interactive(self, vega: VegaServiceNull, page: Page):
page.goto("/") page.goto("/")
# 0007-FUGS-001 # 0007-FUGS-001
expect(page.get_by_test_id("order-connect-wallet")).to_be_visible expect(page.get_by_test_id("order-connect-wallet")).to_be_visible
@@ -130,6 +130,7 @@ class TestGetStarted:
# Assert dialog isn't visible # Assert dialog isn't visible
expect(page.get_by_test_id("welcome-dialog")).not_to_be_visible() expect(page.get_by_test_id("welcome-dialog")).not_to_be_visible()
@pytest.mark.skip("TODO: this test is flakey, needs fixing")
@pytest.mark.usefixtures("risk_accepted") @pytest.mark.usefixtures("risk_accepted")
def test_get_started_seen_already(self, simple_market, page: Page): def test_get_started_seen_already(self, simple_market, page: Page):
page.goto(f"/#/markets/{simple_market}") page.goto(f"/#/markets/{simple_market}")
@@ -166,7 +167,8 @@ class TestGetStarted:
page.wait_for_selector('[data-testid="sidebar-content"]', state="visible") page.wait_for_selector('[data-testid="sidebar-content"]', state="visible")
expect(page.get_by_test_id("get-started-banner")).not_to_be_visible() expect(page.get_by_test_id("get-started-banner")).not_to_be_visible()
def test_redirect_default_market(self, continuous_market, vega: VegaService, page: Page): @pytest.mark.skip("tbd-market-sim")
def test_redirect_default_market(self, continuous_market, vega: VegaServiceNull, page: Page):
page.goto("/") page.goto("/")
# 0007-FUGS-012 # 0007-FUGS-012
expect(page).to_have_url( expect(page).to_have_url(
@@ -177,7 +179,7 @@ class TestGetStarted:
expect(page.get_by_test_id("welcome-dialog")).not_to_be_visible() expect(page.get_by_test_id("welcome-dialog")).not_to_be_visible()
class TestBrowseAll: class TestBrowseAll:
def test_get_started_browse_all(self, simple_market, vega: VegaService, page: Page): def test_get_started_browse_all(self, simple_market, vega: VegaServiceNull, page: Page):
page.goto("/") page.goto("/")
print(simple_market) print(simple_market)
page.get_by_test_id("browse-markets-button").click() page.get_by_test_id("browse-markets-button").click()
@@ -1,6 +1,6 @@
import pytest import pytest
from playwright.sync_api import expect, Page from playwright.sync_api import expect, Page
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from actions.vega import submit_order from actions.vega import submit_order
from conftest import init_vega from conftest import init_vega
from fixtures.market import setup_continuous_market from fixtures.market import setup_continuous_market
@@ -22,7 +22,7 @@ class TestIcebergOrdersValidations:
return setup_continuous_market(vega) return setup_continuous_market(vega)
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_iceberg_submit(self, continuous_market, vega: VegaService, page: Page): def test_iceberg_submit(self, continuous_market, vega: VegaServiceNull, page: Page):
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id("iceberg").click() page.get_by_test_id("iceberg").click()
page.get_by_test_id("order-peak-size").type("2") page.get_by_test_id("order-peak-size").type("2")
@@ -47,7 +47,7 @@ class TestIcebergOrdersValidations:
).to_have_text("Limit (Iceberg)") ).to_have_text("Limit (Iceberg)")
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_iceberg_open_order(continuous_market, vega: VegaService, page: Page): def test_iceberg_open_order(continuous_market, vega: VegaServiceNull, page: Page):
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
submit_order(vega, "Key 1", continuous_market, "SIDE_SELL", 102, 101, 2, 1) submit_order(vega, "Key 1", continuous_market, "SIDE_SELL", 102, 101, 2, 1)
@@ -65,16 +65,17 @@ def test_iceberg_open_order(continuous_market, vega: VegaService, page: Page):
page.wait_for_selector(".ag-center-cols-container .ag-row") page.wait_for_selector(".ag-center-cols-container .ag-row")
expect( expect(
page.locator(".ag-center-cols-container .ag-row [col-id='remaining']") page.locator(".ag-center-cols-container .ag-row [col-id='remaining']").first
).to_have_text("99") ).to_have_text("99")
expect( expect(
page.locator(".ag-center-cols-container .ag-row [col-id='size']") page.locator(".ag-center-cols-container .ag-row [col-id='size']").first
).to_have_text("-102") ).to_have_text("-102")
page.pause()
expect( expect(
page.locator(".ag-center-cols-container .ag-row [col-id='type'] ") page.locator(".ag-center-cols-container .ag-row [col-id='type'] ").first
).to_have_text("Limit (Iceberg)") ).to_have_text("Limit (Iceberg)")
expect( expect(
page.locator(".ag-center-cols-container .ag-row [col-id='status']") page.locator(".ag-center-cols-container .ag-row [col-id='status']").first
).to_have_text("Active") ).to_have_text("Active")
expect(page.get_by_test_id("price-10100000")).to_be_visible expect(page.get_by_test_id("price-10100000")).to_be_visible
expect(page.get_by_test_id("ask-vol-10100000")).to_have_text("3") expect(page.get_by_test_id("ask-vol-10100000")).to_have_text("3")
@@ -1,6 +1,6 @@
import pytest import pytest
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from conftest import init_vega from conftest import init_vega
from fixtures.market import setup_continuous_market from fixtures.market import setup_continuous_market
from actions.utils import next_epoch, truncate_middle, change_keys from actions.utils import next_epoch, truncate_middle, change_keys
@@ -18,7 +18,7 @@ def continuous_market(vega):
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_liquidity_provision_amendment(continuous_market, vega: VegaService, page: Page): def test_liquidity_provision_amendment(continuous_market, vega: VegaServiceNull, page: Page):
# TODO Refactor asserting the grid # TODO Refactor asserting the grid
page.goto(f"/#/liquidity/{continuous_market}") page.goto(f"/#/liquidity/{continuous_market}")
change_keys(page, vega, "market_maker") change_keys(page, vega, "market_maker")
@@ -77,7 +77,7 @@ def test_liquidity_provision_amendment(continuous_market, vega: VegaService, pag
@pytest.mark.skip("Waiting for the ability to cancel LP") @pytest.mark.skip("Waiting for the ability to cancel LP")
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_liquidity_provision_inactive(continuous_market, vega: VegaService, page: Page): def test_liquidity_provision_inactive(continuous_market, vega: VegaServiceNull, page: Page):
# TODO Refactor asserting the grid # TODO Refactor asserting the grid
page.goto(f"/#/liquidity/{continuous_market}") page.goto(f"/#/liquidity/{continuous_market}")
change_keys(page, vega, "market_maker") change_keys(page, vega, "market_maker")
@@ -1,7 +1,7 @@
import pytest import pytest
import re import re
import vega_sim.api.governance as governance import vega_sim.api.governance as governance
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from fixtures.market import setup_continuous_market from fixtures.market import setup_continuous_market
from conftest import init_vega from conftest import init_vega
@@ -14,7 +14,7 @@ def vega():
@pytest.fixture(scope="class") @pytest.fixture(scope="class")
def create_settled_market(vega: VegaService): def create_settled_market(vega: VegaServiceNull):
market_id = setup_continuous_market(vega) market_id = setup_continuous_market(vega)
vega.submit_termination_and_settlement_data( vega.submit_termination_and_settlement_data(
settlement_key="FJMKnwfZdd48C8NqvYrG", settlement_key="FJMKnwfZdd48C8NqvYrG",
@@ -115,7 +115,7 @@ class TestSettledMarket:
@pytest.mark.usefixtures("risk_accepted", "auth") @pytest.mark.usefixtures("risk_accepted", "auth")
def test_terminated_market_no_settlement_date(page: Page, vega: VegaService): def test_terminated_market_no_settlement_date(page: Page, vega: VegaServiceNull):
setup_continuous_market(vega) setup_continuous_market(vega)
print("I have started test_terminated_market_no_settlement_date") print("I have started test_terminated_market_no_settlement_date")
governance.submit_oracle_data( governance.submit_oracle_data(
+10 -6
View File
@@ -1,6 +1,6 @@
import pytest import pytest
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from actions.vega import submit_order from actions.vega import submit_order
from actions.utils import change_keys from actions.utils import change_keys
from wallet_config import MM_WALLET, MM_WALLET2 from wallet_config import MM_WALLET, MM_WALLET2
@@ -30,9 +30,9 @@ initial_volume: float = 1
initial_spread: float = 0.1 initial_spread: float = 0.1
market_name = "BTC:DAI_2023" market_name = "BTC:DAI_2023"
@pytest.mark.skip("tbd")
@pytest.mark.usefixtures("risk_accepted", "auth") @pytest.mark.usefixtures("risk_accepted", "auth")
def test_price_monitoring(simple_market, vega: VegaService, page: Page): def test_price_monitoring(simple_market, vega: VegaServiceNull, page: Page):
page.goto(f"/#/markets/all") page.goto(f"/#/markets/all")
expect(page.locator(table_row_selector).locator(trading_mode_col)).to_have_text( expect(page.locator(table_row_selector).locator(trading_mode_col)).to_have_text(
"Opening auction" "Opening auction"
@@ -202,17 +202,21 @@ COL_ID_FEE = ".ag-center-cols-container [col-id='fee'] .ag-cell-value"
@pytest.mark.usefixtures("risk_accepted", "auth") @pytest.mark.usefixtures("risk_accepted", "auth")
def test_auction_uncross_fees(continuous_market, vega: VegaService, page: Page): def test_auction_uncross_fees(continuous_market, vega: VegaServiceNull, page: Page):
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id("Fills").click() page.get_by_test_id("Fills").click()
expect(page.locator(COL_ID_FEE)).to_have_text("0.00 tDAI") expect(page.locator(COL_ID_FEE)).to_have_text("0.00 tDAI")
page.locator(COL_ID_FEE).hover() # tbd - tooltip is not visible without this wait
page.wait_for_timeout(1000)
page.get_by_role("gridcell", name="0.00 tDAI").nth(0).hover()
expect(page.get_by_test_id("fee-breakdown-tooltip")).to_have_text( expect(page.get_by_test_id("fee-breakdown-tooltip")).to_have_text(
"If the market was suspendedDuring auction, half the infrastructure and liquidity fees will be paid.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee0.00 tDAITotal fees0.00 tDAI" "If the market was suspendedDuring auction, half the infrastructure and liquidity fees will be paid.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee0.00 tDAITotal fees0.00 tDAI"
) )
change_keys(page, vega, "market_maker") change_keys(page, vega, "market_maker")
expect(page.locator(COL_ID_FEE)).to_have_text("0.00 tDAI") expect(page.locator(COL_ID_FEE)).to_have_text("0.00 tDAI")
page.locator(COL_ID_FEE).hover() # tbd - tooltip is not visible without this wait
page.wait_for_timeout(1000)
page.get_by_role("gridcell", name="0.00 tDAI").nth(0).hover()
expect(page.get_by_test_id("fee-breakdown-tooltip")).to_have_text( expect(page.get_by_test_id("fee-breakdown-tooltip")).to_have_text(
"If the market was suspendedDuring auction, half the infrastructure and liquidity fees will be paid.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee0.00 tDAITotal fees0.00 tDAI" "If the market was suspendedDuring auction, half the infrastructure and liquidity fees will be paid.Infrastructure fee0.00 tDAILiquidity fee0.00 tDAIMaker fee0.00 tDAITotal fees0.00 tDAI"
) )
@@ -1,7 +1,7 @@
import re import re
import pytest import pytest
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from fixtures.market import setup_continuous_market from fixtures.market import setup_continuous_market
from conftest import init_page, init_vega, risk_accepted_setup from conftest import init_page, init_vega, risk_accepted_setup
@@ -42,7 +42,7 @@ def validate_info_section(page: Page, fields: [[str, str]]):
page.get_by_test_id("key-value-table-row").nth(rowNumber).locator("dd") page.get_by_test_id("key-value-table-row").nth(rowNumber).locator("dd")
).to_contain_text(value) ).to_contain_text(value)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_current_fees(page: Page): def test_market_info_current_fees(page: Page):
# 6002-MDET-101 # 6002-MDET-101
page.get_by_test_id(market_title_test_id).get_by_text("Current fees").click() page.get_by_test_id(market_title_test_id).get_by_text("Current fees").click()
@@ -54,7 +54,7 @@ def test_market_info_current_fees(page: Page):
] ]
validate_info_section(page, fields) validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_market_price(page: Page): def test_market_info_market_price(page: Page):
# 6002-MDET-102 # 6002-MDET-102
page.get_by_test_id(market_title_test_id).get_by_text("Market price").click() page.get_by_test_id(market_title_test_id).get_by_text("Market price").click()
@@ -66,7 +66,7 @@ def test_market_info_market_price(page: Page):
] ]
validate_info_section(page, fields) validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_market_volume(page: Page): def test_market_info_market_volume(page: Page):
# 6002-MDET-103 # 6002-MDET-103
page.get_by_test_id(market_title_test_id).get_by_text("Market volume").click() page.get_by_test_id(market_title_test_id).get_by_text("Market volume").click()
@@ -80,15 +80,15 @@ def test_market_info_market_volume(page: Page):
] ]
validate_info_section(page, fields) validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_insurance_pool(page: Page): def test_market_info_insurance_pool(page: Page):
# 6002-MDET-104 # 6002-MDET-104
page.get_by_test_id(market_title_test_id).get_by_text("Insurance pool").click() page.get_by_test_id(market_title_test_id).get_by_text("Insurance pool").click()
fields = [["Balance", "0.00 tDAI"]] fields = [["Balance", "0.00 tDAI"]]
validate_info_section(page, fields) validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_key_details(page: Page, vega: VegaService): def test_market_info_key_details(page: Page, vega: VegaServiceNull):
# 6002-MDET-201 # 6002-MDET-201
page.get_by_test_id(market_title_test_id).get_by_text("Key details").click() page.get_by_test_id(market_title_test_id).get_by_text("Key details").click()
market_id = vega.find_market_id("BTC:DAI_2023") market_id = vega.find_market_id("BTC:DAI_2023")
@@ -106,7 +106,7 @@ def test_market_info_key_details(page: Page, vega: VegaService):
] ]
validate_info_section(page, fields) validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_instrument(page: Page): def test_market_info_instrument(page: Page):
# 6002-MDET-202 # 6002-MDET-202
page.get_by_test_id(market_title_test_id).get_by_text("Instrument").click() page.get_by_test_id(market_title_test_id).get_by_text("Instrument").click()
@@ -121,7 +121,7 @@ def test_market_info_instrument(page: Page):
# @pytest.mark.skip("oracle test to be fixed") # @pytest.mark.skip("oracle test to be fixed")
@pytest.mark.skip("tbd-market-sim")
def test_market_info_oracle(page: Page): def test_market_info_oracle(page: Page):
# 6002-MDET-203 # 6002-MDET-203
page.get_by_test_id(market_title_test_id).get_by_text("Oracle").click() page.get_by_test_id(market_title_test_id).get_by_text("Oracle").click()
@@ -135,8 +135,8 @@ def test_market_info_oracle(page: Page):
# "href", re.compile(rf'(\/oracles\/{vega.find_market_id("BTC:DAI_2023")})') # "href", re.compile(rf'(\/oracles\/{vega.find_market_id("BTC:DAI_2023")})')
# ) # )
@pytest.mark.skip("tbd-market-sim")
def test_market_info_settlement_asset(page: Page, vega: VegaService): def test_market_info_settlement_asset(page: Page, vega: VegaServiceNull):
# 6002-MDET-206 # 6002-MDET-206
page.get_by_test_id(market_title_test_id).get_by_text("Settlement asset").click() page.get_by_test_id(market_title_test_id).get_by_text("Settlement asset").click()
tdai_id = vega.find_asset_id("tDAI") tdai_id = vega.find_asset_id("tDAI")
@@ -155,7 +155,7 @@ def test_market_info_settlement_asset(page: Page, vega: VegaService):
] ]
validate_info_section(page, fields) validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_metadata(page: Page): def test_market_info_metadata(page: Page):
# 6002-MDET-207 # 6002-MDET-207
page.get_by_test_id(market_title_test_id).get_by_text("Metadata").click() page.get_by_test_id(market_title_test_id).get_by_text("Metadata").click()
@@ -164,7 +164,7 @@ def test_market_info_metadata(page: Page):
] ]
validate_info_section(page, fields) validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_risk_model(page: Page): def test_market_info_risk_model(page: Page):
# 6002-MDET-208 # 6002-MDET-208
page.get_by_test_id(market_title_test_id).get_by_text("Risk model").click() page.get_by_test_id(market_title_test_id).get_by_text("Risk model").click()
@@ -175,7 +175,7 @@ def test_market_info_risk_model(page: Page):
] ]
validate_info_section(page, fields) validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_margin_scaling_factors(page: Page): def test_market_info_margin_scaling_factors(page: Page):
# 6002-MDET-209 # 6002-MDET-209
page.get_by_test_id(market_title_test_id).get_by_text( page.get_by_test_id(market_title_test_id).get_by_text(
@@ -190,7 +190,7 @@ def test_market_info_margin_scaling_factors(page: Page):
] ]
validate_info_section(page, fields) validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_risk_factors(page: Page): def test_market_info_risk_factors(page: Page):
# 6002-MDET-210 # 6002-MDET-210
page.get_by_test_id(market_title_test_id).get_by_text("Risk factors").click() page.get_by_test_id(market_title_test_id).get_by_text("Risk factors").click()
@@ -204,7 +204,7 @@ def test_market_info_risk_factors(page: Page):
] ]
validate_info_section(page, fields) validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_price_monitoring_bounds(page: Page): def test_market_info_price_monitoring_bounds(page: Page):
# 6002-MDET-211 # 6002-MDET-211
page.get_by_test_id(market_title_test_id).get_by_text( page.get_by_test_id(market_title_test_id).get_by_text(
@@ -220,7 +220,7 @@ def test_market_info_price_monitoring_bounds(page: Page):
] ]
validate_info_section(page, fields) validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_liquidity_monitoring_parameters(page: Page): def test_market_info_liquidity_monitoring_parameters(page: Page):
# 6002-MDET-212 # 6002-MDET-212
page.get_by_test_id(market_title_test_id).get_by_text( page.get_by_test_id(market_title_test_id).get_by_text(
@@ -233,7 +233,7 @@ def test_market_info_liquidity_monitoring_parameters(page: Page):
] ]
validate_info_section(page, fields) validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
# Liquidity resolves to 3 results # Liquidity resolves to 3 results
def test_market_info_liquidit(page: Page): def test_market_info_liquidit(page: Page):
# 6002-MDET-213 # 6002-MDET-213
@@ -246,7 +246,7 @@ def test_market_info_liquidit(page: Page):
] ]
validate_info_section(page, fields) validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_liquidity_price_range(page: Page): def test_market_info_liquidity_price_range(page: Page):
# 6002-MDET-214 # 6002-MDET-214
page.get_by_test_id(market_title_test_id).get_by_text( page.get_by_test_id(market_title_test_id).get_by_text(
@@ -259,8 +259,8 @@ def test_market_info_liquidity_price_range(page: Page):
] ]
validate_info_section(page, fields) validate_info_section(page, fields)
@pytest.mark.skip("tbd-market-sim")
def test_market_info_proposal(page: Page, vega: VegaService): def test_market_info_proposal(page: Page, vega: VegaServiceNull):
# 6002-MDET-301 # 6002-MDET-301
page.get_by_test_id(market_title_test_id).get_by_text("Proposal").click() page.get_by_test_id(market_title_test_id).get_by_text("Proposal").click()
first_link = ( first_link = (
@@ -280,8 +280,9 @@ def test_market_info_proposal(page: Page, vega: VegaService):
"href", re.compile(r"(\/proposals\/propose\/update-market)") "href", re.compile(r"(\/proposals\/propose\/update-market)")
) )
@pytest.mark.skip("tbd-market-sim") @pytest.mark.skip("tbd-market-sim")
def test_market_info_succession_line(page: Page, vega: VegaService): def test_market_info_succession_line(page: Page, vega: VegaServiceNull):
page.get_by_test_id(market_title_test_id).get_by_text("Succession line").click() page.get_by_test_id(market_title_test_id).get_by_text("Succession line").click()
market_id = vega.find_market_id("BTC:DAI_2023") market_id = vega.find_market_id("BTC:DAI_2023")
succession_line = page.get_by_test_id("succession-line-item") succession_line = page.get_by_test_id("succession-line-item")
@@ -9,16 +9,16 @@ def test_market_selector(continuous_market, page: Page):
page.get_by_test_id("header-title").click() page.get_by_test_id("header-title").click()
# 6001-MARK-066 # 6001-MARK-066
expect(page.get_by_test_id("market-selector")).to_be_visible() expect(page.get_by_test_id("market-selector")).to_be_visible()
# 6001-MARK-021 # 6001-MARK-021
# 6001-MARK-022 # 6001-MARK-022
# 6001-MARK-024 # 6001-MARK-024
# 6001-MARK-025 # 6001-MARK-025
btc_market = page.locator('[data-testid="market-selector-list"] a') btc_market = page.locator('[data-testid="market-selector-list"] a')
expect(btc_market.locator("h3")).to_have_text("BTC:DAI_2023Futr") expect(btc_market.locator("h3")).to_have_text("BTC:DAI_2023Futr")
expect(btc_market.locator('[data-testid="market-selector-volume"]')).to_have_text( # tbd - 5465
"0.00" # expect(btc_market.locator('[data-testid="market-selector-volume"]')).to_have_text(
) # "1"
# )
expect(btc_market.locator('[data-testid="market-selector-price"]')).to_have_text( expect(btc_market.locator('[data-testid="market-selector-price"]')).to_have_text(
"107.50 tDAI" "107.50 tDAI"
) )
@@ -43,7 +43,6 @@ def test_market_selector_filter(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
page.get_by_test_id("header-title").click() page.get_by_test_id("header-title").click()
# 6001-MARK-027 # 6001-MARK-027
page.get_by_test_id("product-Spot").click() page.get_by_test_id("product-Spot").click()
expect(page.get_by_test_id("market-selector-list")).to_contain_text( expect(page.get_by_test_id("market-selector-list")).to_contain_text(
"Spot markets coming soon." "Spot markets coming soon."
@@ -58,8 +57,9 @@ def test_market_selector_filter(continuous_market, page: Page):
# 6001-MARK-029 # 6001-MARK-029
page.get_by_test_id("search-term").fill("btc") page.get_by_test_id("search-term").fill("btc")
expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(1) expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(1)
expect(page.locator('[data-testid="market-selector-list"] a').nth(0)).to_have_text( # tbd - 5465
"BTC:DAI_2023107.50 tDAI0.00" expect(page.locator('[data-testid="market-selector-list"] a').nth(0)).to_contain_text(
"BTC:DAI_2023107.50 tDAI"
) )
page.get_by_test_id("search-term").clear() page.get_by_test_id("search-term").clear()
@@ -83,6 +83,7 @@ def test_market_selector_filter(continuous_market, page: Page):
page.get_by_test_id("asset-trigger").click() page.get_by_test_id("asset-trigger").click()
page.get_by_role("menuitemcheckbox").nth(0).get_by_text("tDAI").click() page.get_by_role("menuitemcheckbox").nth(0).get_by_text("tDAI").click()
expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(1) expect(page.locator('[data-testid="market-selector-list"] a')).to_have_count(1)
expect(page.locator('[data-testid="market-selector-list"] a').nth(0)).to_have_text( # tbd - 5465
"BTC:DAI_2023107.50 tDAI0.00" expect(page.locator('[data-testid="market-selector-list"] a').nth(0)).to_contain_text(
"BTC:DAI_2023107.50 tDAI"
) )
@@ -2,7 +2,7 @@ import pytest
import vega_sim.api.governance as governance import vega_sim.api.governance as governance
import re import re
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from conftest import init_vega from conftest import init_vega
from fixtures.market import setup_simple_market from fixtures.market import setup_simple_market
from wallet_config import MM_WALLET from wallet_config import MM_WALLET
@@ -18,7 +18,7 @@ def vega(request):
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def proposed_market(vega: VegaService): def proposed_market(vega: VegaServiceNull):
# setup market without liquidity provided # setup market without liquidity provided
market_id = setup_simple_market(vega, approve_proposal=False) market_id = setup_simple_market(vega, approve_proposal=False)
# approve market # approve market
@@ -1,6 +1,6 @@
import pytest import pytest
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from actions.vega import submit_order from actions.vega import submit_order
from fixtures.market import setup_simple_market from fixtures.market import setup_simple_market
from conftest import init_vega from conftest import init_vega
@@ -20,7 +20,7 @@ def simple_market(vega):
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def setup_market_monitoring_auction(vega: VegaService, simple_market): def setup_market_monitoring_auction(vega: VegaServiceNull, simple_market):
vega.submit_liquidity( vega.submit_liquidity(
key_name=MM_WALLET.name, key_name=MM_WALLET.name,
market_id=simple_market, market_id=simple_market,
@@ -82,7 +82,7 @@ def setup_market_monitoring_auction(vega: VegaService, simple_market):
@pytest.mark.usefixtures("risk_accepted", "auth", "setup_market_monitoring_auction") @pytest.mark.usefixtures("risk_accepted", "auth", "setup_market_monitoring_auction")
def test_market_monitoring_auction_price_volatility_limit_order( def test_market_monitoring_auction_price_volatility_limit_order(
page: Page, simple_market, vega: VegaService page: Page, simple_market, vega: VegaServiceNull
): ):
page.goto(f"/#/markets/{simple_market}") page.goto(f"/#/markets/{simple_market}")
page.get_by_test_id("order-size").clear() page.get_by_test_id("order-size").clear()
@@ -1,16 +1,16 @@
import pytest import pytest
import re import re
import vega_sim.api.governance as governance
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from vega_sim.service import VegaService, PeggedOrder from vega_sim.null_service import VegaServiceNull
from vega_sim.service import PeggedOrder, MarketStateUpdateType
import vega_sim.api.governance as governance import vega_sim.api.governance as governance
from actions.vega import submit_order from actions.vega import submit_order
from actions.utils import next_epoch from actions.utils import next_epoch
from wallet_config import MM_WALLET, MM_WALLET2, GOVERNANCE_WALLET from wallet_config import MM_WALLET, MM_WALLET2, GOVERNANCE_WALLET
from vega_sim.api import governance
@pytest.mark.usefixtures("risk_accepted") @pytest.mark.usefixtures("risk_accepted")
def test_market_lifecycle(proposed_market, vega: VegaService, page: Page): def test_market_lifecycle(proposed_market, vega: VegaServiceNull, page: Page):
# 7002-SORD-001 # 7002-SORD-001
# 7002-SORD-002 # 7002-SORD-002
trading_mode = page.get_by_test_id("market-trading-mode").get_by_test_id( trading_mode = page.get_by_test_id("market-trading-mode").get_by_test_id(
@@ -126,6 +126,44 @@ def test_market_lifecycle(proposed_market, vega: VegaService, page: Page):
expect(trading_mode).to_have_text("Continuous") expect(trading_mode).to_have_text("Continuous")
expect(market_state).to_have_text("Active") expect(market_state).to_have_text("Active")
vega.update_market_state(
market_id = market_id,
proposal_key = MM_WALLET.name,
market_state = MarketStateUpdateType.Suspend,
forward_time_to_enactment = False
)
expect(
page
.get_by_test_id("market-banner")
.get_by_test_id(f"update-state-banner-{market_id}")
).to_be_visible()
vega.forward("60s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
expect(
page.get_by_test_id("market-banner")
).to_have_text("Market was suspended by governance")
# banner should not show after resume
vega.update_market_state(
market_id = market_id,
proposal_key = MM_WALLET.name,
market_state = MarketStateUpdateType.Resume,
forward_time_to_enactment = False
)
vega.forward("60s")
vega.wait_fn(1)
vega.wait_for_total_catchup()
expect(page.get_by_test_id("market-banner")).not_to_be_visible()
# TODO test update market, sim will currently auto approve and enacted
# a market update proposals
# put invalid oracle to trigger market termination # put invalid oracle to trigger market termination
governance.submit_oracle_data( governance.submit_oracle_data(
wallet=vega.wallet, wallet=vega.wallet,
@@ -153,6 +191,9 @@ def test_market_lifecycle(proposed_market, vega: VegaService, page: Page):
# check market state is now settled # check market state is now settled
expect(trading_mode).to_have_text("No trading") expect(trading_mode).to_have_text("No trading")
expect(market_state).to_have_text("Settled") expect(market_state).to_have_text("Settled")
expect(
page.get_by_test_id("market-banner")
).to_have_text("This market has been settled")
""" @pytest.mark.usefixtures("page", "risk_accepted", "continuous_market") """ @pytest.mark.usefixtures("page", "risk_accepted", "continuous_market")
@@ -1,7 +1,7 @@
import pytest import pytest
import re import re
from playwright.sync_api import expect, Page from playwright.sync_api import expect, Page
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from actions.vega import submit_order from actions.vega import submit_order
order_details = [ order_details = [
@@ -42,6 +42,10 @@ def verify_order_value(
expect(element).to_be_visible() expect(element).to_be_visible()
if is_regex: if is_regex:
actual_text = element.text_content() actual_text = element.text_content()
if actual_text is None:
raise Exception(f"no text found for test_id {test_id}")
assert re.match( assert re.match(
expected_text, actual_text expected_text, actual_text
), f"Expected {expected_text}, but got {actual_text}" ), f"Expected {expected_text}, but got {actual_text}"
@@ -52,7 +56,7 @@ def verify_order_value(
@pytest.mark.skip("tbd") @pytest.mark.skip("tbd")
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_order_details_are_correctly_displayed( def test_order_details_are_correctly_displayed(
continuous_market, vega: VegaService, page: Page continuous_market, vega: VegaServiceNull, page: Page
): ):
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
submit_order(vega, "Key 1", vega.all_markets()[0].id, "SIDE_SELL", 102, 101, 2, 1) submit_order(vega, "Key 1", vega.all_markets()[0].id, "SIDE_SELL", 102, 101, 2, 1)
@@ -2,7 +2,7 @@ import pytest
import re import re
import logging import logging
from playwright.sync_api import expect, Page from playwright.sync_api import expect, Page
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from playwright.sync_api import expect from playwright.sync_api import expect
from actions.vega import submit_order from actions.vega import submit_order
@@ -16,11 +16,11 @@ def verify_data_grid(page: Page, data_test_id, expected_pattern):
expect( expect(
page.locator( page.locator(
f'[data-testid^="tab-{data_test_id.lower()}"] >> .ag-center-cols-container .ag-row-first' f'[data-testid^="tab-{data_test_id.lower()}"] >> .ag-center-cols-container .ag-row-first'
) ).first
).to_be_visible() ).to_be_visible()
actual_text = page.locator( actual_text = page.locator(
f'[data-testid^="tab-{data_test_id.lower()}"] >> .ag-center-cols-container .ag-row-first' f'[data-testid^="tab-{data_test_id.lower()}"] >> .ag-center-cols-container .ag-row-first'
).text_content() ).first.text_content()
lines = actual_text.strip().split("\n") lines = actual_text.strip().split("\n")
for expected, actual in zip(expected_pattern, lines): for expected, actual in zip(expected_pattern, lines):
# We are using regex so that we can run tests in different timezones. # We are using regex so that we can run tests in different timezones.
@@ -38,7 +38,7 @@ def verify_data_grid(page: Page, data_test_id, expected_pattern):
raise AssertionError(f"Pattern does not match: {expected} != {actual}") raise AssertionError(f"Pattern does not match: {expected} != {actual}")
def submit_order(vega: VegaService, wallet_name, market_id, side, volume, price): def submit_order(vega: VegaServiceNull, wallet_name, market_id, side, volume, price):
vega.submit_order( vega.submit_order(
trading_key=wallet_name, trading_key=wallet_name,
market_id=market_id, market_id=market_id,
@@ -52,7 +52,7 @@ def submit_order(vega: VegaService, wallet_name, market_id, side, volume, price)
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_limit_order_trade_open_order( def test_limit_order_trade_open_order(
opening_auction_market, vega: VegaService, page: Page opening_auction_market, vega: VegaServiceNull, page: Page
): ):
market_id = opening_auction_market market_id = opening_auction_market
submit_order(vega, "Key 1", market_id, "SIDE_BUY", 1, 110) submit_order(vega, "Key 1", market_id, "SIDE_BUY", 1, 110)
@@ -1,6 +1,7 @@
import pytest import pytest
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from vega_sim.service import VegaService, PeggedOrder from vega_sim.service import PeggedOrder
from vega_sim.null_service import VegaServiceNull
from conftest import auth_setup, init_page, init_vega, risk_accepted_setup from conftest import auth_setup, init_page, init_vega, risk_accepted_setup
from fixtures.market import setup_continuous_market, setup_simple_market from fixtures.market import setup_continuous_market, setup_simple_market
from actions.utils import wait_for_toast_confirmation from actions.utils import wait_for_toast_confirmation
@@ -15,7 +16,7 @@ def vega(request):
@pytest.fixture(scope="module", autouse=True) @pytest.fixture(scope="module", autouse=True)
def markets(vega: VegaService): def markets(vega: VegaServiceNull):
market_1 = setup_continuous_market( market_1 = setup_continuous_market(
vega, vega,
custom_market_name="market-1", custom_market_name="market-1",
@@ -357,7 +358,7 @@ def test_order_status_pegged_mid(page: Page):
) )
def test_order_amend_order(vega: VegaService, page: Page): def test_order_amend_order(vega: VegaServiceNull, page: Page):
# 7002-SORD-053 # 7002-SORD-053
# 7003-MORD-012 # 7003-MORD-012
# 7003-MORD-014 # 7003-MORD-014
@@ -377,7 +378,7 @@ def test_order_amend_order(vega: VegaService, page: Page):
) )
def test_order_cancel_single_order(vega: VegaService, page: Page): def test_order_cancel_single_order(vega: VegaServiceNull, page: Page):
# 7003-MORD-009 # 7003-MORD-009
# 7003-MORD-010 # 7003-MORD-010
# 7003-MORD-011 # 7003-MORD-011
@@ -394,7 +395,7 @@ def test_order_cancel_single_order(vega: VegaService, page: Page):
) )
def test_order_cancel_all_orders(vega: VegaService, page: Page): def test_order_cancel_all_orders(vega: VegaServiceNull, page: Page):
# 7003-MORD-009 # 7003-MORD-009
# 7003-MORD-010 # 7003-MORD-010
# 7003-MORD-011 # 7003-MORD-011
@@ -1,7 +1,7 @@
import pytest import pytest
import re import re
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from vega_sim.service import MarketStateUpdateType from vega_sim.service import MarketStateUpdateType
from datetime import datetime, timedelta from datetime import datetime, timedelta
from conftest import init_vega from conftest import init_vega
@@ -21,7 +21,7 @@ class TestPerpetuals:
yield vega yield vega
@pytest.fixture(scope="class") @pytest.fixture(scope="class")
def perps_market(self, vega: VegaService): def perps_market(self, vega: VegaServiceNull):
perps_market = setup_perps_market(vega) perps_market = setup_perps_market(vega)
submit_multiple_orders( submit_multiple_orders(
vega, MM_WALLET.name, perps_market, "SIDE_SELL", [[1, 110], [1, 105]] vega, MM_WALLET.name, perps_market, "SIDE_SELL", [[1, 110], [1, 105]]
@@ -96,7 +96,7 @@ class TestPerpetuals:
@pytest.mark.usefixtures("risk_accepted", "auth") @pytest.mark.usefixtures("risk_accepted", "auth")
def test_perps_market_termination_proposed(page: Page, vega: VegaService): def test_perps_market_termination_proposed(page: Page, vega: VegaServiceNull):
perpetual_market = setup_perps_market(vega) perpetual_market = setup_perps_market(vega)
page.goto(f"/#/markets/{perpetual_market}") page.goto(f"/#/markets/{perpetual_market}")
vega.update_market_state( vega.update_market_state(
@@ -109,14 +109,15 @@ def test_perps_market_termination_proposed(page: Page, vega: VegaService):
approve_proposal=True, approve_proposal=True,
forward_time_to_enactment=False, forward_time_to_enactment=False,
) )
vega.forward("10s") vega.forward("10s")
vega.wait_fn(1) vega.wait_fn(1)
vega.wait_for_total_catchup() vega.wait_for_total_catchup()
banner_text = page.get_by_test_id( banner_text = page.get_by_test_id(
f"termination-warning-banner-{perpetual_market}" f"update-state-banner-{perpetual_market}"
).text_content() ).text_content()
pattern = re.compile( pattern = re.compile(
r"Trading on Market BTC:DAI_Perpetual may stop on \d{2} [A-Za-z]+\. There is open proposal to close this market\.Proposed final price is 100\.00 BTC\.View proposal" r"Trading on market BTC:DAI_Perpetual may stop on \d{2} [A-Za-z]+\. There is an open proposal to close this market\.Proposed final price is 100\.00 BTC\. View proposal"
) )
assert pattern.search( assert pattern.search(
banner_text banner_text
@@ -124,7 +125,7 @@ def test_perps_market_termination_proposed(page: Page, vega: VegaService):
@pytest.mark.usefixtures("risk_accepted", "auth") @pytest.mark.usefixtures("risk_accepted", "auth")
def test_perps_market_terminated(page: Page, vega: VegaService): def test_perps_market_terminated(page: Page, vega: VegaServiceNull):
perpetual_market = setup_perps_market(vega) perpetual_market = setup_perps_market(vega)
vega.update_market_state( vega.update_market_state(
proposal_key=MM_WALLET.name, proposal_key=MM_WALLET.name,
+2 -2
View File
@@ -1,6 +1,6 @@
import pytest import pytest
from playwright.sync_api import Page from playwright.sync_api import Page
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from actions.vega import submit_order from actions.vega import submit_order
from actions.utils import change_keys from actions.utils import change_keys
@@ -13,7 +13,7 @@ def check_pnl_color_value(element, expected_color, expected_value):
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_pnl(continuous_market, vega: VegaService, page: Page): def test_pnl(continuous_market, vega: VegaServiceNull, page: Page):
page.set_viewport_size({"width": 1748, "height": 977}) page.set_viewport_size({"width": 1748, "height": 977})
submit_order(vega, "Key 1", continuous_market, "SIDE_BUY", 1, 104.50000) submit_order(vega, "Key 1", continuous_market, "SIDE_BUY", 1, 104.50000)
vega.wait_fn(1) vega.wait_fn(1)
@@ -1,6 +1,6 @@
import pytest import pytest
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from conftest import init_vega from conftest import init_vega
from fixtures.market import setup_continuous_market from fixtures.market import setup_continuous_market
@@ -16,7 +16,7 @@ def vega(request):
@pytest.fixture(scope="module") @pytest.fixture(scope="module")
def continuous_market(vega: VegaService): def continuous_market(vega: VegaServiceNull):
return setup_continuous_market(vega) return setup_continuous_market(vega)
@@ -1,13 +1,13 @@
import pytest import pytest
from playwright.sync_api import Page, expect from playwright.sync_api import Page, expect
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from fixtures.market import ( from fixtures.market import (
setup_continuous_market, setup_continuous_market,
) )
@pytest.mark.usefixtures("auth", "risk_accepted") @pytest.mark.usefixtures("auth", "risk_accepted")
def test_closed_market_position(vega: VegaService, page: Page): def test_closed_market_position(vega: VegaServiceNull, page: Page):
market_id = setup_continuous_market(vega) market_id = setup_continuous_market(vega)
vega.submit_termination_and_settlement_data( vega.submit_termination_and_settlement_data(
@@ -1,6 +1,6 @@
import pytest import pytest
from playwright.sync_api import Page, expect from playwright.sync_api import Page
from vega_sim.service import VegaService from vega_sim.null_service import VegaServiceNull
from conftest import init_vega from conftest import init_vega
from fixtures.market import setup_continuous_market, setup_simple_market from fixtures.market import setup_continuous_market, setup_simple_market
from actions.utils import change_keys, create_and_faucet_wallet, forward_time, selector_contains_text from actions.utils import change_keys, create_and_faucet_wallet, forward_time, selector_contains_text
@@ -72,7 +72,7 @@ def create_staking_tier(minimum_staked_tokens, referral_reward_multiplier):
} }
def setup_market_and_referral_scheme(vega: VegaService, continuous_market: str, page: Page): def setup_market_and_referral_scheme(vega: VegaServiceNull, continuous_market: str, page: Page):
page.goto(f"/#/markets/{continuous_market}") page.goto(f"/#/markets/{continuous_market}")
create_and_faucet_wallet(vega=vega, wallet=PARTY_A) create_and_faucet_wallet(vega=vega, wallet=PARTY_A)
@@ -118,7 +118,7 @@ def setup_market_and_referral_scheme(vega: VegaService, continuous_market: str,
@pytest.mark.usefixtures("page", "auth", "risk_accepted") @pytest.mark.usefixtures("page", "auth", "risk_accepted")
def test_can_traverse_up_and_down_through_tiers(continuous_market, vega: VegaService, page: Page): def test_can_traverse_up_and_down_through_tiers(continuous_market, vega: VegaServiceNull, page: Page):
setup_market_and_referral_scheme(vega, continuous_market, page) setup_market_and_referral_scheme(vega, continuous_market, page)
change_keys(page, vega, PARTY_B.name) change_keys(page, vega, PARTY_B.name)
submit_order(vega, PARTY_B.name, continuous_market, "SIDE_BUY", 1, 115) submit_order(vega, PARTY_B.name, continuous_market, "SIDE_BUY", 1, 115)
@@ -162,7 +162,7 @@ def test_can_traverse_up_and_down_through_tiers(continuous_market, vega: VegaSer
@pytest.mark.usefixtures("page", "auth", "risk_accepted") @pytest.mark.usefixtures("page", "auth", "risk_accepted")
def test_does_not_move_up_tiers_when_not_enough_epochs(continuous_market, vega: VegaService, page: Page): def test_does_not_move_up_tiers_when_not_enough_epochs(continuous_market, vega: VegaServiceNull, page: Page):
setup_market_and_referral_scheme(vega, continuous_market, page) setup_market_and_referral_scheme(vega, continuous_market, page)
change_keys(page, vega, PARTY_B.name) change_keys(page, vega, PARTY_B.name)
submit_order(vega, PARTY_B.name, continuous_market, "SIDE_BUY", 2, 115) submit_order(vega, PARTY_B.name, continuous_market, "SIDE_BUY", 2, 115)
@@ -0,0 +1,36 @@
import pytest
from playwright.sync_api import expect, Page
settings_icon = "icon-cog"
settings_column_btn = "popover-trigger"
settings_close_btn = "settings-close"
split_view_view = "split-view-view"
@pytest.mark.usefixtures("page", "continuous_market", "auth", "risk_accepted")
def test_column_settings_is_visible(continuous_market, page: Page):
page.goto(f"/#/markets/{continuous_market}")
expect(page.get_by_test_id(split_view_view).get_by_test_id(settings_column_btn)).to_be_visible()
page.goto("/#/portfolio")
expect(page.get_by_test_id(split_view_view).get_by_test_id(settings_column_btn).nth(0)).to_be_visible()
expect(page.get_by_test_id(split_view_view).get_by_test_id(settings_column_btn).nth(1)).to_be_visible()
page.goto(f"/#/markets/all")
expect(page.get_by_test_id(settings_column_btn)).to_be_visible()
page.click('[data-testid="Proposed markets"]')
expect(page.get_by_test_id(settings_column_btn)).to_be_visible()
page.click('[data-testid="Closed markets"]')
expect(page.get_by_test_id(settings_column_btn)).to_be_visible()
@pytest.mark.usefixtures("page", "continuous_market", "auth", "risk_accepted")
def test_can_reset_columns_state(continuous_market, page: Page):
page.goto(f"/#/markets/all")
col_market = page.locator('[col-id="tradableInstrument.instrument.code"]').first
col_settlement_asset = page.locator('[col-id="tradableInstrument.instrument.product.settlementAsset.symbol"]').first
col_market.drag_to(col_settlement_asset)
# Check the attribute of the dragged element
attribute_value = col_market.get_attribute("aria-colindex")
assert attribute_value != "1"
page.get_by_test_id(settings_column_btn).click()
page.get_by_role("button", name="Reset Columns").click()
attribute_value_after_reset = col_market.get_attribute("aria-colindex")
assert attribute_value_after_reset == "1"

Some files were not shown because too many files have changed in this diff Show More