Книга: React.js. Быстрый старт
Назад: 6. Создание приложения
Дальше: 8. Flux

7. Проверка качества кода, соответствия типов, тестирование, повтор

В главе 8 будет представлена технология Flux — альтернатива управления обменом данными между компонентами (используется вместо таких средств, как метод onDataChange), поэтому структура кода там будет несколько изменена. А ведь неплохо было бы допускать при таком изменении как можно меньше ошибок? Рассмотрим несколько инструментальных средств, помогающих поддерживать работоспособность вашего приложения по мере его неизбежного роста и развития. К числу таких средств относятся ESLint, Flow и Jest.

Но сначала рассмотрим общее необходимое для них средство под названием package.json.

package.json

Как использовать npm (Node Package Manager) для установки библиотек и инструментов сторонних производителей, вы уже знаете. Кроме этого, npm также позволяет упаковывать и совместно использовать ваш проект на сайте и устанавливать его другим людям. Но чтобы воспользоваться услугами, предлагаемыми npm, вам не нужно выгружать свой код на npmjs.com.

Создание пакета связано с использование файла package.json, который можно поместить в корневой каталог вашего приложения для настройки зависимостей и других дополнительных инструментов. Существует масса настроек, которые можно применить в отношении этого средства (их полный перечень можно найти на сайте ), но посмотрим, как им воспользоваться в самом скромном варианте.

Создайте в каталоге своего приложения новый файл по имени package.json:

$ cd ~/reactbook/whinepad2

$ touch package.json

И добавьте к нему следующий код:

{

  "name": "whinepad",

  "version": "2.0.0",

}

Вот и все, что нужно. После этого вы просто будете добавлять к этому файлу дополнительные настройки.

Настройка Babel

Сценарий build.sh, показанный в главе 5, запускал Babel следу­ющим образом:

$ babel --presets react,es2015 js/source -d js/build

Эту команду можно упростить, переместив начальную настройку в package.json:

{

  "name": "whinepad",

  "version": "2.0.0",

  "babel": {

    "presets": [

      "es2015",

      "react"

    ]

  },

}

Теперь команда будет иметь следующий вид:

$ babel js/source -d js/build

Babel (как и многие другие инструментальные средства в экосистеме JavaScript) проверяет наличие файла package.json и забирает из него варианты настройки.

Сценарии

npm позволяет создавать сценарии и запускать их с помощью команды npm run имя_сценария. В качестве примера переместим однострочный сценарий ./scripts/watch.sh, который был показан в главе 3, в файл package.json:

{

  "name": "whinepad",

  "version": "2.0.0",

  "babel": {/* ... */},

  "scripts": {

    "watch": "watch \"sh scripts/build.sh\" js/source css/"

  }

}

Теперь для оперативной сборки кода можно воспользоваться следующей командой:

# до

$ sh ./scripts/watch.sh

 

# после

$ npm run watch

Если продолжить усовершенствования, можно точно так же заменить build.sh, переместив его код в package.json, или же воспользоваться специализированным средством сборки (Grunt, Gulp и т.д.), которое можно настроить в package.json. И это все (применительно к целям изучения React), что вам следует знать об этом файле.

Средство ESLint

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

Установка

Кроме ESLint, вам понадобятся дополнительные модули React и Babel, чтобы помочь ESLint распознать самый передовой синтаксис ECMAScript, а также воспользоваться «правилами», свойственными JSX и React:

$ npm i -g eslint eslint-plugin-react eslint-plugin-babel

Добавьте переменную eslintConfig к файлу package.json:

{

  "name": "whinepad",

  "version": "2.0.0",

  "babel": {},

  "scripts": {},

  "eslintConfig": {

    "parser": "babel-eslint",

    "plugins": [

      "babel",

      "react"

    ],

  }

}

Запуск

Запустите проверку кода в отношении одного файла:

$ eslint js/source/app.js

Эта команда должна быть выполнена без ошибок, это будет означать правильное восприятие средством ESLint синтаксиса JSX и других новшеств. Но здесь есть и негативная сторона, поскольку не проводилась проверка кода на соответствие каким-либо правилам. Для каждой проверки ESLint использует правила. Сначала вам следует воспользоваться той коллекцией правил (расширением), которая рекомендована ESLint:

"eslintConfig": {

  "parser": "babel-eslint",

  "plugins": [],

  "extends": "eslint:recommended"

}

Запуск на соответствие этим правилам приводит к выявлению ошибок:

$ eslint js/source/app.js

 

/Users/stoyanstefanov/reactbook/whinepad2/js/source/app.js

   4:8  error  "React" is defined but never used  no-unused-vars

   9:23 error  "localStorage" is not defined      no-undef

  25:3  error  "document" is not defined          no-undef

 

× 3 problems (3 errors, 0 warnings)

Второе и третье сообщения касаются переменных, не имеющих определений (исходя из правила под названием no-undef), но эти переменные имеют глобальный доступ в браузере, поэтому для устранения ошибки требуется дополнительная настройка:

"env": {

  "browser": true

}

Первая ошибка имеет отношение исключительно к React. С одной стороны, вам необходимо включить React, но с точки зрения ESLint в коде присутствует неиспользуемая переменная, которой здесь быть не должно. Устранить ошибку поможет добавление одного из правил, имеющихся в eslint-plugin-react:

"rules": {

  "react/jsx-uses-react": 1

}

При запуске проверки кода в файле сценария schema.js будет получена ошибка еще одного типа:

$ eslint js/source/schema.js

 

/Users/stoyanstefanov/reactbook/whinepad2/js/source/schema.js

   9:18  error  Unexpected trailing comma  comma-dangle

  16:17  error  Unexpected trailing comma  comma-dangle

  25:18  error  Unexpected trailing comma  comma-dangle

  32:14  error  Unexpected trailing comma  comma-dangle

  38:33  error  Unexpected trailing comma  comma-dangle

  39:4   error  Unexpected trailing comma  comma-dangle

 

× 6 problems (6 errors, 0 warnings)

Слово comma-dangle означает «подвисшая запятая» (как в выражении let a = [1,] в противоположность выражению let a = [1]). Такие запятые могут считаться недопустимыми (поскольку прежде некоторыми браузерами они рассматривались как синтаксические ошибки), но выявление подобных ошибок вам на руку, поскольку упрощает проведение обновлений. Небольшие изменения в настройках превращают практику постоянного использования запятых в поощряемое действие:

"rules": {

  "comma-dangle": [2, "always-multiline"],

  "react/jsx-uses-react": 1

}

