Task/229 Stub api in trading e2e (#211)

* add fixture for markets query

* stub graphql requests

* re-add assertion for tx hash, stub command/sync requests

* refactor to get tests to run with trading page mocked queries

* add test wallet credentials

* split up markets page from trading page

* add portfolio page feature, add market page scenarios

* move hasOperationName helper to support/index

* fix home-page.feature

* fix missing feature step

* Minor changes to BDD steps

* Use in object syntax to get better type safety on hasOperationName helper function

* remove bypass placing orders env var and usage in tests

* use UI_Trading_Test wallet publick key in command/sync mock

* move public key to cypress env

* replace fixtures with generator functions

* colocate query generators with queries

* add custom commands, add index files

* fix dodgy merge, remove duplicate market page feature

* make tsconfig for cypress lib match

* update tsconfig for explorer e2e so commands using merge work

* revert trading step to js

Co-authored-by: Joe <joe@vega.xyz>
This commit is contained in:
Matthew Russell 2022-04-12 04:04:26 -07:00 committed by GitHub
parent 3db9546266
commit 04872522d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 755 additions and 260 deletions

View File

@ -3,8 +3,16 @@
"compilerOptions": {
"sourceMap": false,
"outDir": "../../dist/out-tsc",
"types": ["cypress", "node"],
"allowJs": true,
"types": ["cypress", "node"]
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.js"]
}

View File

@ -14,7 +14,7 @@
"chromeWebSecurity": false,
"projectId": "et4snf",
"env": {
"bypassPlacingOrders": true,
"vegaPublicKey": "47836c253520d2661bf5bed6339c0de08fd02cf5d4db0efee3b4373f20c7d278",
"tsConfig": "tsconfig.json",
"TAGS": "not @todo and not @ignore and not @manual"
}

View File

@ -1,4 +0,0 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io"
}

View File

@ -1,11 +1,8 @@
Feature: Market orders
Feature: Deal ticket
Scenario Outline: Successfull market buy orders
Given I am on the homepage
And I navigate to markets page
When I click on active market
Given I am on the trading page for an active market
And I connect to Vega Wallet
And place a buy '<marketOrderType>' market order
When I place a buy '<marketOrderType>' market order
Then order request is sent
Examples:
@ -14,11 +11,9 @@ Feature: Market orders
| IOC |
Scenario Outline: Successfull Limit buy orders
Given I am on the homepage
And I navigate to markets page
When I click on active market
Given I am on the trading page for an active market
And I connect to Vega Wallet
And place a buy '<limitOrderType>' limit order
When I place a buy '<limitOrderType>' limit order
Then order request is sent
Examples:
@ -30,11 +25,9 @@ Feature: Market orders
| GFN |
Scenario Outline: Successfull market sell order
Given I am on the homepage
And I navigate to markets page
When I click on active market
Given I am on the trading page for an active market
And I connect to Vega Wallet
And place a sell '<marketOrderType>' market order
When I place a sell '<marketOrderType>' market order
Then order request is sent
Examples:
@ -43,11 +36,10 @@ Feature: Market orders
| IOC |
Scenario Outline: Successfull limit sell order
Given I am on the homepage
And I navigate to markets page
When I click on active market
Given I am on the trading page for an active market
And I connect to Vega Wallet
And place a sell '<limitOrderType>' limit order
When I place a sell '<limitOrderType>' limit order
Then order request is sent
Examples:
| limitOrderType |
@ -66,27 +58,20 @@ Feature: Market orders
And place a buy 'FOK' market order
Then error message for insufficient funds is displayed
@ignore
Scenario: Unable to order because market is suspended
Given I am on the homepage
And I navigate to markets page
When I click on suspended market
Given I am on the trading page for a suspended market
And I connect to Vega Wallet
Then place order button is disabled
And "Market is currently suspended" error is shown
Scenario: Unable to order because wallet is not connected
Given I am on the homepage
And I navigate to markets page
When I click on active market
Given I am on the trading page for an active market
Then place order button is disabled
And "No public key selected" error is shown
@ignore
Scenario: Unsuccessfull because quantity is 0
Given I am on the homepage
And I navigate to markets page
When I click on active market
Given I am on the trading page for an active market
And I connect to Vega Wallet
And place a buy 'FOK' market order with amount of 0
Then Order rejected by wallet error shown containing text "must be positive"

View File

@ -7,6 +7,10 @@ Feature: Home page
Given I am on the homepage
And I navigate to portfolio page
Scenario: Visit Markets page
Given I am on the homepage
And I navigate to markets page
Scenario: Unable to connect Vega wallet with incorrect credentials
Given I am on the homepage
When I try to connect Vega wallet with incorrect details

View File

