Compare commits

...
5 changed files with 385 additions and 14 deletions
@@ -7,6 +7,7 @@ import {
waitForSpinner,
navigateTo,
navigation,
turnTelemetryOff,
} from '../../support/common.functions';
import {
clickOnValidatorFromList,
@@ -56,6 +57,7 @@ context(
// 2001-STKE-002, 2001-STKE-032
before('visit staking tab and connect vega wallet', function () {
cy.visit('/');
cy.validatorsSelfDelegate();
ethereumWalletConnect();
// this is a workaround for #2422 which can be removed once issue is resolved
cy.associateTokensToVegaWallet('4');
@@ -67,6 +69,7 @@ context(
'teardown wallet & drill into a specific validator',
function () {
cy.clearLocalStorage();
turnTelemetryOff();
cy.reload();
waitForSpinner();
cy.connectVegaWallet();
@@ -252,10 +255,10 @@ context(
waitForBeginningOfEpoch();
cy.getByTestId(stakeValidatorListStakePercentage).should(
'have.text',
'100%'
'50.02%'
);
navigateTo(navigation.validators);
validateValidatorListTotalStakeAndShare('0', '2.00', '100.00%');
validateValidatorListTotalStakeAndShare('0', '3,002.00', '50.02%');
}
);
@@ -29,7 +29,10 @@ const performancePenaltyToolTip = '[data-testid="performance-penalty-tooltip"]';
const overstakedPenaltyToolTip = '[data-testid="overstaked-penalty-tooltip"]';
const totalPenaltyToolTip = '[data-testid="total-penalty-tooltip"]';
const epochCountDown = '[data-testid="epoch-countdown"]';
const stakeNumberRegex = /^\d*\.?\d*$/;
const stakeNumberRegex = /^\d{1,3}(,\d{3})*(\.\d+)?$/;
// If running locally, validators need to have self-stake to be displayed
// Run cy.validatorsSelfDelegate() in before hook
context('Validators Page - verify elements on page', function () {
before('navigate to validators page', function () {
@@ -84,13 +87,13 @@ context('Validators Page - verify elements on page', function () {
cy.get(stakedByOperatorToolTip)
.invoke('text')
.should('contain', 'Staked by operator: 0.00');
.should('contain', 'Staked by operator: 3,000.00');
cy.get(stakedByDelegatesToolTip)
.invoke('text')
.should('contain', 'Staked by delegates: 0.00');
cy.get(totalStakedToolTip)
.invoke('text')
.should('contain', 'Total stake: 0.00');
.should('contain', 'Total stake: 3,000.00');
});
it('Should be able to see validator normalised voting power', function () {
@@ -106,10 +109,10 @@ context('Validators Page - verify elements on page', function () {
cy.get(unnormalisedVotingPowerToolTip)
.invoke('text')
.should('contain', 'Unnormalised voting power: 0.00%');
.should('contain', 'Unnormalised voting power: 20.00%');
cy.get(normalisedVotingPowerToolTip)
.invoke('text')
.should('contain', 'Normalised voting power: 0.10%');
.should('contain', 'Normalised voting power: 50.00%');
});
// 2002-SINC-018
@@ -126,13 +129,13 @@ context('Validators Page - verify elements on page', function () {
cy.get(performancePenaltyToolTip)
.invoke('text')
.should('contain', 'Performance penalty: 100.00%');
.should('contain', 'Performance penalty: 0.00%');
cy.get(overstakedPenaltyToolTip)
.invoke('text')
.should('contain', 'Overstaked penalty:'); // value not asserted due to #2886
.should('contain', 'Overstaked penalty: 60.00%'); // value not asserted due to #2886
cy.get(totalPenaltyToolTip)
.invoke('text')
.should('contain', 'Total penalties: 0.00%');
.should('contain', 'Total penalties: 60.00%');
});
it('Should be able to see validator pending stake', function () {
@@ -216,7 +216,7 @@ export const ConsensusValidatorsTable = ({
: undefined,
[ValidatorFields.PENDING_USER_STAKE]: pendingUserStake,
[ValidatorFields.USER_STAKE_SHARE]: userStakeShare
? formatNumberPercentage(new BigNumber(userStakeShare))
? formatNumberPercentage(new BigNumber(userStakeShare), 2)
: undefined,
};
}
@@ -0,0 +1,280 @@
import { VegaDataSource } from './data-source';
import type { ApolloClient } from '@apollo/client';
import { Interval } from 'pennant';
import type {
CandleFieldsFragment,
CandlesQuery,
} from './__generated__/Candles';
import * as Schema from '@vegaprotocol/types';
const returnDataMocks = (nodes: CandleFieldsFragment[]): CandlesQuery => {
return {
data: {
market: {
decimalPlaces: 1,
positionDecimalPlaces: 1,
candlesConnection: {
edges: nodes.map((node) => ({ node })),
},
},
},
} as CandlesQuery;
};
const dataMocks: { [key in Schema.Interval]: Partial<CandleFieldsFragment>[] } =
{
[Schema.Interval.INTERVAL_I1M]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-10T12:05:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I5M]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-10T12:25:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I15M]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-10T13:15:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I1H]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-10T17:00:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I6H]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T12:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-11T18:00:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_I1D]: [
{
__typename: 'Candle',
periodStart: '2023-05-10T00:00:00Z',
lastUpdateInPeriod: '',
close: '10',
volume: '1',
},
{
__typename: 'Candle',
periodStart: '2023-05-15T00:00:00Z',
lastUpdateInPeriod: '',
close: '5',
volume: '2',
},
],
[Schema.Interval.INTERVAL_BLOCK]: [],
};
describe('VegaDataSource', () => {
const marketId = 'marketId';
const partyId = 'partyId';
const client = {
query: jest.fn().mockImplementation(({ variables: { interval } }) => {
return returnDataMocks(
dataMocks[interval as Schema.Interval] as CandleFieldsFragment[]
);
}),
} as unknown as ApolloClient<object>;
it('should be properly initialized', () => {
const dataSource = new VegaDataSource(client, marketId, partyId);
expect(dataSource).toBeInstanceOf(VegaDataSource);
expect(dataSource.onReady).toBeDefined();
expect(dataSource.query).toBeDefined();
expect(dataSource.subscribeData).toBeDefined();
expect(dataSource.unsubscribeData).toBeDefined();
expect(dataSource.decimalPlaces).toBeDefined();
expect(dataSource.positionDecimalPlaces).toBeDefined();
});
describe('query should return continuous data', () => {
it('when interval is I1M', async () => {
const dataSource = new VegaDataSource(client, marketId, partyId);
const data = await dataSource.query(Interval.I1M, '');
expect(data).toHaveLength(6);
expect(data[1]).toStrictEqual({
date: new Date('2023-05-10T12:01:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
expect(data[2]).toStrictEqual({
date: new Date('2023-05-10T12:02:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
});
it('when interval is I5M', async () => {
const dataSource = new VegaDataSource(client, marketId, partyId);
const data = await dataSource.query(Interval.I5M, '');
expect(data).toHaveLength(6);
expect(data[1]).toStrictEqual({
date: new Date('2023-05-10T12:05:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
expect(data[2]).toStrictEqual({
date: new Date('2023-05-10T12:10:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
});
it('when interval is I15M', async () => {
const dataSource = new VegaDataSource(client, marketId, partyId);
const data = await dataSource.query(Interval.I15M, '');
expect(data).toHaveLength(6);
expect(data[1]).toStrictEqual({
date: new Date('2023-05-10T12:15:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
expect(data[2]).toStrictEqual({
date: new Date('2023-05-10T12:30:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
});
it('when interval is I1H', async () => {
const dataSource = new VegaDataSource(client, marketId, partyId);
const data = await dataSource.query(Interval.I1H, '');
expect(data).toHaveLength(6);
expect(data[1]).toStrictEqual({
date: new Date('2023-05-10T13:00:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
expect(data[2]).toStrictEqual({
date: new Date('2023-05-10T14:00:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
});
it('when interval is I6H', async () => {
const dataSource = new VegaDataSource(client, marketId, partyId);
const data = await dataSource.query(Interval.I6H, '');
expect(data).toHaveLength(6);
expect(data[1]).toStrictEqual({
date: new Date('2023-05-10T18:00:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
expect(data[2]).toStrictEqual({
date: new Date('2023-05-11T00:00:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
});
it('when interval is I1D', async () => {
const dataSource = new VegaDataSource(client, marketId, partyId);
const data = await dataSource.query(Interval.I1D, '');
expect(data).toHaveLength(6);
expect(data[1]).toStrictEqual({
date: new Date('2023-05-11T00:00:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
expect(data[2]).toStrictEqual({
date: new Date('2023-05-12T00:00:00Z'),
high: 1,
low: 1,
open: 1,
close: 1,
volume: 0,
});
});
});
});
+88 -3
View File
@@ -1,4 +1,11 @@
import type { ApolloClient } from '@apollo/client';
import type { Duration } from 'date-fns';
import {
add,
differenceInDays,
differenceInHours,
differenceInMinutes,
} from 'date-fns';
import type { Candle, DataSource } from 'pennant';
import { Interval as PennantInterval } from 'pennant';
@@ -153,7 +160,6 @@ export class VegaDataSource implements DataSource {
},
fetchPolicy: 'no-cache',
});
if (data?.market?.candlesConnection?.edges) {
const decimalPlaces = data.market.decimalPlaces;
const positionDecimalPlaces = data.market.positionDecimalPlaces;
@@ -163,8 +169,8 @@ export class VegaDataSource implements DataSource {
.filter((node): node is CandleFieldsFragment => !!node)
.map((node) =>
parseCandle(node, decimalPlaces, positionDecimalPlaces)
);
)
.reduce(checkGranulationContinuity(interval), []);
return candles;
} else {
return [];
@@ -213,6 +219,85 @@ export class VegaDataSource implements DataSource {
}
}
const getDuration = (
interval: PennantInterval,
multiplier: number
): Duration => {
switch (interval) {
case 'I1D':
return {
days: 1 * multiplier,
};
case 'I1H':
return {
hours: 1 * multiplier,
};
case 'I1M':
return {
minutes: 1 * multiplier,
};
case 'I5M':
return {
minutes: 5 * multiplier,
};
case 'I6H':
return {
hours: 6 * multiplier,
};
case 'I15M':
return {
minutes: 15 * multiplier,
};
}
};
const getDifference = (
interval: PennantInterval,
dateLeft: Date,
dateRight: Date
): number => {
switch (interval) {
case 'I1D':
return differenceInDays(dateRight, dateLeft);
case 'I6H':
return differenceInHours(dateRight, dateLeft) / 6;
case 'I1H':
return differenceInHours(dateRight, dateLeft);
case 'I15M':
return differenceInMinutes(dateRight, dateLeft) / 15;
case 'I5M':
return differenceInMinutes(dateRight, dateLeft) / 5;
case 'I1M':
return differenceInMinutes(dateRight, dateLeft);
}
};
const checkGranulationContinuity =
(interval: PennantInterval) =>
(agg: Candle[], candle: Candle, i: number): Candle[] => {
if (agg.length && i) {
const previous = agg[agg.length - 1];
const difference = getDifference(interval, previous.date, candle.date);
if (difference > 1) {
for (let j = 1; j < difference; j++) {
const duration = getDuration(interval, j);
const newStartDate = add(previous.date, duration);
const newParsedCandle: Candle = {
date: newStartDate,
high: previous.close,
low: previous.close,
open: previous.close,
close: previous.close,
volume: 0,
};
agg.push(newParsedCandle);
}
}
}
agg.push(candle);
return agg;
};
function parseCandle(
candle: CandleFieldsFragment,
decimalPlaces: number,