diff --git a/projects/dnd/dnd.html b/projects/dnd/dnd.html
new file mode 100644
index 0000000..964c99c
--- /dev/null
+++ b/projects/dnd/dnd.html
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/projects/dnd/functions.js b/projects/dnd/functions.js
new file mode 100644
index 0000000..df16aac
--- /dev/null
+++ b/projects/dnd/functions.js
@@ -0,0 +1,90 @@
+/* ДЗ 6 - DOM Events */
+
+/*
+ Задание 1:
+
+ Функция должна добавлять обработчик fn события eventName к элементу target
+
+ Пример:
+ addListener('click', document.querySelector('a'), () => console.log('...')) // должна добавить указанный обработчик кликов на указанный элемент
+ */
+function addListener(eventName, target, fn) {
+ target.addEventListener(eventName, fn);
+}
+
+/*
+ Задание 2:
+
+ Функция должна удалять у элемента target обработчик fn события eventName
+
+ Пример:
+ removeListener('click', document.querySelector('a'), someHandler) // должна удалить указанный обработчик кликов на указанный элемент
+ */
+function removeListener(eventName, target, fn) {
+ target.removeEventListener(eventName, fn);
+}
+
+/*
+ Задание 3:
+
+ Функция должна добавить к элементу target такой обработчик на события eventName, чтобы он отменял действия по умолчанию
+
+ Пример:
+ skipDefault('click', document.querySelector('a')) // после вызова функции, клики на указанную ссылку не должны приводить к переходу на другую страницу
+ */
+function skipDefault(eventName, target) {
+ target.addEventListener(eventName, (e) => {
+ e.preventDefault();
+ });
+}
+
+/*
+ Задание 4:
+
+ Функция должна эмулировать событие click для элемента target
+
+ Пример:
+ emulateClick(document.querySelector('a')) // для указанного элемента должно быть симулировано события click
+ */
+function emulateClick(target) {
+ const event = new Event('click');
+ target.dispatchEvent(event);
+}
+
+/*
+ Задание 6:
+
+ Функция должна добавить такой обработчик кликов к элементу target,
+ который реагирует (вызывает fn) только на клики по элементам BUTTON внутри target
+
+ Пример:
+ delegate(document.body, () => console.log('кликнули на button')) // добавит такой обработчик кликов для body, который будет вызывать указанную функцию только если кликнули на кнопку (элемент с тегом button)
+ */
+function delegate(target, fn) {
+ target.addEventListener('click', (e) => {
+ if (e.target.tagName === 'BUTTON') {
+ fn();
+ }
+ });
+}
+
+/*
+ Задание 7:
+
+ Функция должна добавить такой обработчик кликов к элементу target,
+ который сработает только один раз и удалится (перестанет срабатывать для последующих кликов по указанному элементу)
+
+ Пример:
+ once(document.querySelector('button'), () => console.log('обработчик выполнился!')) // добавит такой обработчик кликов для указанного элемента, который вызовется только один раз и затем удалится
+ */
+function once(target, fn) {
+ let done = false;
+ target.addEventListener('click', () => {
+ if (!done) {
+ fn();
+ done = true;
+ }
+ });
+}
+
+export { addListener, removeListener, skipDefault, emulateClick, delegate, once };
diff --git a/projects/dnd/functions.spec.js b/projects/dnd/functions.spec.js
new file mode 100644
index 0000000..bbb2f07
--- /dev/null
+++ b/projects/dnd/functions.spec.js
@@ -0,0 +1,106 @@
+import {
+ addListener,
+ delegate,
+ emulateClick,
+ once,
+ removeListener,
+ skipDefault,
+} from './functions';
+
+describe('ДЗ 5.1 - DOM Events', () => {
+ describe('addListener', () => {
+ it('должна добавлять обработчик событий элемента', () => {
+ const target = document.createElement('div');
+ const eventName = 'click';
+ const fn = jest.fn();
+
+ addListener(eventName, target, fn);
+
+ target.dispatchEvent(new CustomEvent(eventName));
+ expect(fn).toBeCalled();
+ });
+ });
+
+ describe('removeListener', () => {
+ it('должна удалять обработчик событий элемента', () => {
+ const target = document.createElement('div');
+ const eventName = 'click';
+ const fn = jest.fn();
+
+ target.addEventListener(eventName, fn);
+
+ removeListener(eventName, target, fn);
+
+ target.dispatchEvent(new CustomEvent(eventName));
+ expect(fn).not.toBeCalled();
+ });
+ });
+
+ describe('skipDefault', () => {
+ it('должна добавлять такой обработчик, который предотвращает действие по умолчанию', () => {
+ const target = document.createElement('div');
+ const eventName = 'click';
+
+ skipDefault(eventName, target);
+
+ const result = target.dispatchEvent(
+ new CustomEvent(eventName, { cancelable: true })
+ );
+ expect(!result);
+ });
+ });
+
+ describe('emulateClick', () => {
+ it('должна эмулировать клик по элементу', () => {
+ const target = document.createElement('div');
+ const eventName = 'click';
+ const fn = jest.fn();
+
+ target.addEventListener(eventName, fn);
+
+ emulateClick(target);
+
+ expect(fn).toBeCalled();
+ });
+ });
+
+ describe('delegate', () => {
+ it('должна добавлять обработчик кликов, который реагирует только на клики по кнопкам', () => {
+ const target = document.createElement('div');
+ const eventName = 'click';
+ const fn = jest.fn();
+
+ target.innerHTML = '';
+
+ delegate(target, fn);
+
+ expect(fn).not.toBeCalled();
+ target.children[0].dispatchEvent(new CustomEvent(eventName, { bubbles: true }));
+ expect(fn).not.toBeCalled();
+ target.children[1].dispatchEvent(new CustomEvent(eventName, { bubbles: true }));
+ expect(fn).not.toBeCalled();
+ target.children[2].dispatchEvent(new CustomEvent(eventName, { bubbles: true }));
+ expect(fn).not.toBeCalled();
+ target.children[3].dispatchEvent(new CustomEvent(eventName, { bubbles: true }));
+ expect(fn).toBeCalled();
+ });
+ });
+
+ describe('once', () => {
+ it('должна добавлять обработчик кликов, который сработает только один раз и удалится', () => {
+ const target = document.createElement('div');
+ const eventName = 'click';
+ let passed = 0;
+ const fn = () => passed++;
+
+ once(target, fn);
+
+ expect(passed).toBe(0);
+ target.dispatchEvent(new CustomEvent(eventName));
+ expect(passed).toBe(1);
+ target.dispatchEvent(new CustomEvent(eventName));
+ expect(passed).toBe(1);
+ target.dispatchEvent(new CustomEvent(eventName));
+ });
+ });
+});
diff --git a/projects/dnd/index.js b/projects/dnd/index.js
new file mode 100644
index 0000000..29e66db
--- /dev/null
+++ b/projects/dnd/index.js
@@ -0,0 +1,64 @@
+/* Задание со звездочкой */
+
+/*
+ Создайте страницу с кнопкой.
+ При нажатии на кнопку должен создаваться div со случайными размерами, цветом и позицией на экране
+ Необходимо предоставить возможность перетаскивать созданные div при помощи drag and drop
+ Запрещено использовать сторонние библиотеки. Разрешено пользоваться только тем, что встроено в браузер
+ */
+
+/*
+ homeworkContainer - это контейнер для всех ваших домашних заданий
+ Если вы создаете новые html-элементы и добавляете их на страницу, то добавляйте их только в этот контейнер
+
+ Пример:
+ const newDiv = document.createElement('div');
+ homeworkContainer.appendChild(newDiv);
+ */
+import './dnd.html';
+
+const homeworkContainer = document.querySelector('#app');
+
+function random(from, to) {
+ return parseInt(from + Math.random() * to - from);
+}
+
+let isDrag;
+let firstpos_x = 0;
+let firstpos_y = 0;
+
+document.addEventListener('mousemove', (e) => {
+ if (isDrag) {
+ isDrag.style.top = e.clientY - firstpos_x + 'px';
+ isDrag.style.left = e.clientX - firstpos_y + 'px';
+ }
+});
+
+export function createDiv() {
+ const div = document.createElement('div');
+ const minSize = 20;
+ const maxSize = 200;
+ const maxColor = 0xffffff;
+
+ div.className = 'draggable-div';
+ div.style.background = '#' + random(0, maxColor).toString(16);
+ div.style.top = random(0, window.innerHeight) + 'px';
+ div.style.left = random(0, window.innerWidth) + 'px';
+ div.style.width = random(minSize, maxSize) + 'px';
+ div.style.height = random(minSize, maxSize) + 'px';
+
+ div.addEventListener('mousedown', (e) => {
+ isDrag = div;
+ firstpos_x = e.offsetX;
+ firstpos_y = e.offsetY;
+ });
+ div.addEventListener('mouseup', () => (isDrag = false));
+ return div;
+}
+
+const addDivButton = homeworkContainer.querySelector('#addDiv');
+
+addDivButton.addEventListener('click', function () {
+ const div = createDiv();
+ homeworkContainer.append(div);
+});
diff --git a/projects/dnd/index.spec.js b/projects/dnd/index.spec.js
new file mode 100644
index 0000000..6650373
--- /dev/null
+++ b/projects/dnd/index.spec.js
@@ -0,0 +1,38 @@
+describe('ДЗ 5.2 - Div D&D', () => {
+ const dndPage = require('./index');
+ const homeworkContainer = document.querySelector('#app');
+ let addDivButton;
+
+ describe('Функциональное тестирование', () => {
+ describe('createDiv', () => {
+ it('должна создавать div с произвольными размерами/позицией/цветом', () => {
+ const result = dndPage.createDiv();
+
+ expect(result).toBeInstanceOf(Element);
+ expect(result.tagName).toBe('DIV');
+ expect(result.style.backgroundColor || result.style.background).not.toBe('');
+ expect(result.style.top).not.toBe('');
+ expect(result.style.left).not.toBe('');
+ expect(result.style.width).not.toBe('');
+ expect(result.style.height).not.toBe('');
+ });
+ });
+ });
+
+ describe('Интеграционное тестирование', () => {
+ it('на старнице должна быть кнопка с id addDiv', () => {
+ addDivButton = homeworkContainer.querySelector('#addDiv');
+ expect(addDivButton).toBeInstanceOf(Element);
+ expect(addDivButton.tagName).toBe('BUTTON');
+ });
+
+ it('создавать div с классом draggable-div при клике на кнопку', () => {
+ const divsCount = homeworkContainer.querySelectorAll('.draggable-div').length;
+
+ addDivButton.dispatchEvent(new MouseEvent('click', { view: window }));
+ const newDivsCount = homeworkContainer.querySelectorAll('.draggable-div').length;
+
+ expect(newDivsCount - divsCount).toBe(1);
+ });
+ });
+});