@ -1,6 +0,0 @@
Feature: Market Page
Scenario: Market table displayed on market page
Given I am on the homepage
And I navigate to markets page
Then the market table is displayed

View File

@ -0,0 +1,17 @@
Feature: Markets page
Scenario: Navigation
Given I am on the homepage
When I navigate to markets page
Then I can view markets
And the market table is displayed
Scenario: Select active market
Given I am on the markets page
When I click on an active market
Then I am on the trading page for an active market
Scenario: Select suspended market
Given I am on the markets page
When I click on a suspended market
Then I am on the trading page for a suspended market

View File

@ -0,0 +1,5 @@
Feature: Portfolio page
Scenario: Navigation
Given I am on the homepage
Then I navigate to portfolio page

View File

@ -1 +0,0 @@
export const getGreeting = () => cy.get('h1');

View File

@ -1,17 +1,11 @@
// ***********************************************************
// This example support/index.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import '@vegaprotocol/cypress';
import type { CyHttpMessages } from 'cypress/types/net-stubbing';
// Utility to match GraphQL mutation based on the operation name
export const hasOperationName = (
req: CyHttpMessages.IncomingHttpRequest,
operationName: string
) => {
const { body } = req;
return 'operationName' in body && body.operationName === operationName;
};

View File

@ -1,56 +1,16 @@
export default class BasePage {
porfolioUrl = '/portfolio';
marketsUrl = '/markets';
connectVegaBtn = 'connect-vega-wallet';
walletConnectors = 'connectors-list';
walletForm = 'rest-connector-form';
walletInputError = 'input-wallet-error';
walletFormError = 'form-error';
inputError = 'input-error-text';
navigateToPortfolio() {
cy.get(`a[href='${this.porfolioUrl}']`).click();
cy.get(`a[href='${this.porfolioUrl}']`).should('be.visible').click();
cy.url().should('include', '/portfolio');
cy.getByTestId('portfolio');
}
navigateToMarkets() {
cy.get(`a[href='${this.marketsUrl}']`)
.should('be.visible')
.click({ force: true });
cy.get(`a[href='${this.marketsUrl}']`).should('be.visible').click();
cy.url().should('include', '/markets');
}
navigateToConnectVegaWallet() {
cy.getByTestId(this.connectVegaBtn).click();
cy.contains('Connects using REST to a running Vega wallet service');
cy.getByTestId(this.walletConnectors).find('button').click();
}
fillInWalletForm(walletName, walletPassphrase) {
cy.getByTestId(this.walletForm)
.find('#wallet')
.click({ force: true })
.type(walletName);
cy.getByTestId(this.walletForm)
.find('#passphrase')
.click({ force: true })
.type(walletPassphrase);
}
clickConnectVegaWallet() {
cy.getByTestId(this.walletForm)
.find('button[type=submit]')
.click({ force: true });
}
validateWalletNotRunningError() {
cy.getByTestId(this.walletFormError).should(
'have.text',
'Authentication failed'
);
}
validateWalletErrorFieldsDisplayed() {
cy.getByTestId(this.walletInputError).should('have.text', 'Required');
cy.getByTestId(this.inputError).should('have.text', 'Required');
cy.getByTestId('markets');
}
}

View File

