web/storagenode: dependencies for testing and payout store tests added

Change-Id: Iae18d073ba35ba8ac48e2d4c88476b38b96bbd9b
This commit is contained in:
NickolaiYurchenko 2020-04-16 17:18:35 +03:00
parent 9b4a3f8fcc
commit 51bf2b6155
23 changed files with 3365 additions and 2694 deletions

View File

@ -2,7 +2,5 @@
// See LICENSE for copying information.
module.exports = {
presets: [
'@vue/app'
]
}
presets: [ [ "@vue/app", { useBuiltIns: "entry" } ] ]
};

View File

@ -0,0 +1,9 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import { GlobalWithFetchMock } from 'jest-fetch-mock';
const customGlobal: GlobalWithFetchMock = global as GlobalWithFetchMock;
customGlobal.fetch = require('jest-fetch-mock');
customGlobal.fetchMock = customGlobal.fetch;

File diff suppressed because it is too large Load Diff

View File

@ -6,17 +6,15 @@
"serve": "vue-cli-service serve",
"lint": "vue-cli-service lint && stylelint '**/*.{vue,scss}' --fix",
"build": "vue-cli-service build",
"debug": "vue-cli-service build --mode development"
"debug": "vue-cli-service build --mode development",
"test": "vue-cli-service test:unit"
},
"dependencies": {
"chart.js": "2.9.3",
"stylelint": "13.0.0",
"stylelint-config-standard": "19.0.0",
"stylelint-scss": "3.14.2",
"stylelint-webpack-plugin": "1.2.1",
"vue": "2.6.11",
"vue-chartjs": "3.5.0",
"vue-class-component": "7.2.2",
"vue-jest": "3.0.5",
"vue-property-decorator": "8.3.0",
"vue-router": "3.1.5",
"vuex": "3.1.2"
@ -24,18 +22,27 @@
"devDependencies": {
"@babel/core": "7.8.4",
"@babel/plugin-proposal-object-rest-spread": "7.8.3",
"@types/sinon": "7.5.1",
"@vue/cli-plugin-babel": "4.1.2",
"@vue/cli-plugin-typescript": "4.1.2",
"@vue/cli-plugin-unit-jest": "4.1.2",
"@vue/cli-service": "4.2.3",
"@vue/cli-plugin-babel": "4.1.1",
"@vue/cli-plugin-typescript": "4.1.1",
"@vue/cli-plugin-unit-jest": "4.1.1",
"@vue/cli-service": "4.1.1",
"@vue/test-utils": "1.0.0-beta.30",
"babel-core": "7.0.0-bridge.0",
"compression-webpack-plugin": "3.0.1",
"core-js": "3.6.5",
"jest-fetch-mock": "3.0.0",
"node-sass": "4.13.1",
"sass-loader": "8.0.2",
"tslint": "6.0.0",
"sass-loader": "8.0.0",
"sinon": "7.5.0",
"stylelint": "12.0.1",
"stylelint-config-standard": "19.0.0",
"stylelint-scss": "3.13.0",
"stylelint-webpack-plugin": "1.2.1",
"ts-jest": "24.2.0",
"tslint": "5.20.1",
"tslint-consistent-codestyle": "1.16.0",
"tslint-loader": "3.5.4",
"typescript": "3.7.5",
"typescript": "3.7.4",
"vue-svg-loader": "0.15.0",
"vue-template-compiler": "2.6.11",
"vue-tslint": "0.3.2",
@ -87,5 +94,45 @@
"media-feature-colon-space-before": "never",
"media-feature-colon-space-after": "always"
}
},
"jest": {
"automock": false,
"setupFiles": [
"./jestSetup.ts"
],
"globals": {
"ts-jest": {
"diagnostics": false
}
},
"moduleFileExtensions": [
"js",
"jsx",
"json",
"vue",
"ts",
"tsx"
],
"collectCoverage": true,
"transform": {
"^.+\\.js$": "babel-jest",
"^.+\\.vue$": "vue-jest",
".+\\.(css|styl|less|sass|scss|png|jpg|ttf|woff|woff2)$": "jest-transform-stub",
"^.+\\.tsx?$": "ts-jest",
"^.+\\.svg$": "<rootDir>/tests/unit/mock/svgTransform.js"
},
"transformIgnorePatterns": [
"/node_modules/(?!(apollo-client|apollo-link))"
],
"moduleNameMapper": {
"^@/(.*)$": "<rootDir>/src/$1"
},
"snapshotSerializers": [
"jest-serializer-vue"
],
"testMatch": [
"**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)"
],
"testURL": "http://localhost/"
}
}

