# Тестирование

Для e2e тестирования на мобильных платформах используется библиотека [Detox](https://github.com/wix/Detox) и test-runner [Jest](https://jestjs.io/)



## Установка

Установите необходимые зависимости `yarn add -D @startupjs/e2e eslint-plugin-detox eslint-plugin-jest`

Добавте следующие правила в `.eslint.json`:

```js
  "env": {
    "detox/detox": true,
    "jest": true,
    "jest/globals": true
  },
  "extends": [
    "plugin:jest/recommended"
  ],
  "plugins": [
    "jest",
    "detox",
  ],
  "rules": {
    "jest/expect-expect": "off",
  }
```

## Запуск тестов

Для запуска команд `@startupjs/e2e` необходимо глобально установить `detox-cli` -- `npm i detox-cli -g`

Для локального запуска e2e-тестирования необходимо выполнить следующие команды:
  - `npx startupjs test --init` - создаст необходимые файлы конфигураций для Detox в корне проекта (папку e2e, .detoxrc.js, .env.detox)
  - `npx startupjs test --build` - соберет приложение iOS (обязательно в первый раз или после установки новых пакетов)
  - `npx startupjs test --ios` - запустит тесты в iOS симуляторе. (с флагом `--artifacts` во время тестов появятся скриншоты приложения во время тестов в папке `/artifacts`)

## Написание тестов

В первую очередь вам необходимо ознакомиться с документацией [API Detox](https://github.com/wix/Detox/tree/master/docs#api-reference). Здесь можно найти все стандартные команды для тестирования.

### Создание файла с тестами

В папке `e2e` в корне вашего проекта, создайте файл `*.e2e.js` и добавте в него минимальный шаблон:

```js
const { x } = require('@startupjs/e2e/helpers')

describe('Example', () => {
  it('should visible button', async () => {
    await x('#button').toBeVisible()
  })
})
```

### testID

detox умеет находить элементы по `id` как это делается в традиционном `html`. Для того чтобы присвоить `id` компоненту передайте ему пропс `testID` со значением `id`. Например:

```pug
Button(
  testID='confirmButton'
  onPress=() => console.log('confirm')
) Confirm
```

Таким образом мы сможем найти данный компонент по `x('#confirmButton')`.

### Функция `x`

Для выбора нужного компонента в приложении используется функция `x`, она может принимать следующие значения:
- Поиск по `testID` - должно начинаться с `#`, например: `x('#myButton')`
- Поиск по тексту - должно начинаться с `= `, например: `x('= Подписаться')`
- Поиск по типу - просто название типа, например: `x('UIPickerView')` для `Select`

Данная функция упрощает взаимодействие с Detox API. Она полностью заменяет функции поиска элемента в проекте.

Также `x` поддерживает поиск вложенных элементов. К примеру, у нас есть компонент с текстом `Test text`, который вложен в компонент с `testID='parentComponent'`:

```pug
Div(testID='parentComponent')
  Span Test text
```

Тогда чтобы получить компонент `Span Test text` нам необходимо вызвать `x` следующим образом:

```js
await x('#parentComponent ="Test text"')
```

Это крайне полезно в ситуациях, когда одинаковые вложенные элементы имеют различных родителей. Например:

```pug
Div(testID='grandParent')
  Div(testID='parentComponent1')
    Span Test text
  Div(testID='parentComponent2')
    Span Test text
```

В данном случае у нас одинаковый текст в различных родителях. Чтобы получить `Span`, который вложен в `parentComponent2` нужно использовать:

```js
await x('#parentComponent2 ="Test text"')
```

# Действия

После того как мы нашли элемент, к нему можно применять некоторые действия. Например нажать на него, ввести текст и так далее. Все доступные действия над элементом можно увидеть в [сооветствующем разделе документации Detox](https://github.com/wix/Detox/blob/master/docs/APIRef.ActionsOnElement.md).

Давайте разберем несколько основных действий, чтобы понять принцип их работы.

## .tap()

`.tap()` позволяет нажать на элемент. То есть, чтобы проверить работу, например, кнопки, на нее необходимо нажать и так далее. Важно помнить, что данное действие можно применить к любому элементу. Пусть у нас есть форма, которая содержит две кнопки (принять и отклонить):

```pug
Div(testID='userForm')
  // any code
  Button(
    testID='acceptButton'
    onPress=() => console.log('accept')
  ) Accept
  Button(
    testID='dismissButton'
    onPress=() => console.log('dismiss')
  ) Dismiss
```

Для того, чтобы проверить поведение приложения после нажатия на кнопку `Accept`, необходимо выполнить следующие действия:
1. Найти элемент который соответствует кнопке `Accept`
2. Вызвать у него метод `.tap`.

```js
await x('#acceptButton').tap()
```

Таким образом мы произвели нажатие на кнопку `Accept`.

## .typeText(text)

`.typeText(text)` позволяет ввести в элемент текст. Это необходимо для проверки заполнения полей ввода. Пусть у нас есть форма, которая содержит поля ввода:

```pug
Div(testID='userForm')
  TextInput(
    testID='userNameInput'
    label='Name'
    value=userName
    onChangeText=setUserName
  )
  TextInput(
    testID='userSurnameInput'
    label='Surname'
    value=userSurname
    onChangeText=setUserSurname
  )
```

Давайте заполним поля вода `Name` и `Surname`.

```js
await x('#userNameInput').typeText('Harry')
await x('#userSurnameInput').typeText('Potter')
```

## .scroll(offset, direction, startPositionX, startPositionY)

Производит скролл элемента на основании переданных параметров. Метод очень важен, так как для того чтобы найти найти элемент находящийся ниже или выше текущей области видимости, необходимо сделать скролл до того момента, пока нужный элемент не появится в поле видимости.

Парметры:
`offset` — смещение для прокрутки в еденицах
`direction` — направление скрола (допустимые значения: `left`/`right`/`up`/`down`)
`startPositionX` — нормализованный процент x элемента, который будет использоваться в качестве начальной точки прокрутки (необязательный, допустимые значения: [0.0, 1.0], `NaN` - выберает оптимальное значение автоматически, по умолчанию - `NaN`)
`startPositionY` — нормализованный процент y элемента, который будет использоваться в качестве начальной точки прокрутки (необязательный, допустимые значения: [0.0, 1.0], `NaN` - выберает оптимальное значение автоматически, по умолчанию - `NaN`)

Давайте рассмотрим пример. Например, мы точно знаем что в форме `userForm` примерно через 100 едениц скролла появится интресующий нас элемент `userEmail`. Форма выглядит так:

```pug
Div(testID='userForm')
  // any other components
  TextInput(
    testID='userEmail'
    label='Email'
    value=userName
    onChangeText=setUserEmail
  )
```

Тогда, для того чтобы ввести текст в `userEmail` нужно сначала проскролить элемент `userForm`.

```pug
await x('#userForm').scroll(100, 'down')
await x('#userEmail').typeText('harryWizzard@test.com')
```


# Ожидания

После того как мы нашли элемент, произвели некоторые действия, нам нужно проверить состояние элементов. Например виден ли в данный момент элемент, содержит ли определенный текст и так далее. Все доступные ожидания для элемента можно увидеть в [сооветствующем разделе документации Detox](https://github.com/wix/Detox/blob/master/docs/APIRef.Expect.md).

## .toBeVisible()

Проверяет находится ли элемент в текущий области видимости. Таким образом мы можем проверять появление или исчизновение элементов при совршении действий.

Пусть у нас есть кнопка `openModalButton`. При нажатии на нее появляется модальное окно `modal`. Тогда тест для этого поведения такой:

```pug
await x('#openModalButton').tap()
await x('#modal').toBeVisible()
```

## .toExist()

Проверяет находится ли элемент на текущей странице (без учета области видимости). Данный метод полезн для того, чтобы просто узнать есть элемент на странице.

Пусть у нас есть кнопка `openModalButton`. При нажатии на нее появляется длинное модальное окно `modal` и где-то в этом окне должна быть кнопка `change`. Тогда тест для этого поведения такой:

```pug
await x('#openModalButton').tap()
await x('#modal').toBeVisible()
await x('#change').toExist()
```

## .toHaveToggleValue(value)

Проверяет значение переключаемых элементов(Switch или a Check-Box). Предназначен спецально для элементов которые переключаются в два положения `true` и `false`.

Пусть у нас есть элемент `checkBox`, изначально он должен быть `false`, но после нажатия на него его значение становится `true`. Тест выглядит так:
```js
await x('#checkBox').toBeVisible()
await x('#checkBox').toHaveToggleValue(false)
await x('#checkBox').tap()
await x('#checkBox').toHaveToggleValue(true)
```

## .not

Свойство, которое производит логическое отрицание. Например, если нужно проверить, что элемент `invisiable` НЕ виден в данный момент, то нужно писать:

```js
await x('#invisiable').not.toBeVisible()
```

Точно также для `.toExist()`
```js
await x('#invisiable').not.toExist()
```


## Скриншот тестирование

`@startupjs/e2e` позволяет проводить скришнот тестирование.
Примечание: название теста со скриншотов обязательно должно начинаться со `Screenshots: `

Пример:

```js
const { matchImageSnapshot } = require('@startupjs/e2e')

describe('Button', () => {
  it('Screenshots: Buttons page', async () => {
    const imagePath = await device.takeScreenshot('Buttons page')
    matchImageSnapshot(imagePath, __dirname)
  })
})
```

После первого запуска теста создаcтся скриншот в папке компонента `tests/__image_snapshots__`. Если при последующем запуске теста, скриншоты не будут совпадать, то в папке `e2e/__diff_output__` создаcтся файл с отображением различий между первоначальным скриншотом и текущим состоянием теста.

Для перезаписи первоначального скриншота запустите комманду `npx startupjs test --ios --updateScreenshot`
