Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions projects/async/functions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/* ДЗ 5 - Асинхронность и работа с сетью */

/*
Задание 1:

Функция должна возвращать Promise, который должен быть разрешен через указанное количество секунд

Пример:
delayPromise(3) // вернет promise, который будет разрешен через 3 секунды
*/
function delayPromise(seconds) {
return new Promise((resolve) => setTimeout(resolve, seconds * 1000));
}

/*
Задание 2:

2.1: Функция должна вернуть Promise, который должен быть разрешен с массивом городов в качестве значения

Массив городов можно получить отправив асинхронный запрос по адресу
https://raw.githubusercontent.com/smelukov/citiesTest/master/cities.json

2.2: Элементы полученного массива должны быть отсортированы по имени города

Пример:
loadAndSortTowns().then(towns => console.log(towns)) // должна вывести в консоль отсортированный массив городов
*/
function loadAndSortTowns() {
return fetch('https://raw.githubusercontent.com/smelukov/citiesTest/master/cities.json')
.then((res) => res.json())
.then((towns) => towns.sort((a, b) => a.name.localeCompare(b.name)));
}

export { delayPromise, loadAndSortTowns };
52 changes: 52 additions & 0 deletions projects/async/functions.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { delayPromise, loadAndSortTowns } from './functions';

const hasOwnProperty = Object.prototype.hasOwnProperty;

describe('ДЗ 6.1 - Асинхронность и работа с сетью', () => {
describe('delayPromise', () => {
it('должна возвращать Promise', () => {
const result = delayPromise(1);

expect(result).toBeInstanceOf(Promise);
});

it('Promise должен быть resolved через указанное количество секунд', (done) => {
const result = delayPromise(1);
const startTime = new Date();

result
.then(() => {
expect(new Date() - startTime).toBeGreaterThanOrEqual(1000);
done();
})
.catch(done);
});
});

describe('loadAndSortTowns', () => {
it('должна возвращать Promise', () => {
const result = loadAndSortTowns();

expect(result).toBeInstanceOf(Promise);
});

it('Promise должен разрешаться массивом из городов', (done) => {
const result = loadAndSortTowns();

result
.then((towns) => {
expect(Array.isArray(towns));
expect(towns.length).toBe(50);
towns.forEach((town, i, towns) => {
expect(hasOwnProperty.call(towns, 'name'));

if (i) {
expect(towns[i - 1].name.localeCompare(town.name)).toBeLessThanOrEqual(0);
}
});
done();
})
.catch(done);
});
});
});
115 changes: 115 additions & 0 deletions projects/async/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
Страница должна предварительно загрузить список городов из
https://raw.githubusercontent.com/smelukov/citiesTest/master/cities.json
и отсортировать в алфавитном порядке.

При вводе в текстовое поле, под ним должен появляться список тех городов,
в названии которых, хотя бы частично, есть введенное значение.
Регистр символов учитываться не должен, то есть "Moscow" и "moscow" - одинаковые названия.

Во время загрузки городов, на странице должна быть надпись "Загрузка..."
После окончания загрузки городов, надпись исчезает и появляется текстовое поле.

Разметку смотрите в файле towns.html

Запрещено использовать сторонние библиотеки. Разрешено пользоваться только тем, что встроено в браузер

*** Часть со звездочкой ***
Если загрузка городов не удалась (например, отключился интернет или сервер вернул ошибку),
то необходимо показать надпись "Не удалось загрузить города" и кнопку "Повторить".
При клике на кнопку, процесс загрузки повторяется заново
*/

/*
homeworkContainer - это контейнер для всех ваших домашних заданий
Если вы создаете новые html-элементы и добавляете их на страницу, то добавляйте их только в этот контейнер

Пример:
const newDiv = document.createElement('div');
homeworkContainer.appendChild(newDiv);
*/

import { loadAndSortTowns } from './functions';
import './towns.html';

const homeworkContainer = document.querySelector('#app');

/*
Функция должна вернуть Promise, который должен быть разрешен с массивом городов в качестве значения

Массив городов пожно получить отправив асинхронный запрос по адресу
https://raw.githubusercontent.com/smelukov/citiesTest/master/cities.json
*/
function loadTowns() {
return loadAndSortTowns();
}

/*
Функция должна проверять встречается ли подстрока chunk в строке full
Проверка должна происходить без учета регистра символов

Пример:
isMatching('Moscow', 'moscow') // true
isMatching('Moscow', 'mosc') // true
isMatching('Moscow', 'cow') // true
isMatching('Moscow', 'SCO') // true
isMatching('Moscow', 'Moscov') // false
*/
function isMatching(full, chunk) {
return full.toLowerCase().includes(chunk.toLowerCase());
}