View File

@ -25,7 +25,7 @@ import { SNO_THEME } from '@/app/types/theme';
components: {
SunIcon,
MoonIcon,
}
},
})
export default class OptionsDropdown extends Vue {
/**

View File

@ -29,7 +29,7 @@ import WalletIcon from '@/../static/images/wallet.svg';
@Component({
components: {
WalletIcon
WalletIcon,
},
})
export default class PayoutArea extends Vue {

View File

@ -475,7 +475,7 @@ export default class SNOContentFilling extends Vue {
line-height: 21px;
&__link {
color: var(--navigation-link-color)
color: var(--navigation-link-color);
}
}
}
@ -501,7 +501,7 @@ export default class SNOContentFilling extends Vue {
line-height: 21px;
&__link {
color: var(--navigation-link-color)
color: var(--navigation-link-color);
}
}
}

View File

@ -126,7 +126,7 @@ export default class SNOContentTitle extends Vue {
public get currentMonth(): string {
const monthNames = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'
'July', 'August', 'September', 'October', 'November', 'December',
];
const date = new Date();

View File

@ -30,12 +30,12 @@ class DayShowingConditions {
}
@Component({
extends: VueChart.Line
extends: VueChart.Line,
})
export default class VChart extends Vue {
@Prop({default: '$'})
private readonly currency: string;
@Prop({default: () => { console.error('Tooltip constructor is undefined'); }, })
@Prop({default: () => { console.error('Tooltip constructor is undefined'); } })
private tooltipConstructor: (tooltipModel) => void;
@Prop({default: {}})
private readonly chartData: ChartData;
@ -69,7 +69,7 @@ export default class VChart extends Vue {
radius: 0,
hoverRadius: 0,
hitRadius: 500,
}
},
},
scales: {
yAxes: [{
@ -80,8 +80,8 @@ export default class VChart extends Vue {
},
gridLines: {
borderDash: [2, 5],
drawBorder: false
}
drawBorder: false,
},
}],
xAxes: [{
display: true,
@ -100,7 +100,7 @@ export default class VChart extends Vue {
layout: {
padding: {
left: 25,
}
},
},
tooltips: {
enabled: false,
@ -111,8 +111,8 @@ export default class VChart extends Vue {
labels: {
enabled: true,
}
}
},
},
};
}

View File

@ -38,7 +38,7 @@ import { RouteConfig } from '@/app/router';
@Component({
components: {
SNONotification,
}
},
})
export default class NotificationsPopup extends Vue {
/**

View File

@ -39,7 +39,7 @@ const monthNames = [
PayoutPeriodCalendar,
BlackArrowExpand,
BlackArrowHide,
}
},
})
export default class EstimationPeriodDropdown extends Vue {
/**

View File

@ -77,7 +77,7 @@ class MonthButton {
@Component({
components: {
GrayArrowLeftIcon,
}
},
})
export default class PayoutPeriodCalendar extends Vue {
private now: Date = new Date();

View File

@ -28,22 +28,22 @@ export const router = new Router({
{
path: RouteConfig.Root.path,
name: RouteConfig.Root.name,
component: DashboardArea
component: DashboardArea,
},
{
path: RouteConfig.Notifications.path,
name: RouteConfig.Notifications.name,
component: NotificationsArea
component: NotificationsArea,
},
{
path: RouteConfig.Payout.path,
name: RouteConfig.Payout.name,
component: PayoutArea
component: PayoutArea,
},
{
path: '*',
name: '404',
component: Page404,
},
]
],
});

View File

@ -8,12 +8,14 @@ import { makeNotificationsModule } from '@/app/store/modules/notifications';
import { makePayoutModule } from '@/app/store/modules/payout';
import { NotificationsHttpApi } from '@/storagenode/api/notifications';
import { PayoutHttpApi } from '@/storagenode/api/payout';
import { SNOApi } from '@/storagenode/api/storagenode';
import { appStateModule } from './modules/appState';
import { node } from './modules/node';
import { makeNodeModule } from './modules/node';
const notificationsApi = new NotificationsHttpApi();
const payoutApi = new PayoutHttpApi();
const nodeApi = new SNOApi();
Vue.use(Vuex);
@ -22,7 +24,7 @@ Vue.use(Vuex);
*/
export const store = new Vuex.Store({
modules: {
node,
node: makeNodeModule(nodeApi),
appStateModule,
notificationsModule: makeNotificationsModule(notificationsApi),
payoutModule: makePayoutModule(payoutApi),

View File

@ -29,141 +29,142 @@ const {
} = NODE_MUTATIONS;
const statusThreshHoldMinutes = 120;
const snoAPI = new SNOApi();
export const node = {
state: {
info: {
id: '',
status: StatusOffline,
lastPinged: new Date(),
startedAt: new Date(),
version: '',
allowedVersion: '',
wallet: '',
isLastVersion: false
},
utilization: {
bandwidth: {
used: 0,
export function makeNodeModule(api: SNOApi) {
return {
state: {
info: {
id: '',
status: StatusOffline,
lastPinged: new Date(),
startedAt: new Date(),
version: '',
allowedVersion: '',
wallet: '',
isLastVersion: false,
},
diskSpace: {
used: 0,
remaining: 1,
available: 1,
utilization: {
bandwidth: {
used: 0,
},
diskSpace: {
used: 0,
remaining: 1,
available: 1,
},
},
},
satellites: new Array<SatelliteInfo>(),
disqualifiedSatellites: new Array<SatelliteInfo>(),
suspendedSatellites: new Array<SatelliteInfo>(),
selectedSatellite: {
id: null,
disqualified: null,
joinDate: new Date(),
},
bandwidthChartData: new Array<BandwidthUsed>(),
egressChartData: new Array<EgressUsed>(),
ingressChartData: new Array<IngressUsed>(),
storageChartData: new Array<Stamp>(),
storageSummary: 0,
bandwidthSummary: 0,
egressSummary: 0,
ingressSummary: 0,
checks: {
uptime: 0,
audit: 0,
},
},
mutations: {
[POPULATE_STORE](state: any, nodeInfo: Dashboard): void {
state.info.id = nodeInfo.nodeID;
state.info.isLastVersion = nodeInfo.isUpToDate;
state.info.version = nodeInfo.version;
state.info.allowedVersion = nodeInfo.allowedVersion;
state.info.wallet = nodeInfo.wallet;
state.utilization.diskSpace.used = nodeInfo.diskSpace.used;
state.utilization.diskSpace.remaining = nodeInfo.diskSpace.available - nodeInfo.diskSpace.used;
state.utilization.diskSpace.available = nodeInfo.diskSpace.available;
state.utilization.bandwidth.used = nodeInfo.bandwidth.used;
state.disqualifiedSatellites = nodeInfo.satellites.filter((satellite: SatelliteInfo) => satellite.disqualified);
state.suspendedSatellites = nodeInfo.satellites.filter((satellite: SatelliteInfo) => satellite.suspended);
state.satellites = nodeInfo.satellites || [];
state.info.status = StatusOffline;
state.info.startedAt = nodeInfo.startedAt;
state.info.lastPinged = nodeInfo.lastPinged;
const minutesPassed = Duration.difference(new Date(), new Date(nodeInfo.lastPinged)) / millisecondsInSecond / secondsInMinute;
if (minutesPassed < statusThreshHoldMinutes) {
state.info.status = StatusOnline;
}
},
[SELECT_SATELLITE](state: any, satelliteInfo: Satellite): void {
const selectedSatellite = state.satellites.find(satellite => satelliteInfo.id === satellite.id);
if (!selectedSatellite) {
return;
}
state.selectedSatellite = {
id: satelliteInfo.id,
disqualified: selectedSatellite.disqualified,
joinDate: satelliteInfo.joinDate,
url: selectedSatellite.url,
suspended: selectedSatellite.suspended,
};
state.checks.audit = parseFloat(parseFloat(`${satelliteInfo.audit.score * 100}`).toFixed(1));
state.checks.uptime = satelliteInfo.uptime.totalCount ? 100 : satelliteInfo.uptime.successCount / satelliteInfo.uptime.totalCount * 100;
},
[SELECT_ALL_SATELLITES](state: any, satelliteInfo: Satellites): void {
state.selectedSatellite = {
satellites: new Array<SatelliteInfo>(),
disqualifiedSatellites: new Array<SatelliteInfo>(),
suspendedSatellites: new Array<SatelliteInfo>(),
selectedSatellite: {
id: null,
disqualified: null,
joinDate: satelliteInfo.joinDate,
};
joinDate: new Date(),
},
bandwidthChartData: new Array<BandwidthUsed>(),
egressChartData: new Array<EgressUsed>(),
ingressChartData: new Array<IngressUsed>(),
storageChartData: new Array<Stamp>(),
storageSummary: 0,
bandwidthSummary: 0,
egressSummary: 0,
ingressSummary: 0,
checks: {
uptime: 0,
audit: 0,
},
},
[SET_DAILY_DATA](state: any, satelliteInfo: Satellite): void {
state.bandwidthChartData = satelliteInfo.bandwidthDaily;
state.egressChartData = satelliteInfo.egressDaily;
state.ingressChartData = satelliteInfo.ingressDaily;
state.storageChartData = satelliteInfo.storageDaily;
state.bandwidthSummary = satelliteInfo.bandwidthSummary;
state.egressSummary = satelliteInfo.egressSummary;
state.ingressSummary = satelliteInfo.ingressSummary;
state.storageSummary = satelliteInfo.storageSummary;
},
},
actions: {
[NODE_ACTIONS.GET_NODE_INFO]: async function ({ commit }: any): Promise<void> {
const response = await snoAPI.dashboard();
mutations: {
[POPULATE_STORE](state: any, nodeInfo: Dashboard): void {
state.info.id = nodeInfo.nodeID;
state.info.isLastVersion = nodeInfo.isUpToDate;
state.info.version = nodeInfo.version;
state.info.allowedVersion = nodeInfo.allowedVersion;
state.info.wallet = nodeInfo.wallet;
state.utilization.diskSpace.used = nodeInfo.diskSpace.used;
state.utilization.diskSpace.remaining = nodeInfo.diskSpace.available - nodeInfo.diskSpace.used;
state.utilization.diskSpace.available = nodeInfo.diskSpace.available;
state.utilization.bandwidth.used = nodeInfo.bandwidth.used;
commit(NODE_MUTATIONS.POPULATE_STORE, response);
},
[NODE_ACTIONS.SELECT_SATELLITE]: async function ({ commit }, id?: string): Promise<void> {
let response: Satellite | Satellites;
if (id) {
response = await snoAPI.satellite(id);
commit(NODE_MUTATIONS.SELECT_SATELLITE, response);
} else {
response = await snoAPI.satellites();
commit(NODE_MUTATIONS.SELECT_ALL_SATELLITES, response);
}
state.disqualifiedSatellites = nodeInfo.satellites.filter((satellite: SatelliteInfo) => satellite.disqualified);
state.suspendedSatellites = nodeInfo.satellites.filter((satellite: SatelliteInfo) => satellite.suspended);
commit(NODE_MUTATIONS.SET_DAILY_DATA, response);
},
},
getters: {
monthsOnNetwork: (state): number => {
const now = new Date();
const secondsInMonthApproximately = 2628000;
const differenceInSeconds = (now.getTime() - state.selectedSatellite.joinDate.getTime()) / 1000;
state.satellites = nodeInfo.satellites || [];
return Math.ceil(differenceInSeconds / secondsInMonthApproximately);
state.info.status = StatusOffline;
state.info.startedAt = nodeInfo.startedAt;
state.info.lastPinged = nodeInfo.lastPinged;
const minutesPassed = Duration.difference(new Date(), new Date(nodeInfo.lastPinged)) / millisecondsInSecond / secondsInMinute;
if (minutesPassed < statusThreshHoldMinutes) {
state.info.status = StatusOnline;
}
},
[SELECT_SATELLITE](state: any, satelliteInfo: Satellite): void {
const selectedSatellite = state.satellites.find(satellite => satelliteInfo.id === satellite.id);
if (!selectedSatellite) {
return;
}
state.selectedSatellite = {
id: satelliteInfo.id,
disqualified: selectedSatellite.disqualified,
joinDate: satelliteInfo.joinDate,
url: selectedSatellite.url,
suspended: selectedSatellite.suspended,
};
state.checks.audit = parseFloat(parseFloat(`${satelliteInfo.audit.score * 100}`).toFixed(1));
state.checks.uptime = satelliteInfo.uptime.totalCount === 0 ? 100 : satelliteInfo.uptime.successCount / satelliteInfo.uptime.totalCount * 100;
},
[SELECT_ALL_SATELLITES](state: any, satelliteInfo: Satellites): void {
state.selectedSatellite = {
id: null,
disqualified: null,
joinDate: satelliteInfo.joinDate,
};
},
[SET_DAILY_DATA](state: any, satelliteInfo: Satellite): void {
state.bandwidthChartData = satelliteInfo.bandwidthDaily;
state.egressChartData = satelliteInfo.egressDaily;
state.ingressChartData = satelliteInfo.ingressDaily;
state.storageChartData = satelliteInfo.storageDaily;
state.bandwidthSummary = satelliteInfo.bandwidthSummary;
state.egressSummary = satelliteInfo.egressSummary;
state.ingressSummary = satelliteInfo.ingressSummary;
state.storageSummary = satelliteInfo.storageSummary;
},
},
},
};
actions: {
[NODE_ACTIONS.GET_NODE_INFO]: async function ({commit}: any): Promise<void> {
const response = await api.dashboard();
commit(NODE_MUTATIONS.POPULATE_STORE, response);
},
[NODE_ACTIONS.SELECT_SATELLITE]: async function ({commit}, id?: string): Promise<void> {
let response: Satellite | Satellites;
if (id) {
response = await api.satellite(id);
commit(NODE_MUTATIONS.SELECT_SATELLITE, response);
} else {
response = await api.satellites();
commit(NODE_MUTATIONS.SELECT_ALL_SATELLITES, response);
}
commit(NODE_MUTATIONS.SET_DAILY_DATA, response);
},
},
getters: {
monthsOnNetwork: (state): number => {
const now = new Date();
const secondsInMonthApproximately = 2628000;
const differenceInSeconds = (now.getTime() - state.selectedSatellite.joinDate.getTime()) / 1000;
return Math.ceil(differenceInSeconds / secondsInMonthApproximately);
},
},
};
}

View File

@ -5,8 +5,10 @@ import {
HeldInfo,
PaymentInfoParameters,
PayoutApi,
PayoutInfoRange, PayoutPeriod,
PayoutState, TotalPayoutInfo,
PayoutInfoRange,
PayoutPeriod,
PayoutState,
TotalPayoutInfo,
} from '@/app/types/payout';
import { TB } from '@/app/utils/converter';
@ -72,7 +74,7 @@ export function makePayoutModule(api: PayoutApi) {
new PayoutPeriod(now.getUTCFullYear(), now.getUTCMonth()),
satelliteId,
));
const currentBandwidthDownload = (rootState.node.egressChartData || [])
.map(data => data.egress.usage)
.reduce((previous, current) => previous + current, 0);
@ -104,7 +106,7 @@ export function makePayoutModule(api: PayoutApi) {
* Returns held percentage depends on number of months that node is online.
* @param startedAt date since node is online.
*/
function getHeldPercentage(startedAt: Date): number {
export function getHeldPercentage(startedAt: Date): number {
const now = new Date();
const secondsInMonthApproximately = 2628000;
const differenceInSeconds = (now.getTime() - startedAt.getTime()) / 1000;

View File

@ -41,7 +41,7 @@ export class NotificationsHttpApi implements NotificationsApi {
item.message,
!!item.readAt,
new Date(item.createdAt),
)
),
);
pageCount = notificationResponse.page.pageCount;

View File

@ -104,7 +104,7 @@ export class PayoutHttpApi implements PayoutApi {
* @returns total payout information
* @throws Error
*/
private async getHeld(path): Promise<HeldInfo> {
public async getHeld(path): Promise<HeldInfo> {
const response = await this.client.get(path);
if (!response.ok) {

View File

@ -0,0 +1,26 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
import {
HeldInfo,
PaymentInfoParameters,
PayoutApi,
TotalPayoutInfo,
} from '@/app/types/payout';
/**
* Mock for PayoutApi.
*/
export class PayoutApiMock implements PayoutApi {
public getHeldInfoByMonth(paymentInfoParameters: PaymentInfoParameters): Promise<HeldInfo> {
return Promise.resolve(new HeldInfo());
}
public getHeldInfoByPeriod(paymentInfoParameters: PaymentInfoParameters): Promise<HeldInfo> {
return Promise.resolve(new HeldInfo());
}
public getTotal(paymentInfoParameters: PaymentInfoParameters): Promise<TotalPayoutInfo> {
return Promise.resolve(new TotalPayoutInfo());
}
}

View File

@ -0,0 +1,17 @@
// Copyright (C) 2019 Storj Labs, Inc.
// See LICENSE for copying information.
const vueJest = require('vue-jest/lib/template-compiler');
module.exports = {
process(content) {
const { render } = vueJest({
content,
attrs: {
functional: false,
},
});
return `module.exports = { render: ${render} }`;
},
};

View File

@ -0,0 +1,182 @@
// Copyright (C) 2020 Storj Labs, Inc.
// See LICENSE for copying information.
import Vuex from 'vuex';
import { makeNodeModule } from '@/app/store/modules/node';
import { getHeldPercentage, makePayoutModule, PAYOUT_ACTIONS, PAYOUT_MUTATIONS } from '@/app/store/modules/payout';
import { HeldInfo, PayoutInfoRange, PayoutPeriod, TotalPayoutInfo } from '@/app/types/payout';
import { PayoutHttpApi } from '@/storagenode/api/payout';
import { SNOApi } from '@/storagenode/api/storagenode';
import { createLocalVue } from '@vue/test-utils';
const Vue = createLocalVue();
const payoutApi = new PayoutHttpApi();
const payoutModule = makePayoutModule(payoutApi);
const nodeApi = new SNOApi();
const nodeModule = makeNodeModule(nodeApi);
Vue.use(Vuex);
const store = new Vuex.Store({ modules: { payoutModule, node: nodeModule } });
const state = store.state as any;
describe('mutations', () => {
beforeEach(() => {
createLocalVue().use(Vuex);
});
it('sets held information', () => {
const heldInfo = new HeldInfo(13, 12, 11);
store.commit(PAYOUT_MUTATIONS.SET_HELD_INFO, heldInfo);
expect(state.payoutModule.heldInfo.usageAtRest).toBe(13);
expect(state.payoutModule.heldInfo.usageGet).toBe(12);
expect(state.payoutModule.heldInfo.usagePut).toBe(11);
});
it('sets total payout information', () => {
const totalInfo = new TotalPayoutInfo(50, 100);
store.commit(PAYOUT_MUTATIONS.SET_TOTAL, totalInfo);
expect(state.payoutModule.totalHeldAmount).toBe(50);
expect(state.payoutModule.totalEarnings).toBe(100);
});
it('sets period range', () => {
const range = new PayoutInfoRange(new PayoutPeriod(2019, 2), new PayoutPeriod(2020, 3));
store.commit(PAYOUT_MUTATIONS.SET_RANGE, range);
if (!state.payoutModule.periodRange.start) {
fail('periodRange.start is null');
}
expect(state.payoutModule.periodRange.start.period).toBe('2019-03');
expect(state.payoutModule.periodRange.end.period).toBe('2020-04');
});
it('sets held percentage', () => {
const expectedHeldPercentage = 75;
store.commit(PAYOUT_MUTATIONS.SET_HELD_PERCENT, expectedHeldPercentage);
expect(state.payoutModule.heldPercentage).toBe(expectedHeldPercentage);
});
});
describe('actions', () => {
beforeEach(() => {
jest.resetAllMocks();
});
it('success get held info by month', async () => {
jest.spyOn(payoutApi, 'getHeldInfoByMonth').mockReturnValue(
Promise.resolve(new HeldInfo(1, 2 , 3, 4, 5)),
);
const range = new PayoutInfoRange(null, new PayoutPeriod(2020, 3));
store.commit(PAYOUT_MUTATIONS.SET_RANGE, range);
await store.dispatch(PAYOUT_ACTIONS.GET_HELD_INFO);
expect(state.payoutModule.heldInfo.usagePut).toBe(3);
expect(state.payoutModule.heldInfo.held).toBe(0);
expect(state.payoutModule.heldPercentage).toBe(getHeldPercentage(new Date()));
});
it('get held info by month throws an error when api call fails', async () => {
jest.spyOn(payoutApi, 'getHeldInfoByMonth').mockImplementation(() => { throw new Error(); });
try {
await store.dispatch(PAYOUT_ACTIONS.GET_HELD_INFO);
expect(true).toBe(false);
} catch (error) {
expect(state.payoutModule.heldInfo.usagePut).toBe(3);
expect(state.payoutModule.heldInfo.held).toBe(0);
}
});
it('success get held info by period', async () => {
jest.spyOn(payoutApi, 'getHeldInfoByPeriod').mockReturnValue(
Promise.resolve(new HeldInfo(1, 2 , 3, 4, 5)),
);
const range = new PayoutInfoRange(new PayoutPeriod(2019, 2), new PayoutPeriod(2020, 3));
store.commit(PAYOUT_MUTATIONS.SET_RANGE, range);
await store.dispatch(PAYOUT_ACTIONS.GET_HELD_INFO);
expect(state.payoutModule.heldInfo.usagePut).toBe(3);
expect(state.payoutModule.heldInfo.held).toBe(0);
});
it('get held info by period throws an error when api call fails', async () => {
jest.spyOn(payoutApi, 'getHeldInfoByPeriod').mockImplementation(() => { throw new Error(); });
try {
await store.dispatch(PAYOUT_ACTIONS.GET_HELD_INFO);
expect(true).toBe(false);
} catch (error) {
expect(state.payoutModule.heldInfo.usagePut).toBe(3);
expect(state.payoutModule.heldInfo.held).toBe(0);
}
});
it('success get total', async () => {
jest.spyOn(payoutApi, 'getTotal').mockReturnValue(
Promise.resolve(new TotalPayoutInfo(10, 20)),
);
await store.dispatch(PAYOUT_ACTIONS.GET_TOTAL);
expect(state.payoutModule.totalHeldAmount).toBe(10);
expect(state.payoutModule.totalEarnings).toBe(0);
});
it('get total throws an error when api call fails', async () => {
jest.spyOn(payoutApi, 'getTotal').mockImplementation(() => { throw new Error(); });
try {
await store.dispatch(PAYOUT_ACTIONS.GET_TOTAL);
expect(true).toBe(false);
} catch (error) {
expect(state.payoutModule.totalHeldAmount).toBe(10);
expect(state.payoutModule.totalEarnings).toBe(0);
}
});
it('success sets period range', async () => {
await store.dispatch(
PAYOUT_ACTIONS.SET_PERIODS_RANGE,
new PayoutInfoRange(
new PayoutPeriod(2020, 1),
new PayoutPeriod(2020, 2),
),
);
expect(state.payoutModule.periodRange.start.period).toBe('2020-02');
expect(state.payoutModule.periodRange.end.period).toBe('2020-03');
});
});
describe('utils functions', () => {
it('get correct help percentage', () => {
const nowTime = new Date().getTime();
const testDifferencesInMilliseconds: number[] = [5e9, 1.4e10, 2.3e10, 4e10];
const expectedHeldPercentages: number[] = [75, 50, 25, 0];
for (let i = 0; i < testDifferencesInMilliseconds.length; i++) {
const date = new Date(nowTime - testDifferencesInMilliseconds[i]);
const heldPercentage = getHeldPercentage(date);
expect(heldPercentage).toBe(expectedHeldPercentages[i]);
}
});
});

View File

@ -14,7 +14,8 @@
"baseUrl": ".",
"strictPropertyInitialization": false,
"types": [
"webpack-env"
"webpack-env",
"jest"
],
"paths": {
"@/*": [
@ -32,7 +33,9 @@
"src/**/*.ts",
"src/**/*.tsx",
"src/**/*.vue",
"src/app/types/svg.d.ts"
"src/app/types/svg.d.ts",
"tests/**/*.ts",
"tests/**/*.tsx"
],
"exclude": [
"node_modules"

View File

@ -35,7 +35,9 @@
"no-eval": true,
"no-invalid-template-strings": true,
"no-invalid-this": true,
"no-misused-new": true,
"no-static-this": true,
"no-trailing-whitespace": true,
"no-var-keyword": true,
"newline-before-return": true,
"object-literal-sort-keys": false,
@ -64,11 +66,14 @@
}]
}],
"prefer-const": true,
"prefer-method-signature": true,
"prefer-switch": [true, {"min-cases": 2}],
"prefer-while": true,
"quotemark": [true, "single", "avoid-escape"],
"semicolon": [true, "always"],
"space-within-parens": 0,
"static-this": true,
"trailing-comma": [true, {"multiline": "always", "singleline": "never"}],
"triple-equals": true,
"typedef": [
true,
@ -80,11 +85,13 @@
true,
"check-branch",
"check-decl",
"check-operator",
"check-module",
"check-operator",
"check-preblock",
"check-rest-spread",
"check-separator",
"check-type-operator",
"check-preblock"
"check-type",
"check-type-operator"
]
}
}