diff --git a/apps/browser-extension-wallet/package.json b/apps/browser-extension-wallet/package.json index 4935b1ce5..d9fafa054 100644 --- a/apps/browser-extension-wallet/package.json +++ b/apps/browser-extension-wallet/package.json @@ -39,14 +39,14 @@ }, "dependencies": { "@ant-design/icons": "^4.7.0", - "@cardano-sdk/cardano-services-client": "0.18.0", + "@cardano-sdk/cardano-services-client": "0.19.0", "@cardano-sdk/core": "0.30.0", "@cardano-sdk/dapp-connector": "0.12.14", - "@cardano-sdk/input-selection": "0.12.26", - "@cardano-sdk/tx-construction": "0.18.2", + "@cardano-sdk/input-selection": "0.12.27", + "@cardano-sdk/tx-construction": "0.18.3", "@cardano-sdk/util": "0.15.0", - "@cardano-sdk/wallet": "0.36.0", - "@cardano-sdk/web-extension": "0.26.2", + "@cardano-sdk/wallet": "0.37.0", + "@cardano-sdk/web-extension": "0.27.0", "@emurgo/cip14-js": "~3.0.1", "@koralabs/handles-public-api-interfaces": "^1.6.6", "@lace/cardano": "0.1.0", @@ -59,7 +59,7 @@ "@vespaiach/axios-fetch-adapter": "^0.3.0", "antd": "^4.24.10", "are-you-es5": "^2.1.2", - "axios": "0.21.4", + "axios": "0.28.0", "bignumber.js": "9.0.1", "bip39": "^3.0.4", "blake2b-no-wasm": "2.1.4", diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmDRepRetirementContainer.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmDRepRetirementContainer.test.tsx index bf927ebba..ac55acc54 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmDRepRetirementContainer.test.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/ConfirmDRepRetirementContainer.test.tsx @@ -33,7 +33,9 @@ const hash = Crypto.Hash28ByteBase16(Buffer.from('dRepCredentialHashdRepCreden') const getPubDRepKey = async () => await hash; const inMemoryWallet = { - getPubDRepKey, + governance: { + getPubDRepKey + }, assetInfo$, balance: { utxo: { diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/hooks.test.tsx b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/hooks.test.tsx index 56f0beb42..0ce143d7e 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/hooks.test.tsx +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/__tests__/hooks.test.tsx @@ -307,7 +307,9 @@ describe('Testing hooks', () => { mockUseWalletStore.mockReset(); mockUseWalletStore.mockReturnValue({ inMemoryWallet: { - getPubDRepKey: jest.fn(async () => await ed25519PublicKeyHexMock) + governance: { + getPubDRepKey: jest.fn(async () => await ed25519PublicKeyHexMock) + } } }); diff --git a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts index 99785804c..7e40ce065 100644 --- a/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts +++ b/apps/browser-extension-wallet/src/features/dapp/components/confirm-transaction/hooks.ts @@ -205,7 +205,7 @@ export const useGetOwnPubDRepKeyHash = (): UseGetOwnPubDRepKeyHash => { useEffect(() => { if (!inMemoryWallet) return; const get = async () => { - const ownPubDRepKey = await inMemoryWallet.getPubDRepKey(); + const ownPubDRepKey = await inMemoryWallet.governance.getPubDRepKey(); const ownDRepKeyHash = await pubDRepKeyToHash(ownPubDRepKey); setOwnPubDRepKeyHash(ownDRepKeyHash); diff --git a/apps/browser-extension-wallet/src/hooks/useWalletState.ts b/apps/browser-extension-wallet/src/hooks/useWalletState.ts index e4a6aaac5..ea9f95dd5 100644 --- a/apps/browser-extension-wallet/src/hooks/useWalletState.ts +++ b/apps/browser-extension-wallet/src/hooks/useWalletState.ts @@ -52,6 +52,7 @@ const combineObservable = ({ wallet }: Wallet.CardanoWallet): Observable { // communicate from popup to background script window.addEventListener('message', (event) => { + if (event.origin !== AllowedOrigins.TREZOR_CONNECT) throw new Error('Origin not allowed'); if (port && event.source === window && event.data) { port.postMessage({ data: event.data }); } diff --git a/apps/browser-extension-wallet/src/lib/scripts/trezor/trezor-usb-permissions.ts b/apps/browser-extension-wallet/src/lib/scripts/trezor/trezor-usb-permissions.ts index 4fbfff55e..06423a09f 100644 --- a/apps/browser-extension-wallet/src/lib/scripts/trezor/trezor-usb-permissions.ts +++ b/apps/browser-extension-wallet/src/lib/scripts/trezor/trezor-usb-permissions.ts @@ -1,8 +1,5 @@ import { runtime, tabs } from 'webextension-polyfill'; - -// Handling messages from usb permissions iframe - -const url = 'https://connect.trezor.io/8/'; +import { AllowedOrigins } from './types'; /* Handling messages from usb permissions iframe */ const switchToPopupTab = async (event?: BeforeUnloadEvent) => { @@ -21,13 +18,15 @@ const switchToPopupTab = async (event?: BeforeUnloadEvent) => { // find tab by popup pattern and switch to it const currentTabs = await tabs.query({ - url: `${url}popup.html` + url: `${AllowedOrigins.TREZOR_CONNECT_POPUP_BASE_URL}/popup.html` }); if (currentTabs.length < 0) return; tabs.update(currentTabs[0].id, { active: true }); }; window.addEventListener('message', async (event) => { + if (event.origin !== AllowedOrigins.TREZOR_CONNECT) throw new Error('Origin not allowed'); + if (event.data === 'usb-permissions-init') { const iframe = document.querySelector('#trezor-usb-permissions'); if (!iframe || !(iframe instanceof HTMLIFrameElement)) { @@ -55,7 +54,7 @@ window.addEventListener('load', () => { instance.style.border = '0px'; instance.style.width = '100%'; instance.style.height = '100%'; - instance.setAttribute('src', `${url}extension-permissions.html`); + instance.setAttribute('src', `${AllowedOrigins.TREZOR_CONNECT_POPUP_BASE_URL}/extension-permissions.html`); instance.setAttribute('allow', 'usb'); if (document.body) { diff --git a/apps/browser-extension-wallet/src/lib/scripts/trezor/types.ts b/apps/browser-extension-wallet/src/lib/scripts/trezor/types.ts new file mode 100644 index 000000000..e4690c226 --- /dev/null +++ b/apps/browser-extension-wallet/src/lib/scripts/trezor/types.ts @@ -0,0 +1,4 @@ +export enum AllowedOrigins { + TREZOR_CONNECT = 'https://connect.trezor.io', + TREZOR_CONNECT_POPUP_BASE_URL = 'https://connect.trezor.io/8' +} diff --git a/packages/cardano/package.json b/packages/cardano/package.json index eb10574bb..d3dffaf30 100644 --- a/packages/cardano/package.json +++ b/packages/cardano/package.json @@ -38,15 +38,15 @@ "watch": "yarn build --watch" }, "dependencies": { - "@cardano-sdk/cardano-services-client": "0.18.0", + "@cardano-sdk/cardano-services-client": "0.19.0", "@cardano-sdk/core": "0.30.0", "@cardano-sdk/crypto": "0.1.22", - "@cardano-sdk/hardware-ledger": "0.9.0", - "@cardano-sdk/hardware-trezor": "0.4.19", + "@cardano-sdk/hardware-ledger": "0.9.1", + "@cardano-sdk/hardware-trezor": "0.4.20", "@cardano-sdk/key-management": "0.20.1", "@cardano-sdk/util": "0.15.0", - "@cardano-sdk/wallet": "0.36.0", - "@cardano-sdk/web-extension": "0.26.2", + "@cardano-sdk/wallet": "0.37.0", + "@cardano-sdk/web-extension": "0.27.0", "@lace/common": "0.1.0", "@ledgerhq/devices": "^8.2.1", "@stablelib/chacha20poly1305": "1.0.1", diff --git a/packages/common/src/ui/components/Loader/Loader.tsx b/packages/common/src/ui/components/Loader/Loader.tsx index 9aa1930ee..036b3e079 100644 --- a/packages/common/src/ui/components/Loader/Loader.tsx +++ b/packages/common/src/ui/components/Loader/Loader.tsx @@ -8,5 +8,5 @@ export interface LoaderProps { } export const Loader = ({ className }: LoaderProps): React.ReactElement => ( - + ); diff --git a/packages/core/package.json b/packages/core/package.json index 5be490dda..7e7ab54c8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -45,7 +45,7 @@ "@lace/icons": "0.1.0", "@lace/ui": "^0.1.0", "antd": "^4.24.10", - "axios": "0.21.4", + "axios": "0.28.0", "axios-cache-adapter": "2.7.3", "classnames": "^2.3.1", "debounce-promise": "^3.1.2", diff --git a/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupConnectHardwareWalletStepRevamp.tsx b/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupConnectHardwareWalletStepRevamp.tsx index 1e645e54f..c1be02422 100644 --- a/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupConnectHardwareWalletStepRevamp.tsx +++ b/packages/core/src/ui/components/WalletSetupRevamp/WalletSetupConnectHardwareWalletStepRevamp.tsx @@ -36,7 +36,12 @@ export const WalletSetupConnectHardwareWalletStepRevamp = ({ )} {state === 'error' && ( <> - hardware wallet connection error image + hardware wallet connection error image )} diff --git a/packages/e2e-tests/src/assert/multidelegation/MultidelegationPageAssert.ts b/packages/e2e-tests/src/assert/multidelegation/MultidelegationPageAssert.ts index 3fe3f6ffb..e38af3f71 100644 --- a/packages/e2e-tests/src/assert/multidelegation/MultidelegationPageAssert.ts +++ b/packages/e2e-tests/src/assert/multidelegation/MultidelegationPageAssert.ts @@ -8,6 +8,10 @@ import { StakePoolListItem } from '../../elements/multidelegation/StakePoolListI import Tooltip from '../../elements/Tooltip'; import testContext from '../../utils/testContext'; import { StakePoolGridCard } from '../../elements/multidelegation/StakePoolGridCard'; +import { StakePoolListColumnName } from '../../types/staking'; +import { SortingOrder } from '../../types/sortingOrder'; +import { mapColumnNameStringToEnum, sortColumnContent } from '../../utils/stakePoolListContent'; +import { StakePoolListColumn } from '../../enums/StakePoolListColumn'; class MultidelegationPageAssert { assertSeeStakingOnPoolsCounter = async (poolsCount: number) => { @@ -188,36 +192,37 @@ class MultidelegationPageAssert { expect(await firstStakePool.ticker.getText()).to.equal(expectedTicker); }; - assertSeeTooltipForColumn = async (columnName: string) => { + assertSeeTooltipForColumn = async (column: StakePoolListColumn) => { + await MultidelegationPage.tooltip.waitForStable(); await MultidelegationPage.tooltip.waitForDisplayed(); let expectedTooltipText; - switch (columnName) { - case 'Ticker': + switch (column) { + case StakePoolListColumn.Ticker: expectedTooltipText = await t('browsePools.tooltips.ticker', 'staking'); break; - case 'Saturation': + case StakePoolListColumn.Saturation: expectedTooltipText = await t('browsePools.tooltips.saturation', 'staking'); break; - case 'ROS': + case StakePoolListColumn.ROS: expectedTooltipText = await t('browsePools.tooltips.ros', 'staking'); break; - case 'Cost': + case StakePoolListColumn.Cost: expectedTooltipText = await t('browsePools.tooltips.cost', 'staking'); break; - case 'Margin': + case StakePoolListColumn.Margin: expectedTooltipText = await t('browsePools.tooltips.margin', 'staking'); break; - case 'Blocks': + case StakePoolListColumn.Blocks: expectedTooltipText = await t('browsePools.tooltips.blocks', 'staking'); break; - case 'Pledge': + case StakePoolListColumn.Pledge: expectedTooltipText = await t('browsePools.tooltips.pledge', 'staking'); break; - case 'Live Stake': + case StakePoolListColumn.LiveStake: expectedTooltipText = await t('browsePools.tooltips.liveStake', 'staking'); break; default: - throw new Error(`Unsupported column name: ${columnName}`); + throw new Error(`Unsupported column name: ${column}`); } expect(await MultidelegationPage.tooltip.getText()).to.equal(expectedTooltipText); }; @@ -300,6 +305,39 @@ class MultidelegationPageAssert { const cardsInARow = Math.floor(rowWidth / cardWidth); expect(cardsInARow).to.equal(expectedCardsCount); }; + + assertSeeColumnSortingIndicator = async (column: StakePoolListColumnName, order: 'ascending' | 'descending') => { + await ( + await MultidelegationPage.getColumnSortingIndicator(mapColumnNameStringToEnum(column), order) + ).waitForDisplayed(); + }; + + assertSeeStakePoolsSorted = async ( + stakePoolsDisplayType: 'list rows' | 'cards', + sortingOption: StakePoolListColumnName, + order: SortingOrder, + poolLimit?: number + ) => { + await MultidelegationPage.waitForPoolsCounterToBeGreaterThanZero(); + poolLimit ??= await MultidelegationPage.getNumberOfPoolsFromCounter(); + if (stakePoolsDisplayType === 'cards') { + // TODO: add code to handle grid cards - LW-10284 + throw new Error('Please add validation for grid cards sorting'); + } else { + const columnContent = await MultidelegationPage.extractColumnContent( + mapColumnNameStringToEnum(sortingOption), + poolLimit + ); + const sortedColumnContent = await sortColumnContent( + columnContent, + mapColumnNameStringToEnum(sortingOption), + order + ); + + expect(columnContent).to.not.be.empty; + expect(columnContent).to.deep.equal(sortedColumnContent); + } + }; } export default new MultidelegationPageAssert(); diff --git a/packages/e2e-tests/src/assert/onboarding/ConnectYourDevicePageAssert.ts b/packages/e2e-tests/src/assert/onboarding/ConnectYourDevicePageAssert.ts new file mode 100644 index 000000000..5e5b761e7 --- /dev/null +++ b/packages/e2e-tests/src/assert/onboarding/ConnectYourDevicePageAssert.ts @@ -0,0 +1,42 @@ +import ConnectYourDevicePage from '../../elements/onboarding/ConnectYourDevicePage'; +import { t } from '../../utils/translationService'; +import { expect } from 'chai'; +import OnboardingCommonAssert from './onboardingCommonAssert'; + +class ConnectYourDevicePageAssert extends OnboardingCommonAssert { + async assertSeeConnectYourDevicePage() { + await this.assertSeeStepTitle(await t('core.walletSetupConnectHardwareWalletStepRevamp.title')); + // TODO: replace subtitle assertions when USE_TREZOR_HW=true + // await this.assertSeeStepSubtitle(await t('core.walletSetupConnectHardwareWalletStepRevamp.subTitle')); + await this.assertSeeStepSubtitle(await t('core.walletSetupConnectHardwareWalletStepRevamp.subTitleLedgerOnly')); + + await ConnectYourDevicePage.loader.waitForDisplayed(); + + await this.assertSeeBackButton(); + await this.assertSeeTryAgainButton(false); + + await this.assertSeeLegalLinks(); + await this.assertSeeHelpAndSupportButton(); + } + + async assertSeeError(expectedErrorMessage: string) { + await ConnectYourDevicePage.errorImage.waitForDisplayed(); + await ConnectYourDevicePage.banner.container.waitForDisplayed(); + expect(await ConnectYourDevicePage.banner.description.getText()).to.equal(expectedErrorMessage); + } + + async assertSeeTryAgainButton(shouldBeVisible: boolean) { + await ConnectYourDevicePage.tryAgainButton.waitForDisplayed({ reverse: !shouldBeVisible }); + if (shouldBeVisible) { + expect(await ConnectYourDevicePage.tryAgainButton.getText()).to.equal( + await t('core.walletSetupConnectHardwareWalletStepRevamp.errorCta') + ); + } + } + + async assertSeeTryAgainButtonEnabled(shouldBeEnabled: boolean) { + await ConnectYourDevicePage.tryAgainButton.waitForEnabled({ reverse: !shouldBeEnabled }); + } +} + +export default new ConnectYourDevicePageAssert(); diff --git a/packages/e2e-tests/src/assert/onboarding/onboardingConnectHWPageAssert.ts b/packages/e2e-tests/src/assert/onboarding/onboardingConnectHWPageAssert.ts deleted file mode 100644 index db1f3b31d..000000000 --- a/packages/e2e-tests/src/assert/onboarding/onboardingConnectHWPageAssert.ts +++ /dev/null @@ -1,47 +0,0 @@ -import OnboardingConnectHardwareWalletPage from '../../elements/onboarding/connectHardwareWalletPage'; -import { t } from '../../utils/translationService'; -import { expect } from 'chai'; -import OnboardingCommonAssert from './onboardingCommonAssert'; - -class OnboardingConnectHardwareWalletPageAssert extends OnboardingCommonAssert { - async assertSeeConnectHardwareWalletPageSubTitle() { - await OnboardingConnectHardwareWalletPage.subTitle.waitForDisplayed(); - expect(await OnboardingConnectHardwareWalletPage.subTitle.getText()).to.equal( - await t('core.walletSetupConnectHardwareWalletStep.subTitle') - ); - } - - async assertSeeSupportedDevicesText() { - await OnboardingConnectHardwareWalletPage.supportedDevices.waitForDisplayed(); - expect(await OnboardingConnectHardwareWalletPage.supportedDevices.getText()).to.equal( - await t('core.walletSetupConnectHardwareWalletStep.supportedDevices') - ); - } - - async assertSeeLedgerButtonDisplayed() { - await OnboardingConnectHardwareWalletPage.ledgerButton.waitForDisplayed(); - } - - async assertSeeConnectDeviceText() { - await OnboardingConnectHardwareWalletPage.connectDevice.waitForDisplayed(); - expect(await OnboardingConnectHardwareWalletPage.connectDevice.getText()).to.equal( - await t('core.walletSetupConnectHardwareWalletStep.connectDevice') - ); - } - - async assertSeeConnectHardwareWalletPage() { - await this.assertSeeStepTitle(await t('core.walletSetupConnectHardwareWalletStep.title')); - await this.assertSeeConnectHardwareWalletPageSubTitle(); - await this.assertSeeSupportedDevicesText(); - await this.assertSeeLedgerButtonDisplayed(); - await this.assertSeeConnectDeviceText(); - - await this.assertSeeBackButton(); - await this.assertSeeNextButton(); - - await this.assertSeeLegalLinks(); - await this.assertSeeHelpAndSupportButton(); - } -} - -export default new OnboardingConnectHardwareWalletPageAssert(); diff --git a/packages/e2e-tests/src/assert/stakingPageAssert.ts b/packages/e2e-tests/src/assert/stakingPageAssert.ts index d35d8e577..a296741be 100644 --- a/packages/e2e-tests/src/assert/stakingPageAssert.ts +++ b/packages/e2e-tests/src/assert/stakingPageAssert.ts @@ -1,13 +1,8 @@ import StakingPage from '../elements/staking/stakingPage'; import { TestnetPatterns } from '../support/patterns'; -import webTester from '../actor/webTester'; import StakingInfoComponent from '../elements/staking/stakingInfoComponent'; import { t } from '../utils/translationService'; -import { StakePoolListItem } from '../elements/staking/StakePoolListItem'; import StakingSuccessDrawer from '../elements/staking/StakingSuccessDrawer'; -import { Logger } from '../support/logger'; -import StakingExtendedPageObject from '../pageobject/stakingExtendedPageObject'; -import { sortColumnContent } from '../utils/stakePoolListContent'; import { expect } from 'chai'; import { StakePool } from '../data/expectedStakePoolsData'; import StakingPasswordDrawer from '../elements/staking/StakingPasswordDrawer'; @@ -114,18 +109,6 @@ class StakingPageAssert { }); }; - assertStakePoolItemsOrder = async (columnName: string, order: string) => { - const stakePoolListItem = new StakePoolListItem(); - await webTester.waitUntilSeeElement(stakePoolListItem.container(), 60_000); - const columnContent: string[] = await StakingExtendedPageObject.extractColumnContent(columnName); - Logger.log(`EXTRACTED DATA: ${columnContent}`); - const sortedColumnContent = await sortColumnContent(columnContent, columnName, order); - Logger.log(`SORTED DATA: ${sortedColumnContent}`); - - expect(columnContent).to.not.be.empty; - expect(columnContent).to.deep.equal(sortedColumnContent); - }; - assertSeeStakingPasswordDrawer = async () => { await StakingPasswordDrawer.title.waitForDisplayed(); expect(await StakingPasswordDrawer.title.getText()).to.equal( diff --git a/packages/e2e-tests/src/elements/multidelegation/MultidelegationPage.ts b/packages/e2e-tests/src/elements/multidelegation/MultidelegationPage.ts index d6311743d..7c738530d 100644 --- a/packages/e2e-tests/src/elements/multidelegation/MultidelegationPage.ts +++ b/packages/e2e-tests/src/elements/multidelegation/MultidelegationPage.ts @@ -8,11 +8,11 @@ import StakePoolDetails from '../staking/stakePoolDetails'; import testContext from '../../utils/testContext'; import { isPopupMode } from '../../utils/pageUtils'; import CommonDrawerElements from '../CommonDrawerElements'; -import { StakePoolListColumnType } from '../../types/staking'; import { StakePoolListItem } from './StakePoolListItem'; import { StakePoolGridCard } from './StakePoolGridCard'; import StakePoolDetailsDrawer from './StakePoolDetailsDrawer'; import MoreOptionsComponent from './MoreOptionsComponent'; +import { StakePoolListColumn } from '../../enums/StakePoolListColumn'; class MultidelegationPage { private ACTIVITY_TAB = '[data-testid="activity-tab"]'; @@ -41,6 +41,7 @@ class MultidelegationPage { private COLUMN_HEADER_BLOCKS = '[data-testid="stake-pool-list-header-blocks"]'; private COLUMN_HEADER_PLEDGE = '[data-testid="stake-pool-list-header-pledge"]'; private COLUMN_HEADER_LIVE_STAKE = '[data-testid="stake-pool-list-header-liveStake"]'; + private COLUMN_SORTING_INDICATOR_TEMPLATE = '[data-testid="stake-pool-sort-order-###"]'; private MANAGE_STAKING_BTN_NEXT = '[data-testid="preferences-next-button"]'; private CONFIRMATION_BTN_NEXT = '[data-testid="stake-pool-confirmation-btn"]'; private DELEGATED_POOL_ITEM = '[data-testid="delegated-pool-item"]'; @@ -73,6 +74,7 @@ class MultidelegationPage { private STAKE_POOL_CARD_SKELETON = '[data-testid="stake-pool-card-skeleton"]'; private SELCECTED_STAKE_POOLS_IN_GRID_VIEW = '[data-testid="selected-pools-list"] [data-testid="stake-pool-card"]'; private SELCECTED_STAKE_POOLS_IN_LIST_VIEW = '[data-testid="selected-pools-list"] [data-testid="stake-pool-item"]'; + private POOLS_COUNTER = '[data-testid="pools-counter"]'; get title() { return SectionTitle.sectionTitle; @@ -214,6 +216,10 @@ class MultidelegationPage { return $(this.MANAGE_BTN); } + get poolsCounter() { + return $(this.POOLS_COUNTER); + } + delegatedPoolLogo(index: number): ChainablePromiseElement { return $$(this.DELEGATED_POOL_ITEM)[index].$(this.DELEGATED_POOL_LOGO); } @@ -300,6 +306,43 @@ class MultidelegationPage { )) as WebdriverIO.Element; } + async getColumnSortingIndicator(columnName: StakePoolListColumn, order: 'ascending' | 'descending') { + const orderDirection = order === 'ascending' ? 'asc' : 'desc'; + const orderDirectionSelector = `${this.COLUMN_SORTING_INDICATOR_TEMPLATE.replace('###', orderDirection)}`; + let selector = ''; + + switch (columnName) { + case StakePoolListColumn.Ticker: + selector = `${this.COLUMN_HEADER_TICKER} ${orderDirectionSelector}`; + break; + case StakePoolListColumn.Saturation: + selector = `${this.COLUMN_HEADER_SATURATION} ${orderDirectionSelector}`; + break; + case StakePoolListColumn.ROS: + selector = `${this.COLUMN_HEADER_ROS} ${orderDirectionSelector}`; + break; + case StakePoolListColumn.Cost: + selector = `${this.COLUMN_HEADER_COST} ${orderDirectionSelector}`; + break; + case StakePoolListColumn.Margin: + selector = `${this.COLUMN_HEADER_MARGIN} ${orderDirectionSelector}`; + break; + case StakePoolListColumn.Blocks: + selector = `${this.COLUMN_HEADER_BLOCKS} ${orderDirectionSelector}`; + break; + case StakePoolListColumn.Pledge: + selector = `${this.COLUMN_HEADER_PLEDGE} ${orderDirectionSelector}`; + break; + case StakePoolListColumn.LiveStake: + selector = `${this.COLUMN_HEADER_LIVE_STAKE} ${orderDirectionSelector}`; + break; + default: + throw new Error(`Unsupported column name: ${columnName}`); + } + + return $(selector); + } + async clickAndGetTabStateAttribute(tab: 'Overview' | 'Browse pools') { let tabElement; switch (tab) { @@ -418,68 +461,69 @@ class MultidelegationPage { await poolItem.click(); } - async hoverOverColumnWithName(columnName: StakePoolListColumnType) { + async hoverOverColumn(column: StakePoolListColumn) { let header; - switch (columnName) { - case 'Ticker': + + switch (column) { + case StakePoolListColumn.Ticker: header = await this.columnHeaderTicker; break; - case 'Saturation': + case StakePoolListColumn.Saturation: header = await this.columnHeaderSaturation; break; - case 'ROS': + case StakePoolListColumn.ROS: header = await this.columnHeaderROS; break; - case 'Cost': + case StakePoolListColumn.Cost: header = await this.columnHeaderCost; break; - case 'Margin': + case StakePoolListColumn.Margin: header = await this.columnHeaderMargin; break; - case 'Blocks': + case StakePoolListColumn.Blocks: header = await this.columnHeaderBlocks; break; - case 'Pledge': + case StakePoolListColumn.Pledge: header = await this.columnHeaderPledge; break; - case 'Live Stake': + case StakePoolListColumn.LiveStake: header = await this.columnHeaderLiveStake; break; default: - throw new Error(`Unsupported column name: ${columnName}`); + throw new Error(`Unsupported column name: ${column}`); } // make hovering over ANTD component more stable await header?.$('span span span').moveTo(); } - async clickOnColumnWithName(columnName: StakePoolListColumnType) { - switch (columnName) { - case 'Ticker': + async clickOnColumn(column: StakePoolListColumn) { + switch (column) { + case StakePoolListColumn.Ticker: await this.columnHeaderTicker.click(); break; - case 'Saturation': + case StakePoolListColumn.Saturation: await this.columnHeaderSaturation.click(); break; - case 'ROS': + case StakePoolListColumn.ROS: await this.columnHeaderROS.click(); break; - case 'Cost': + case StakePoolListColumn.Cost: await this.columnHeaderCost.click(); break; - case 'Margin': + case StakePoolListColumn.Margin: await this.columnHeaderMargin.click(); break; - case 'Blocks': + case StakePoolListColumn.Blocks: await this.columnHeaderBlocks.click(); break; - case 'Pledge': + case StakePoolListColumn.Pledge: await this.columnHeaderPledge.click(); break; - case 'Live Stake': + case StakePoolListColumn.LiveStake: await this.columnHeaderLiveStake.click(); break; default: - throw new Error(`Unsupported column name: ${columnName}`); + throw new Error(`Unsupported column name: ${column}`); } } @@ -543,6 +587,69 @@ class MultidelegationPage { const selectedTickers = await this.getTickersOfSelectedPools(viewType); testContext.save('selectedTickers', selectedTickers); } + + async getNumberOfPoolsFromCounter(): Promise { + const poolsCounterText = await this.poolsCounter.getText(); + return Number(Number(poolsCounterText.replace(/.*\(/, '').replace(')', '').replace(',', ''))); + } + + async waitForPoolsCounterToBeGreaterThanZero(): Promise { + await this.poolsCounter.waitForDisplayed(); + await browser.waitUntil(async () => (await this.getNumberOfPoolsFromCounter()) > 0, { + timeoutMsg: 'No stake pools!' + }); + } + + async extractColumnContent(columnName: StakePoolListColumn, poolLimit = 100): Promise { + const columnContent: string[] = []; + + await this.listContainer.waitForStable(); + await browser.pause(500); + + for (let i = 0; i < poolLimit; i++) { + const displayedPoolsCounter = await this.displayedPools.length; + const listItem = new StakePoolListItem(i); + // Load more pools if all visible ones were processed + if (i % (displayedPoolsCounter - 1) === 0) { + await listItem.container.scrollIntoView(); + await this.stakePoolListRowSkeleton.waitForExist({ + reverse: true, + interval: 100, + timeout: 30_000 + }); + } + switch (columnName) { + case StakePoolListColumn.Ticker: + columnContent.push(await listItem.ticker.getText()); + break; + case StakePoolListColumn.Saturation: + columnContent.push(await listItem.saturation.getText()); + break; + case StakePoolListColumn.ROS: + columnContent.push(await listItem.ros.getText()); + break; + case StakePoolListColumn.Cost: + columnContent.push(await listItem.cost.getText()); + break; + case StakePoolListColumn.Margin: + columnContent.push(await listItem.margin.getText()); + break; + case StakePoolListColumn.Blocks: + columnContent.push(await listItem.blocks.getText()); + break; + case StakePoolListColumn.Pledge: + columnContent.push(await listItem.pledge.getText()); + break; + case StakePoolListColumn.LiveStake: + columnContent.push(await listItem.liveStake.getText()); + break; + default: + throw new Error(`Not supported column name: ${columnName}`); + } + } + + return columnContent; + } } export default new MultidelegationPage(); diff --git a/packages/e2e-tests/src/elements/multidelegation/StakePoolListItem.ts b/packages/e2e-tests/src/elements/multidelegation/StakePoolListItem.ts index 3543d5846..bfe82ddb4 100644 --- a/packages/e2e-tests/src/elements/multidelegation/StakePoolListItem.ts +++ b/packages/e2e-tests/src/elements/multidelegation/StakePoolListItem.ts @@ -3,7 +3,6 @@ import { ChainablePromiseElement } from 'webdriverio'; export class StakePoolListItem { private SELECTED_POOLS_LIST = '[data-testid="selected-pools-list"]'; - private AVAILABLE_POOLS_LIST = '[data-testid="stake-pool-list-scroll-wrapper"]'; private LIST_ITEM = '[data-testid="stake-pool-item"]'; private CHECKBOX = '[data-testid="stake-pool-list-checkbox"]'; private TICKER = '[data-testid="stake-pool-list-ticker"]'; @@ -18,9 +17,9 @@ export class StakePoolListItem { protected listItem; constructor(index = 0, isOnSelectedPoolsList = false) { - this.listItem = $(isOnSelectedPoolsList ? this.SELECTED_POOLS_LIST : this.AVAILABLE_POOLS_LIST).$$(this.LIST_ITEM)[ - index - ]; + this.listItem = $( + isOnSelectedPoolsList ? `${this.SELECTED_POOLS_LIST} ${this.LIST_ITEM}` : `[data-item-index="${index}"]` + ); } get container(): ChainablePromiseElement { diff --git a/packages/e2e-tests/src/elements/onboarding/ConnectYourDevicePage.ts b/packages/e2e-tests/src/elements/onboarding/ConnectYourDevicePage.ts new file mode 100644 index 000000000..6649de9d5 --- /dev/null +++ b/packages/e2e-tests/src/elements/onboarding/ConnectYourDevicePage.ts @@ -0,0 +1,27 @@ +/* eslint-disable no-undef*/ +import CommonOnboardingElements from './commonOnboardingElements'; +import { ChainablePromiseElement } from 'webdriverio'; +import Banner from '../banner'; + +export class ConnectYourDevicePage extends CommonOnboardingElements { + private LOADER_IMAGE = '[data-testid="loader-image"]'; + private ERROR_IMAGE = '[data-testid="error-image"]'; + + get loader(): ChainablePromiseElement { + return $(this.LOADER_IMAGE); + } + + get errorImage(): ChainablePromiseElement { + return $(this.ERROR_IMAGE); + } + + get tryAgainButton(): ChainablePromiseElement { + return this.nextButton; + } + + get banner(): typeof Banner { + return Banner; + } +} + +export default new ConnectYourDevicePage(); diff --git a/packages/e2e-tests/src/elements/onboarding/connectHardwareWalletPage.ts b/packages/e2e-tests/src/elements/onboarding/connectHardwareWalletPage.ts deleted file mode 100644 index bf702eb08..000000000 --- a/packages/e2e-tests/src/elements/onboarding/connectHardwareWalletPage.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* eslint-disable no-undef*/ -import CommonOnboardingElements from './commonOnboardingElements'; -import { ChainablePromiseElement } from 'webdriverio'; - -export class OnboardingConnectHardwareWalletPage extends CommonOnboardingElements { - private SUBTITLE_TEXT = '[data-testid="connect-hardware-wallet-subtitle"]'; - private SUPPORTED_DEVICES_TEXT = '[data-testid="connect-hardware-wallet-supported-devices-text"]'; - private LEDGER_BUTTON = '[data-testid="connect-hardware-wallet-button-ledger"]'; - private TREZOR_BUTTON = '[data-testid="connect-hardware-wallet-button-trezor"]'; - - private CONNECT_DEVICE_TEXT = '[data-testid="connect-hardware-wallet-connect-device-text"]'; - - get subTitle(): ChainablePromiseElement { - return $(this.SUBTITLE_TEXT); - } - - get supportedDevices(): ChainablePromiseElement { - return $(this.SUPPORTED_DEVICES_TEXT); - } - - get ledgerButton(): ChainablePromiseElement { - return $(this.LEDGER_BUTTON); - } - - get trezorButton(): ChainablePromiseElement { - return $(this.TREZOR_BUTTON); - } - - get connectDevice(): ChainablePromiseElement { - return $(this.CONNECT_DEVICE_TEXT); - } -} - -export default new OnboardingConnectHardwareWalletPage(); diff --git a/packages/e2e-tests/src/elements/staking/StakePoolListItem.ts b/packages/e2e-tests/src/elements/staking/StakePoolListItem.ts deleted file mode 100644 index ef31ac490..000000000 --- a/packages/e2e-tests/src/elements/staking/StakePoolListItem.ts +++ /dev/null @@ -1,103 +0,0 @@ -/* eslint-disable no-undef */ -import webTester, { LocatorStrategy } from '../../actor/webTester'; -import { WebElement, WebElementFactory as Factory } from './../webElement'; - -export class StakePoolListItem extends WebElement { - protected TABLE_ROW = '//div[@data-testid="stake-pool-table-item"]'; - private LOGO = '//img[@data-testid="stake-pool-list-logo"]'; - private NAME = '//h6[@data-testid="stake-pool-list-name"]'; - private TICKER = '//p[@data-testid="stake-pool-list-ticker"]'; - private ROS = '//p[@data-testid="stake-pool-list-ros"]'; - private COST = '//p[@data-testid="stake-pool-list-cost"]'; - private SATURATION = '//p[@data-testid="stake-pool-list-saturation"]'; - - constructor(index?: number) { - super(); - this.TABLE_ROW = - typeof index === 'undefined' || index.toString() === '' ? this.TABLE_ROW : `(${this.TABLE_ROW})[${index}]`; - } - - container(): WebElement { - return Factory.fromSelector(`${this.TABLE_ROW}`, 'xpath'); - } - - tableRowWithName(poolName: string): WebElement { - return Factory.fromSelector(`${this.TABLE_ROW}[.//h6[contains(text(), '${poolName}')]]`, 'xpath'); - } - - async getRows(): Promise { - return $$(`${this.TABLE_ROW}`); - } - - logo(): WebElement { - return Factory.fromSelector(`${this.TABLE_ROW}${this.LOGO}`, 'xpath'); - } - - logoWithIndex(index: number): WebElement { - return Factory.fromSelector(`(${this.LOGO})[${index}]`, 'xpath'); - } - - name(): WebElement { - return Factory.fromSelector(`${this.TABLE_ROW}${this.NAME}`, 'xpath'); - } - - nameWithIndex(index: number): WebElement { - return Factory.fromSelector(`(${this.NAME})[${index}]`, 'xpath'); - } - - ticker(): WebElement { - return Factory.fromSelector(`${this.TABLE_ROW}${this.TICKER}`, 'xpath'); - } - - tickerWithIndex(index: number): WebElement { - return Factory.fromSelector(`(${this.TICKER})[${index}]`, 'xpath'); - } - - ros(): WebElement { - return Factory.fromSelector(`${this.TABLE_ROW}${this.ROS}`, 'xpath'); - } - - cost(): WebElement { - return Factory.fromSelector(`${this.TABLE_ROW}${this.COST}`, 'xpath'); - } - - saturation(): WebElement { - return Factory.fromSelector(`${this.TABLE_ROW}${this.SATURATION}`, 'xpath'); - } - - async getName(): Promise { - return await webTester.getTextValueFromElement(this.name()); - } - - async getTicker(): Promise { - return await webTester.getTextValueFromElement(this.ticker()); - } - - async getRos(): Promise { - return await webTester.getTextValueFromElement(this.ros()); - } - - async getCost(): Promise { - return await webTester.getTextValueFromElement(this.cost()); - } - - async getSaturation(): Promise { - return await webTester.getTextValueFromElement(this.saturation()); - } - - async getNameWithIndex(index: number): Promise { - return await webTester.getTextValueFromElement(this.nameWithIndex(index)); - } - - async getTickerWithIndex(index: number): Promise { - return await webTester.getTextValueFromElement(this.tickerWithIndex(index)); - } - - toJSLocator(): string { - return this.TABLE_ROW; - } - - locatorStrategy(): LocatorStrategy { - return 'xpath'; - } -} diff --git a/packages/e2e-tests/src/elements/staking/stakingPage.ts b/packages/e2e-tests/src/elements/staking/stakingPage.ts index 12f702cd7..1793d9bcb 100644 --- a/packages/e2e-tests/src/elements/staking/stakingPage.ts +++ b/packages/e2e-tests/src/elements/staking/stakingPage.ts @@ -2,17 +2,11 @@ import SectionTitle from '../sectionTitle'; class StakingPage { - private SEARCH_ICON = '[data-testid="search-icon"]'; private SEARCH_INPUT = '.ant-select-selection-search input'; - private SEARCH_INPUT_PLACEHOLDER_IN_POPUP = '.ant-select-selection-placeholder'; - private STAKE_POOL_LIST_HEADER_TEMPLATE = '[data-testid="stake-pool-list-header-###COLUMN_NAME###"]'; - private EMPTY_SEARCH_RESULTS_IMAGE = '[data-testid="stake-pool-table-empty-image"]'; - private EMPTY_SEARCH_RESULTS_MESSAGE = '[data-testid="stake-pool-table-empty-message"]'; private SEARCH_LOADER = '[data-testid="search-loader"]'; private STAKE_POOL_LIST_COST = '[data-testid="stake-pool-list-cost"]'; private STATS_TITLE = '[data-testid="stats-title"]'; private STATS_VALUE = '[data-testid="stats-value"]'; - private STAKE_POOL_TABLE_ROW = '[data-testid="stake-pool-table-item"]'; get title() { return SectionTitle.sectionTitle; @@ -22,10 +16,6 @@ class StakingPage { return SectionTitle.sectionCounter; } - get stakingPageSearchIcon() { - return $(this.SEARCH_ICON); - } - get statsTitle() { return $$(this.STATS_TITLE); } @@ -34,14 +24,6 @@ class StakingPage { return $(this.SEARCH_INPUT); } - get searchInputPlaceholderInPopup() { - return $(this.SEARCH_INPUT_PLACEHOLDER_IN_POPUP); - } - - get rows() { - return $$(this.STAKE_POOL_TABLE_ROW); - } - get statsValues() { return $$(this.STATS_VALUE); } @@ -62,27 +44,10 @@ class StakingPage { return $$(this.STAKE_POOL_LIST_COST); } - get statsValue() { - return $$(this.STATS_VALUE); - } - - get emptySearchResultsImage() { - return $(this.EMPTY_SEARCH_RESULTS_IMAGE); - } - - get emptySearchResultsMessage() { - return $(this.EMPTY_SEARCH_RESULTS_MESSAGE); - } - get searchLoader() { return $(this.SEARCH_LOADER); } - stakingPoolListColumnHeader(listHeader: string) { - const headerColumnSelector = this.STAKE_POOL_LIST_HEADER_TEMPLATE.replace('###COLUMN_NAME###', listHeader); - return $(headerColumnSelector); - } - stakingPoolWithName(poolName: string) { return $(`h6=${poolName}`); } diff --git a/packages/e2e-tests/src/enums/StakePoolListColumn.ts b/packages/e2e-tests/src/enums/StakePoolListColumn.ts new file mode 100644 index 000000000..1c90d58b6 --- /dev/null +++ b/packages/e2e-tests/src/enums/StakePoolListColumn.ts @@ -0,0 +1,11 @@ +/* eslint-disable no-unused-vars */ +export enum StakePoolListColumn { + Ticker = 'Ticker', + Saturation = 'Saturation', + ROS = 'ROS', + Cost = 'Cost', + Margin = 'Margin', + Blocks = 'Blocks', + Pledge = 'Pledge', + LiveStake = 'Live Stake' +} diff --git a/packages/e2e-tests/src/features/MultiDelegationPageExtended.feature b/packages/e2e-tests/src/features/MultiDelegationPageExtended.part1.feature similarity index 89% rename from packages/e2e-tests/src/features/MultiDelegationPageExtended.feature rename to packages/e2e-tests/src/features/MultiDelegationPageExtended.part1.feature index 91e855d6e..335fc1244 100644 --- a/packages/e2e-tests/src/features/MultiDelegationPageExtended.feature +++ b/packages/e2e-tests/src/features/MultiDelegationPageExtended.part1.feature @@ -88,22 +88,27 @@ Feature: Staking Page - Extended View Then I see the Network Info component with the expected content @LW-8499 @Testnet @Mainnet - Scenario Outline: Extended View - Staking - Show tooltip for column in browse pools section + Scenario: Extended View - Staking - Show tooltip for columns in browse pools section When I navigate to Staking extended page And I open Browse pools tab And I switch to list view on "Browse pools" tab - When I hover over "" column name in stake pool list - Then tooltip for "" column is displayed - Examples: - | column_name | - | Ticker | - | Saturation | -# | ROS | #TODO: Uncomment when USE_ROS_STAKING_COLUMN=true - | Cost | - | Margin | - | Blocks | - | Pledge | - | Live Stake | + When I hover over "Ticker" column name in stake pool list + Then tooltip for "Ticker" column is displayed + When I hover over "Saturation" column name in stake pool list + Then tooltip for "Saturation" column is displayed + #TODO: Uncomment when USE_ROS_STAKING_COLUMN=true + #When I hover over "ROS" column name in stake pool list + #Then tooltip for "ROS" column is displayed + When I hover over "Cost" column name in stake pool list + Then tooltip for "Cost" column is displayed + When I hover over "Margin" column name in stake pool list + Then tooltip for "Margin" column is displayed + When I hover over "Blocks" column name in stake pool list + Then tooltip for "Blocks" column is displayed + When I hover over "Pledge" column name in stake pool list + Then tooltip for "Pledge" column is displayed + When I hover over "Live Stake" column name in stake pool list + Then tooltip for "Live Stake" column is displayed @LW-8637 @Testnet @Mainnet Scenario: Extended View - Staking password screen details @@ -186,12 +191,6 @@ Feature: Staking Page - Extended View | list | I refresh the page | | list | I open Overview tab | - @LW-10143 @Testnet @Mainnet - Scenario: Extended View - Staking - More options - Sorting options are displayed - When I am on Staking extended page - And I open Browse pools tab - Then "More options" component with stake pool sorting options is displayed - @LW-9996 @Testnet @Mainnet Scenario: Extended View - Grid - display stake pool cards based on browser width When I am on Staking extended page diff --git a/packages/e2e-tests/src/features/MultiDelegationPageExtended.part2.feature b/packages/e2e-tests/src/features/MultiDelegationPageExtended.part2.feature new file mode 100644 index 000000000..b1975ca61 --- /dev/null +++ b/packages/e2e-tests/src/features/MultiDelegationPageExtended.part2.feature @@ -0,0 +1,51 @@ +@Staking-NonDelegatedFunds-Extended +Feature: Staking Page - Extended View + + Background: + Given Lace is ready for test + + @LW-10143 @Testnet @Mainnet + Scenario: Extended View - Staking - More options - Sorting options are displayed + When I am on Staking extended page + And I open Browse pools tab + Then "More options" component with stake pool sorting options is displayed + + @LW-10139 @LW-10141 @LW-10142 @Testnet @Mainnet + Scenario: Extended View - Staking - List View - Stake pool list sorting by ticker (default) + When I am on Staking extended page + And I open Browse pools tab + And I switch to list view on "Browse pools" tab + Then stake pool list view is displayed + And ascending sorting indicator is displayed for "Ticker" column + And stake pool list rows are sorted by "Ticker" in ascending order + When I click on stake pools table "Ticker" column header + Then descending sorting indicator is displayed for "Ticker" column + And stake pool list rows are sorted by "Ticker" in descending order + When I click on stake pools table "Ticker" column header + Then ascending sorting indicator is displayed for "Ticker" column + And stake pool list rows are sorted by "Ticker" in ascending order + + @LW-10141 @LW-10142 @Testnet @Mainnet + Scenario Outline: Extended View - Staking - List View - sorting by column - + When I am on Staking extended page + And I open Browse pools tab + And I switch to list view on "Browse pools" tab + Then stake pool list view is displayed + When I click on stake pools table "" column header + Then sorting indicator is displayed for "" column + And stake pool list rows are sorted by "" in order + When I click on stake pools table "" column header + Then sorting indicator is displayed for "" column + And stake pool list rows are sorted by "" in order + When I click on stake pools table "" column header + Then sorting indicator is displayed for "" column + And stake pool list rows are sorted by "" in order + Examples: + | column | default_order | modified_order | + | Saturation | descending | ascending | +# | ROS | descending | ascending |# TODO: Uncomment when USE_ROS_STAKING_COLUMN=true + | Cost | ascending | descending | + | Margin | ascending | descending | + | Blocks | descending | ascending | + | Pledge | descending | ascending | + | Live Stake | descending | ascending | diff --git a/packages/e2e-tests/src/features/OnboardingHardwareWallet.feature b/packages/e2e-tests/src/features/OnboardingHardwareWallet.feature index 4ab90655e..0a5b85a7c 100755 --- a/packages/e2e-tests/src/features/OnboardingHardwareWallet.feature +++ b/packages/e2e-tests/src/features/OnboardingHardwareWallet.feature @@ -4,20 +4,12 @@ Feature: Onboarding - Hardware wallet @LW-3367 Scenario: Hardware Wallet - Connect button click When I click "Connect" button on wallet setup page - And I click "OK" button on "Limited support for DApp" modal - Then "Connect Hardware Wallet" page is displayed - - @LW-3368 - Scenario: Hardware wallet - Legal page - next button disabled - When I click "Connect" button on wallet setup page - And I click "OK" button on "Limited support for DApp" modal - And "Next" button is disabled during onboarding process + Then "Connect your device" page is displayed @LW-3374 - Scenario: Hardware wallet - Connect Hardware Wallet - back button click + Scenario: Hardware wallet - Connect your device - back button click Given I click "Connect" button on wallet setup page - And I click "OK" button on "Limited support for DApp" modal - And "Connect Hardware Wallet" page is displayed + And "Connect your device" page is displayed When I click "Back" button during wallet setup Then "Get started" page is displayed @@ -27,10 +19,17 @@ Feature: Onboarding - Hardware wallet When "Get started" page is displayed Then I see current onboarding page in mode And I click "Connect" button on wallet setup page - And I click "OK" button on "Limited support for DApp" modal - And "Connect Hardware Wallet" page is displayed + And "Connect your device" page is displayed Then I see current onboarding page in mode Examples: | mode | | dark | | light | + + @LW-10309 + Scenario: Hardware wallet - Connect your device - "No hardware wallet device was chosen." error + When I click "Connect" button on wallet setup page + # Step below triggers error by closing HID window + And I switch to window with Lace + Then "No hardware wallet device was chosen." error is displayed on "Connect your device" page + And "Try again" button is enabled on "Connect your device" page diff --git a/packages/e2e-tests/src/features/StakingPageExtended.feature b/packages/e2e-tests/src/features/StakingPageExtended.feature deleted file mode 100644 index 4cd6d3f21..000000000 --- a/packages/e2e-tests/src/features/StakingPageExtended.feature +++ /dev/null @@ -1,41 +0,0 @@ -@Staking-NonDelegatedFunds-Extended @Pending -Feature: Staking Page - Extended Browser View - - Background: - Given Wallet is synced - - @LW-4024 @Pending @Testnet @Mainnet - @issue=ADP-2344 - Scenario: Extended View - Stake pool list default sorting by ROS - When I navigate to Staking extended page - And I reveal all stake pools - Then the results are in descending order according to "ros" column - - @LW-2706 @Pending @Testnet @Mainnet - @issue=ADP-2344 - Scenario Outline: Extended View - Sort lists ascending - column: - When I navigate to Staking extended page - And I click on the "" column header - And I reveal all stake pools - Then the results are in ascending order according to "" column - Examples: - | column | - | name | - | ros | - | cost | - | saturation | - - @LW-2706 @Pending @Testnet @Mainnet - @issue=ADP-2344 - Scenario Outline: Extended View - Sort lists descending - column - When I navigate to Staking extended page - And I click on the "" column header - And I click on the "" column header - And I reveal all stake pools - Then the results are in descending order according to "" column - Examples: - | column | - | name | - | ros | - | cost | - | saturation | diff --git a/packages/e2e-tests/src/features/e2e/SendNftExtendedE2E.feature b/packages/e2e-tests/src/features/e2e/SendNftExtendedE2E.feature index 5a05ab44f..3abefd644 100644 --- a/packages/e2e-tests/src/features/e2e/SendNftExtendedE2E.feature +++ b/packages/e2e-tests/src/features/e2e/SendNftExtendedE2E.feature @@ -6,7 +6,8 @@ Feature: Send NFT - Extended Browser View - E2E And I am on NFTs extended page And I use a single wallet with "Ibilecoin" NFT in extended mode - @LW-2502 @Smoke + @LW-2502 @Smoke @Pending + @issue=LW-10306 Scenario: Extended-view - Send NFT E2E And I'm sending the NFT with name: "Ibilecoin" in extended mode When I enter correct password and confirm the transaction diff --git a/packages/e2e-tests/src/features/e2e/SendTransactionDappE2E.feature b/packages/e2e-tests/src/features/e2e/SendTransactionDappE2E.feature index d54705a36..ba3fceb66 100644 --- a/packages/e2e-tests/src/features/e2e/SendTransactionDappE2E.feature +++ b/packages/e2e-tests/src/features/e2e/SendTransactionDappE2E.feature @@ -4,7 +4,8 @@ Feature: Send Transactions from Dapp - E2E Background: Given Wallet is synced - @LW-3761 @Testnet @Smoke + @LW-3761 @Testnet @Smoke @Pending + @issue=LW-10306 Scenario: Send ADA from DApp E2E And I save token: "Cardano" balance And I open and authorize test DApp with "Only once" setting diff --git a/packages/e2e-tests/src/features/e2e/StakingInitialFundsE2E.feature b/packages/e2e-tests/src/features/e2e/StakingInitialFundsE2E.feature index a0b538118..69d27b640 100644 --- a/packages/e2e-tests/src/features/e2e/StakingInitialFundsE2E.feature +++ b/packages/e2e-tests/src/features/e2e/StakingInitialFundsE2E.feature @@ -1,7 +1,8 @@ @Staking-initial-E2E @E2E @Testnet Feature: Delegating funds to new pool E2E - @LW-2685 @Smoke + @LW-2685 @Smoke @Pending + @issue=LW-10306 Scenario: Extended view - Staking - Delegating funds to new pool (if not staked yet) E2E. Given I create new wallet and save wallet information And Wallet is synced diff --git a/packages/e2e-tests/src/features/e2e/StakingSwitchingPoolsExtendedE2E.feature b/packages/e2e-tests/src/features/e2e/StakingSwitchingPoolsExtendedE2E.feature index ba9e1a07e..8b3c3bb02 100644 --- a/packages/e2e-tests/src/features/e2e/StakingSwitchingPoolsExtendedE2E.feature +++ b/packages/e2e-tests/src/features/e2e/StakingSwitchingPoolsExtendedE2E.feature @@ -13,7 +13,7 @@ Feature: Staking Page - Switching pools - Extended Browser View - E2E And I wait for single search result And I click stake pool with name "OtherStakePool" Then I see drawer with "OtherStakePool" stake pool details and a button available for staking - And I save stake pool info + And I save stake pool details When I click "Stake on this pool" button on stake pool details drawer And I click "Fine by me" button on "Switching pool?" modal Then I see drawer with stakepool: "OtherStakePool" confirmation screen in extended mode @@ -37,7 +37,7 @@ Feature: Staking Page - Switching pools - Extended Browser View - E2E And I wait for single search result And I click stake pool with name "-" Then I see drawer with stake pool details without metadata and a button available for staking - And I save stake pool info + And I save stake pool details When I click "Stake on this pool" button on stake pool details drawer And I click "Fine by me" button on "Switching pool?" modal And I click "Next" button on staking confirmation drawer diff --git a/packages/e2e-tests/src/features/e2e/StakingSwitchingPoolsPopupE2E.feature b/packages/e2e-tests/src/features/e2e/StakingSwitchingPoolsPopupE2E.feature index f61f7b43f..553979ca9 100644 --- a/packages/e2e-tests/src/features/e2e/StakingSwitchingPoolsPopupE2E.feature +++ b/packages/e2e-tests/src/features/e2e/StakingSwitchingPoolsPopupE2E.feature @@ -13,7 +13,7 @@ Feature: Staking Page - Switching pools - Popup View - E2E And I wait for single search result And I click stake pool with name "OtherStakePool" Then I see drawer with "OtherStakePool" stake pool details and a button available for staking - And I save stake pool info + And I save stake pool details When I click "Stake on this pool" button on stake pool details drawer And I click "Fine by me" button on "Switching pool?" modal Then I see drawer with stakepool: "OtherStakePool" confirmation screen in popup mode @@ -36,7 +36,7 @@ Feature: Staking Page - Switching pools - Popup View - E2E And I wait for single search result And I click stake pool with name "-" Then I see drawer with stake pool details without metadata and a button available for staking - When I save stake pool info + When I save stake pool details And I click "Stake on this pool" button on stake pool details drawer And I click "Fine by me" button on "Switching pool?" modal And I click "Next" button on staking confirmation drawer diff --git a/packages/e2e-tests/src/features/trezor/Trezor.feature b/packages/e2e-tests/src/features/trezor/Trezor.feature index b6d5c2751..66ad86a78 100644 --- a/packages/e2e-tests/src/features/trezor/Trezor.feature +++ b/packages/e2e-tests/src/features/trezor/Trezor.feature @@ -4,11 +4,12 @@ Feature: Trezor Onboarding Scenario: Onboarding Trezor wallet And I connect, unlock and enter correct pin on Trezor emulator Given I click "Connect" button on wallet setup page - And I click "OK" button on "Limited support for DApp" modal - And I am on "Lace terms of use" page and accept terms - And I am on "Help us improve your experience" page + # TODO: remove/replace outdated steps +# And I click "OK" button on "Limited support for DApp" modal +# And I am on "Lace terms of use" page and accept terms +# And I am on "Help us improve your experience" page When I click "Agree" button on Analytics page - And I click Trezor wallet icon +# And I click Trezor wallet icon And I click "Next" button during wallet setup And I select 1 account on Select Account page When I click "Next" button during wallet setup @@ -19,6 +20,6 @@ Feature: Trezor Onboarding And I click "Export" on Trezor Connect page And I confirm exporting public key on Trezor emulator And I switch to window with Lace - Then "All done" page is displayed +# Then "All done" page is displayed When I click "Go to my wallet" button on "All done" page Then I see LW homepage diff --git a/packages/e2e-tests/src/pageobject/stakingExtendedPageObject.ts b/packages/e2e-tests/src/pageobject/stakingExtendedPageObject.ts deleted file mode 100644 index 414ad5568..000000000 --- a/packages/e2e-tests/src/pageobject/stakingExtendedPageObject.ts +++ /dev/null @@ -1,60 +0,0 @@ -import webTester from '../actor/webTester'; -import StakingPage from '../elements/staking/stakingPage'; -import { StakePoolListItem } from '../elements/staking/StakePoolListItem'; -import testContext from '../utils/testContext'; -import StakePoolDetails from '../elements/staking/stakePoolDetails'; - -class StakingExtendedPageObject { - async clickStakePoolListHeader(listHeader: string) { - await StakingPage.stakingPoolListColumnHeader(listHeader).scrollIntoView(); - await StakingPage.stakingPoolListColumnHeader(listHeader).click(); - } - - async revealAllStakePools(): Promise { - const stakePoolListItem = new StakePoolListItem(); - await webTester.waitUntilSeeElement(stakePoolListItem.container(), 6000); - - const expectedTotalRows = Number((await StakingPage.counter.getText()).replace(/\D/g, '')); - let displayedRows = (await stakePoolListItem.getRows()).length; - - while (displayedRows < expectedTotalRows) { - await $(new StakePoolListItem(displayedRows).toJSLocator()).scrollIntoView(); - displayedRows = (await stakePoolListItem.getRows()).length; - } - } - - async extractColumnContent(columnName: string): Promise { - const rowsNumber = (await new StakePoolListItem().getRows()).length; - const columnContent: string[] = []; - for (let i = 1; i <= rowsNumber; i++) { - const listItem = new StakePoolListItem(i); - switch (columnName) { - case 'name': - columnContent.push((await listItem.getName()) as string); - break; - case 'ros': - columnContent.push((await listItem.getRos()) as string); - break; - case 'cost': - columnContent.push((await listItem.getCost()) as string); - break; - case 'saturation': - columnContent.push((await listItem.getSaturation()) as string); - break; - } - } - - return columnContent; - } - - saveStakePoolInfo = async () => { - const poolName = (await StakePoolDetails.poolName.getText()) as string; - testContext.save('poolName', poolName); - const poolTicker = (await StakePoolDetails.poolTicker.getText()) as string; - testContext.save('poolTicker', poolTicker); - const poolID = (await StakePoolDetails.poolId.getText()) as string; - testContext.save('poolID', poolID); - }; -} - -export default new StakingExtendedPageObject(); diff --git a/packages/e2e-tests/src/steps/multidelegationSteps.ts b/packages/e2e-tests/src/steps/multidelegationSteps.ts index 8bbe8713c..8fa11fa1f 100644 --- a/packages/e2e-tests/src/steps/multidelegationSteps.ts +++ b/packages/e2e-tests/src/steps/multidelegationSteps.ts @@ -27,9 +27,10 @@ import StartStakingPage from '../elements/multidelegation/StartStakingPage'; import PortfolioBar from '../elements/multidelegation/PortfolioBar'; import PortfolioBarAssert from '../assert/multidelegation/PortfolioBarAssert'; import ChangingStakingPreferencesModalAssert from '../assert/multidelegation/ChangingStakingPreferencesModalAssert'; -import { StakePoolListColumnType, StakePoolSortingOptionType } from '../types/staking'; +import { StakePoolListColumnName, StakePoolSortingOptionType } from '../types/staking'; import SwitchingStakePoolModal from '../elements/staking/SwitchingStakePoolModal'; import MoreOptionsComponentAssert from '../assert/multidelegation/MoreOptionsComponentAssert'; +import { mapColumnNameStringToEnum } from '../utils/stakePoolListContent'; const validPassword = 'N_8J@bne87A'; @@ -235,15 +236,15 @@ Then(/^\(if applicable\) first stake pool search result has "([^"]*)" ticker$/, When( /^I hover over "(Ticker|Saturation|ROS|Cost|Margin|Blocks|Pledge|Live Stake)" column name in stake pool list$/, - async (columnName: StakePoolListColumnType) => { - await MultidelegationPage.hoverOverColumnWithName(columnName); + async (columnName: StakePoolListColumnName) => { + await MultidelegationPage.hoverOverColumn(mapColumnNameStringToEnum(columnName)); } ); Then( /^tooltip for "(Ticker|Saturation|ROS|Cost|Margin|Blocks|Pledge|Live Stake)" column is displayed$/, - async (columnName: StakePoolListColumnType) => { - await MultidelegationPageAssert.assertSeeTooltipForColumn(columnName); + async (columnName: StakePoolListColumnName) => { + await MultidelegationPageAssert.assertSeeTooltipForColumn(mapColumnNameStringToEnum(columnName)); } ); @@ -493,8 +494,8 @@ Then(/^I see (\d+) stake pool cards in a row$/, async (cardsCount: number) => { When( /^I click on stake pools table "(Ticker|Saturation|ROS|Cost|Margin|Blocks|Pledge|Live Stake)" column header$/, - async (headerName: StakePoolListColumnType) => { - await MultidelegationPage.clickOnColumnWithName(headerName); + async (headerName: StakePoolListColumnName) => { + await MultidelegationPage.clickOnColumn(mapColumnNameStringToEnum(headerName)); } ); @@ -504,9 +505,29 @@ When( await MultidelegationPage.moreOptionsComponent.selectSortingOption(sortingOption); } ); + Then( /^"More options" component with stake pool (sorting|filtering) options is displayed$/, async (tab: 'sorting' | 'filtering') => { await MoreOptionsComponentAssert.assertSeeMoreOptionsComponent(tab); } ); + +Then( + /^(ascending|descending) sorting indicator is displayed for "(Ticker|Saturation|ROS|Cost|Margin|Blocks|Pledge|Live Stake)" column$/, + async (order: 'ascending' | 'descending', sortingOption: StakePoolListColumnName) => { + await MultidelegationPageAssert.assertSeeColumnSortingIndicator(sortingOption, order); + } +); + +Then( + /^stake pool (list rows|cards) are sorted by "(Ticker|Saturation|ROS|Cost|Margin|Blocks|Pledge|Live Stake)" in (ascending|descending) order$/, + async ( + stakePoolsDisplayType: 'list rows' | 'cards', + sortingOption: StakePoolListColumnName, + order: 'ascending' | 'descending' + ) => { + const poolLimit = 100; // Limit verification to 100 stake pools due to time constraints + await MultidelegationPageAssert.assertSeeStakePoolsSorted(stakePoolsDisplayType, sortingOption, order, poolLimit); + } +); diff --git a/packages/e2e-tests/src/steps/onboardingSteps.ts b/packages/e2e-tests/src/steps/onboardingSteps.ts index 628672b68..6c7ebfd82 100644 --- a/packages/e2e-tests/src/steps/onboardingSteps.ts +++ b/packages/e2e-tests/src/steps/onboardingSteps.ts @@ -8,7 +8,6 @@ import Modal from '../elements/modal'; import ModalAssert from '../assert/modalAssert'; import OnboardingAnalyticsPage from '../elements/onboarding/analyticsPage'; import OnboardingCommonAssert from '../assert/onboarding/onboardingCommonAssert'; -import OnboardingConnectHWPageAssert from '../assert/onboarding/onboardingConnectHWPageAssert'; import OnboardingMainPage from '../elements/onboarding/mainPage'; import OnboardingMainPageAssert from '../assert/onboarding/onboardingMainPageAssert'; import OnboardingWalletSetupPage from '../elements/onboarding/walletSetupPage'; @@ -17,7 +16,6 @@ import TokensPageAssert from '../assert/tokensPageAssert'; import TopNavigationAssert from '../assert/topNavigationAssert'; import testContext from '../utils/testContext'; import CommonAssert from '../assert/commonAssert'; -import OnboardingConnectHardwareWalletPage from '../elements/onboarding/connectHardwareWalletPage'; import SelectAccountPage from '../elements/onboarding/selectAccountPage'; import { browser } from '@wdio/globals'; import type { RecoveryPhrase } from '../types/onboarding'; @@ -31,6 +29,7 @@ import { getWalletsFromRepository } from '../fixture/walletRepositoryInitializer import OnboardingWalletSetupPageAssert from '../assert/onboarding/onboardingWalletSetupPageAssert'; import OnboardingAnalyticsBannerAssert from '../assert/onboarding/onboardingAnalyticsBannerAssert'; import { shuffle } from '../utils/arrayUtils'; +import ConnectYourDevicePageAssert from '../assert/onboarding/ConnectYourDevicePageAssert'; const mnemonicWords: string[] = getTestWallet(TestWalletName.TestAutomationWallet).mnemonic ?? []; const invalidMnemonicWords: string[] = getTestWallet(TestWalletName.InvalidMnemonic).mnemonic ?? []; @@ -160,12 +159,19 @@ Then(/^I select (12|15|24) word passphrase length$/, async (length: RecoveryPhra await RecoveryPhrasePage.selectMnemonicLength(length); }); -Then(/^"Connect Hardware Wallet" page is displayed$/, async () => { - await OnboardingConnectHWPageAssert.assertSeeConnectHardwareWalletPage(); +Then(/^"Connect your device" page is displayed$/, async () => { + await ConnectYourDevicePageAssert.assertSeeConnectYourDevicePage(); }); -Then(/^I click Trezor wallet icon$/, async () => { - await OnboardingConnectHardwareWalletPage.trezorButton.click(); +Then(/^"No hardware wallet device was chosen." error is displayed on "Connect your device" page$/, async () => { + await ConnectYourDevicePageAssert.assertSeeError( + await t('core.walletSetupConnectHardwareWalletStepRevamp.errorMessage.devicePickerRejected') + ); +}); + +When(/^"Try again" button is enabled on "Connect your device" page$/, async () => { + await ConnectYourDevicePageAssert.assertSeeTryAgainButton(true); + await ConnectYourDevicePageAssert.assertSeeTryAgainButtonEnabled(true); }); Then(/^"Restoring a multi-address wallet\?" modal is displayed$/, async () => { diff --git a/packages/e2e-tests/src/steps/stakingSteps.ts b/packages/e2e-tests/src/steps/stakingSteps.ts index f89e4f049..ba2382abc 100644 --- a/packages/e2e-tests/src/steps/stakingSteps.ts +++ b/packages/e2e-tests/src/steps/stakingSteps.ts @@ -1,13 +1,10 @@ import { Then, When } from '@cucumber/cucumber'; import stakingPageAssert from '../assert/stakingPageAssert'; import stakePoolDetailsAssert from '../assert/stakePoolDetailsAssert'; -import stakingExtendedPageObject from '../pageobject/stakingExtendedPageObject'; import drawerCommonExtendedAssert from '../assert/drawerCommonExtendedAssert'; import { getStakePoolById, getStakePoolByName, StakePoolsData } from '../data/expectedStakePoolsData'; import testContext from '../utils/testContext'; import transactionDetailsAssert, { ExpectedActivityDetails } from '../assert/transactionDetailsAssert'; -import { StakePoolListItem } from '../elements/staking/StakePoolListItem'; -import webTester from '../actor/webTester'; import StakingExitModalAssert from '../assert/stakingExitModalAssert'; import extensionUtils from '../utils/utils'; import stakingConfirmationScreenAssert from '../assert/stakingConfirmationScreenAssert'; @@ -125,12 +122,6 @@ Then(/^the stakepool drawer is opened with "([^"]*)" stake pool information$/, a await drawerCommonExtendedAssert.assertSeeDrawerWithTitle(poolName); }); -When(/^I click on the "(.*)" column header$/, async (listHeader: string) => { - const stakePoolListItem = new StakePoolListItem(); - await webTester.waitUntilSeeElement(stakePoolListItem.container(), 60_000); - await stakingExtendedPageObject.clickStakePoolListHeader(listHeader); -}); - // eslint-disable-next-line no-unused-vars,@typescript-eslint/no-unused-vars Then(/^The Tx details are displayed for Staking (with|without) metadata$/, async (_ignored: 'with' | 'without') => { // no need to distinguish between pools with/without metadata @@ -144,22 +135,6 @@ Then(/^The Tx details are displayed for Staking (with|without) metadata$/, async await transactionDetailsAssert.assertSeeActivityDetails(expectedActivityDetails); }); -Then( - /^the results are in (ascending|descending) order according to "([^"]*)" column$/, - async (order: 'ascending' | 'descending', column: string) => { - await stakingPageAssert.assertStakePoolItemsOrder(column, order); - } -); - -When(/^I reveal all stake pools$/, async () => { - await webTester.waitUntilSeeElement(new StakePoolListItem().container(), 60_000); - await stakingExtendedPageObject.revealAllStakePools(); -}); - -When(/^I save stake pool info$/, async () => { - await stakingExtendedPageObject.saveStakePoolInfo(); -}); - Then(/^Staking password screen is displayed$/, async () => { await stakingPageAssert.assertSeeStakingPasswordDrawer(); }); diff --git a/packages/e2e-tests/src/types/sortingOrder.ts b/packages/e2e-tests/src/types/sortingOrder.ts new file mode 100644 index 000000000..22b91c4cd --- /dev/null +++ b/packages/e2e-tests/src/types/sortingOrder.ts @@ -0,0 +1 @@ +export type SortingOrder = 'ascending' | 'descending'; diff --git a/packages/e2e-tests/src/types/staking.ts b/packages/e2e-tests/src/types/staking.ts index be72dc088..d8edd320b 100644 --- a/packages/e2e-tests/src/types/staking.ts +++ b/packages/e2e-tests/src/types/staking.ts @@ -1,4 +1,4 @@ -export type StakePoolListColumnType = +export type StakePoolListColumnName = | 'Ticker' | 'Saturation' | 'ROS' diff --git a/packages/e2e-tests/src/utils/stakePoolListContent.ts b/packages/e2e-tests/src/utils/stakePoolListContent.ts index 4151e756c..24738e188 100644 --- a/packages/e2e-tests/src/utils/stakePoolListContent.ts +++ b/packages/e2e-tests/src/utils/stakePoolListContent.ts @@ -1,104 +1,138 @@ -import { Asset } from '../data/Asset'; +import type { SortingOrder } from '../types/sortingOrder'; +import type { StakePoolListColumnName } from '../types/staking'; +import { StakePoolListColumn } from '../enums/StakePoolListColumn'; -interface Cost { - percentage: number; - ada: number; +interface AbbreviatedValue { + value: number; + suffix: '-' | 'K' | 'M'; } +const suffixOrderPriority = { + '-': 0, + K: 1, + M: 2 +}; + const emojiRegex = - // eslint-disable-next-line max-len /([\u2700-\u27BF]|[\uE000-\uF8FF]|\uD83C[\uDC00-\uDFFF]|\uD83D[\uDC00-\uDFFF]|[\u2011-\u26FF]|\uD83E[\uDD10-\uDDFF])/g; -const parseCostStringToObject = (cost: string): Cost => { - let parsedItem: Cost = { percentage: 0, ada: 0 }; - if (cost.includes('%') && cost.includes('ADA')) { - const splitItem = cost.split('+'); - parsedItem = { - percentage: Number.parseFloat(splitItem[0].replace(/%/, '')), - ada: Number.parseFloat(splitItem[1].replace(/ADA/, '')) - }; - } +const sortTickerColumnContent = (columnContent: string[], order: SortingOrder): string[] => { + const itemsWithNoTicker = columnContent.filter((item) => item === '-'); + const itemsWithTicker = columnContent.filter((item) => item !== '-'); - if (cost.includes('%') && !cost.includes(Asset.CARDANO.ticker)) { - parsedItem = { - percentage: Number.parseFloat(cost.replace(/%/, '')), - ada: 0 - }; - } + const sortedItems = [...itemsWithTicker].sort((a, b) => { + const nameA = a.replace(emojiRegex, '').replace(' ', '').trim(); + const nameB = b.replace(emojiRegex, '').replace(' ', '').trim(); + return nameA.localeCompare(nameB); + }); - if (!cost.includes('%') && cost.includes(Asset.CARDANO.ticker)) { - parsedItem = { - percentage: 0, - ada: Number.parseFloat(cost.replace(/ADA/, '')) - }; + if (order === 'descending') { + sortedItems.reverse(); } - return parsedItem; + sortedItems.push(...itemsWithNoTicker); + + return sortedItems; }; -const parseCostObjectToString = (cost: Cost): string => { - let parsedItem = `${Number(cost.percentage).toFixed(2)}%`; +const sortBlocksColumnContent = (columnContent: string[], order: SortingOrder): string[] => { + const parsedColumnContent = columnContent.map((item) => Number(item.replace(',', ''))); + const sortedColumnContent = [...parsedColumnContent].sort((a, b) => a - b); - if (cost.ada > 0) { - parsedItem = `${parsedItem} + ${Number(cost.ada)}${Asset.CARDANO.ticker}`; + if (order === 'descending') { + sortedColumnContent.reverse(); } - return parsedItem; + return sortedColumnContent.map((item) => item.toLocaleString()); }; -export const sortNameColumn = (columnContent: string[], order: string): string[] => { - const itemsWithNoName = columnContent.filter((item) => item === '-'); - const itemsWithName = columnContent.filter((item) => item !== '-'); - - const sortedItems = [...itemsWithName].sort((a, b) => { - const nameA = a.replace(emojiRegex, '').replace(' ', '').trim(); - const nameB = b.replace(emojiRegex, '').replace(' ', '').trim(); - return nameA.localeCompare(nameB); - }); - if (order === 'descending') { - sortedItems.reverse(); +const parseValueFromColumnIntoAbbreviatedValueObject = (valueFromColumn: string): AbbreviatedValue => { + if (valueFromColumn.endsWith('K')) { + return { + value: Number(valueFromColumn.slice(0, -1)), + suffix: 'K' + }; + } + if (valueFromColumn.endsWith('M')) { + return { + value: Number(valueFromColumn.slice(0, -1)), + suffix: 'M' + }; } - sortedItems.push(...itemsWithNoName); + return { + value: Number(valueFromColumn), + suffix: '-' + }; +}; - return sortedItems; +const parseAbbreviatedValueObjectIntoString = (abbreviatedValueObject: AbbreviatedValue): string => + `${abbreviatedValueObject.value}${ + ['K', 'M'].includes(abbreviatedValueObject.suffix) ? abbreviatedValueObject.suffix : '' + }`; + +const compareAbbreviatedValues = (abbreviatedValue1: AbbreviatedValue, abbreviatedValue2: AbbreviatedValue): number => { + if (suffixOrderPriority[abbreviatedValue1.suffix] - suffixOrderPriority[abbreviatedValue2.suffix] === 0) { + return abbreviatedValue1.value - abbreviatedValue2.value; + } + return suffixOrderPriority[abbreviatedValue1.suffix] - suffixOrderPriority[abbreviatedValue2.suffix]; }; -export const sortCostColumn = (columnContent: string[], order: string): string[] => { - const parsedColumnContent = columnContent.map((item) => parseCostStringToObject(item)); - const costSorted = [...parsedColumnContent].sort((a, b) => a.ada - b.ada || a.percentage - b.percentage); +const sortColumnWithPercentageValues = (columnContent: string[], order: string): string[] => { + const parsedColumnContent = columnContent.map((item) => Number(item.replace(/%/, '')).toFixed(2)); + const sortedColumnContent = [...parsedColumnContent].sort((a, b) => Number(a) - Number(b)); + if (order === 'descending') { - costSorted.reverse(); + sortedColumnContent.reverse(); } - return costSorted.map((item) => parseCostObjectToString(item)); + + return sortedColumnContent.map((item) => String(`${item}%`)); }; -export const sortColumnWithPercentageValues = (columnContent: string[], order: string): string[] => { - const columnContentWithNumbers = columnContent.map((item) => Number.parseFloat(item.replace(/%/, ''))); - const sortedColumnContentWithNumbers = [...columnContentWithNumbers].sort((a, b) => a - b); +const sortColumnWithAbbreviatedNumbers = (columnContent: string[], order: string): string[] => { + const parsedColumnContent: AbbreviatedValue[] = columnContent.map((item) => + parseValueFromColumnIntoAbbreviatedValueObject(item) + ); + const sortedColumnContent = [...parsedColumnContent].sort((a, b) => compareAbbreviatedValues(a, b)); + if (order === 'descending') { - sortedColumnContentWithNumbers.reverse(); + sortedColumnContent.reverse(); } - return sortedColumnContentWithNumbers.map((item) => String(`${item}%`)); + + return sortedColumnContent.map((item) => parseAbbreviatedValueObjectIntoString(item)); }; export const sortColumnContent = async ( columnContent: string[], - columnName: string, - order: string + sortingOption: StakePoolListColumn, + order: SortingOrder ): Promise => { let sortedColumnContent: string[] = []; - if (columnName === 'name') { - sortedColumnContent = sortNameColumn(columnContent, order); - } - - if (['ros', 'saturation'].includes(columnName)) { - sortedColumnContent = sortColumnWithPercentageValues(columnContent, order); - } - - if (columnName === 'cost') { - sortedColumnContent = sortCostColumn(columnContent, order); + switch (sortingOption) { + case StakePoolListColumn.Ticker: + sortedColumnContent = sortTickerColumnContent(columnContent, order); + break; + case StakePoolListColumn.Saturation: + case StakePoolListColumn.ROS: + case StakePoolListColumn.Margin: + sortedColumnContent = sortColumnWithPercentageValues(columnContent, order); + break; + case StakePoolListColumn.Blocks: + sortedColumnContent = sortBlocksColumnContent(columnContent, order); + break; + case StakePoolListColumn.Cost: + case StakePoolListColumn.Pledge: + case StakePoolListColumn.LiveStake: + sortedColumnContent = sortColumnWithAbbreviatedNumbers(columnContent, order); + break; + default: + throw new Error(`Unsupported sorting option: ${sortingOption}`); } return sortedColumnContent; }; + +export const mapColumnNameStringToEnum = (columnName: StakePoolListColumnName): StakePoolListColumn => + columnName === 'Live Stake' + ? StakePoolListColumn.LiveStake + : StakePoolListColumn[columnName as keyof typeof StakePoolListColumn]; diff --git a/packages/staking/package.json b/packages/staking/package.json index becb11234..22e73457d 100644 --- a/packages/staking/package.json +++ b/packages/staking/package.json @@ -72,11 +72,11 @@ "devDependencies": { "@babel/core": "^7.21.0", "@cardano-sdk/core": "0.30.0", - "@cardano-sdk/input-selection": "0.12.26", - "@cardano-sdk/tx-construction": "0.18.2", + "@cardano-sdk/input-selection": "0.12.27", + "@cardano-sdk/tx-construction": "0.18.3", "@cardano-sdk/util": "0.15.0", - "@cardano-sdk/wallet": "0.36.0", - "@cardano-sdk/web-extension": "0.26.2", + "@cardano-sdk/wallet": "0.37.0", + "@cardano-sdk/web-extension": "0.27.0", "@storybook/addon-actions": "^7.6.7", "@storybook/addon-essentials": "^7.6.7", "@storybook/addon-interactions": "^7.6.7", diff --git a/packages/staking/src/features/BrowsePools/BrowsePoolsPreferencesCard/BrowsePoolsPreferencesCard.stories.tsx b/packages/staking/src/features/BrowsePools/BrowsePoolsPreferencesCard/BrowsePoolsPreferencesCard.stories.tsx index 18576f082..b87bdfd6a 100644 --- a/packages/staking/src/features/BrowsePools/BrowsePoolsPreferencesCard/BrowsePoolsPreferencesCard.stories.tsx +++ b/packages/staking/src/features/BrowsePools/BrowsePoolsPreferencesCard/BrowsePoolsPreferencesCard.stories.tsx @@ -2,11 +2,12 @@ import { Box, Cell, Flex, Grid, LocalThemeProvider, Section, ThemeColorScheme, V import { action } from '@storybook/addon-actions'; import { useArgs } from '@storybook/preview-api'; import { expect, userEvent, waitFor, within } from '@storybook/test'; -import { DEFAULT_SORT_OPTIONS, StakePoolSortOptions } from 'features/BrowsePools'; import { useCallback, useState } from 'react'; +import type { StakePoolSortOptions } from '../types'; import type { Meta, StoryObj } from '@storybook/react'; import { PoolsFilter, QueryStakePoolsFilters } from '../../store'; +import { DEFAULT_SORT_OPTIONS } from '../constants'; import { BrowsePoolsPreferencesCard } from './BrowsePoolsPreferencesCard'; import { SortAndFilterTab } from './types'; diff --git a/packages/staking/src/features/BrowsePools/constants.ts b/packages/staking/src/features/BrowsePools/constants.ts index fca7546d3..bae32b93e 100644 --- a/packages/staking/src/features/BrowsePools/constants.ts +++ b/packages/staking/src/features/BrowsePools/constants.ts @@ -1,4 +1,5 @@ -import { BrowsePoolsView, StakePoolSortOptions, StakingBrowserPreferences } from './types'; +import type { StakePoolSortOptions, StakingBrowserPreferences } from './types'; +import { BrowsePoolsView } from './types'; import { getDefaultSortOrderByField } from './utils'; export const SEARCH_DEBOUNCE_IN_MS = 300; diff --git a/yarn.lock b/yarn.lock index 6a4e1308c..57f15a3b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7675,17 +7675,17 @@ __metadata: languageName: node linkType: hard -"@cardano-sdk/cardano-services-client@npm:0.18.0": - version: 0.18.0 - resolution: "@cardano-sdk/cardano-services-client@npm:0.18.0" +"@cardano-sdk/cardano-services-client@npm:0.19.0": + version: 0.19.0 + resolution: "@cardano-sdk/cardano-services-client@npm:0.19.0" dependencies: "@cardano-sdk/core": ~0.30.0 "@cardano-sdk/util": ~0.15.0 - axios: ^0.27.2 + axios: ^0.28.0 class-validator: ^0.14.0 json-bigint: ~1.0.0 ts-log: ^2.2.4 - checksum: f722a14121c944ecd37360525ef664da41cad3076042acc43961e4313935a4a4acbda3e3d5019979f1e14a1a3df3889e5509a453b93b99b94c8cf79c7fb2cfe7 + checksum: 7a691c624394b374207dbd8b16a6d0e53b852c46e5291526e9085f12bb1fafc2cf539176be32c9f08c43e36399f0b7172eb7e1028fccb62f44caef1c3bcac81e languageName: node linkType: hard @@ -7756,15 +7756,15 @@ __metadata: languageName: node linkType: hard -"@cardano-sdk/hardware-ledger@npm:0.9.0, @cardano-sdk/hardware-ledger@npm:~0.9.0": - version: 0.9.0 - resolution: "@cardano-sdk/hardware-ledger@npm:0.9.0" +"@cardano-sdk/hardware-ledger@npm:0.9.1, @cardano-sdk/hardware-ledger@npm:~0.9.1": + version: 0.9.1 + resolution: "@cardano-sdk/hardware-ledger@npm:0.9.1" dependencies: "@cardano-foundation/ledgerjs-hw-app-cardano": ^6.0.0 "@cardano-sdk/core": ~0.30.0 "@cardano-sdk/crypto": ~0.1.22 "@cardano-sdk/key-management": ~0.20.1 - "@cardano-sdk/tx-construction": ~0.18.2 + "@cardano-sdk/tx-construction": ~0.18.3 "@cardano-sdk/util": ~0.15.0 "@ledgerhq/hw-transport": ^6.28.1 "@ledgerhq/hw-transport-node-hid-noevents": ^6.27.12 @@ -7772,31 +7772,31 @@ __metadata: node-hid: ^2.1.2 ts-custom-error: ^3.2.0 ts-log: ^2.2.4 - checksum: 46811188b5e055de130fc661c8235dfc40881427b570ff4ed301620808e413c7693e4f052e94b2c49947262310b3a44e65e44caa96055c9b930e5eb1f63cc2ff + checksum: 1d2c4ad341b1029058f55689eec8ec4beddb8086c22ce0905331b62da8eed5822176d92e3ee59df93d24ceddd8e6941d64d44330fa5f919e423e402f22dd0ca8 languageName: node linkType: hard -"@cardano-sdk/hardware-trezor@npm:0.4.19, @cardano-sdk/hardware-trezor@npm:~0.4.19": - version: 0.4.19 - resolution: "@cardano-sdk/hardware-trezor@npm:0.4.19" +"@cardano-sdk/hardware-trezor@npm:0.4.20, @cardano-sdk/hardware-trezor@npm:~0.4.20": + version: 0.4.20 + resolution: "@cardano-sdk/hardware-trezor@npm:0.4.20" dependencies: "@cardano-sdk/core": ~0.30.0 "@cardano-sdk/crypto": ~0.1.22 "@cardano-sdk/key-management": ~0.20.1 - "@cardano-sdk/tx-construction": ~0.18.2 + "@cardano-sdk/tx-construction": ~0.18.3 "@cardano-sdk/util": ~0.15.0 "@trezor/connect": 9.1.6 "@trezor/connect-web": 9.1.6 lodash: ^4.17.21 ts-custom-error: ^3.2.0 ts-log: ^2.2.4 - checksum: 51e32f95d39bb3b4d3f1af50178a00ac6142b43f840070b0d0e78dd372644586040ac4b60b60b362519a68e59729d34101aa9ef851b9f5946136a007cda71f67 + checksum: c8f23d141cb7db50a289b4acd61aa7baeb80f4fe331cdf65c192b17dcaf6d331caa1a397f1d965b2eddb2d7269cb7c91cfc7d2702e85e73ec1dcd3d54a7ab11f languageName: node linkType: hard -"@cardano-sdk/input-selection@npm:0.12.26, @cardano-sdk/input-selection@npm:~0.12.26": - version: 0.12.26 - resolution: "@cardano-sdk/input-selection@npm:0.12.26" +"@cardano-sdk/input-selection@npm:0.12.27, @cardano-sdk/input-selection@npm:~0.12.27": + version: 0.12.27 + resolution: "@cardano-sdk/input-selection@npm:0.12.27" dependencies: "@cardano-sdk/core": ~0.30.0 "@cardano-sdk/key-management": ~0.20.1 @@ -7804,7 +7804,7 @@ __metadata: bignumber.js: ^9.1.1 lodash: ^4.17.21 ts-custom-error: ^3.2.0 - checksum: 0bc9f3bf84cb061196d61c38bc3590f284c9313f932a88c69207c1ab8919d08b47a76f59f1b16ce92c45a9ccf0c4cb3112b0f9cc5829166a5f803e99d9df8811 + checksum: 0d39ac9a32bcf17cf5b0485757dde49603bfa9af5adfccacba18f5e7a7a2d785686d834bd69a21365847d05ff72f4023770fcc07e6d6e140755961e7eea71980 languageName: node linkType: hard @@ -7829,22 +7829,22 @@ __metadata: languageName: node linkType: hard -"@cardano-sdk/tx-construction@npm:0.18.2, @cardano-sdk/tx-construction@npm:~0.18.2": - version: 0.18.2 - resolution: "@cardano-sdk/tx-construction@npm:0.18.2" +"@cardano-sdk/tx-construction@npm:0.18.3, @cardano-sdk/tx-construction@npm:~0.18.3": + version: 0.18.3 + resolution: "@cardano-sdk/tx-construction@npm:0.18.3" dependencies: "@cardano-sdk/core": ~0.30.0 "@cardano-sdk/crypto": ~0.1.22 - "@cardano-sdk/input-selection": ~0.12.26 + "@cardano-sdk/input-selection": ~0.12.27 "@cardano-sdk/key-management": ~0.20.1 "@cardano-sdk/util": ~0.15.0 - "@cardano-sdk/util-rxjs": ~0.7.9 + "@cardano-sdk/util-rxjs": ~0.7.10 lodash: ^4.17.21 npm: ^9.3.0 rxjs: ^7.4.0 ts-custom-error: ^3.2.0 ts-log: ^2.2.4 - checksum: 9b3e7d319421a2c8237afd289ef241a429e81a49e20b466c436919116536115da5fe62c4124fb1d452a4d90a3b4262468dc1150f402fcf21a74f01e3d7433bc7 + checksum: 47125f2a3887ead7652ce11fd4642d2bd66c39c6f587531e95b65c6054e62b77a66358b27ba4fb8e528f6aa7971b0adc0e0c2d64da892cd5c4167f36b7aa4e63 languageName: node linkType: hard @@ -7871,14 +7871,14 @@ __metadata: languageName: node linkType: hard -"@cardano-sdk/util-rxjs@npm:~0.7.9": - version: 0.7.9 - resolution: "@cardano-sdk/util-rxjs@npm:0.7.9" +"@cardano-sdk/util-rxjs@npm:~0.7.10": + version: 0.7.10 + resolution: "@cardano-sdk/util-rxjs@npm:0.7.10" dependencies: "@cardano-sdk/util": ~0.15.0 backoff-rxjs: ^7.0.0 rxjs: ^7.4.0 - checksum: 8474a90a5f7a3715e2ba333ceafbc3bffff715c11ce2b67121f29b486697064727d8dcf51a8dc954f0a35976ce31f0cc7c21beeedb7159aa5482d2ca2e7509fa + checksum: 9d36075e86b58dcd3c52beff3876ea08c25df889ad6b4484e4279dd724668310d67c01d61ebcb2e3c580ca62c72f720fe7807ea3ad22eb2b1740fb72287f1a77 languageName: node linkType: hard @@ -7896,20 +7896,20 @@ __metadata: languageName: node linkType: hard -"@cardano-sdk/wallet@npm:0.36.0, @cardano-sdk/wallet@npm:~0.36.0": - version: 0.36.0 - resolution: "@cardano-sdk/wallet@npm:0.36.0" +"@cardano-sdk/wallet@npm:0.37.0, @cardano-sdk/wallet@npm:~0.37.0": + version: 0.37.0 + resolution: "@cardano-sdk/wallet@npm:0.37.0" dependencies: "@cardano-sdk/core": ~0.30.0 "@cardano-sdk/crypto": ~0.1.22 "@cardano-sdk/dapp-connector": ~0.12.14 - "@cardano-sdk/hardware-ledger": ~0.9.0 - "@cardano-sdk/hardware-trezor": ~0.4.19 - "@cardano-sdk/input-selection": ~0.12.26 + "@cardano-sdk/hardware-ledger": ~0.9.1 + "@cardano-sdk/hardware-trezor": ~0.4.20 + "@cardano-sdk/input-selection": ~0.12.27 "@cardano-sdk/key-management": ~0.20.1 - "@cardano-sdk/tx-construction": ~0.18.2 + "@cardano-sdk/tx-construction": ~0.18.3 "@cardano-sdk/util": ~0.15.0 - "@cardano-sdk/util-rxjs": ~0.7.9 + "@cardano-sdk/util-rxjs": ~0.7.10 backoff-rxjs: ^7.0.0 bignumber.js: ^9.1.1 delay: ^5.0.0 @@ -7919,24 +7919,24 @@ __metadata: rxjs: ^7.4.0 ts-custom-error: ^3.2.0 ts-log: ^2.2.3 - checksum: 9fadb07be8ab4b603383e41b6712f2b09ed32075d60480bcf9bbfe798d2d858a769fceb308febea842b83d3473a225984f665834b190e9bee050386917a1a23c + checksum: 1ae1b5d009053491fb520fe677a6281e7098b72b9582c6a61fde17664d9968420e70ff185b4b90a5ad6fa43e7015729e5dd7dfdb8c7e988c51a71e94f4c673b6 languageName: node linkType: hard -"@cardano-sdk/web-extension@npm:0.26.2": - version: 0.26.2 - resolution: "@cardano-sdk/web-extension@npm:0.26.2" +"@cardano-sdk/web-extension@npm:0.27.0": + version: 0.27.0 + resolution: "@cardano-sdk/web-extension@npm:0.27.0" dependencies: "@cardano-sdk/core": ~0.30.0 "@cardano-sdk/crypto": ~0.1.22 "@cardano-sdk/dapp-connector": ~0.12.14 - "@cardano-sdk/hardware-ledger": ~0.9.0 - "@cardano-sdk/hardware-trezor": ~0.4.19 + "@cardano-sdk/hardware-ledger": ~0.9.1 + "@cardano-sdk/hardware-trezor": ~0.4.20 "@cardano-sdk/key-management": ~0.20.1 - "@cardano-sdk/tx-construction": ~0.18.2 + "@cardano-sdk/tx-construction": ~0.18.3 "@cardano-sdk/util": ~0.15.0 - "@cardano-sdk/util-rxjs": ~0.7.9 - "@cardano-sdk/wallet": ~0.36.0 + "@cardano-sdk/util-rxjs": ~0.7.10 + "@cardano-sdk/wallet": ~0.37.0 backoff-rxjs: ^7.0.0 lodash: ^4.17.21 rxjs: ^7.4.0 @@ -7944,7 +7944,7 @@ __metadata: ts-log: ^2.2.3 uuid: ^8.3.2 webextension-polyfill: ^0.8.0 - checksum: 08bb56073d783f730b254e7c6a8e51448e29d9ce436d0156da6f32c4420868ad1890126b2e24085721d150ceea693fa7d3e6640e24f02f97790263e6b718b3f9 + checksum: 89d81e8c9c45c652bfe7ad5f6af6cb9e594471e0cebeac2733ba47b54ce66e08581e59eba542f25e16e13749c21785afdee40540f470395d5d4cfb4e3f21a2a4 languageName: node linkType: hard @@ -10578,14 +10578,14 @@ __metadata: resolution: "@lace/browser-extension-wallet@workspace:apps/browser-extension-wallet" dependencies: "@ant-design/icons": ^4.7.0 - "@cardano-sdk/cardano-services-client": 0.18.0 + "@cardano-sdk/cardano-services-client": 0.19.0 "@cardano-sdk/core": 0.30.0 "@cardano-sdk/dapp-connector": 0.12.14 - "@cardano-sdk/input-selection": 0.12.26 - "@cardano-sdk/tx-construction": 0.18.2 + "@cardano-sdk/input-selection": 0.12.27 + "@cardano-sdk/tx-construction": 0.18.3 "@cardano-sdk/util": 0.15.0 - "@cardano-sdk/wallet": 0.36.0 - "@cardano-sdk/web-extension": 0.26.2 + "@cardano-sdk/wallet": 0.37.0 + "@cardano-sdk/web-extension": 0.27.0 "@emurgo/cardano-message-signing-asmjs": 1.0.1 "@emurgo/cip14-js": ~3.0.1 "@koralabs/handles-public-api-interfaces": ^1.6.6 @@ -10605,7 +10605,7 @@ __metadata: "@vespaiach/axios-fetch-adapter": ^0.3.0 antd: ^4.24.10 are-you-es5: ^2.1.2 - axios: 0.21.4 + axios: 0.28.0 bignumber.js: 9.0.1 bip39: ^3.0.4 blake2b-no-wasm: 2.1.4 @@ -10648,16 +10648,16 @@ __metadata: version: 0.0.0-use.local resolution: "@lace/cardano@workspace:packages/cardano" dependencies: - "@cardano-sdk/cardano-services-client": 0.18.0 + "@cardano-sdk/cardano-services-client": 0.19.0 "@cardano-sdk/core": 0.30.0 "@cardano-sdk/crypto": 0.1.22 - "@cardano-sdk/hardware-ledger": 0.9.0 - "@cardano-sdk/hardware-trezor": 0.4.19 + "@cardano-sdk/hardware-ledger": 0.9.1 + "@cardano-sdk/hardware-trezor": 0.4.20 "@cardano-sdk/key-management": 0.20.1 "@cardano-sdk/util": 0.15.0 "@cardano-sdk/util-dev": 0.19.18 - "@cardano-sdk/wallet": 0.36.0 - "@cardano-sdk/web-extension": 0.26.2 + "@cardano-sdk/wallet": 0.37.0 + "@cardano-sdk/web-extension": 0.27.0 "@emurgo/cardano-message-signing-browser": 1.0.1 "@lace/common": 0.1.0 "@ledgerhq/devices": ^8.2.1 @@ -10737,7 +10737,7 @@ __metadata: "@types/debounce-promise": ^3.1.6 "@types/uuid": ^8 antd: ^4.24.10 - axios: 0.21.4 + axios: 0.28.0 axios-cache-adapter: 2.7.3 classnames: ^2.3.1 debounce-promise: ^3.1.2 @@ -10821,11 +10821,11 @@ __metadata: "@ant-design/icons": ^4.7.0 "@babel/core": ^7.21.0 "@cardano-sdk/core": 0.30.0 - "@cardano-sdk/input-selection": 0.12.26 - "@cardano-sdk/tx-construction": 0.18.2 + "@cardano-sdk/input-selection": 0.12.27 + "@cardano-sdk/tx-construction": 0.18.3 "@cardano-sdk/util": 0.15.0 - "@cardano-sdk/wallet": 0.36.0 - "@cardano-sdk/web-extension": 0.26.2 + "@cardano-sdk/wallet": 0.37.0 + "@cardano-sdk/web-extension": 0.27.0 "@lace/cardano": ^0.1.0 "@lace/common": ^0.1.0 "@lace/core": 0.1.0 @@ -23242,7 +23242,18 @@ __metadata: languageName: node linkType: hard -"axios@npm:0.21.4, axios@npm:^0.21.1": +"axios@npm:0.28.0": + version: 0.28.0 + resolution: "axios@npm:0.28.0" + dependencies: + follow-redirects: ^1.15.0 + form-data: ^4.0.0 + proxy-from-env: ^1.1.0 + checksum: d3782377512e67510787bf325b664f16c8b595ccdbcf52fca58433fbd082e33c3ef58a19e7d016ce92666be5a1a5ea82028add0cb841077981df9617bd071615 + languageName: node + linkType: hard + +"axios@npm:^0.21.1": version: 0.21.4 resolution: "axios@npm:0.21.4" dependencies: @@ -23261,6 +23272,17 @@ __metadata: languageName: node linkType: hard +"axios@npm:^0.28.0": + version: 0.28.1 + resolution: "axios@npm:0.28.1" + dependencies: + follow-redirects: ^1.15.0 + form-data: ^4.0.0 + proxy-from-env: ^1.1.0 + checksum: 5115a38d79064d07437c5a28f15841e3607634040e3120ec06a2c4367a7d07cf213b16496eab53b6f58ebc5fb377a440ba9ed4782529b14449a1e285734bfb54 + languageName: node + linkType: hard + "axios@npm:^1.6.1": version: 1.6.3 resolution: "axios@npm:1.6.3"