/* Блок с надписью "Загрузка" */
const loadingBlock = homeworkContainer.querySelector('#loading-block');
/* Блок с надписью "Не удалось загрузить города" и кнопкой "Повторить" */
const loadingFailedBlock = homeworkContainer.querySelector('#loading-failed');
/* Кнопка "Повторить" */
const retryButton = homeworkContainer.querySelector('#retry-button');
/* Блок с текстовым полем и результатом поиска */
const filterBlock = homeworkContainer.querySelector('#filter-block');
/* Текстовое поле для поиска по городам */
const filterInput = homeworkContainer.querySelector('#filter-input');
/* Блок с результатами поиска */
const filterResult = homeworkContainer.querySelector('#filter-result');

let towns = [];

retryButton.addEventListener('click', () => {
tryToLoad();
});

filterInput.addEventListener('input', function () {
updateFilter(this.value);
});

loadingFailedBlock.classList.add('hidden');
filterBlock.classList.add('hidden');

async function tryToLoad() {
try {
towns = await loadTowns();
loadingBlock.classList.add('hidden');
loadingFailedBlock.classList.add('hidden');
filterBlock.classList.remove('hidden');
} catch (e) {
loadingBlock.classList.add('hidden');
loadingFailedBlock.classList.remove('hidden');
}
}

function updateFilter(filterValue) {
filterResult.innerHTML = '';
const fragment = document.createDocumentFragment();
for (const town of towns) {
if (filterValue && isMatching(town.name, filterValue)) {
const townDiv = document.createElement('div');
townDiv.textContent = town.name;
fragment.append(townDiv);
}
}
filterResult.append(fragment);
}

tryToLoad();

export { loadTowns, isMatching };
82 changes: 82 additions & 0 deletions projects/async/index.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const hasOwnProperty = Object.prototype.hasOwnProperty;

describe('ДЗ 6.2 - Фильтр городов', () => {
const filterPage = require('./index');
const homeworkContainer = document.querySelector('#app');
let loadingBlock;
let filterBlock;
let filterInput;
let filterResult;

beforeAll(() => filterPage.loadTowns());

describe('Функциональное тестирование', () => {
describe('isMatching', () => {
it('должна определять присутствие подстроки в строке', () => {
expect(filterPage.isMatching('Moscow', 'moscow'));
expect(filterPage.isMatching('Moscow', 'mos'));
expect(filterPage.isMatching('Moscow', 'MoS'));
expect(filterPage.isMatching('Moscow', 'cow'));
expect(!filterPage.isMatching('Moscow', 'Berlin'));
});
});
describe('loadTowns', () => {
it('должна возвращать Promise', () => {
const result = filterPage.loadTowns();
expect(result).toBeInstanceOf(Promise);
});

it('Promise должен разрешаться массивом из городов', (done) => {
/* eslint-disable max-nested-callbacks */
const result = filterPage.loadTowns();

result
.then((towns) => {
expect(Array.isArray(towns));
expect(towns.length).toBe(50);
towns.forEach((town, i, towns) => {
expect(hasOwnProperty.call(town, 'name'));

if (i) {
expect(towns[i - 1].name.localeCompare(town.name)).toBeLessThanOrEqual(0);
}
});
done();
})
.catch(done);
});
});
});

describe('Интеграционное тестирование', () => {
it('на старнице должны быть элементы с нужными id', () => {
loadingBlock = homeworkContainer.querySelector('#loading-block');
filterBlock = homeworkContainer.querySelector('#filter-block');
filterInput = homeworkContainer.querySelector('#filter-input');
filterResult = homeworkContainer.querySelector('#filter-result');

expect(loadingBlock).toBeInstanceOf(Element);
expect(filterBlock).toBeInstanceOf(Element);
expect(filterInput).toBeInstanceOf(Element);
expect(filterResult).toBeInstanceOf(Element);
});

it('должен показываться список городов, соответствующих фильтру', (done) => {
filterInput.value = 'fr';
filterInput.dispatchEvent(new KeyboardEvent('input'));
setTimeout(() => {
expect(filterResult.children.length).toBe(3);
done();
}, 1000);
});

it('результат должен быть пуст, если в поле пусто', (done) => {
filterInput.value = '';
filterInput.dispatchEvent(new KeyboardEvent('input'));
setTimeout(() => {
expect(filterResult.children.length).toBe(0);
done();
}, 1000);
});
});
});
12 changes: 12 additions & 0 deletions projects/async/towns.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<div id="app">
<div id="loading-block">Загрузка...</div>
<div id="loading-failed">
<div>Не удалось загрузить города</div>
<button id="retry-button">Повторить</button>
</div>
<div id="filter-block">
<input id="filter-input" placeholder="название города" type="text">

<div id="filter-result"></div>
</div>
</div>