diff --git a/projects/dom/index.js b/projects/dom/index.js
new file mode 100644
index 000000000..ebfe1e02f
--- /dev/null
+++ b/projects/dom/index.js
@@ -0,0 +1,164 @@
+/* ДЗ 4 - работа с DOM */
+
+/*
+ Задание 1:
+
+ 1.1: Функция должна создать элемент с тегом DIV
+
+ 1.2: В созданный элемент необходимо поместить текст, переданный в параметр text
+
+ Пример:
+ createDivWithText('loftschool') // создаст элемент div, поместит в него 'loftschool' и вернет созданный элемент
+ */
+function createDivWithText(text) {
+ const element = document.createElement('div');
+ element.textContent = text;
+ return element;
+}
+
+/*
+ Задание 2:
+
+ Функция должна вставлять элемент, переданный в параметре what в начало элемента, переданного в параметре where
+
+ Пример:
+ prepend(document.querySelector('#one'), document.querySelector('#two')) // добавит элемент переданный первым аргументом в начало элемента переданного вторым аргументом
+ */
+function prepend(what, where) {
+ return where.prepend(what);
+}
+
+/*
+ Задание 3:
+
+ 3.1: Функция должна перебрать все дочерние элементы узла, переданного в параметре where
+
+ 3.2: Функция должна вернуть массив, состоящий из тех дочерних элементов следующим соседом которых является элемент с тегом P
+
+ Пример:
+ Представим, что есть разметка:
+
+
+
+
+
+
+
+
+ findAllPSiblings(document.body) // функция должна вернуть массив с элементами div и span т.к. следующим соседом этих элементов является элемент с тегом P
+ */
+function findAllPSiblings(where) {
+ const array = [];
+ for (let i = 0; i < where.children.length; i++) {
+ if (where.children[i].tagName === 'P') {
+ array.push(where.children[i].previousElementSibling);
+ }
+ }
+ return array;
+}
+
+/*
+ Задание 4:
+
+ Функция представленная ниже, перебирает все дочерние узлы типа "элемент" внутри узла переданного в параметре where и возвращает массив из текстового содержимого найденных элементов
+ Но похоже, что в код функции закралась ошибка и она работает не так, как описано.
+
+ Необходимо найти и исправить ошибку в коде так, чтобы функция работала так, как описано выше.
+
+ Пример:
+ Представим, что есть разметка:
+
+ привет
+ loftschool
+
+
+ findError(document.body) // функция должна вернуть массив с элементами 'привет' и 'loftschool'
+ */
+function findError(where) {
+ const result = [];
+
+ for (const child of where.children) {
+ result.push(child.textContent);
+ }
+
+ return result;
+}
+
+/*
+ Задание 5:
+
+ Функция должна перебрать все дочерние узлы элемента переданного в параметре where и удалить из него все текстовые узлы
+
+ Задачу необходимо решить без использования рекурсии, то есть можно не уходить вглубь дерева.
+ Так же будьте внимательны при удалении узлов, т.к. можно получить неожиданное поведение при переборе узлов
+
+ Пример:
+ После выполнения функции, дерево приветloftchool!!!
+ должно быть преобразовано в
+ */
+function deleteTextNodes(where) {
+ for (const child of where.childNodes) {
+ if (child.nodeType === 3) {
+ child.parentNode.removeChild(child);
+ }
+ }
+
+ return where.childNodes;
+}
+
+/*
+ Задание 6 *:
+
+ Необходимо собрать статистику по всем узлам внутри элемента переданного в параметре root и вернуть ее в виде объекта
+ Статистика должна содержать:
+ - количество текстовых узлов
+ - количество элементов каждого класса
+ - количество элементов каждого тега
+ Для работы с классами рекомендуется использовать classList
+ Постарайтесь не создавать глобальных переменных
+
+ Пример:
+ Для дерева привет! loftschool
+ должен быть возвращен такой объект:
+ {
+ tags: { DIV: 1, B: 2},
+ classes: { "some-class-1": 2, "some-class-2": 1 },
+ texts: 3
+ }
+ */
+function collectDOMStat(root) {
+ const result = {
+ tags: {},
+ classes: {},
+ texts: 0,
+ };
+
+ for (const child of root.childNodes) {
+ if (child.nodeType === 3) {
+ result.texts++;
+ } else if (child.nodeType === 1) {
+ if (child.tagName in result.tags) {
+ result.tags[child.tagName]++;
+ } else {
+ result.tags[child.tagName] = 1;
+ }
+ for (const className of child.classList) {
+ if (className in result.classes) {
+ result.classes[className]++;
+ } else {
+ result.classes[className] = 1;
+ }
+ }
+ }
+ }
+ return result;
+}
+
+export {
+ createDivWithText,
+ prepend,
+ findAllPSiblings,
+ findError,
+ deleteTextNodes,
+ collectDOMStat,
+};
diff --git a/projects/dom/index.spec.js b/projects/dom/index.spec.js
new file mode 100644
index 000000000..3d85e7c67
--- /dev/null
+++ b/projects/dom/index.spec.js
@@ -0,0 +1,112 @@
+import { randomValue } from '../../scripts/helper';
+import {
+ collectDOMStat,
+ createDivWithText,
+ deleteTextNodes,
+ findAllPSiblings,
+ findError,
+ prepend,
+} from './index';
+
+function random(type) {
+ const result = randomValue(type);
+
+ if (type === 'string') {
+ return encodeURIComponent(result);
+ }
+
+ return result;
+}
+
+describe('ДЗ 4 - Работа с DOM', () => {
+ describe('createDivWithText', () => {
+ it('должна возвращать элемент с тегом DIV', () => {
+ const text = random('string');
+ const result = createDivWithText(text);
+
+ expect(result).toBeInstanceOf(Element);
+ expect(result.tagName).toBe('DIV');
+ });
+
+ it('должна добавлять текст в элемент', () => {
+ const text = random('string');
+ const result = createDivWithText(text);
+
+ expect(result.textContent).toBe(text);
+ });
+ });
+
+ describe('prepend', () => {
+ it('должна добавлять элемент в начало', () => {
+ const where = document.createElement('div');
+ const what = document.createElement('p');
+ const whereText = random('string');
+ const whatText = random('string');
+
+ where.innerHTML = `, ${whereText}!`;
+ what.textContent = whatText;
+
+ prepend(what, where);
+
+ expect(where.firstChild).toBe(what);
+ expect(where.innerHTML).toBe(`${whatText}
, ${whereText}!`);
+ });
+ });
+
+ describe('findAllPSiblings', () => {
+ it('должна возвращать массив с элементами, соседями которых являются P', () => {
+ const where = document.createElement('div');
+
+ where.innerHTML = '';
+ const result = findAllPSiblings(where);
+
+ expect(Array.isArray(result));
+ expect(result).toEqual([where.children[0], where.children[3]]);
+ });
+ });
+
+ describe('findError', () => {
+ it('должна возвращать массив из текстового содержимого элементов', () => {
+ const where = document.createElement('div');
+ const text1 = random('string');
+ const text2 = random('string');
+
+ where.innerHTML = ` ${text1}
, ${text2}
!!!`;
+ const result = findError(where);
+
+ expect(Array.isArray(result));
+ expect(result).toEqual([text1, text2]);
+ });
+ });
+
+ describe('deleteTextNodes', () => {
+ it('должна удалить все текстовые узлы', () => {
+ const where = document.createElement('div');
+
+ where.innerHTML = ` ${random('string')}${random('string')}`;
+ deleteTextNodes(where);
+
+ expect(where.innerHTML).toBe('');
+ });
+ });
+
+ describe('collectDOMStat', () => {
+ it('должна вернуть статистику по переданному дереву', () => {
+ const where = document.createElement('div');
+ const class1 = `class-${random('number')}`;
+ const class2 = `class-${random('number')}-${random('number')}`;
+ const text1 = random('string');
+ const text2 = random('string');
+ const stat = {
+ tags: { P: 1, B: 2 },
+ classes: { [class1]: 2, [class2]: 1 },
+ texts: 3,
+ };
+
+ where.innerHTML = `${text1} ${text2}
`;
+ const result = collectDOMStat(where);
+
+ expect(result).toEqual(stat);
+ });
+ });
+});