Skip to content

Commit

Permalink
fix: wait for identity success action before fetching catalog (#1974)
Browse files Browse the repository at this point in the history
  • Loading branch information
juanmahidalgo authored Aug 15, 2023
1 parent 588ce77 commit 8392277
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 48 deletions.
163 changes: 116 additions & 47 deletions webapp/src/modules/item/sagas.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { expectSaga } from 'redux-saga-test-plan'
import * as matchers from 'redux-saga-test-plan/matchers'
import { call, select, take } from 'redux-saga/effects'
import { ChainId, Item, Network, Rarity } from '@dcl/schemas'
import { Wallet } from 'decentraland-dapps/dist/modules/wallet/types'
import { sendTransaction } from 'decentraland-dapps/dist/modules/wallet/utils'
import { setPurchase } from 'decentraland-dapps/dist/modules/gateway/actions'
import { TradeType } from 'decentraland-dapps/dist/modules/gateway/transak/types'
Expand Down Expand Up @@ -47,7 +48,7 @@ import {
fetchCollectionItemsFailure,
FETCH_ITEMS_CANCELLED_ERROR_MESSAGE
} from './actions'
import { itemSaga } from './sagas'
import { CANCEL_FETCH_ITEMS, itemSaga } from './sagas'
import { getData as getItems } from './selectors'
import { getItem } from './utils'
import { ItemBrowseOptions } from './types'
Expand Down Expand Up @@ -418,62 +419,128 @@ describe('when handling the fetch items request action', () => {
pathname = locations.browse()
})
describe('and there is an ongoing fetch item request', () => {
let wallet: Wallet | undefined
let originalBrowseOptions = itemBrowseOptions
let newBrowseOptions: ItemBrowseOptions = {
...itemBrowseOptions,
filters: { ...itemBrowseOptions.filters, rarities: [Rarity.COMMON] }
}
it('should dispatch a successful action with the fetched items and cancel the ongoing one', () => {
return expectSaga(itemSaga, getIdentity)
.provide([
[
matchers.call.fn(waitForWalletConnectionIfConnecting),
undefined
],
[matchers.call.fn(waitForFeatureFlagsToBeLoaded), undefined],
[select(getIsMarketplaceServerEnabled), true],
[select(getLocation), { pathname }],
{
call(effect, next) {
if (
effect.fn === CatalogAPI.prototype.get &&
effect.args[0] === originalBrowseOptions.filters
) {
// Add a setTimeout so it gives time to get it cancelled
return new Promise(() => {})
describe('and there is a wallet connected', () => {
beforeEach(() => {
wallet = {} as Wallet
})

it('should dispatch a successful action with the fetched items and cancel the ongoing one', () => {
return expectSaga(itemSaga, getIdentity)
.provide([
[
matchers.call.fn(waitForWalletConnectionIfConnecting),
undefined
],
[matchers.call.fn(waitForFeatureFlagsToBeLoaded), undefined],
[select(getWallet), wallet],
[select(getIsMarketplaceServerEnabled), true],
[select(getLocation), { pathname }],
{
call(effect, next) {
if (
effect.fn === CatalogAPI.prototype.get &&
effect.args[0] === originalBrowseOptions.filters
) {
// Add a setTimeout so it gives time to get it cancelled
return new Promise(() => {})
}
if (
effect.fn === CatalogAPI.prototype.get &&
effect.args[0] === newBrowseOptions.filters
) {
// Mock without timeout
return fetchResult
}
return next()
}
if (
effect.fn === CatalogAPI.prototype.get &&
effect.args[0] === newBrowseOptions.filters
) {
// Mock without timeout
return fetchResult
}
])
.call.like({
fn: CatalogAPI.prototype.get,
args: [newBrowseOptions.filters]
})
.put(
fetchItemsFailure(
FETCH_ITEMS_CANCELLED_ERROR_MESSAGE,
originalBrowseOptions
)
)
.put(
fetchItemsSuccess(
fetchResult.data,
fetchResult.total,
newBrowseOptions,
nowTimestamp
)
)
.dispatch(fetchItemsRequest(originalBrowseOptions))
.dispatch({ type: CANCEL_FETCH_ITEMS })
.dispatch(fetchItemsRequest(newBrowseOptions))
.run({ silenceTimeout: true })
})
})

describe('and there is no wallet connected', () => {
it('should dispatch a successful action with the fetched items and cancel the ongoing one', () => {
return expectSaga(itemSaga, getIdentity)
.provide([
[
matchers.call.fn(waitForWalletConnectionIfConnecting),
undefined
],
[matchers.call.fn(waitForFeatureFlagsToBeLoaded), undefined],
[select(getWallet), wallet],
[select(getIsMarketplaceServerEnabled), true],
[select(getLocation), { pathname }],
{
call(effect, next) {
if (
effect.fn === CatalogAPI.prototype.get &&
effect.args[0] === originalBrowseOptions.filters
) {
// Add a setTimeout so it gives time to get it cancelled
return new Promise(() => {})
}
if (
effect.fn === CatalogAPI.prototype.get &&
effect.args[0] === newBrowseOptions.filters
) {
// Mock without timeout
return fetchResult
}
return next()
}
return next()
}
}
])
.call.like({
fn: CatalogAPI.prototype.get,
args: [newBrowseOptions.filters]
})
.put(
fetchItemsFailure(
FETCH_ITEMS_CANCELLED_ERROR_MESSAGE,
originalBrowseOptions
])
.call.like({
fn: CatalogAPI.prototype.get,
args: [newBrowseOptions.filters]
})
.put(
fetchItemsFailure(
FETCH_ITEMS_CANCELLED_ERROR_MESSAGE,
originalBrowseOptions
)
)
)
.put(
fetchItemsSuccess(
fetchResult.data,
fetchResult.total,
newBrowseOptions,
nowTimestamp
.put(
fetchItemsSuccess(
fetchResult.data,
fetchResult.total,
newBrowseOptions,
nowTimestamp
)
)
)
.dispatch(fetchItemsRequest(originalBrowseOptions))
.dispatch(fetchItemsRequest(newBrowseOptions))
.run({ silenceTimeout: true })
.dispatch(fetchItemsRequest(originalBrowseOptions))
.dispatch({ type: CANCEL_FETCH_ITEMS })
.dispatch(fetchItemsRequest(newBrowseOptions))
.run({ silenceTimeout: true })
})
})
})
})
Expand All @@ -488,6 +555,7 @@ describe('when handling the fetch items request action', () => {
[matchers.call.fn(CatalogAPI.prototype.get), fetchResult],
[matchers.call.fn(waitForWalletConnectionIfConnecting), undefined],
[matchers.call.fn(waitForFeatureFlagsToBeLoaded), undefined],
[select(getWallet), undefined],
[select(getLocation), { pathname }],
[select(getIsMarketplaceServerEnabled), false]
])
Expand All @@ -511,6 +579,7 @@ describe('when handling the fetch items request action', () => {
.provide([
[select(getLocation), { pathname: '' }],
[select(getIsMarketplaceServerEnabled), true],
[select(getWallet), undefined],
[matchers.call.fn(CatalogAPI.prototype.get), Promise.reject(anError)],
[matchers.call.fn(waitForWalletConnectionIfConnecting), undefined],
[matchers.call.fn(waitForFeatureFlagsToBeLoaded), undefined]
Expand Down
28 changes: 27 additions & 1 deletion webapp/src/modules/item/sagas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from 'decentraland-dapps/dist/modules/gateway/actions'
import { isNFTPurchase } from 'decentraland-dapps/dist/modules/gateway/utils'
import { PurchaseStatus } from 'decentraland-dapps/dist/modules/gateway/types'
import { Wallet } from 'decentraland-dapps/dist/modules/wallet/types'
import { isErrorWithMessage } from '../../lib/error'
import { config } from '../../config'
import { ItemAPI } from '../vendor/decentraland/item/api'
Expand All @@ -36,6 +37,7 @@ import { fetchSmartWearableRequiredPermissionsRequest } from '../asset/actions'
import { MARKETPLACE_SERVER_URL } from '../vendor/decentraland'
import { getIsMarketplaceServerEnabled } from '../features/selectors'
import { waitForFeatureFlagsToBeLoaded } from '../features/utils'
import { GENERATE_IDENTITY_SUCCESS } from '../identity/actions'
import {
buyItemFailure,
BuyItemRequestAction,
Expand Down Expand Up @@ -66,12 +68,14 @@ import {
fetchCollectionItemsSuccess,
fetchCollectionItemsFailure,
FETCH_COLLECTION_ITEMS_REQUEST,
FETCH_ITEMS_CANCELLED_ERROR_MESSAGE
FETCH_ITEMS_CANCELLED_ERROR_MESSAGE,
FetchItemsFailureAction
} from './actions'
import { getData as getItems } from './selectors'
import { getItem } from './utils'

export const NFT_SERVER_URL = config.get('NFT_SERVER_URL')!
export const CANCEL_FETCH_ITEMS = 'CANCEL_FETCH_ITEMS'

export function* itemSaga(getIdentity: () => AuthIdentity | undefined) {
const API_OPTS = {
Expand Down Expand Up @@ -109,6 +113,7 @@ export function* itemSaga(getIdentity: () => AuthIdentity | undefined) {

// if we have a task running in the browse path, we cancel the previous one
if (matchPath(currentPathname, { path }) && task && task.isRunning()) {
yield put({ type: CANCEL_FETCH_ITEMS }) // to unblock the saga waiting for the identity success
yield cancel(task)
}
task = yield fork(handleFetchItemsRequest, action)
Expand Down Expand Up @@ -176,13 +181,34 @@ export function* itemSaga(getIdentity: () => AuthIdentity | undefined) {
}
}

function* waitForIdentityToBeGenerated(action: FetchItemsRequestAction) {
const {
wasFetchedCancelled
}: {
wasFetchedCancelled: FetchItemsFailureAction
} = yield race({
wasFetchedCancelled: take(CANCEL_FETCH_ITEMS),
identiyGenerated: take(GENERATE_IDENTITY_SUCCESS)
})
if (wasFetchedCancelled) {
yield put(
fetchItemsFailure(FETCH_ITEMS_CANCELLED_ERROR_MESSAGE, action.payload)
)
}
}

function* handleFetchItemsRequest(
action: FetchItemsRequestAction
): SagaIterator {
const { filters, view } = action.payload

// If the wallet is getting connected, wait until it finishes to fetch the items so it can fetch them with authentication
yield call(waitForWalletConnectionIfConnecting)
const wallet: Wallet | undefined = yield select(getWallet)

if (wallet) {
yield call(waitForIdentityToBeGenerated, action)
}

try {
yield call(waitForFeatureFlagsToBeLoaded)
Expand Down

0 comments on commit 8392277

Please sign in to comment.