> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/contributor/contributor-es/guias-para-colaboradores/testing-standards/testing-uis.md).

# Pruebas de UI

Esta sección describe cómo los desarrolladores probarán la base de código relacionada con la UI.

## Stack de pruebas

Todas nuestras pruebas de UI DEBEN hacerse usando [Jest](https://jestjs.io/) como el framework principal de pruebas con [redux-saga-test-plan](https://github.com/jfairbank/redux-saga-test-plan) como la herramienta de pruebas de Sagas y [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) como herramienta de pruebas de componentes de UI. El código de las pruebas DEBE escribirse usando [TypeScript](https://www.typescriptlang.org/) y ejecutarse usando [ts-jest](https://github.com/kulshekhar/ts-jest) para tener soporte de verificación de tipos.

## Qué probar

* Componentes de UI
* Sagas
* Reducers
* Selectors
* creadores de acciones
* Funciones de utilidad

## Pruebas de Sagas

Sagas nos permite manejar efectos secundarios permitiéndonos crear generadores que manejan acciones de Redux. Una de las principales ventajas de Sagas es su capacidad de prueba, ya que los efectos ampliamente usados en Sagas son muy fáciles de probar.

Estos handlers DEBEN probarse usando [redux-saga-test-plan](https://github.com/jfairbank/redux-saga-test-plan), y la prueba DEBE ejercitar toda la ejecución, desde el handler principal del módulo (su handler principal o Sagas) hasta toda la ejecución de los handlers que reaccionarán a la acción que vamos a despachar. Este enfoque hace que las pruebas de Sagas sean fáciles de construir y mantener, y aporta valor a las pruebas al recorrer la mayor parte posible del flujo completo.

Las pruebas de los handlers DEBEN probarse a través de su interacción con los efectos, principalmente con el `put` ya que su propósito es manejar acciones y producir nuevas. La mayoría de las pruebas seguramente contendrán un `provide` call, usado para simular efectos, uno o múltiples `put` calls, para verificar que los handlers despachan las acciones como se espera y un único `dispatch` seguido de un `run` call para ejecutar todas las Sagas hasta su finalización.

La simulación de recursos externos al módulo que se está probando DEBE hacerse usando los providers de redux-saga-test-plan aprovechando los diferentes efectos proporcionados por redux-sagas. Effects como call, apply u otros DEBEN usarse siempre que sea posible cuando se llama a una función en una prueba para permitir simularlas fácilmente. Jest NO DEBE usarse para simular módulos o funciones.

El texto en el describe y sus tests DEBE seguir estas pautas:

* El describe principal de una saga o del handler principal DEBE empezar con *when handling*, lo que indica que lo que se va a probar es un handler. Siguiendo la *when handling* frase, DEBE escribirse una descripción de la acción (o de la intención de la acción) que se va a manejar. Por ejemplo: *cuando se maneja la acción que señala una obtención exitosa.*
* Los describes internos diferenciarán contextos de ejecución; siguen las mismas reglas descritas en la [Descripción y construcción del contexto](/contributor/contributor-es/guias-para-colaboradores/testing-standards/writing-tests.md#describing-and-building-context) sección.
* El **pruebas**Los it DEBERÍAN describir lo que se espera de la prueba, empezando con un *should put* y la descripción de las acciones que deberían hacer put al ejecutar el handler.

### Ejemplo de Sagas

```tsx
...
export function* tiersSaga(builder: BuilderAPI) {
	function* handleBuyThirdPartyItemTierRequest(action: BuyThirdPartyItemTiersRequestAction) {
    const { thirdParty, tier } = action.payload
    try {
			// Obtener el chain id puede fallar, se debe hacer una prueba para asegurar que este escenario se ejercite
      const maticChainId: ChainId = yield call(getChainIdByNetwork, Network.MATIC)
      const thirdPartyContract = yield call(getContract, ContractName.ThirdPartyRegistry, maticChainId)
			// Enviar la transacción puede tener éxito o fallar, ambos casos deben ejercitarse en una prueba
      const txHash: string = yield call(sendTransaction, thirdPartyContract, instantiatedThirdPartyContractContract =>
        instantiatedThirdPartyContractContract.buyItemSlots(thirdParty.id, tier.id, tier.price)
      )
      yield put(buyThirdPartyItemTiersSuccess(txHash, maticChainId, thirdParty, tier))
    } catch (error) {
      yield put(buyThirdPartyItemTiersFailure(error.message, thirdParty.id, tier))
    }
  }
}
```

### Ejemplo de prueba de Sagas

```tsx
// El describe principal empieza con 'when handling...'
describe('cuando se maneja la solicitud para comprar un nivel de item tier de un tercero', () => {
	// Se ejercitan todos los casos, negativos y positivos
  describe('y no se pudo obtener el chain id', () => {
    it('debería poner la acción que señala el fallo en la compra de un nivel de item slots con el tier y el id del tercero', () => {
      return expectSaga(tiersSaga, mockedBuilderApi)
				// Provide calls simulan los diferentes efectos, construyendo distintos escenarios
        .provide([[call(getChainIdByNetwork, Network.MATIC), Promise.reject(new Error(defaultError))]])
				// Put calls verifica que el resultado final esperado sucedió
        .put(buyThirdPartyItemTiersFailure(defaultError, thirdParty.id, thirdPartyItemTier))
				// Se hace un único dispatch por prueba para ejercitar el manejo de la acción que estamos probando
        .dispatch(buyThirdPartyItemTiersRequest(thirdParty, thirdPartyItemTier))
        .run({ silenceTimeout: true })
    })
  })

  describe('y el envío de la transacción falla', () => {
    it('debería poner la acción que señala el fallo en la compra de un nivel de item slots con el tier y el id del tercero', () => {
      return expectSaga(tiersSaga, mockedBuilderApi)
        .provide([
					// Call coincide con el efecto call exacto, teniendo en cuenta los parámetros
					// Esto hace que nuestra prueba asegure que el código ejecutado antes de un put hizo lo que esperábamos
          [call(getChainIdByNetwork, Network.MATIC), ChainId.MATIC_MUMBAI],
          [call(getContract, ContractName.ThirdPartyRegistry, ChainId.MATIC_MUMBAI), contract],
					// Los matchers se usan para hacer match solo con la función y no con los parámetros
          [matchers.call.fn(sendTransaction), Promise.reject(new Error(defaultError))]
        ])
        .put(buyThirdPartyItemTiersFailure(defaultError, thirdParty.id, thirdPartyItemTier))
        .dispatch(buyThirdPartyItemTiersRequest(thirdParty, thirdPartyItemTier))
        .run({ silenceTimeout: true })
    })
  })

  describe('y el envío de la transacción tiene éxito', () => {
    let contract: any
    beforeEach(() => {
      contract = { buyItemSlots: jest.fn() }
    })

    it('debería poner la acción que señala la compra exitosa de un nivel de item slots con el tier, el tercero y los detalles de la transacción', () => {
      return expectSaga(tiersSaga, mockedBuilderApi)
        .provide([
          [call(getChainIdByNetwork, Network.MATIC), ChainId.MATIC_MUMBAI],
          [call(getContract, ContractName.ThirdPartyRegistry, ChainId.MATIC_MUMBAI), contract],
          [matchers.call.fn(sendTransaction), Promise.resolve(txHash)]
        ])
        .put(buyThirdPartyItemTiersSuccess(txHash, ChainId.MATIC_MUMBAI, thirdParty, thirdPartyItemTier))
        .dispatch(buyThirdPartyItemTiersRequest(thirdParty, thirdPartyItemTier))
        .run({ silenceTimeout: true })
    })
  })
})
```

## Pruebas de reducers

Los reducers son funciones que toman un estado y una acción y devuelven un nuevo estado. Este nuevo estado PUEDE contener cambios programados para producirse con la acción dada.

Las pruebas DEBERÍAN variar los estados iniciales y los diferentes parámetros de la acción que se va a probar si es necesario. Las aserciones DEBEN hacerse sobre todo el estado devuelto, ya que una acción podría modificar cualquier parte del estado inicial dado.

Al reducir una acción, DEBERÍAMOS usar el creador de acciones, lo que hace que las pruebas sean más fáciles de mantener.

Para estandarizar la forma en que escribimos **describe**s y **pruebas**s:

* Los describes principales DEBERÍAN comenzar describiendo qué acciones se van a reducir usando la frase *when reducing the action* seguido de una descripción de la acción. NO DEBERÍAMOS usar el tipo de la acción para describir la acción que se va a probar, ya que el tipo podría cambiar.
* El **pruebas**Los it DEBERÍAN describir de forma clara cómo cambió el estado devuelto. Por ejemplo: *debería devolver un estado con el error anulado y las frutas establecidas*.

### Ejemplo de reducer

```tsx
const INITIAL_STATE: FruitsState = {
  data: {
    fruits: []
  },
  loading: []
}

export function fruitsReducer(state = INITIAL_STATE, action: any): FruitsState {
  switch (action.type) {
    case FETCH_FRUIT_REQUEST: {
      return {
        ...state,
        loading: loadingReducer(state.loading, action),
				error: null
      }
    }
		case FETCH_FUIT_ERROR: {
			const { error } = action
      return {
        ...state,
				error,
        loading: loadingReducer(state.loading, action)
      }
		}
		case FETCH_FRUIT_SUCCESS: {
			const { fruits } = action
      return {
        ...state,
				fruits,
        loading: loadingReducer(state.loading, action)
      }
		}
}
```

### Ejemplo de prueba de reducers

```tsx
let state: FruitsState

beforeEach(() => {
	state = {
	  data: {
	    fruits: []
	  },
	  loading: [],
		error: null
	}
})

describe('cuando se reduce la acción que señala la obtención de frutas', () => {
	beforeEach(() => {
		// Se establece un error para asegurar que se limpie al reducir la acción.
		state.error = 'anError'
	})

	it('debería devolver un estado donde el error se limpia y el estado de carga se establece como cargando', () => {
		// Los action creators se usan para probar el reducer para que se pueda mantener con facilidad.
		const action = fetchFruitsRequest()

		expect(fruitsReducer(state, action)).toEqual({
			...state,
			error: null,
			/* Como el reducer de carga forma parte de otro reducer (y no necesita probarse),
				 podemos elegir usarlo tal cual o podemos usar un helper que lo emule.
			*/
			loading: loadingReducer(state.loading, action),
		})
	})
})

describe('cuando se reduce la acción que señala el fallo al obtener frutas', () => {
	const error = 'anError'

	it('debería devolver un estado donde el error se establece y el estado de carga se limpia de la acción de obtención de frutas', () => {
		const action = fetchFruitsFailure(error)

		expect(fruitsReducer(state, action)).toEqual({
			...state,
			error,
			loading: loadingReducer(state.loading, action),
		})
	})
})

describe('cuando se reduce la acción que señala el éxito al obtener frutas', () => {
	let fruits: string[]
	beforeEach(() => {
		fruits = ['apples', 'pears']
	})

	it('debería devolver un estado donde las frutas se establecen y el reducer de carga se limpia de la acción de obtención de frutas', () => {
		const action = fetchFruitsSuccess(fruits)

		expect(fruitsReducer(state, action)).toEqual({
			...state,
			fruits,
			loading: loadingReducer(state.loading, action),
		})
	})
})
```

## Pruebas de selectors

Los selectors son métodos que toman un estado y devuelven una sección de ese estado o una sección transformada del estado.

Hay dos tipos de selectors: selectors memorizados y selectors normales o comunes. Los selectors normales o comunes DEBEN probarse como cualquier otra función probada unitariamente y los selectors memorizados DEBERÍAN probarse usando la `resultFunc` o la función que recibe las partes memorizadas del estado y devuelve el valor deseado.

**Describe**s y **pruebas**s DEBEN escribirse como se describe en la [Descripción y construcción del contexto](/contributor/contributor-es/guias-para-colaboradores/testing-standards/writing-tests.md#describing-and-building-context) sección.

### Ejemplo de selectores

```tsx
// Selector común
export const collectionExists = (state: RootState, id: string): boolean => {
	return state.collections.data.some((collection) => collection.id === id)
}

// Selector memorizado usando reselect que devuelve una lista de collections mejoradas
export const getEnhancedCollections = createSelector(
	getCollections,
	getExtraData,
	(collections, extraData) => {
		return collections.map((collection) =>
			({ ...collection, ...extraData[collection[id]] }))
	}
)
```

### Ejemplo de prueba de selectores

```tsx
let state: RootState
let collection: { id: string }
beforeEach(() => {
	collection = {
		id: 'anId'
	}
	state = {
		collections: {
			data: []
		}
	}
})

beforeEach(() => {
	state.collections.data = [collection]
})

// Los selectors comunes se prueban como funciones normales
describe('cuando se verifica si existe una collection', () => {
	describe('y no hay ninguna collection con el id dado', () => {
		it('debería devolver false', () => {
			expect(collectionExists(state, "anotherId")).toBe(false)
		})
	})

	describe('y hay una collection con el id dado', () => {
		it('debería devolver true', () => {
			expect(collectionExists(state, collection.id)).toBe(true)
		})
	})
})

describe('cuando se obtienen las collections mejoradas', () => {
	let enhacements: Record<string, Enhacement>

	describe('y no hay datos para mejorarlas', () => {
		beforeEach(() => {
			enhacements = {}
		})

		// Los selectors memorizados deberían probarse unitariamente usando resultFunc
		it('debería recuperar todas las collections sin mejorarlas', () => {
			expect(getEnhancedCollections.resultFunc(collections, enhacements)).toEqual([
				collection
			])
		})
	})

	describe('y hay datos para mejorarlas', () => {
		beforeEach(() => {
			enhacements = { [collection.id]: { aNewProperty: true } }
		})

		it('debería recuperar todas las collections mejoradas', () => {
			expect(getEnhancedCollections.resultFunc(collections, enhacements)).toEqual([
				{ ...collection, ...enhacements[collection.id] }
			])
		})
	})
})
```

## Pruebas de creadores de acciones

Los action creators se encargan de crear las acciones que luego se procesarán en los reducers. Aunque los action creators suelen ser simples, DEBEN probarse para asegurarnos de que hacen lo que esperamos.

**Describe**s y **pruebas**s DEBEN escribirse como se describe en la [Descripción y construcción del contexto](/contributor/contributor-es/guias-para-colaboradores/testing-standards/writing-tests.md#describing-and-building-context) sección.

### Ejemplo de action creators

```tsx
export const BUY_THIRD_PARTY_ITEM_TIERS_REQUEST = '[Request] Comprar un nivel de item tier de un tercero'
export const BUY_THIRD_PARTY_ITEM_TIERS_SUCCESS = '[Success] Comprar un nivel de item tier de un tercero'
export const BUY_THIRD_PARTY_ITEM_TIERS_FAILURE = '[Failure] Comprar un nivel de item tier de un tercero'

export const buyThirdPartyItemTiersRequest = (thirdParty: ThirdParty, tier: ThirdPartyItemTier) =>
  action(BUY_THIRD_PARTY_ITEM_TIERS_REQUEST, { thirdParty, tier })
export const buyThirdPartyItemTiersSuccess = (txHash: string, chainId: ChainId, thirdParty: ThirdParty, tier: ThirdPartyItemTier) =>
  action(BUY_THIRD_PARTY_ITEM_TIERS_SUCCESS, { thirdParty, tier, ...buildTransactionPayload(chainId, txHash, { tier, thirdParty }) })
export const buyThirdPartyItemTiersFailure = (error: string, thirdPartyId: string, tier: ThirdPartyItemTier) =>
  action(BUY_THIRD_PARTY_ITEM_TIERS_FAILURE, { error, thirdPartyId, tier })
```

### Ejemplo de prueba de action creators

```tsx
describe('cuando se crea la acción que señala el inicio de una solicitud de obtención de tiers', () => {
  it('debería devolver una acción que señala el inicio de la obtención de tiers', () => {
    expect(fetchThirdPartyItemTiersRequest()).toEqual({ type: FETCH_THIRD_PARTY_ITEM_TIERS_REQUEST })
  })
})

describe('cuando se crea la acción que señala la obtención exitosa de tiers', () => {
  it('debería devolver una acción que señala el éxito de la obtención de tiers', () => {
    expect(fetchThirdPartyItemTiersSuccess([thirdPartyItemTier])).toEqual({
      type: FETCH_THIRD_PARTY_ITEM_TIERS_SUCCESS,
      payload: { tiers: [thirdPartyItemTier], error: undefined, meta: undefined }
    })
  })
})

describe('cuando se crea la acción que señala la obtención fallida de tiers', () => {
  it('debería devolver una acción que señala el fallo de la obtención de tiers', () => {
    expect(fetchThirdPartyItemTiersFailure(defaultError)).toEqual({
      type: FETCH_THIRD_PARTY_ITEM_TIERS_FAILURE,
      payload: { error: defaultError }
    })
  })
})
```

## Pruebas de componentes de UI

Para probar componentes de UI estamos usando [React Testing Library](https://testing-library.com/docs). Estamos usando esta librería en lugar de otras opciones como enzyme porque nos permite probar componentes teniendo en cuenta cómo interactuará el usuario con ellos en lugar de la implementación en sí. No interactuaremos con instancias de componentes de React; en su lugar, interactuaremos con elementos del DOM tal como se renderizan.

### Qué probar

El propósito de esta prueba de componentes de UI es probar el comportamiento del componente y cómo interactúa el usuario con él. En muchos casos puede haber componentes conectados al store de Redux para obtener algunas propiedades y llamar a algunas acciones. Normalmente manejamos esto creando un `contenedor` archivo separado que interactuará con el store. Estos archivos están fuera del alcance de este tipo de pruebas. Estamos probando cómo se comporta el componente con distintos valores de props; no nos debería importar el origen de esos valores (store de Redux, context, componente padre), sino cómo impactan el render final.

### Renderizado

Cada prueba debería empezar llamando a la función render. Esto no solo renderizará el componente que enviamos como parámetro, sino también a todos los children. Deberíamos evitar hacer esto en el setup de la prueba (`beforeEach`) ya que puede causar problemas al hacer que el árbol DOM tenga más componentes de los que queremos. Deberíamos guardar el resultado en una propiedad llamada **screen**. Hacer esto nos permitirá obtener todas las propiedades que necesitamos en la prueba sin tener que cambiar constantemente la desestructuración.

```jsx
it('debería renderizar el componente hello world', () => {
	const screen = render(<HelloWorld />)
})
```

Si el archivo tendrá múltiples pruebas y el componente tiene múltiples props, podemos crear una función en la parte superior del archivo que se encargue de establecer props por defecto y renderizar el componente.

```jsx
function renderSelectedFilters(props: Partial<Props> = {}) {
  return render(
    <SelectedFilters
      isLandSection={false}
      category={undefined}
      browseOptions={{}}
      onBrowse={jest.fn()}
      {...props}
    />
  )
}
```

### Queries

Las Queries son los métodos que Testing Library te da para encontrar elementos en la página. Existen diferentes [tipos de queries](https://testing-library.com/docs/queries/about/#priority) que podemos usar para acceder a los elementos. Deberíamos intentar usar siempre las queries que sean `accesibles para todos` ya que son las que reflejan la experiencia de todos los usuarios (visual/ratón y usando tecnología de asistencia).

Esas queries son `getByRole`, `getByLabelText`, `getByPlaceholderText`, `getByText`, `getByDisplayValue`. Si podemos acceder al elemento usando estas queries, probablemente significa que el componente no es accesible.

### Pruebas de eventos de usuario

No podremos acceder a las funciones dentro del componente, así que debemos imitar el comportamiento del usuario que lleva a que esas funciones se llamen. Para ello usamos `userEvent` librería.

```jsx
const screen = renderSelectedFilters({
      browseOptions: raritiesOptions,
      onBrowse: onBrowseMock
    })
const commonPill = screen.getByTestId('pill-common')
await userEvent.click(within(commonPill).getByRole('button'))
```

### Depuración

Para depurar la prueba y ver cuál es el estado actual del DOM en un momento específico podemos usar `debug` función, que es una de las propiedades que se devuelven al llamar a render

```jsx
const { debug } = render(<HelloWorld />)

debug()
```

### Ejemplo completo de prueba de componente de UI

```jsx
function renderSelectedFilters(props: Partial<Props> = {}) {
  return render(
    <SelectedFilters
      isLandSection={false}
      category={undefined}
      browseOptions={{}}
      onBrowse={jest.fn()}
      {...props}
    />
  )
}

describe('filtro de rarezas', () => {
  it('debería renderizar rarezas', () => {
    const raritiesOptions = { rarities: [Rarity.COMMON, Rarity.EPIC] }
    const screen = renderSelectedFilters({
      browseOptions: raritiesOptions
    })
    expect(screen.getByText(Rarity.COMMON)).toBeInTheDocument()
    expect(screen.getByText(Rarity.EPIC)).toBeInTheDocument()
  })

  it('debería llamar a onBrowse sin la rareza eliminada', async () => {
    const raritiesOptions = { rarities: [Rarity.COMMON, Rarity.EPIC] }
    const onBrowseMock = jest.fn()

    const screen = renderSelectedFilters({
      browseOptions: raritiesOptions,
      onBrowse: onBrowseMock
    })
    const commonPill = screen.getByTestId('pill-common')
    await userEvent.click(within(commonPill).getByRole('button'))
    expect(onBrowseMock).toHaveBeenCalledWith({ rarities: [Rarity.EPIC] })
  })
})
```

## Estructura de directorios

Las pruebas DEBEN colocarse junto a los archivos o módulos que van a ejercitar, con el mismo nombre, pero con la extensión `spec.ts` en lugar de `ts`.

Una estructura de archivos DEBE verse así:

```
actions.ts
action.spec.ts
reducer.ts
reducer.spec.ts
sagas.ts
sagas.spec.ts
selectors.ts
selectors.spec.ts
utils.ts
utils.spec.ts
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.decentraland.org/contributor/contributor-es/guias-para-colaboradores/testing-standards/testing-uis.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
