В этой серии из четырех частей давайте создадим простой компонент для создания заметок Vue.js под названием Annotate, используя TDD с Unite.js. Здесь я сосредоточусь на функциональной части и не буду беспокоиться о стилях или внутренней реализации.
ТОС
- Часть 1. Базовый компонент
- Часть 2: Удаление, фильтрация и передача реквизита
- Часть 3: Рефакторинг нашего компонента
- Часть 4: Подключение к серверной части и тестирование запросов Ajax
Я попытался сделать эти примеры поэтапно, показывая ошибки и решения TDD. Это получилось немного длинным, но я надеюсь, вам понравится!
Запуск проекта
Для начала создадим папку проекта. Если вы используете терминал:
mkdir annotate && cd annotate
И чтобы запустить npm, запустите:
npm init -y
Если все прошло хорошо, должно получиться что-то вроде этого:
Открыв папку в редакторе кода, у вас должно получиться что-то вроде этого:
Установка зависимостей
В этом проекте мы будем использовать Vue.js
и Unite.js
. Итак, через терминал потянем Unite.js:
npm install --save-dev unite.js
Этот пакет содержит все необходимое для начала разработки.
Настройка тестовой среды
Теперь откройте ваш ./package.json
файл и измените объект scripts
на:
// ./package.json "scripts": { "test": "unite" },
Теперь нам нужно создать папку с нашими компонентами. Давайте создадим папку src, в которой будут располагаться наши файлы компонентов:
mkdir src
Наконец, нам нужно указать Unite.js
использовать этот src
каталог в качестве папки тестирования. Для этого создадим unite.config.js
файл в корне проекта:
touch unite.config.js
Теперь откройте его в своем редакторе и добавьте:
// ./unite.config.js module.exports = { path: "./src" }
Теперь, если вы откроете свой терминал и запустите npm run test
, вы должны получить:
Аннотировать тесты SFC
Теперь, когда у нас настроена среда тестирования, давайте создадим файл нашего компонента в нашей src
папке:
touch src/annotate.tests.vue
Откройте наш annotate.tests.vue
файл в своем редакторе и добавьте базовую структуру компонентов:
Чтобы убедиться, что все работает правильно, давайте создадим два теста в нашем теге tests
: успешный и неудачный:
Давайте рассмотрим код:
test
tag содержит:
let wrapper;
Определяет переменную `wrapper`, которую мы будем использовать для монтирования нашего компонента.
Unite.beforeEachTest();
Определяет функцию, которая будет запускаться перед каждым тестом. Здесь мы можем монтировать наш компонент перед каждым тестом.
Unite.$mount();
Это функция
vue-test-utils
mount, которая монтирует компонент vue и возвращает `Wrapper`$component
Это наш компонент vue, который определяется как переменная
unite.js
внутри*.tests.vue
filesUnite.test();
Определяет новый тест. Он принимает два параметра: `name`: имя теста; callback: тестовая функция;
Давайте посмотрим на тест:
@test: это действительный тест:
expect(wrapper.text()).toEqual("Was mounted correctly");
Ожидается, что текстwrapper
будет равен «Был смонтирован правильно».
@test: это недопустимый тест
expect(wrapper.text()).toEqual("This will fail");
Ожидается, что текстwrapper
будет равен «Это не удастся».
Теперь, если мы запустим npm run test
и вы должны получить что-то вроде этого:
Как и ожидалось, мы получили один провальный тест. Теперь, когда мы знаем, что все работает, давайте начнем самое интересное и создадим наш компонент !!
Выбор структуры компонентов
Первое, что нам нужно сделать, это решить, как мы хотим, чтобы наш компонент работал. Базовый функционал, который мы собираемся реализовать здесь:
- Объект заметки должен содержать уникальный идентификатор, заголовок и тело.
- Список заметок
- Форма для создания / редактирования заметок
- Когда создается новая заметка, ее нужно добавить в список
- При нажатии на заметку мы должны увидеть ее в форме
- Когда заметка редактируется и сохраняется, она должна обновлять соответствующую заметку в списке.
- При нажатии новой кнопки мы должны увидеть пустую форму
I. Объект заметки должен содержать уникальный идентификатор, заголовок и тело.
Как мы описали, каждая нота должна содержать title
и body
. Для простоты объект note
будет:
{ id: {UNIQUE ID}, title: "Note title", body: "Note body" }
II. Список заметок
Давайте подумаем о списке заметок:
- Это должен быть массив, содержащий
note
объектов. - Каждый
note
объект должен отображаться внутри.notes
элемента в шаблоне. - Каждый
.notes
дочерний элемент должен отображать свойtitle
Имея это в виду, давайте создадим тест, утверждающий, что для каждого note
объекта мы должны видеть дочерний элемент .notes
, содержащий заголовок note
:
Давайте рассмотрим код:
@test: отображает все примечания:
addNote("Note 1", "Note body");
Добавляет новую заметку с помощью
addNote
helper (см. Ниже)addNote("Note 2", "Note body");
Добавляет новую заметку с помощью
addNote
helper (см. Ниже)let notes = wrapper.find(".notes");
Находит.notes
элемент DOM.expect(notes.vnode.children.length).toEqual(2);
Ожидается, что элемент
.notes
DOM содержит двух дочерних
@helper: addNote (заголовок, текст):
let noteKey = wrapper.vm.notes.length;
Получает текущую длину массива
ntoes
.wrapper.vm.notes.push();
P Отправляет новую заметку в массив
notes
:
-id:(new Date).getTime(),
Присваивает ей уникальный
id
(для использования в качествеkey
в цикле v-for)
-title,
Устанавливает объект
note
title
на заданноеtitle
-body
Устанавливает для объекта
note
body
заданное значениеbody
wrapper.update();
Triggers vue для обновления DOM
return wrapper.vm.notes[noteKey];
Возвращает последний объект `note`
Время запачкать руки !!
npm run test
// ERROR 1) annotate.tests.vue @ It displays all the notes TypeError: Cannot read property ‘push’ of undefined
Это означает, что мы пытаемся использовать push
для неопределенной переменной. В нашем коде push
используется в помощнике addNote
для передачи объекта note
в свойство notes
. Итак, давайте создадим свойство notes
:
2. npm run test
// ERROR 1) annotate.tests.vue @ It displays all the notes TypeError: Cannot read property ‘children’ of undefined
Теперь мы пытаемся прочитать свойство children
неопределенной переменной. Это означает, что vue-test-utils
не удалось найти элемент .notes
.
Давайте создадим .notes:
3. npm run test
// ERROR 1) annotate.tests.vue @ It displays all the notes TypeError: Cannot read property ‘length’ of undefined
Сейчас мы пытаемся получить длину children
, но их нет. В этом случае vue-test-utils
устанавливает children
как неопределенное. Итак, давайте создадим цикл v-for для заметок:
4. npm run test
Получаем зелёный!
III. Форма для создания / редактирования заметок
Теперь нам нужна форма для создания и редактирования заметок. Итак, давайте создадим тест, подтверждающий, что он видит форму, содержащую входные данные для заголовка и тела:
Давайте рассмотрим код:
@test: видит форму для создания / редактирования заметок:
wrapper.find("notes");
Получает оболочку `.form`
expect(form.find("input[name=id]").exists()).toBe(true);
Мы ожидаем, что существует вход с именем
id
expect(form.find("input[name=title]").exists()).toBe(true);
Мы ожидаем, что существует вход с именем
title
expect(form.find("textarea[name=body]").exists()).toBe(true);
Мы ожидаем, что существует вход с именем
body
expect(form.find("button").exists()).toBe(true);
Мы ожидаем, что
button
будет существовать
Давайте код!
npm run test
// ERROR 1) annotate.tests.vue @ It sees the form to create/edit notes Error: [vue-test-utils]: find did not return .form, cannot call find() on empty Wrapper
vute-test-utils
не удалось найти .form
элемент. Добавим:
2. npm run test
// ERROR 1) annotate.tests.vue @ It sees the form to create/edit notes expect(received).toBe(expected) Expected value to be (using ===): true Received: false
Здесь наши ожидания не оправдываются! Итак, давайте продолжим и создадим наши входные данные:
3. npm run test
И мы становимся зелеными!
IV. Когда создается новая заметка, ее нужно добавить в список
Теперь, когда у нас есть форма, если мы заполним ее и нажмем button
, мы ожидаем, что наша заметка будет добавлена в список notes
. Напишем тест:
@test: при нажатии кнопки добавляется новая заметка:
type("input[name=title]", "New Note");
В поле
title
введите «New Note»type("textarea[name=body]", "New note body");
В поле
body
введите "Основная часть заметки"expect(wrapper.find(".notes").isEmpty()).toBe(true);
Ожидайте, что элемент
.notes
DOM не содержит дочерних элементовwrapper.find("button").trigger("click");
Запускает
click
событие на кнопкеexpect(wrapper.find(".notes").vnode.children.length).toEqual(1);
Ожидайте, что
.notes
элемент DOM имеет одного дочернего элементаexpect(wrapper.find(".note:first-child").text()).toEqual("New Note");
Ожидайте, что первый
.note
дочерний элемент DOM будет иметь текстовое содержание «Новое примечание».
@helper: тип (селектор, значение):
let node = wrapper.find(selector);
Находит элемент с заданным селектором
node.element.value = value;
Устанавливает значение элемента с заданным значением
node.trigger("input");
Запускает событие vue `input`
Давайте рассмотрим ошибки, чтобы реализовать это!
1. npm run test
// ERROR 1) annotate.tests.vue @ It adds a new note when the button is clicked expect(received).toEqual(expected) Expected value to equal: 1 Received: 0
Мы ожидали, что при нажатии кнопки будет создана заметка и будет создан дочерний элемент .notes
. Но прямо сейчас ничего не было спускового механизма.
Первое, что нам нужно сделать, это запустить функцию при нажатии button
:
Давайте создадим метод save
:
Чтобы сохранить заметку, сначала нам нужно знать входное содержимое. Давайте создадим note
переменную в экземпляре vue и добавим v-модели во входные данные. Затем мы можем использовать метод save
, чтобы поместить новый note
в список заметок:
@methods: save ():
this.note.id = (new Date).getTime();
Устанавливает уникальный идентификатор для нового
note
объекта.this.notes.push(this.note);
Он помещает новый объект
note
в список заметок
2. npm run test
Теперь мы должны стать зелеными!
V. При нажатии на заметку мы должны увидеть ее в форме
Итак, если у нас есть заметка, когда мы щелкаем по дочернему элементу .notes
, мы должны видеть данные заметки на входах .form
. Напишем тест:
@test: показывает нажатую заметку в форме:
let note = addNote("Note 1", "Note's body");
Добавляет новую заметку с помощью помощника
addNote
wrapper.find(".note:first-child").trigger("click");
Обнаруживает первого дочернего элемента DOM `.notes` и запускает событие click
let form = wrapper.find(".form");
Обнаруживает
.form
элемент DOMexpect(form.find("input[name=id]").element.value).toEqual(String(node.id));
Ожидается, что значение ввода с именем
id
должно быть равно идентификатору добавленной заметки (id
будет преобразован в строку)expect(form.find("input[name=title]").element.value).toEqual(node.title);
Ожидается, что значение ввода с именем
title
должно быть равно заголовку добавленной заметки.expect(form.find("textarea[name=body]").element.value).toEqual(node.body);
Ожидается, что значение ввода с именем
body
должно быть равно тексту добавленной заметки.
Приступим к тестам:
1. npm run test
// ERROR 1) annotate.tests.vue @ It shows the clicked note on the form expect(received).toEqual(expected) Expected value to equal: 1508775148045 Received: ""
Эта ошибка показывает нам, что после того, как мы щелкнули элемент .note
, вход с именем id
не имеет ожидаемого значения. Прямо сейчас, когда мы нажимаем на элемент .note
, ничего не происходит. Итак, давайте вызовем edit
метод при щелчке по элементу .note
:
А теперь создадим метод edit
:
@methods: edit (примечание)
this.note = note;
Устанавливает объект `note` для данной заметки
2. npm run test
Теперь мы стали зелеными!
VI. Когда заметка редактируется и сохраняется, она должна обновлять соответствующую заметку в списке.
Итак, учитывая, что у нас есть заметка и мы ее редактируем, когда мы изменяем входные данные и щелкаем button
, мы ожидаем, что объект note
будет обновлен с новым входным значением.
Напишем тест:
@test: обновляет объект заметки входными данными:
addNote("Note 1", "Note's body");
Добавляет новый объектnote
в списокnotes
.addNote("Note 2", "Note's body");
Добавляет новый объектnote
в списокnotes
.addNote("Note 3", "Note's body");
Добавляет новый объектnote
в списокnotes
.wrapper.find(".note:nth-child(2)").trigger("click");
Обнаруживает второй
.note
элемент DOM и запускаетclick
событиеtype("input[name=title]", "New Title");
Устанавливает для ввода формы заголовка значение «Новый заголовок»
wrapper.find("button").trigger("click");
Запускает событиеclick
в элементеbutton
.expect(wrapper.vm.notes.length).toEqual(3);
Утверждает, что количество нот - 3expect(wrapper.find(".note:nth-child(2)").text()).toEqual("New Title");
Утверждается, что заголовок второй заметки - «Новый заголовок».
Запустим тесты:
npm run test
// ERROR 1) annotate.tests.vue @ It updates a note object with the input data expect(received).toEqual(expected) Expected value to equal: 3 Received: 4
Это показывает, что вместо обновления второй заметки мы добавляем в список новый объект note
! Чтобы исправить это, давайте обновим функцию save
:
Давайте рассмотрим новый код:
@methods: save:
let note = this.note;
Получает значения формы
if(note.id !== "")
Еслиid
примечания не пустое
-this.notes = this.notes.map()
Он сопоставляется с элементами списка `notes`
-if(item.id === note.id)
Если` id` элемента равен `id` заметки:
return note;
—
else: return item
else
Если примечаниеid
пусто
-note.id = (new Date).getTime();
Устанавливает уникальный
id
-this.notes.push(note);
Он помещает новый объект
note
в списокnotes
2. npm run test
:
Теперь мы стали зелеными!
VII. Когда нажимается кнопка `new`, мы должны увидеть пустую форму
Наконец, нам нужна кнопка для создания новой заметки. Когда мы щелкаем по ней, мы очищаем форму и можем начать новую заметку.
Приступим к тесту:
@test: при нажатии новой кнопки отображается пустая форма:
addNote("Note Title", "Note's body");
Добавляет новую заметку со вспомогательной функцией addNote.
wrapper.find(".note:first-child").trigger("click");
Обнаруживает первый элемент DOM `.note` и запускает событие щелчка
expect(wrapper.find("input[name=title]").element.value).toEqual("Note Title");
Ожидается, что значение ввода `title` является значением заметки
wrapper.find(".new-note").trigger("click");
Запускает событие щелчка на элементе `.new-note`
expect(wrapper.find("input[name=id]").element.value).toEqual("");
Ожидается, что входное значение
id
будет пустымexpect(wrapper.find("input[name=title]").element.value).toEqual("");
Ожидается, что входное значение
title
будет пустымexpect(wrapper.find("textarea[name=body]").element.value).toEqual("");
Ожидается, что входное значениеbody
будет пустым
Приступим к тестированию:
1. npm run test
// ERROR 1) annotate.tests.vue @ It shows the blank form when the new button is clicked Error: [vue-test-utils]: find did not return .new-note, cannot call trigger() on empty Wrapper
Он пытается найти элемент .new-note
и вызвать событие click
, но его не существует! Создадим его:
2. npm run test
// ERROR 1) annotate.tests.vue @ It shows the blank form when the new button is clicked expect(received).toEqual(expected) Expected value to equal: "" Received: "1508781636415"
После того, как он щелкнул .new-button
, он ожидал, что ввод формы будет очищен. Но в настоящее время он ничего не делает. Давайте добавим триггер в newNote
и создадим метод:
@methods: newПримечание:
this.note = {id: "", title: "", body: ""}
Устанавливает для переменнойnote
пустойnote
объект.
3. npm run test
И мы на зеленом!
Заключительный компонент
Выводы
В этом примере мы создали очень простой компонент Vue.js
для заметок, используя TDD с Unite.js
. Исходный код можно найти здесь!
Отсюда необходимо сделать две вещи:
- Мы не беспокоились о стилизации нашего компонента. Так что нам нужно сделать его красивым!
- Мы сосредоточились на интерфейсе, и заметки нигде не хранятся. Для этого вы должны изменить
save
method для отправки запроса ajax в серверную часть, где вы можете сохранить данные.
Так что покажите свой собственный компонент Annotate ниже! Кроме того, если у вас есть какие-либо вопросы или идеи по улучшению нашего компонента «Аннотации», оставьте свои мысли ниже!
Если вам понравилось, покажите свою поддержку и расскажите своим друзьям!
Спасибо!