@ -7,15 +7,10 @@ export default class MarketPage extends BasePage {
'tradableInstrument.instrument.product.settlementAsset.symbol';
marketRowPrices = 'flash-cell';
marketRowDescription = 'name';
chartTab = 'chart';
ticketTab = 'ticket';
orderbookTab = 'orderbook';
ordersTab = 'orders';
positionsTab = 'positions';
collateralTab = 'collateral';
tradesTab = 'trades';
completedTrades = 'market-trades';
orderBookTab = 'orderbook';
validateMarketsAreDisplayed() {
cy.get('.ag-root-wrapper').should('be.visible');
}
validateMarketTableDisplayed() {
const expectedMarketHeaders = [
@ -58,39 +53,7 @@ export default class MarketPage extends BasePage {
);
}
validateCompletedTradesDisplayed() {
cy.getByTestId(this.completedTrades).should('not.be.empty');
}
clickOnMarket(marketText) {
cy.contains(marketText).click();
}
clickOnActiveMarket() {
cy.contains('Active', { timeout: 8000 }).click({ force: true });
}
clickOnTopMarketRow() {
cy.get('[col-id="data"]').eq(1).click();
}
clickOnOrdersTab() {
cy.getByTestId(this.ordersTab).click();
}
clickOnTicketTab() {
cy.getByTestId(this.ticketTab).click();
}
clickOnCollateralTab() {
cy.getByTestId(this.collateralTab).click();
}
clickOnTradesTab() {
cy.getByTestId(this.tradesTab).click();
}
clickOrderBookTab() {
cy.getByTestId(this.orderBookTab).click();
clickOnMarket(text) {
cy.contains(text).click();
}
}

View File

@ -0,0 +1,3 @@
import BasePage from './base-page';
export default class PortfolioPage extends BasePage {}

View File

@ -0,0 +1,33 @@
import BasePage from './base-page';
export default class TradingPage extends BasePage {
chartTab = 'chart';
ticketTab = 'ticket';
orderbookTab = 'orderbook';
ordersTab = 'orders';
positionsTab = 'positions';
collateralTab = 'collateral';
tradesTab = 'trades';
completedTrades = 'market-trades';
orderBookTab = 'orderbook';
clickOnOrdersTab() {
cy.getByTestId(this.ordersTab).click();
}
clickOnTicketTab() {
cy.getByTestId(this.ticketTab).click();
}
clickOnCollateralTab() {
cy.getByTestId(this.collateralTab).click();
}
clickOnTradesTab() {
cy.getByTestId(this.tradesTab).click();
}
clickOrderBookTab() {
cy.getByTestId(this.orderBookTab).click();
}
}

View File

@ -1,27 +0,0 @@
import { Given, When } from 'cypress-cucumber-preprocessor/steps';
import MarketsPage from '../pages/markets-page';
import DealTicketPage from '../pages/deal-ticket-page';
const marketsPage = new MarketsPage();
const dealTicketPage = new DealTicketPage();
Given('I am on the homepage', () => {
cy.visit('/');
});
Given('I navigate to markets page', () => {
marketsPage.navigateToMarkets();
});
Given('I navigate to portfolio page', () => {
marketsPage.navigateToPortfolio();
});
When('I connect to Vega Wallet', () => {
marketsPage.navigateToConnectVegaWallet();
marketsPage.fillInWalletForm(
'UI_Trading_Test',
Cypress.env('tradingWalletPassphrase')
);
marketsPage.clickConnectVegaWallet();
dealTicketPage.reloadPageIfPublicKeyErrorDisplayed();
});

View File

@ -0,0 +1,5 @@
import { Given } from 'cypress-cucumber-preprocessor/steps';
Given('I am on the homepage', () => {
cy.visit('/');
});

View File

@ -0,0 +1,52 @@
import { Then, When } from 'cypress-cucumber-preprocessor/steps';
import DealTicket from '../trading-windows/deal-ticket';
const dealTicket = new DealTicket();
When('I place a buy {string} market order', (orderType) => {
dealTicket.placeMarketOrder(true, 100, orderType);
dealTicket.clickPlaceOrder();
});
When('I place a sell {string} market order', (orderType) => {
dealTicket.placeMarketOrder(false, 100, orderType);
dealTicket.clickPlaceOrder();
});
When('I place a buy {string} limit order', (limitOrderType) => {
dealTicket.placeLimitOrder(true, 100, 2000, limitOrderType);
dealTicket.clickPlaceOrder();
});
When('I place a sell {string} limit order', (limitOrderType) => {
dealTicket.placeLimitOrder(false, 100, 2000, limitOrderType);
dealTicket.clickPlaceOrder();
});
When('I place a buy {string} market order with amount of 0', (orderType) => {
dealTicket.placeMarketOrder(true, 0, orderType);
dealTicket.clickPlaceOrder();
});
Then('order request is sent', () => {
dealTicket.verifyOrderRequestSent();
});
Then('error message for insufficient funds is displayed', () => {
dealTicket.verifyOrderFailedInsufficientFunds();
});
Then('place order button is disabled', () => {
dealTicket.verifyPlaceOrderBtnDisabled();
});
Then('{string} error is shown', (errorMsg) => {
dealTicket.verifySubmitBtnErrorText(errorMsg);
});
Then(
'Order rejected by wallet error shown containing text {string}',
(expectedError) => {
dealTicket.verifyOrderRejected(expectedError);
}
);

View File

@ -1,22 +1,23 @@
import { Then, When } from 'cypress-cucumber-preprocessor/steps';
import MarketsPage from '../pages/markets-page';
const marketsPage = new MarketsPage();
import VegaWallet from '../vega-wallet';
const vegaWallet = new VegaWallet();
When('I try to connect Vega wallet with incorrect details', () => {
marketsPage.navigateToConnectVegaWallet();
marketsPage.fillInWalletForm('name', 'wrong passphrase');
marketsPage.clickConnectVegaWallet();
vegaWallet.openVegaWalletConnectDialog();
vegaWallet.fillInWalletForm('name', 'wrong passphrase');
vegaWallet.clickConnectVegaWallet();
});
When('I try to connect Vega wallet with blank fields', () => {
marketsPage.navigateToConnectVegaWallet();
marketsPage.clickConnectVegaWallet();
vegaWallet.openVegaWalletConnectDialog();
vegaWallet.clickConnectVegaWallet();
});
Then('wallet not running error message is displayed', () => {
marketsPage.validateWalletNotRunningError();
vegaWallet.validateWalletNotRunningError();
});
Then('wallet field validation errors are shown', () => {
marketsPage.validateWalletErrorFieldsDisplayed();
vegaWallet.validateWalletErrorFieldsDisplayed();
});

View File

@ -1,54 +0,0 @@
import { Then, When } from 'cypress-cucumber-preprocessor/steps';
import DealTicketPage from '../pages/deal-ticket-page';
const dealTicketPage = new DealTicketPage();
When('place a buy {string} market order', (orderType) => {
dealTicketPage.placeMarketOrder(true, 100, orderType);
dealTicketPage.clickPlaceOrder();
});
When('place a sell {string} market order', (orderType) => {
dealTicketPage.placeMarketOrder(false, 100, orderType);
dealTicketPage.clickPlaceOrder();
});
When('place a buy {string} limit order', (limitOrderType) => {
dealTicketPage.placeLimitOrder(true, 100, 2000, limitOrderType);
dealTicketPage.clickPlaceOrder();
});
When('place a sell {string} limit order', (limitOrderType) => {
dealTicketPage.placeLimitOrder(false, 100, 2000, limitOrderType);
dealTicketPage.clickPlaceOrder();
});
When('place a buy {string} market order with amount of 0', (orderType) => {
dealTicketPage.placeMarketOrder(true, 0, orderType);
dealTicketPage.clickPlaceOrder();
});
Then('order request is sent', () => {
if (Cypress.env('bypassPlacingOrders' != true)) {
dealTicketPage.verifyOrderRequestSent();
}
});
Then('error message for insufficient funds is displayed', () => {
dealTicketPage.verifyOrderFailedInsufficientFunds();
});
Then('place order button is disabled', () => {
dealTicketPage.verifyPlaceOrderBtnDisabled();
});
Then('{string} error is shown', (errorMsg) => {
dealTicketPage.verifySubmitBtnErrorText(errorMsg);
});
Then(
'Order rejected by wallet error shown containing text {string}',
(expectedError) => {
dealTicketPage.verifyOrderRejected(expectedError);
}
);

View File

@ -1,23 +1,47 @@
import { When, Then } from 'cypress-cucumber-preprocessor/steps';
import { And, Given, Then, When } from 'cypress-cucumber-preprocessor/steps';
import { hasOperationName } from '..';
import MarketsPage from '../pages/markets-page';
// eslint-disable-next-line @nrwl/nx/enforce-module-boundaries
import { generateMarkets } from '../../../../../libs/market-list/src/__tests__/generate-markets';
const marketsPage = new MarketsPage();
When('I click on market for {string}', (marketText) => {
marketsPage.clickOnMarket(marketText);
const mockMarkets = () => {
cy.mockGQL('Markets', (req) => {
if (hasOperationName(req, 'Markets')) {
req.reply({
body: { data: generateMarkets() },
});
}
});
};
beforeEach(() => {
mockMarkets();
});
When('I click on active market', () => {
if (Cypress.env('bypassPlacingOrders' != true)) {
marketsPage.clickOnMarket('Active');
} else {
marketsPage.clickOnTopMarketRow();
}
Then('I navigate to markets page', () => {
marketsPage.navigateToMarkets();
});
When('I click on suspended market', () => {
marketsPage.clickOnMarket('Suspended');
Given('I am on the markets page', () => {
cy.visit('/markets');
cy.wait('@Markets');
});
Then('the market table is displayed', () => {
Then('I can view markets', () => {
cy.wait('@Markets');
marketsPage.validateMarketsAreDisplayed();
});
And('the market table is displayed', () => {
marketsPage.validateMarketTableDisplayed();
});
When('I click on an active market', () => {
marketsPage.clickOnMarket('Active');
});
When('I click on a suspended market', () => {
marketsPage.clickOnMarket('Suspended');
});

View File

@ -0,0 +1,8 @@
import { Then } from 'cypress-cucumber-preprocessor/steps';
import PortfolioPage from '../pages/portfolio-page';
const portfolioPage = new PortfolioPage();
Then('I navigate to portfolio page', () => {
portfolioPage.navigateToPortfolio();
});

View File

@ -0,0 +1,68 @@
import { Given } from 'cypress-cucumber-preprocessor/steps';
import { hasOperationName } from '..';
import { MarketState } from '@vegaprotocol/types';
/* eslint-disable @nrwl/nx/enforce-module-boundaries */
import { generateTrades } from '../../../../../libs/trades/src/__tests__';
import {
generateChart,
generateCandles,
} from '../../../../../libs/chart/src/__tests__';
import { generateDealTicketQuery } from '../../../../../libs/deal-ticket/src/__tests__';
import { generateMarket } from '../../../../trading/pages/markets/__tests__';
/* eslint-enable @nrwl/nx/enforce-module-boundaries */
const mockMarket = (state) => {
cy.mockGQL('Market', (req) => {
if (hasOperationName(req, 'Market')) {
req.reply({
body: {
data: generateMarket({
market: {
name: `${state.toUpperCase()} MARKET`,
},
}),
},
});
}
if (hasOperationName(req, 'DealTicketQuery')) {
req.reply({
body: { data: generateDealTicketQuery({ market: { state } }) },
});
}
if (hasOperationName(req, 'Trades')) {
req.reply({
body: { data: generateTrades() },
});
}
if (hasOperationName(req, 'Chart')) {
req.reply({
body: { data: generateChart() },
});
}
if (hasOperationName(req, 'Candles')) {
req.reply({
body: { data: generateCandles() },
});
}
});
};
Given('I am on the trading page for an active market', () => {
mockMarket(MarketState.Active);
cy.visit('/markets/market-id');
cy.wait('@Market');
cy.contains('Market: ACTIVE MARKET');
});
Given('I am on the trading page for a suspended market', () => {
mockMarket(MarketState.Suspended);
cy.visit('/markets/market-id');
cy.wait('@Market');
cy.contains('Market: SUSPENDED MARKET');
});

View File

@ -0,0 +1,30 @@
import { When } from 'cypress-cucumber-preprocessor/steps';
import VegaWallet from '../vega-wallet';
const vegaWallet = new VegaWallet();
beforeEach(() => {
// The two important values here are the signature.value and the From.Pubkey.
// From.PubKey is the first public key in the UI_Trading_Test wallet. We assert that the returned pubkey matches
// what is used to sign the transaction
// The tx.signature.value isn't currently used in any tests but the value returned is used to create IDs objects such
// as orders, if its invalid an erro will be thrown
cy.mockVegaCommandSync({
txHash: 'test-tx-hash',
tx: {
signature: {
value:
'd86138bba739bbc1069b3dc975d20b3a1517c2b9bdd401c70eeb1a0ecbc502ec268cf3129824841178b8b506b0b7d650c76644dbd96f524a6cb2158fb7121800',
},
},
});
});
When('I connect to Vega Wallet', () => {
vegaWallet.openVegaWalletConnectDialog();
vegaWallet.fillInWalletForm(
'UI_Trading_Test',
Cypress.env('tradingWalletPassphrase')
);
vegaWallet.clickConnectVegaWallet();
});

View File

@ -1,5 +1,4 @@
import BasePage from './base-page';
export default class DealTicketPage extends BasePage {
export default class DealTicket {
marketOrderType = 'order-type-TYPE_MARKET';
limitOrderType = 'order-type-TYPE_LIMIT';
buyOrder = 'order-side-SIDE_BUY';
@ -53,8 +52,7 @@ export default class DealTicketPage extends BasePage {
);
cy.getByTestId(this.orderTransactionHash)
.invoke('text')
.should('contain', 'Tx hash: ')
.and('have.length.above', 64);
.should('contain', 'Tx hash: test-tx-hash');
}
verifyOrderFailedInsufficientFunds() {
@ -65,9 +63,8 @@ export default class DealTicketPage extends BasePage {
}
clickPlaceOrder() {
if (Cypress.env('bypassPlacingOrders' != true)) {
cy.getByTestId(this.placeOrderBtn).click();
}
cy.getByTestId(this.placeOrderBtn).click();
cy.contains('Awaiting network confirmation');
}
verifyPlaceOrderBtnDisabled() {
@ -75,7 +72,10 @@ export default class DealTicketPage extends BasePage {
}
verifySubmitBtnErrorText(expectedText) {
cy.getByTestId(this.inputError).should('have.text', expectedText);
cy.getByTestId('dealticket-error-message').should(
'have.text',
expectedText
);
}
verifyOrderRejected(errorMsg) {

View File

@ -0,0 +1,43 @@
export default class VegaWallet {
connectVegaBtn = 'connect-vega-wallet';
walletConnectors = 'connectors-list';
walletForm = 'rest-connector-form';
walletInputError = 'input-wallet-error';
walletFormError = 'form-error';
inputError = 'input-error-text';
openVegaWalletConnectDialog() {
cy.getByTestId(this.connectVegaBtn).click();
cy.contains('Connects using REST to a running Vega wallet service');
cy.getByTestId(this.walletConnectors).find('button').click();
}
fillInWalletForm(walletName, walletPassphrase) {
cy.getByTestId(this.walletForm)
.find('#wallet')
.click({ force: true })
.type(walletName);
cy.getByTestId(this.walletForm)
.find('#passphrase')
.click({ force: true })
.type(walletPassphrase);
}
clickConnectVegaWallet() {
cy.getByTestId(this.walletForm)
.find('button[type=submit]')
.click({ force: true });
}
validateWalletNotRunningError() {
cy.getByTestId(this.walletFormError).should(
'have.text',
'Authentication failed'
);
}
validateWalletErrorFieldsDisplayed() {
cy.getByTestId(this.walletInputError).should('have.text', 'Required');
cy.getByTestId(this.inputError).should('have.text', 'Required');
}
}

View File

@ -4,7 +4,15 @@
"sourceMap": false,
"outDir": "../../dist/out-tsc",
"allowJs": true,
"types": ["cypress", "node"]
"types": ["cypress", "node"],
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*.ts", "src/**/*.js"]
}

View File

@ -60,7 +60,7 @@ function VegaTradingApp({ Component, pageProps }: AppProps) {
<ThemeSwitcher onToggle={toggleTheme} className="-my-4" />
</div>
</div>
<main>
<main data-testid={pageProps.page}>
<Component {...pageProps} />
</main>
<VegaConnectDialog

View File

@ -22,4 +22,8 @@ export function Index() {
);
}
Index.getInitialProps = () => ({
page: 'home',
});
export default Index;

View File

@ -60,6 +60,10 @@ const MarketPage = () => {
);
};
MarketPage.getInitialProps = () => ({
page: 'market',
});
export default MarketPage;
const useWindowSize = () => {

View File

@ -0,0 +1,15 @@
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import type { Market } from '../__generated__/Market';
export const generateMarket = (override?: PartialDeep<Market>): Market => {
const defaultResult = {
market: {
id: 'market-id',
name: 'MARKET NAME',
__typename: 'Market',
},
};
return merge(defaultResult, override);
};

View File

@ -0,0 +1 @@
export * from './generate-market';

View File

@ -4,4 +4,8 @@ const Markets = () => {
return <MarketsContainer />;
};
Markets.getInitialProps = () => ({
page: 'markets',
});
export default Markets;

View File

@ -31,4 +31,8 @@ const Deposit = () => {
);
};
Deposit.getInitialProps = () => ({
page: 'deposit',
});
export default Deposit;

View File

@ -5,8 +5,8 @@ import { useVegaWallet } from '@vegaprotocol/wallet';
const Portfolio = () => {
const { keypair } = useVegaWallet();
return (
<div>
<h1>{t('Portfolio')}</h1>
<div className="p-24">
<h1 className="text-h3 mb-12">{t('Portfolio')}</h1>
{keypair && <p>{t(`Keypair: ${keypair.name} ${keypair.pub}`)}</p>}
<div className="flex gap-4">
<AnchorButton href="/portfolio/deposit">{t('Deposit')}</AnchorButton>
@ -18,4 +18,8 @@ const Portfolio = () => {
);
};
Portfolio.getInitialProps = () => ({
page: 'portfolio',
});
export default Portfolio;

View File

@ -0,0 +1,56 @@
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import type {
Candles,
Candles_market_candles,
} from '../lib/__generated__/Candles';
export const generateCandles = (override?: PartialDeep<Candles>): Candles => {
const candles: Candles_market_candles[] = [
{
datetime: '2022-04-06T09:15:00Z',
high: '17481092',
low: '17403651',
open: '17458833',
close: '17446470',
volume: '82721',
__typename: 'Candle',
},
{
datetime: '2022-04-06T09:30:00Z',
high: '17491202',
low: '17361138',
open: '17446470',
close: '17367174',
volume: '62637',
__typename: 'Candle',
},
{
datetime: '2022-04-06T09:45:00Z',
high: '17424522',
low: '17337719',
open: '17367174',
close: '17376455',
volume: '60259',
__typename: 'Candle',
},
];
const defaultResult = {
market: {
id: 'market-id',
decimalPlaces: 5,
tradableInstrument: {
instrument: {
id: '',
name: 'Apple Monthly (30 Jun 2022)',
code: 'AAPL.MF21',
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
candles,
__typename: 'Market',
},
};
return merge(defaultResult, override);
};

View File

@ -0,0 +1,27 @@
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import type {
Chart,
Chart_market_data_priceMonitoringBounds,
} from '../lib/__generated__/Chart';
export const generateChart = (override?: PartialDeep<Chart>): Chart => {
const priceMonitoringBound: Chart_market_data_priceMonitoringBounds = {
minValidPrice: '16256291',
maxValidPrice: '18296869',
referencePrice: '17247489',
__typename: 'PriceMonitoringBounds',
};
const defaultResult = {
market: {
decimalPlaces: 5,
data: {
priceMonitoringBounds: [priceMonitoringBound],
__typename: 'MarketData',
},
__typename: 'Market',
},
};
return merge(defaultResult, override);
};

View File

@ -0,0 +1,2 @@
export * from './generate-candles';
export * from './generate-chart';

View File

@ -1,5 +1,9 @@
import { addGetTestIdcommand } from './lib/commands/get-by-test-id';
import { addMockGQLCommand } from './lib/commands/mock-gql';
import { addMockVegaWalletCommands } from './lib/commands/mock-vega-wallet';
import { addSlackCommand } from './lib/commands/slack';
addGetTestIdcommand();
addSlackCommand();
addMockGQLCommand();
addMockVegaWalletCommands();

View File

@ -0,0 +1,21 @@
import type { RouteHandler } from 'cypress/types/net-stubbing';
// eslint-disable-next-line @typescript-eslint/no-namespace
declare namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
mockGQL(alias: string, handler: RouteHandler): void;
}
}
export function addMockGQLCommand() {
Cypress.Commands.add(
// @ts-ignore - ignoring Cypress type error which gets resolved when Cypress uses the command
'mockGQL',
(alias: string, handler: RouteHandler) => {
cy.intercept('POST', 'https://lb.testnet.vega.xyz/query', handler).as(
alias
);
}
);
}

View File

@ -0,0 +1,45 @@
import merge from 'lodash/merge';
import type { TransactionResponse } from '@vegaprotocol/vegawallet-service-api-client';
import type { PartialDeep } from 'type-fest';
// eslint-disable-next-line @typescript-eslint/no-namespace
declare namespace Cypress {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
interface Chainable<Subject> {
mockVegaCommandSync(txHash: string, signature: string): void;
}
}
export function addMockVegaWalletCommands() {
Cypress.Commands.add(
// @ts-ignore - ignoring Cypress type error which gets resolved when Cypress uses the command
'mockVegaCommandSync',
(override?: PartialDeep<TransactionResponse>) => {
const defaultTransactionResponse = {
txHash: 'tx-hash',
tx: {
input_data:
'CPe6vpiqsPqxDBDC1w7KPkoKQGE4Y2M0NjUwMjhiMGY4OTM4YTYzZTEzNDViYzM2ODc3ZWRmODg4MjNmOWU0ZmI4ZDRlN2VkMmFlMzAwNzA3ZTMYASABKAM4Ag==',
signature: {
// sig value needs to be 'real' so sigToId function doesn't error out
value: 'signature',
algo: 'vega/ed25519',
version: 1,
},
From: {
PubKey: Cypress.env('vegaPublicKey'),
},
version: 2,
pow: {
tid: '0CEEC2FDFDB5D68BC0C1E2640440E4AA11E49986CB2929E0F3572E16CB7DC59C',
nonce: 23525,
},
},
};
cy.intercept('POST', 'http://localhost:1789/api/v1/command/sync', {
body: merge(defaultTransactionResponse, override),
}).as('VegaCommandSync');
}
);
}

View File

@ -11,8 +11,13 @@
}
],
"compilerOptions": {
"allowJs": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitOverride": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}

View File

@ -0,0 +1,37 @@
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import type { DealTicketQuery } from '../__generated__/DealTicketQuery';
export const generateDealTicketQuery = (
override?: PartialDeep<DealTicketQuery>
): DealTicketQuery => {
const defaultResult: DealTicketQuery = {
market: {
id: 'market-id',
decimalPlaces: 2,
state: MarketState.Active,
tradingMode: MarketTradingMode.Continuous,
tradableInstrument: {
instrument: {
product: {
quoteName: 'BTC',
__typename: 'Future',
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
depth: {
__typename: 'MarketDepth',
lastTrade: {
__typename: 'Trade',
price: '100',
},
},
__typename: 'Market',
},
};
return merge(defaultResult, override);
};

View File

@ -0,0 +1 @@
export * from './generate-deal-ticket-query';

View File

@ -79,7 +79,11 @@ export const SubmitButton = ({
>
{transactionStatus === 'pending' ? t('Pending...') : t('Place order')}
</Button>
{invalidText && <InputError className="mb-8">{invalidText}</InputError>}
{invalidText && (
<InputError className="mb-8" data-testid="dealticket-error-message">
{invalidText}
</InputError>
)}
</>
);
};

View File

@ -0,0 +1,78 @@
import merge from 'lodash/merge';
import { MarketState, MarketTradingMode } from '@vegaprotocol/types';
import type { PartialDeep } from 'type-fest';
import type { Markets, Markets_markets } from '../lib/__generated__/Markets';
export const generateMarkets = (override?: PartialDeep<Markets>): Markets => {
const markets: Markets_markets[] = [
{
id: 'market-id',
name: 'ACTIVE MARKET',
decimalPlaces: 5,
data: {
market: {
id: '10cd0a793ad2887b340940337fa6d97a212e0e517fe8e9eab2b5ef3a38633f35',
state: MarketState.Active,
tradingMode: MarketTradingMode.Continuous,
__typename: 'Market',
},
bestBidPrice: '0',
bestOfferPrice: '0',
markPrice: '4612690058',
__typename: 'MarketData',
},
tradableInstrument: {
instrument: {
code: 'BTCUSD.MF21',
product: {
settlementAsset: {
symbol: 'tDAI',
__typename: 'Asset',
},
__typename: 'Future',
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
__typename: 'Market',
},
{
id: 'test-market-suspended',
name: 'SUSPENDED MARKET',
decimalPlaces: 2,
data: {
market: {
id: '34d95e10faa00c21d19d382d6d7e6fc9722a96985369f0caec041b0f44b775ed',
state: MarketState.Suspended,
tradingMode: MarketTradingMode.Continuous,
__typename: 'Market',
},
bestBidPrice: '0',
bestOfferPrice: '0',
markPrice: '8441',
__typename: 'MarketData',
},
tradableInstrument: {
instrument: {
code: 'SOLUSD',
product: {
settlementAsset: {
symbol: 'XYZalpha',
__typename: 'Asset',
},
__typename: 'Future',
},
__typename: 'Instrument',
},
__typename: 'TradableInstrument',
},
__typename: 'Market',
},
];
const defaultResult = {
markets,
};
return merge(defaultResult, override);
};

View File

@ -0,0 +1,53 @@
import merge from 'lodash/merge';
import type { PartialDeep } from 'type-fest';
import type { Trades, Trades_market_trades } from '../lib/__generated__/Trades';
export const generateTrades = (override?: PartialDeep<Trades>): Trades => {
const trades: Trades_market_trades[] = [
{
id: 'FFFFBC80005C517A10ACF481F7E6893769471098E696D0CC407F18134044CB16',
price: '17116898',
size: '24',
createdAt: '2022-04-06T16:19:42.692598951Z',
market: {
id: '0c3c1490db767f926d24fb674b4235a9aa339614915a4ab96cbfc0e1ad83c0ff',
decimalPlaces: 5,
__typename: 'Market',
},
__typename: 'Trade',
},
{
id: 'FFFFB91453AC8F26EDAC223E2FB6C4A61461B1837946B51D943D675FB94FDF72',
price: '17209102',
size: '7',
createdAt: '2022-04-07T06:59:44.835686754Z',
market: {
id: '0c3c1490db767f926d24fb674b4235a9aa339614915a4ab96cbfc0e1ad83c0ff',
decimalPlaces: 5,
__typename: 'Market',
},
__typename: 'Trade',
},
{
id: 'FFFFAD1BF47AA2853E5C375B6B3A62375F62D5B10807583D32EF3119CC455CD1',
price: '17106734',
size: '18',
createdAt: '2022-04-07T17:56:47.997938583Z',
market: {
id: '0c3c1490db767f926d24fb674b4235a9aa339614915a4ab96cbfc0e1ad83c0ff',
decimalPlaces: 5,
__typename: 'Market',
},
__typename: 'Trade',
},
];
const defaultResult = {
market: {
id: 'market-id',
trades,
__typename: 'Market',
},
};
return merge(defaultResult, override);
};

View File

@ -0,0 +1 @@
export * from './generate-trades';

View File

@ -124,6 +124,7 @@
"sass": "1.43.2",
"storybook-addon-themes": "^6.1.0",
"ts-jest": "27.0.5",
"type-fest": "^2.12.2",
"typescript": "~4.5.2",
"url-loader": "^3.0.0"
},

View File

@ -20171,6 +20171,11 @@ type-fest@^0.8.1:
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d"
integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==
type-fest@^2.12.2:
version "2.12.2"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.12.2.tgz#80a53614e6b9b475eb9077472fb7498dc7aa51d0"
integrity sha512-qt6ylCGpLjZ7AaODxbpyBZSs9fCI9SkL3Z9q2oxMBQhs/uyY+VD8jHA8ULCGmWQJlBgqvO3EJeAngOHD8zQCrQ==
type-is@~1.6.18:
version "1.6.18"
resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"