Skip to content
This repository was archived by the owner on Oct 14, 2024. It is now read-only.
This repository was archived by the owner on Oct 14, 2024. It is now read-only.

Improve state mocking #111

@bolismauro

Description

@bolismauro

This is a proposal on how to improve UI testing in the Katana environment

The problem

We push a lot people to use the global application state to store information. This has several advantages such more or less related to testability. The key point in this area is that, thanks to the props mechanism, it is very easy to mock the ui in a certain state (and then take screenshots, check conditions on the returned children and so on). Most of the time this works, but there are some cases where it is simply not possible to store certain information about the UI in the global state. In these cases we should use the internal NodeDescription state. This has a huge impact when it comes to reproduce some UI states, since it is simply not possible to easily mock them. Here are a list of (some) cases we encountered in our applications:

  • Highlighted state of the Button / Table (or Grid) cells / anything has an highlighted state
  • Failed/Loading state of the Image component that take the image from a remote URL (or the camera roll or any async source really)

We end up fixing the issue with more or less hackish ways which are not related each other and that are tied to the specific problem.

This issue wants to created an unified method to handle all these cases.

Proposed Solution

Technically speaking, the initialization/handling of the node states is deep inside the Node instances that back the descriptions / UIs. The key idea is to create an object that holds the logic to mock the state in the Katana UI. When a node initializes itself, instead of using the default state (that is, invoke the init() method on the state type), it will use this object to get the proper state, if any. If there isn't any mocked state it will fallback to the default case (the empty init).

From the API perspective this is how you would use it

let renderer = Renderer(rootDescription: description, store: store, stateMockProvider: stateMockProvider)

Basically, the Renderer takes one extra argument that is used to understand how to mock the state, if needed.

Here is how to create a stateMockProvider:

let mock = StateMockProvider()

mock.mockState(ADescription.State(), for: ADescription.Type, passingFilter: { props in
  // return true or false based on the props
})

The idea is that you can ask Katana to use the first parameter (the mocked state) for a specific description type, when the filter returns true. You can use the filter to only highlight specific buttons or specific table rows.

Filters are evaluated in the order are passed to the provider, and the first valid state will be picked.

One important thing to note is that the highlighted state is permanent. In case of live screenshots (e.g., test screens of the app where is possible to interact with the UI) the update of the state will be ignored. This is to ensure consistency and avoid things like: "The button is highlighted, you tap on it and the highlight disappear (since the update function will be invoked and the highlighted state removed)". If a state is not mocked, everything will work as usual.

A real world example would be the following:

// supopose that we have a Table element, where each cell has the following props
struct TableCellProps {
  // usual props for frame, alpha, ..
  let index: Int
}

// and the following state
struct TableCellState {
  let isHighlighted: Bool
}

// we want to highlight only the first row of each table, here is the mock state
let highlightedState = TableCellState(isHighlighted: true)
mockStateProvider.mockState(highlightedState, for: Table.self, passingFilter: { (props: TableCellProps) -> Bool in
  return props.index == 1
})

Technical Details

The idea is to leverage the renderer property in the Node to access to the state mock. This value is used to mock the state, if possible.
The node should also check whether an instance of state mock is available and, if this is the case, skip the update phase when the state changes.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions