В тази поредица от четири части нека създадем прост компонент Vue.js за водене на бележки, наречен Annotate, използвайки TDD с Unite.js. Тук ще се съсредоточа върху функционалната част и няма да се тревожа за стила или бек-енд изпълнението.
TOC
- Част 1: Основен компонент
- Част 2: Изтриване, филтриране и предаване на реквизити
- Част 3: Рефакторинг на нашия компонент
- Част 4: Свързване към Back-end и тестване на 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
съдържа:
let wrapper;
Дефинира променлива `wrapper`, която ще използваме за монтиране на нашия компонент
Unite.beforeEachTest();
Дефинира функция, която ще се изпълнява преди всеки тест. Тук можем да монтираме нашия компонент преди всеки тест.
Unite.$mount();
Тази функция е функцията за монтиране
vue-test-utils
, която монтира компонент vue и връща `Wrapper`$component
Това е нашият vue компонент, дефиниран като променлива от
unite.js
в*.tests.vue
filesUnite.test();
Определя нов тест. Приема два параметъра: `name`: Името на теста; `обратно повикване`: тестовата функция;
Нека да разгледаме теста:
@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
object ще бъде:
{ 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(title, body):
let noteKey = wrapper.vm.notes.length;
Получава текущата дължина на масива
ntoes
wrapper.vm.notes.push();
Изпраща нова бележка към масива
notes
:
—id:(new Date).getTime(),
Присвоява уникален
id
на бележката (да се използва катоkey
в цикъла v-for)
—title,
Настройва
note
обектаtitle
към даденияtitle
—body
Задава
note
обектаbody
на даденияbody
wrapper.update();
Задейства vue за актуализиране на DOM
return wrapper.vm.notes[noteKey];
Връща последния обект „бележка“.
Време е да си изцапаме ръцете!!
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
„Нова бележка“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");
Очаквайте, че първият дъщерен DOM елемент
.note
има текстово съдържание на „Нова бележка“
@helper: тип (селектор, стойност):
let node = wrapper.find(selector);
Намира елемента с дадения селектор
node.element.value = value;
Задава стойността на елемента с дадената стойност
node.trigger("input");
Задейства събитието `input` на vue
Нека да прегледаме грешките, за да приложим това!
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` и задейства събитието кликване
let form = wrapper.find(".form");
Намира
.form
DOM елементаexpect(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: редактиране(бележка)
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()
Той преобразува елементите от списъка на `бележките`
—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. Когато се щракне върху бутон „нов“, трябва да видим празен формуляр
И накрая, имаме нужда от бутон за създаване на нова бележка. Когато щракнете върху него, изчистваме формуляра, за да можем да започнем новата бележка.
Да започнем с теста:
@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: нова бележка:
this.note = {id: "", title: "", body: ""}
Задава променливатаnote
на празен обектnote
3. npm run test
И сме на зелено!
Краен компонент
Изводи
В този пример създадохме много прост Vue.js
компонент за водене на бележки, използвайки TDD с Unite.js
. Изходният код може да бъде намерен тук!
Оттук нататък има две неща, които трябва да се направят:
- Не се тревожехме за стилизирането на нашия компонент. Така че трябва да го направим красив!
- Фокусирахме се върху предния край и бележките не се съхраняват никъде. За да направите това, трябва да промените
save
method, за да изпратите ajax заявка до задния край, където можете да запазите данните.
Така че покажете своя собствен компонент за анотиране по-долу! Също така, ако имате някакви въпроси или идеи за подобряване на нашия компонент Annotate, оставете вашите мисли по-долу!
Ако ви е харесало това, покажете своята подкрепа и кажете на приятелите си!
Благодаря ти!