Все правила

За полным списком правил следует обратиться к хранилищу кода, сопровождающему книгу, — этот список (как выражение лояльности к проекту) представляет собой копию собственного списка правил библиотеки React.

И наконец, добавьте проверку кода, сделав ее частью build.sh, чтобы средство ESLint контролировало ситуацию в процессе разработки, гарантируя постоянную поддержку высокого качества вашего кода:

# QA

eslint js/source

Средство Flow

Flow — статическое средство проверки соответствия типов для JavaScript. По поводу типов в целом и особенно типов в JavaScript существуют два мнения.

Кому-то нравится, что ему, образно говоря, заглядывают через плечо, чтобы убедиться, что программа работает с правильными данными. Точно так же, как проверка кода и блочное тестирование, это вселяет уверенность, что где-нибудь, где код не проверялся (или этому не придавалось особого значения), этот код не был испорчен. Ценность соблюдения типизации повышается с ростом объема приложения и неизбежно сопутствующим ему ростом количества работающих с кодом людей.

Другим же нравится динамическая, позволяющая не придерживаться строгой типизации природа JavaScript, и они полагают, что с контролем типов слишком много мороки, поскольку приведением типов приходится заниматься лишь изредка.

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

Установка

$ npm install -g flow-bin

$ cd ~/reactbook/whinepad2

$ flow init

Команда init создает в вашем каталоге пустой файл .flowconfig. Добавьте к нему разделы ignore и include:

[ignore]

.*/react/node_modules/.*

 

[include]

node_modules/react

node_modules/react-dom

node_modules/classnames

 

[libs]

[options]

Запуск

Для запуска нужно лишь набрать следующую команду:

$ flow

Или эту же команду — для проверки только одного файла либо каталога:

$ flow js/source/app.js

И наконец, добавьте к сценарию сборки в виде части процесса контроля качества — QA (quality assurance) — следующие команды:

# QA

eslint js/source

flow

Подписка на проверку соответствия типов

В первом комментарии файла, который нужно проверить на соответствие типов, следует воспользоваться текстом @flow. Однако дело это сугубо добровольное.

Начнем с подписки простого компонента <Button> из предыдущей главы:

/* @flow */

 

import classNames from 'classnames';

import React, {PropTypes} from 'react';

 

const Button = props =>

  props.href

    ? <a {...props} className={classNames('Button',

      props.className)} />

    : <button {...props} className={classNames('Button',

      props.className)} />

 

Button.propTypes = {

  href: PropTypes.string,

};

 

export default Button

Запустим Flow:

$ flow js/source/components/Button.js

js/source/components/Button.js:6

  6: const Button = props =>

                     ^^^^^ parameter 'props'. Missing annotation

Found 1 error

Найдена ошибка, но это положительный момент — у нас появилась возможность улучшить код! Flow жалуется, что неизвестно, для чего предназначен аргумент props.

Flow ожидает, что такая функция:

function sum(a, b) {

  return a + b;

}

должна быть аннотирована следующим образом:

function sum(a: number, b: number): number {

  return a + b;

}

чтобы не получалось неожиданных результатов вроде:

sum('1' + 2); // "12"

Исправление кода компонента <Button>

Аргумент props, получаемый функцией, является объектом. Поэтому можно сделать следующее:

const Button = (props: Object) =>

и не вызвать никаких нареканий у Flow:

$ flow js/source/components/Button.js

No errors!

Аннотация Object срабатывает, но можно конкретизировать происходящее, создав пользовательский тип:

type Props = {

  href: ?string,

};

 

const Button = (props: Props) =>

  props.href

    ? <a {...props} className={classNames('Button',

      props.className)} />

    : <button {...props} className={classNames('Button',

      props.className)} />

 

export default Button

Как видите, переключение на пользовательский тип позволяет заменить определение свойства propTypes, имеющееся в React. Это означает:

прекращение проверки соответствия типов в ходе выполнения приложения. В результате выполнение кода ускоряется;

клиенту отправляется меньше кода (меньше байтов).

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

Знак вопроса в href: ?string означает, что это свойство может иметь значение null.

162064.png

Теперь, когда propTypes отсутствует, жалобы на переменную PropTypes у ESLint перестанут появляться. Следовательно:

import React, {PropTypes} from 'react';

становится:

import React from 'react';

Разве плохо иметь такие средства, как ESLint, отслеживающие незначительные досадные упущения вроде этого?

Запуск Flow выявит другую ошибку:

$ flow js/source/components/Button.js

js/source/components/Button.js:12

  12: ? <a {...props} className={classNames('Button',

    props.className)} />

          ^^^^^^^^^ property 'className'.

          Property not found in

  12: ? <a {...props} className={classNames('Button',

    props.className)} />

    ^^^^^ object type

Проблема в том, что средство Flow не ожидало найти переменную className в объекте prop, который теперь имеет тип Prop. Для устранения проблемы добавьте атрибут className к новому типу:

type Props = {

  href: ?string,

  className: ?string,

};

app.js

При запуске Flow в отношении основного кода в app.js возникает следующая неприятность:

$ flow js/source/app.js

js/source/app.js:11

  11: let data = JSON.parse(localStorage.getItem('data'));

                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

                            call of method 'getItem'

  11: let data = JSON.parse(localStorage.getItem('data'));

                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

                            null. This type is

                            incompatible with

383: static parse(text: string, reviver?: (key: any,

  value: any) => any):any;

                        ^^^^^^ string. See lib: /private/tmp/

                        flow/flow lib_28f8ac7e/core.js:383

Средство Flow ожидает, что методу JSON.parse() будут передаваться только строки, и любезно предоставляет вам сигнатуру метода parse(). Поскольку от localStorage может быть получено значение null, такая ситуация неприемлема. Проще всего можно справиться с данной неприятностью, просто добавив значение по умолчанию:

let data = JSON.parse(localStorage.getItem('data') || '');

Но выражение JSON.parse('') выдаст ошибку в браузере (хотя с точки зрения проверки соответствия типов ничего нарушено не будет), поскольку пустая строка не является приемлемыми данными, закодированными в формате JSON. Чтобы не вызывать претензий у Flow и в то же время не сталкиваться с ошибками в браузере, код нужно немного переделать.

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

Соответствующей частью кода app.js является:

let data = JSON.parse(localStorage.getItem('data'));

 

// исходный пример данных, считываемых из схемы

if (!data) {

  data = {};

  schema.forEach((item) => data[item.id] = item.sample);

  data = [data];

}

Еще одна проблема этого кода в том, что данные, которые ранее были массивом, затем превращаются в объект, после чего опять становятся массивом. У JavaScript проблем с этим нет, но то, что значение, имеющее сейчас один тип, позже превращается в значение другого типа, считается порочной практикой. Движки JavaScript, имеющиеся в браузерах, стремясь оптимизировать код, фактически выполняют назначения типов внутри себя. Следовательно, при смене типов на лету браузер может отбросить «оптимизирующий» режим, что неблагоприятно скажется на скорости его работы.

Устраним все эти проблемы.

Можно проявить особую требовательность и определить данные в качестве массива объектов:

let data: Array<Object>;

Затем попытаться считать любые сохраненные элементы в строку (или в null, поскольку использован символ ?) под названием storage:

const storage: ?string = localStorage.getItem('data');

Если в storage обнаружится строка, вы просто проанализируете ее, и дело в шляпе. Или же нужно хранить данные в массиве, первый элемент которого заполнить образцом значений:

if (!storage) {

  data = [{}];

  schema.forEach(item => data[0][item.id] = item.sample);

} else {

  data = JSON.parse(storage);

}

Теперь два файла стали Flow-совместимыми. Сэкономим немного бумаги и не станем перечислять в данной главе весь набранный код, а сфокусируем внимание на более интересных особенностях Flow. Полную версию кода можно найти в хранилище, сопровождающем книгу.

Подробнее о проверке соответствия типов свойств и состояния

Когда React-компонент создается с помощью функции без поддержки состояния, свойства можно аннотировать так, как это было показано ранее:

type Props = {/* ... */};

const Button = (props: Props) => {/* ... */};

Аналогичную аннотацию можно применить с конструктором класса:

type Props = {/* ... */};

class Rating extends Component {

  constructor(props: Props) {/* ... */}

}

А если конструктор не нужен? Например, как в данном случае:

class Form extends Component {

  getData(): Object {}

  render() {}

}

На помощь придет еще одна особенность ECMAScript — свойство класса:

type Props = {/* ... */};

class Form extends Component {

  props: Props;

  getData(): Object {}

  render() {}

}

162070.png

На момент написания этих строк свойства класса еще не были приняты в качестве стандарта ECMAScript, но вы можете воспользоваться ими благодаря имеющейся в Babel самой передовой предустановке stage-0. Вам нужно установить NPM-пакет babel-preset-stage-0 и обновить раздел Babel в файле package.json следующим образом:

{

  "babel": {

    "presets": [

      "es2015",

      "react",

      "stage-0"

    ]

  }

}

Точно так же можно проаннотировать состояние вашего компонента. Кроме пользы от проверки типов, определение со­стояния в верхних строчках кода служит документацией для тех, кто охотится за ошибками в вашем компоненте. Рассмотрим пример:

type Props = {

  defaultValue: number,

  readonly: boolean,

  max: number,

};

 

type State = {

  rating: number,

  tmpRating: number,

};

 

class Rating extends Component {

  props: Props;

  state: State;

  constructor(props: Props) {

    super(props);

    this.state = {

      rating: props.defaultValue,

      tmpRating: props.defaultValue,

    };

  }

}

И конечно же, при каждом удобном случае вам следует применять свои собственные пользовательские типы:

componentWillReceiveProps(nextProps: Props) {

  this.setRating(nextProps.defaultValue);

}

Типы экспорта и импорта

Посмотрим на компонент <FormInput>:

type FormInputFieldType = 'year' | 'suggest' | 'rating' | 'text' | 'input';

 

export type FormInputFieldValue = string | number;

 

export type FormInputField = {

  type: FormInputFieldType,

  defaultValue?: FormInputFieldValue,

  id?: string,

  options?: Array<string>,

  label?: string,

};

 

class FormInput extends Component {

  props: FormInputField;

  getValue(): FormInputFieldValue {}

  render() {}

}

Здесь показано, как можно проаннотировать использование списка допустимых значений подобно React-методу oneOf(), применяемому для типа свойств.

Также можно увидеть, как пользовательский тип (FormInputFi­eldType) применяется в качестве части другого пользовательского типа (FormInputField).

И наконец, экспорт типов. Когда другой компонент использует точно такой же тип, переопределение не требуется. Он может его импортировать при условии, что ваш компонент окажет любезность по его экспорту. Посмотрим, как компонент <Form> использует тип из компонента <FormInput>:

import type FormInputField from './FormInput';

 

type Props = {

  fields: Array<FormInputField>,

  initialData?: Object,

  readonly?: boolean,

};

Вообще-то форме нужны оба типа из FormInput, и синтаксис будет иметь следующий вид:

import type {FormInputField, FormInputFieldValue} from './FormInput';

Приведение типов

Flow позволяет указать, что конкретное значение не относится к типу, ожидаемому этим средством. Примером могут послужить обработчики событий, когда им передается объект события, а Flow воспринимает событие target не так, как вы рассчитывали. Рассмотрим следующий фрагмент кода из компонента Excel:

_showEditor(e: Event) {

  const target = e.target;

  this.setState({edit: {

    row: parseInt(target.dataset.row, 10),

    key: target.dataset.key,

  }});

}

Flow не нравится это:

js/source/components/Excel.js:87

  87: row: parseInt(target.dataset.row, 10),

                           ^^^^^^^ property 'dataset'.

                           Property not found in

  87: row: parseInt(target.dataset.row, 10),

                           ^^^^^^ EventTarget

 

js/source/components/Excel.js:88

  88: key: target.dataset.key,

                  ^^^^^^^ property 'dataset'. Property

                  not found in

  88: key: target.dataset.key,

                  ^^^^^^ EventTarget

 

Found 2 errors

Если посмотреть на определения, находящиеся по адресу , то можно увидеть, что у объекта EventTarget нет свойства dataset. Но у объекта HTMLElement оно есть. Следовательно, на выручку придет приведение типов:

const target = ((e.target: any): HTMLElement);

Поначалу синтаксис может показаться немного странным, но, если его разобрать по частям, он обретет смысл: значение, двое­точие, тип и круглые скобки, охватывающие все три составляющие. Значение типа А становится типом Б. В данном случае объект любого типа становится таким же значением, но относится к типу HTMLElement.

Инварианты

Состояние в компоненте Excel использует два свойства, чтобы отслеживать редактирование пользователем поля и режим активного диалога:

this.state = {

  // ...

  edit: null, // {row index, schema.id},

  dialog: null, // {type, idx}

};

Эти два свойства имеют либо значения null (редактирование не выполняется, диалог не ведется), либо значения в виде объектов, содержащих некую информацию о редактировании или диалоге. Тип этих двух свойств может быть следующим:

type EditState = {

  row: number,

  key: string,

};

 

type DialogState = {

  idx: number,

  type: string,

};

 

type State = {

  data: Data,

  sortby: ?string,

  descending: boolean,

  edit: ?EditState,

  dialog: ?DialogState,

};

Теперь проблема в целом заключается в том, что иногда у свойств значения null, а иногда — нет. У Flow это вызывает вполне резонные подозрения. При попытке использования свойства this.sta­te.edit.row или this.state.edit.key Flow выдает ошибку:

Property cannot be accessed on possibly null value (Свойство недоступно по причине возможного null-значения)

Вы используете эти свойства, только когда знаете об их доступности. Но Flow-то этого не знает. И никто не обещает, что по мере роста объема вашего приложения вы в конечном итоге не столкнетесь с неожиданным состоянием. А когда это произойдет, вам захочется узнать о случившемся. Чтобы не вызывать претензий у Flow и в то же время получать уведомления о неправильном поведении приложения, можно выполнить проверку на работу с пустым (null) значением.

До:

data[this.state.edit.row][this.state.edit.key] = value;

После:

if (!this.state.edit) {

  throw new Error('В состоянии редактирования возникла

    путаница');

}

data[this.state.edit.row][this.state.edit.key] = value;

Теперь все на своих местах. И когда фрагмент кода с условием на выдачу ошибки станет слишком часто повторяться, можно будет переключиться на использование функции invariant(). Такую функцию можно создать самостоятельно или позаимствовать из открытого исходного кода.

В этом вас поддержит NPM:

$ npm install --save-dev invariant

Добавьте в файл .flowconfig следующий код:

[include]

node_modules/react

node_modules/react-dom

node_modules/classnames

node_modules/invariant

А теперь обратитесь к вызову функции:

invariant(this.state.edit, 'В состоянии редактирования

  возникла путаница');

data[this.state.edit.row][this.state.edit.key] = value;

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

Следующая остановка на пути к беспроблемному росту объема приложения называется автоматизированным тестированием. Когда дело доходит до тестирования, перед вами опять открывается широкий выбор вариантов. Для запуска тестов в React используется инструментальное средство Jest, поэтому посмотрим на деле, что это такое и как оно может нам помочь. В качестве вспомогательного средства в React предоставляется пакет под названием react-addons-test-utils.

Итак, настало время установки дополнительных средств.

Установка

Установите интерфейс командной строки Jest:

$ npm i -g jest-cli

Вам также понадобятся babel-jest (чтобы можно было создавать тесты в стиле ES6) и пакет утилит тестирования из коллекции React:

$ npm i --save-dev babel-jest react-addons-test-utils

Далее следует обновить содержимое файла package.json:

{

  /* ... */

  "eslintConfig": {

    /* ... */

    "env": {

        "browser": true,

        "jest": true

    },

  /* ... */

  "scripts": {

    "watch": "watch \"sh scripts/build.sh\"

      js/source js/__tests__ css/",

    "test": "jest"

  },

  "jest": {

    "scriptPreprocessor": "node_modules/babel-jest",

    "unmockedModulePathPatterns": [

    "node_modules/react",

    "node_modules/react-dom",

    "node_modules/react-addons-test-utils",

    "node_modules/fbjs"

    ]

  }

}

Теперь можно запустить Jest с использованием следующей команды:

$ jest testname.js

Или с использованием npm:

$ npm test testname.js

Jest ищет тесты в каталоге __tests__, поэтому поместим их по путевому имени js/__tests__.

И наконец, обновим сценарий сборки, чтобы частью каждого ее процесса стали проверка кода и запуск:

# QA

eslint js/source js/__tests__

flow

npm test

Настроим также отслеживатель в файле watch.sh, чтобы он реагировал на все изменения в тестах (не забывайте, что эти функцио­нальные возможности продублированы в package.json):

watch "sh scripts/build.sh" js/source js/__tests__ css/

Первый тест

Jest является надстройкой над популярной средой Jasmine, имеющей API-интерфейс, который звучит как разговорный английский. Сначала с помощью метода describe('набор', функция_обратного_вызова) дается определение набору тестов, одной или нескольким спецификациям тестов, для чего используется метод it('название теста', функция_обратного_вызова), а внутри каждой спецификации с помощью функции expect() задается утверждение.

Самый простой полноценный пример имеет следующий вид:

describe('Набор', () => {

  it('спецификация', () => {

    expect(1).toBe(1);

  });

});

Запуск теста выглядит следующим образом:

$ npm test js/__tests__/dummy-test.js

 

> [email protected] test /Users/stoyanstefanov/reactbook/whinepad2

> jest "js/__tests__/dummy-test.js"

 

Using Jest CLI v0.8.2, jasmine1

  PASS js/__tests__/dummy-test.js (0.206s)

1 test passed (1 total in 1 test suite, run time 0.602s)

Если в тесте указано неверное утверждение:

expect(1).toBeFalsy();

он при выполнении дает сбой и выводит сообщение (рис. 7.1).

07_01.tif 

Рис. 7.1. Запуск сбойного теста

Первый React-тест

Вооружившись знаниями о Jest в мире React, можно приступить к тестированию простой DOM-кнопки. Сначала выполним операции импорта:

import React from 'react';

import ReactDOM from 'react-dom';

import TestUtils from 'react-addons-test-utils';

Настроим набор тестов:

describe('Мы можем отобразить кнопку', () => {

  it('кнопка изменяет текст после щелчка', () => {

    // ...

  });

});

Покончив с шаблоном, займемся отображением и тестированием. Отобразим ряд простых элементов JSX:

const button = TestUtils.renderIntoDocument(

  <button

    onClick={ev => ev.target.innerHTML = 'Bye'}>

    Hello

  </button>

);

Здесь для вывода элемента JSX (в данном случае кнопки, изменяющей текст, когда на ней производится щелчок) мы воспользовались библиотекой утилит тестирования, имеющейся в React.

После того как что-то отображено на экране, пора проверить, соответствует ли это вашим ожиданиям:

expect(ReactDOM.findDOMNode(button).textContent).toEqual('Hello');

Как видите, для получения доступа к DOM-узлу применяется метод ReactDOM.findDOMNode(). Таким образом, для проверки узла можно использовать общеизвестный API-интерфейс DOM-модели.

Зачастую нужно протестировать работу пользователя с вашим пользовательским интерфейсом. Для этого React любезно предоставляет вам метод TestUtils.simulate:

TestUtils.Simulate.click(button);

И последнее, что нужно проверить, — реагирует ли пользовательский интерфейс на работу с ним:

expect(ReactDOM.findDOMNode(button).textContent).toEqual('Bye');

Далее в главе представлены дополнительные примеры и API-интерфейсы, которыми можно воспользоваться, но основным инструментарием будут такие методы:

TestUtils.renderIntoDocument(произвольный_JSX);

TestUtils.Simulate.* для работы с интерфейсом;

ReactDOM.findDOMNode() (или ряд других методов TestUtils) для получения ссылки на DOM-узел и проверки, имеет ли он должный вид.

Тестирование компонента <Button>

Код компонента <Button> имеет следующий вид:

/* @flow */

 

import React from 'react';

import classNames from 'classnames';

 

type Props = {

  href: ?string,

  className: ?string,

};

 

const Button = (props: Props) =>

  props.href

    ? <a {...props} className={classNames('Button',

      props.className)} />

    : <button {...props} className={classNames('Button',

      props.className)} />

 

export default Button

Протестируем его по следующим характерным особенностям:

выводит <a> или <button> — в зависимости от наличия свойства href (первая спецификация);

допускает использование пользовательских имен классов (вторая спецификация).

Начнем создание нового теста:

jest

  .dontMock('../source/components/Button')

  .dontMock('classnames')

;

 

import React from 'react';

import ReactDOM from 'react-dom';

import TestUtils from 'react-addons-test-utils';

Инструкции import такие же, как и прежде, но теперь есть новые вызовы метода jest.dontMock().

Имитация (mock) представляет собой замену части функцио­нального наполнения искусственным кодом-заглушкой, якобы выполняющим работу. Заглушки часто встречаются в блочном тестировании, поскольку от него требуется протестировать блок (небольшой фрагмент кода, находящийся в изоляции) и сократить побочные эффекты относительно всего остального содержимого системы. На создание заглушек тратится немало усилий, поэтому в Jest принят противоположный подход: по умолчанию глушатся все действия. И вам предоставляется возможность отказа от глушения с помощью функции dontMock(), поскольку требуется протестировать не заглушку, а реальный код.

В предыдущем примере объявляется, что вы не желаете глушить <Button> или используемую этим компонентом библиотеку classnames.

Затем наступает черед включения компонента <Button>:

const Button = require('../source/components/Button');

162077.png

На момент написания этих строк, несмотря на имеющееся в Jest-документации описание, вызов require() не работал. Вместо него требовался следующий код:

const Button = require('../source/components/Button').default;

Также не работала инструкция import:

import Button from '../source/components/Button';

Хотя со следующим кодом было все нормально:

import _Button from '../source/components/Button';

const Button = _Button.default;

Еще один вариант предполагает использование в компоненте <Button> вместо кода export default Button кода export {Button}. А затем выполнение импорта с помощью инструкции import {Button} from '../source/component/Button'.

Надеюсь, что на момент вашего прочтения данной книги исходный импорт уже работает в соответствии с вашими ожиданиями.

Первая спецификация

Настроим набор (с помощью describe()) и первую спецификацию (с помощью it()):

describe('Отображение компонентов Button', () => {

  it('отображает <a> вместо <button>', () => {

    /* ... код, задающий отображение и ожидание (expect())

       ... */

  });

});

А теперь отобразим простую кнопку, у которой нет свойства href, поэтому код должен вывести элемент <button>:

const button = TestUtils.renderIntoDocument(

  <div>

    <Button>

      Hello

    </Button>

  </div>

);

Обратите внимание на необходимость заключения функциональных компонентов, не поддерживающих состояние, таких как <Button>, в еще один DOM-узел, чтобы позже их можно было найти с помощью метода ReactDOM.

Теперь вызов метода ReactDOM.findDOMNode(button) предоставит вам элемент-оболочку <div>; чтобы получить <button>, берется первый дочерний элемент и проверяется, что он действительно является кнопкой:

expect(ReactDOM.findDOMNode(button).children[0].nodeName).

171878.pngtoEqual('BUTTON');

По аналогии в качестве составной части той же спецификации теста задается проверка, определяющая, что при наличии свойства href используется узел гипертекстовой ссылки:

const a = TestUtils.renderIntoDocument(

  <div>

    <Button href="#">

      Hello

    </Button>

  </div>

);

expect(ReactDOM.findDOMNode(a).children[0].nodeName).

171846.pngtoEqual('A');

Вторая спецификация

Во второй спецификации добавляются имена пользовательских классов, а затем проверяется, можно ли их найти в нужном месте:

it('разрешает применять пользовательские классы CSS', () => {

  const button = TestUtils.renderIntoDocument(

    <div><Button className="good bye">Hello</Button></div>

  );

  const buttonNode = ReactDOM.findDOMNode(button).children[0];

  expect(buttonNode.getAttribute('class')).toEqual('Button

    good bye');

});

Здесь важно подчеркнуть факт, касающийся Jest-глушения. Порой написанный таким образом тест работает совершенно не так, как ожидалось. Такое может произойти, если забыли снять Jest-заглушку. Следовательно, если в самом начале теста находится следующий код:

jest

  .dontMock('../source/components/Button')

  // .dontMock('classnames')

;

то Jest глушит модуль classnames — и тест ничего не делает. Убедиться в этом можно, написав следующий код:

const button = TestUtils.renderIntoDocument(

  <div><Button className="good bye">Hello</Button></div>

);

console.log(ReactDOM.findDOMNode(button).outerHTML);

Он запишет в консоль этот сгенерированный код HTML:

<div data-reactid=".2">

  <button data-reactid=".2.0">Hello</button>

</div>

Как видите, никаких нужных имен классов вообще не наблюдается, поскольку в заглушенном состоянии метод classNames() ничего не делает.

Вернем на место вызов метода dontMock():

jest

  .dontMock('../source/components/Button')

  .dontMock('classnames')

;

и вы увидите, что вызов атрибута outerHTML показал следующий код:

<div data-reactid=".2">

  <button class="Button good bye"

    data-reactid=".2.0">Hello</button>

</div>

и тест был пройден успешно.

162143.png

Когда тест ведет себя непонятно и вы интересуетесь, как выглядит созданная разметка, проще всего воспользоваться инструкцией console.log(node.outerHTML), показывающей сам код HTML.

Тестирование компонента <Actions>

<Actions> является еще одним компонентом, не поддерживающим состояние, следовательно, чтобы позже его можно было изучить, он нуждается в оболочке. Один из вариантов, как уже было показано на примере <Button>, предусматривает заключение его в div-контейнер и получение к нему доступа при помощи следующего кода:

const actions = TestUtils.renderIntoDocument(

  <div><Actions /></div>

);

ReactDOM.findDOMNode(actions).children[0];

// Корневой узел <Actions>

Оболочка компонента

Еще один вариант — использование элемента-оболочки от React, который затем позволит вам применять множество методов TestUtils для охоты на проверяемые узлы.

Оболочка не содержит сложного кода. Определить оболочку можно в ее собственном модуле, это дает возможность многократного использования:

import React from 'react';

class Wrap extends React.Component {

  render() {

    return <div>{this.props.children}</div>;

  }

}

export default Wrap

Теперь шаблонная часть теста приобрела следующий вид:

jest

  .dontMock('../source/components/Actions')

  .dontMock('./Wrap')

;

 

import React from 'react';

import TestUtils from 'react-addons-test-utils';

 

const Actions = require('../source/components/Actions');

const Wrap = require('./Wrap');

 

describe('Щелчок на значке действия', () => {

  it('вызывает определенную реакцию', () => {

    /* отображение */

    const actions = TestUtils.renderIntoDocument(

      <Wrap><Actions /></Wrap>

    );

    /* ... поиск и проверка */

  });

});

Mock-функции

В компоненте действий <Actions> нет ничего необычного. Его код выглядит следующим образом:

const Actions = (props: Props) =>

  <div className="Actions">

    <span

      tabIndex="0"

      className="ActionsInfo"

      title="More info"

      onClick={props.onAction.bind(null,

        'info')}>&#8505;</span>

    {/* ... еще два span-узла */}

  </div>

При тестировании единственное, что нужно проверить, — это надлежащий вызов при щелчке на значках действий вашей функции обратного вызова onAction. Jest позволяет определять функции-имитаторы (или mock-функции) и проверять факт их вызова. При использовании функций обратного вызова этого вполне достаточно.

В теле теста создается новая mock-функция, которая передается компоненту Actions в качестве функции обратного вызова:

const callback = jest.genMockFunction();

const actions = TestUtils.renderIntoDocument(

  <Wrap><Actions onAction={callback} /></Wrap>

);

Затем следует заняться щелчками на значках действий:

TestUtils

  .scryRenderedDOMComponentsWithTag(actions, 'span')

  .forEach(span => TestUtils.Simulate.click(span));

Обратите внимание на использование одного из методов TestUtils для поиска DOM-узлов. Он возвращает массив из трех <span>-узлов, и вы имитируете щелчок на каждом из них.

Теперь ваша mock-функция обратного вызова должна вызываться три раза. Подтвердите с помощью метода expect(), что именно это вам и нужно:

const calls = callback.mock.calls;

expect(calls.length).toEqual(3);

Как видите, свойство вызовов callback.mock.calls является массивом. У каждого вызова также имеется массив аргументов, переданных ему в процессе вызова.

Первое действие называется 'info', и оно вызывает onAction, передавая тип действия 'info' и используя для этого код props.onActi­on.bind(null, 'info'). Следовательно, первым аргументом (0) для первой mock-функции обратного вызова (0) должен быть 'info':

expect(calls[0][0]).toEqual('info');

Аналогично этому ожидаемые результаты двух других действий задаются следующим кодом:

expect(calls[1][0]).toEqual('edit');

expect(calls[2][0]).toEqual('delete');

Методы find и scry

Библиотека TestUtils предоставляет вам ряд функций для поиска DOM-узлов в дереве отображения React. Например, поиск узла по имени тега или имени класса. Один пример вы уже видели:

TestUtils.scryRenderedDOMComponentsWithTag(actions, 'span')

А вот еще один:

TestUtils.scryRenderedDOMComponentsWithClass(actions,

  'ActionsInfo')

Наряду с методами scry* в вашем распоряжении есть соответствующие методы find*. Например:

TestUtils.findRenderedDOMComponentWithClass(actions,

  'ActionsInfo')

Обратите внимание на использование в имени составляющей слова Component, а не Components. В отличие от методов scry*, которые дают массив совпадений (даже если совпадение всего одно или их вовсе нет), методы find* возвращают только одно совпадение. Если совпадений нет или их сразу несколько, возникает ошибка. Поэтому поиски с помощью методов find* всегда ведутся с полной уверенностью, что в дереве имеется всего лишь один искомый DOM-узел.

Дополнительные имитируемые взаимодействия

Протестируем виджет Rating. Он изменяет состояние при наступлении событий прохождения указателя мыши над элементом (mouseover), увода указателя с элемента (mouseout) и щелчка на элементе (click). Используемый шаблон имеет следующий вид:

jest

  .dontMock('../source/components/Rating')

  .dontMock('classnames')

;

 

import React from 'react';

import TestUtils from 'react-addons-test-utils';

 

const Rating = require('../source/components/Rating');

 

describe('работы', () => {

  it('обрабатывает пользовательские действия', () => {

    const input = TestUtils.renderIntoDocument(<Rating />);

    /* изложите здесь в методе expect() ваши ожидания */

  });

});

Обратите внимание, что заключать <Rating> при его отображении в какую-либо оболочку не нужно. Это не функциональный компонент, поддерживающий состояние, поэтому он вполне работоспособен и без оболочки.

У виджета имеется несколько звезд (по умолчанию пять), каждая из которых заключена в span-контейнер. Найдем их:

const stars = TestUtils.scryRenderedDOMComponentsWithTag(input, 'span');

Теперь тест имитирует действия, вызывающие наступление события mouseOver, затем mouseOut и следом click на четвертой звезде (span[3]). Когда это произойдет, звезды с первой по че­твертую должны перейти в состояние «включено», иными словами, получить имя класса RatingOn, а пятая звезда должна оставаться «выключенной»:

TestUtils.Simulate.mouseOver(stars[3]);

expect(stars[0].className).toBe('RatingOn');

expect(stars[3].className).toBe('RatingOn');

expect(stars[4].className).toBeFalsy();

expect(input.state.rating).toBe(0);

expect(input.state.tmpRating).toBe(4);

 

TestUtils.Simulate.mouseOut(stars[3]);

expect(stars[0].className).toBeFalsy();

expect(stars[3].className).toBeFalsy();

expect(stars[4].className).toBeFalsy();

expect(input.state.rating).toBe(0);

expect(input.state.tmpRating).toBe(0);

 

TestUtils.Simulate.click(stars[3]);

expect(input.getValue()).toBe(4);

expect(stars[0].className).toBe('RatingOn');

expect(stars[3].className).toBe('RatingOn');

expect(stars[4].className).toBeFalsy();

expect(input.state.rating).toBe(4);

expect(input.state.tmpRating).toBe(4);

Обратите также внимание на то, как тест добирается до состояния компонента, чтобы проверить корректность значений state.rating и state.tmpRating. Возможно, это несколько бесцеремонно, но все же, если ожидаются «открытые» результаты, какая разница, какое внутреннее состояние компонент выбирает для управления? Но выяснить это, конечно же, возможно.

Тестирование полного взаимодействия

Напишем несколько тестов для компонента Excel. Все же он достаточно большой и способен серьезно нарушить поведение приложения, если что-нибудь пойдет не так. Для начала создадим следующий код:

jest.autoMockOff();

 

import React from 'react';

import TestUtils from 'react-addons-test-utils';

 

const Excel = require('../source/components/Excel');

const schema = require('../source/schema');

 

let data = [{}];

schema.forEach(item => data[0][item.id] = item.sample);

 

describe('Редактирование данных', () => {

  it('сохраняет новые данные', () => {

    /* ... отображение, взаимодействие, проверка */

  });

});

В первую очередь обратите внимание на вызов метода jest.auto­MockOff(); в самом начале кода. Вместо перечисления всех компонентов, используемых компонентом Excel (и компонентов, которые те, в свою очередь, применяют), можно одним махом выключить полностью все глушение.

Затем вам, очевидно, понадобятся схема и образцовые данные для инициализации компонента (аналогично app.js).

Теперь перейдем к отображению:

const callback = jest.genMockFunction();

const table = TestUtils.renderIntoDocument(

  <Excel

    schema={schema}

    initialData={data}

    onDataChange={callback} />

);

Все это, конечно, хорошо, но теперь изменим значение в первой ячейке первой строки. Зададим новое значение:

const newname = '$2.99 chuck';

Нас интересует следующая ячейка:

const cell = TestUtils.scryRenderedDOMComponentsWithTag(table,

  'td')[0];

162084.png

На момент написания книги для предоставления поддержки dataset, отсутствующей в используемой Jest реализации DOM-модели, требовался обходной вариант:

cell.dataset = { // обход недостатков поддержки DOM,

                 // имеющихся в Jest

  row: cell.getAttribute('data-row'),

  key: cell.getAttribute('data-key'),

};

Двойной щелчок на ячейке превращает ее содержимое в форму с полем ввода данных:

TestUtils.Simulate.doubleClick(cell);

Изменение значения поля ввода данных и отправка формы:

cell.getElementsByTagName('input')[0].value = newname;

TestUtils.Simulate.submit(cell.getElementsByTagName('form')[0]);

Теперь содержимое ячейки уже является не формой, а простым текстом:

expect(cell.textContent).toBe(newname);

И функция обратного вызова onDataChange была вызвана с массивом, содержащим объекты, состоящие из данных таблицы в виде пар «ключ — значение». Можно проверить, что mock-функция обратного вызова получает новые данные надлежащим образом:

expect(callback.mock.calls[0][0][0].name).toBe(newname);

Здесь [0][0][0] означает, что первым аргументом mock-функции выступает массив, в котором первый элемент является объектом (соответствующим записи в таблице) со свойством name, равным "$2.99 chuck".

162149.png

Вместо использования TestUtils.Simulate.submit можно выбрать TestUtils.Simulate.keyDown и выдать событие нажатия клавиши Enter, при котором также осуществляется отправка данных формы.

В качестве второй спецификации теста удалим одну строку образцовых данных:

it('deletes data', () => {

  // То же, что и раньше

  const callback = jest.genMockFunction();

  const table = TestUtils.renderIntoDocument(

    <Excel

      schema={schema}

      initialData={data}

      onDataChange={callback} />

  );

 

  TestUtils.Simulate.click( // значок x

    TestUtils.findRenderedDOMComponentWithClass(table,

    'ActionsDelete')

  );

 

  TestUtils.Simulate.click( // диалог подтверждения

    TestUtils.findRenderedDOMComponentWithClass(table,

    'Button')

  );

 

  expect(callback.mock.calls[0][0].length).toBe(0);

});

Как и в предыдущем примере, callback.mock.calls[0][0] является новым массивом данных после взаимодействия. Только на этот раз в нем ничего не осталось, поскольку одну запись тест удалил.

Полнота охвата

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

Использование тестов — разумное решение, поскольку они помогают ускорить разработку, приобрести уверенность в своих действиях и проводить реорганизацию кода без лишних опасений за конечный результат. Тесты помогают поставить на место ваших соисполнителей, которые думают, что вносимые ими изменения носят изолированный характер, а на самом деле последствия распространяются гораздо шире. Один из способов придать процессу написания тестов форму некой игры заключается в использовании возможности охвата кода.

Можно применить следующую команду:

$ jest --coverage

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

07_02.tif 

Рис. 7.2. Отчет об охвате тестированием всего кода

Как видите, не все прошло идеально и, несомненно, есть потенциальная возможность для написания дополнительных тестов.

Одной из полезных особенностей отчета об охватывающем тестировании является возможность выявления не прошедших тестирование строк кода. То есть, даже если вы протестировали компонент FormInput, строка 22 не была протестирована. Номера строк, вызывающих вопросы, возвращаются инструкцией return:

getValue(): FormInputFieldValue {

  return 'value' in this.refs.input

    ? this.refs.input.value

    : this.refs.input.getValue();

}

Оказывается, что эта функция никогда тестами не проверялась. Настало время исправить ситуацию, оперативно задав тестовую спецификацию:

it('возвращает введенное значение', () => {

  let input = TestUtils.renderIntoDocument(<FormInput

    type="year" />);

  expect(input.getValue()).toBe(String(new

    Date().getFullYear()));

  input = TestUtils.renderIntoDocument(

    <FormInput type="rating" defaultValue="3" />

  );

  expect(input.getValue()).toBe(3);

});

Первым вызовом expect() тестируется поле ввода, встроенное в DOM-модель, а вторым вызовом тестируется специализированное поле ввода. Теперь должны быть выполнены оба итога применения тернарного оператора в методе getValue().

Отчет об охвате кода вознаграждает вас результатом, показывающим, что теперь строка 22 также охвачена тестированием (рис. 7.3).

07_03.tif 

Рис. 7.3. Обновленный отчет об охвате тестированием всего кода

Назад: 6. Создание приложения
Дальше: 8. Flux

DalExcax
Весьма полезная штука ---- продажа оборудования окон пвх Здесь не может быть ошибки? ---- магазин большой одежды для женщин Я думаю, что Вы ошибаетесь. Пишите мне в PM, пообщаемся. ---- купить землю в сочи под строительство Теперь всё понятно, большое спасибо за информацию. ---- фонарь bailong bl Это просто замечательный ответ ---- отдых на море краснодарский край Присоединяюсь. Всё выше сказанное правда. Можем пообщаться на эту тему. ---- купить дом в сочи Я извиняюсь, но, по-моему, Вы не правы. Пишите мне в PM, пообщаемся. ---- как правильно кидать снюс Конечно. И я с этим столкнулся. Можем пообщаться на эту тему. ---- пицца краснодар меню Офигенно! Спасибо!!! ---- где купить аккаунт фейсбук для рекламы По моему мнению Вы не правы. ---- колумб играть бесплатно без регистрации
AnthonyTap
Это условность, ни больше, ни меньше minecraft who killed noob
stilnyeokna
Высококачественные пластиковые окошки плюс двери ото прямого изготовителя дают возможность заказчику сделать вполне индивидуальный приобретение в надобном наличие, потому наш магазин хотим клиентам сделать самые качественный сервис по производству и доставке окошек к тому же дверных проемов от иркутского изготовителя. На сайте указанного компании изготовление окон Вы можете посмотреть изготавливаемый ассортимент такой уникальной дверей затем сделать оснащение для индивидуальное помещение, коттедж либо большого жилого жилья. Наша компания указана единственным изготовщиком, в которого покупатель напрямую сможете купить пластиковые или алюминиевые окошки или двери, остекление лоджии, фасадные ансамбли у помещение, двойные входные концепции также иные виды застекления для личного помещения. Не включая низких расценок без наценок плюс диллеров компания предоставляем новым заказчикам гибкую систему уценок до сорока пяти процентов, потому по нашем сайте пользователи имеют возможность заказать окна и двери вовсе не только в Иркутске, но и на ближнюю район страны.
ecorfru
Разработка проектов касательно экологической консультации также подобная услуги, сведенная на полевыми-экологическими анализом плюс разработкой плана всяческих направлений –такая обязательная процедура, что исполняется только по закону и предоставляет выполнение норм исходя с охране внешней сферы. Компания Сиблидер выполняет полноценный величину действий именно в теме окружающей среды и предоставляет именно Вам лучшие условия сотрудничества касательно исполнение системного аспекта разработки всех видов документов и аутсорсинга на счет экологии нашими профессионалами. На источнике указанной фирмы организация государственной экологической экспертизы Вы имеют возможность пролистать всяческие подтверждающие удостоверения также разрешение, еще и заказать рекомендации от наших менеджеров либо услуги, какие фирма предоставляет. Наша компания максимально качественно просмотрим ваш заказ затем указываем наилучшие пути резолюции любой случая в небольшой промежуток времени, также еще специалисты совершаем хорошее инжиниринг юрилического лица от самого старта также к завершению договора. Заполняйте номер телефону на страничке и делайте работу в руки специалистами в области экологии каковы клиентам содействуют.
LikefilmsNet
Киносайт – является место, собственно где кинозритель найдет индивидуально такое-же по своей вкусу, определенно не найдете лучшего отвлечения от вашего рутины, чем включение захватывающего сериала обожаемого почерка, тот что зритель выбираете по личное предпочтение. Сайт качественных кинотеатра Зверополис 2 (2022) смотреть мультфильм онлайн бесплатно вмещает огромную число известных кинолент последних выпусков плюс анонсы данного 2021 года, какие постоянные посетители смогут просмотреть полностью в открытом доступе плюс без личного кабинета. Приспособленная навигация по сайту фильмов, мультфильмов либо сериалов легко обозначит кино у хорошем разрешении и звук, также Вы постоянно можете разделить популярное фильм из личными знакомыми с помощью нажатия кнопки социальной сети также написать свой отзыв, есле ж предполагаете разделить первые впечатлениями из другими зрителями. Здесь на презентованном сайте разных фильмов зритель сумеет подобрать фильмы именно по стилю, годам либо рейтингу, заходите и всегда пересматривайте лучшим кинокартиной с большим восторгом на нашему Like Films .
hd1080pred
День кинофильмов сейчас есть целостная порция вашего досуга по вечернее время и в течении дня, на свободных днях и регулярно, в момент изоляции или же небольшим сообщества потому вполне ценно найти сайт высококачественных фильмов, что постоянно рядом с добавленных. На нашем веб-сайте сборника кино Смотреть фильм Гарри Поттер и Тайная комната (2002) онлайн бесплатно и в хорошем качестве, ценители высококачественных показа смогут найти пред вышедшее показ, серийное кино или мультики, или определить видеофильм по жанру. В случае если посетитель всемирный обожатель фильмов, тогда благодаря веб-сайте фильмов есть возможность создать личный аккаунт, чтоб сохранять примечания, обозначить фильм, тот что надо посмотреть. На нашей визитной шапке всегда можно отследить популярные сюжеты, каковы ожидают на больших экранах к тому же увидеть видеоролик, а ожидаемое фильмы мы предоставляем под наших читателей только с хорошем hd разрешении, потому уверенно можете входить на ресурс потом кликнуть «Старт» указанный кино.
KinogoBlue
Зачастую мучаетесь касательно предмету, что стоит запустить интересное на вечерний досуг? На нашем онлайн кинотеатре бесплатного плюс новых кино Киного Я иду искать (2019) смотреть онлайн бесплатно пользователи можете мгновенно обнаружить хороший вариант кинофильма распространенного стиля с подмогой функционала навигации, отбора или же окна ввода. Сайт все это определил за посетителей также разработали просмотр нового кино более проще, именно на начальной стороне Вы сможет обозреть вновь свежую кино, известные многосерийное кино также предельно высокие показы, ну а во время когда надумаете увидеть трейлеры мировых фильмов этого времени, тогда кликайте в шапку «В скором времени в кино» и включайте известные спешные фильмы в прокате. Короткое воссоздание сюжета, сформированный показатель со стороны пользователей и подходящие примечания подсказывают польователю подобрать кино, какое понравится вовсе не лишь посетителю, однако и всем близким. Кликайте и ищите новую фильмы непосредственно сегодня!