From 1b4214ed7a4509652396635b0ab411e1be944ca2 Mon Sep 17 00:00:00 2001 From: Tatiana Lagodich Date: Sun, 1 Sep 2024 19:45:59 +0400 Subject: [PATCH] feat: add test/fix rangeiterator/update docs --- README.md | 28 +- README.ru.md | 29 ++- jsdoc-ru.json | 7 +- jsdoc.json | 7 +- src/core/DigitsIterable.js | 31 ++- src/core/Iter8or.js | 295 +--------------------- src/core/RangeIterable.js | 21 +- src/core/__tests__/DigitsIterable.spec.js | 50 ++++ src/core/__tests__/Iter8or.spec.js | 121 +++++++++ src/core/__tests__/ObjectIterable.spec.js | 93 +++++++ src/core/__tests__/RangeIterable.spec.js | 53 ++++ 11 files changed, 428 insertions(+), 307 deletions(-) create mode 100644 src/core/__tests__/DigitsIterable.spec.js create mode 100644 src/core/__tests__/Iter8or.spec.js create mode 100644 src/core/__tests__/ObjectIterable.spec.js create mode 100644 src/core/__tests__/RangeIterable.spec.js diff --git a/README.md b/README.md index d8c5b2d..764149c 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,13 @@ Iter8or is a versatile library for working with iterators and asynchronous itera - **Support for both synchronous and asynchronous iterators**: Process both synchronous and asynchronous data using a single API. - **Creation of iterators from non-iterable objects:**: - - For numbers (`number`): By default, it creates an iterator that iterates from 1 to the specified number (range). - - `digits` option: If the `{ digits: true }` option is passed, the number is split into individual digits, creating an iterator over the digits of the number. - - For objects (`object`): An iterator is created that iterates over the keys and values of the object. + - For numbers (`number`): + - **By default**, an iterator is created that iterates from _1_ (or _-1_ if the number is negative) to the specified number inclusively (range). + - **The digits option**: If the `{ digits: true }` option is provided, the number is split into individual digits, creating an iterator over the digits of the number. This supports both regular numbers and `BigInt`. + - If a range iterator is used, the allowed range is _from -1e7 to 1e7_. This limitation is in place to prevent excessive memory and resource usage. + - **For objects (`object`)**: An iterator is created that iterates over the keys and values of the object. - **Powerful data processing methods**: Built-in methods for filtering, mapping, grouping, splitting, combining, and other data operations. +- **Support for ES Modules and CommonJS**: The library can be used with both modern ES modules and traditional CommonJS modules. ## Installation @@ -72,7 +75,24 @@ for await (const value of asyncIter) { // 'cherry' ``` -Numbers (`number`) are always processed as **_synchronous_** iterators, regardless of whether the `{ async: true }` option is passed or not. +## Important Notes on Numbers +- **The `digits: true` option**: Allows iterating over individual digits of a number, supporting both regular numbers and `BigInt`. +- Numbers (`number`) are always processed as **_synchronous_** iterators, regardless of whether the `{ async: true }` option is passed. + +### Iteration Over Ranges (RangeIterable): +Range iterators (`range`) are limited to numbers **_from -1e7 to 1e7_**. This limitation is in place to prevent excessive memory and resource usage. If you try to create a range beyond these values, the library will throw a _RangeError_. + +### Example with RangeIterable: +```javascript +const rangeIter = new Iter8or(5); +console.log([...rangeIter]); // [1, 2, 3, 4, 5] +``` + +### Example with BigInt and the digits: true Option: +```javascript +const bigIntIter = new Iter8or(12345678901234567890n, { digits: true }); +console.log([...bigIntIter]); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0] +``` [🔍 Learn more about each method of the Iter8or class](https://tanyalagodich.github.io/Iter8or/Iter8or.html) diff --git a/README.ru.md b/README.ru.md index 2e1efe3..83301b8 100644 --- a/README.ru.md +++ b/README.ru.md @@ -9,11 +9,13 @@ Iter8or — это универсальная библиотека для раб - **Поддержка синхронных и асинхронных итераторов**: Обрабатывайте как синхронные, так и асинхронные данные с помощью единого API. - **Создание итераторов из неитерируемых объектов**: - - Для чисел (`number`): По умолчанию создается итератор, который проходит от 1 до указанного числа включительно (диапазон). - - Опция `digits`: Если передана опция `{ digits: true }`, число разбивается на отдельные разряды, создавая итератор по цифрам числа. - - Для объектов (`object`): Создается итератор, который проходит по ключам и значениям объекта. + - Для чисел (`number`): + - По умолчанию создается итератор, который проходит от _1_ (или _-1_, если передано число меньше 0) до указанного числа включительно (диапазон). + - Опция `digits`: Если передана опция `{ digits: true }`, число разбивается на отдельные разряды, создавая итератор по цифрам числа. Поддерживается как обычные числа, так и `BigInt`. + - Если используется итератор диапазона, можно передавать числа в пределах от _-1e7_ до _1e7_. Это ограничение введено для предотвращения чрезмерного использования памяти и ресурсов. + - Для объектов (`object`): Создается итератор, который проходит по ключам и значениям объекта. - **Мощные методы обработки данных**: Встроенные методы для фильтрации, маппинга, группировки, разделения, объединения и других операций с данными. - +- **Поддержка ES-модулей и CommonJS**: Библиотека может использоваться как с современными ES-модулями, так и с традиционными CommonJS-модулями. ## Установка Установите библиотеку с помощью npm: @@ -71,7 +73,24 @@ for await (const value of asyncIter) { // 'cherry' ``` -Цифры (`number`) всегда обрабатываются как **_синхронные_** итераторы, независимо от того, передана опция `{ async: true }` или нет. +## Важные замечания о числах +- **Опция `digits: true`:** Позволяет итерировать по отдельным цифрам числа, поддерживая как обычные числа, так и `BigInt`. +- Цифры (`number`) всегда обрабатываются как **_синхронные_** итераторы, независимо от того, передана опция `{ async: true }` или нет. + +### Итерация по диапазонам (RangeIterable): +Итераторы диапазонов (`range`) ограничены числом **от -1e7 до 1e7**. Это ограничение введено для предотвращения чрезмерного использования памяти и ресурсов. Если пытаться создать диапазон за пределами этих значений, библиотека выбросит _RangeError_. + +### Пример с диапазоном RangeIterable: +```javascript +const rangeIter = new Iter8or(5); +console.log([...rangeIter]); // [1, 2, 3, 4, 5] +``` + +### Пример с BigInt и опцией digits: true: +```javascript +const bigIntIter = new Iter8or(12345678901234567890n, { digits: true }); +console.log([...bigIntIter]); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0] +``` [🔍 Подробнее о каждом методе класса Iter8or](https://tanyalagodich.github.io/Iter8or/ru/Iter8or.html) diff --git a/jsdoc-ru.json b/jsdoc-ru.json index edd0b28..1a1bfeb 100644 --- a/jsdoc-ru.json +++ b/jsdoc-ru.json @@ -1,7 +1,7 @@ { "source": { "include": ["src"], - "includePattern": ".+\\.js$", + "includePattern": "core/docs/iter8or.ru.jsdoc.js", "excludePattern": "(^|\\/|\\\\)node_modules" }, "opts": { @@ -19,6 +19,11 @@ "id": "github", "link": "https://github.com/TanyaLagodich/Iter8or" }, + { + "title": "NPM", + "id": "npm", + "link": "https://www.npmjs.com/package/iter8or" + }, { "title": "English Version", "id": "en", diff --git a/jsdoc.json b/jsdoc.json index df48773..48c69f5 100644 --- a/jsdoc.json +++ b/jsdoc.json @@ -1,7 +1,7 @@ { "source": { "include": ["src"], - "includePattern": ".+\\.js$", + "includePattern": "/core/docs/iter8or.jsdoc.js", "excludePattern": "(^|\\/|\\\\)node_modules" }, "opts": { @@ -19,6 +19,11 @@ "id": "github", "link": "https://github.com/TanyaLagodich/Iter8or" }, + { + "title": "NPM", + "id": "npm", + "link": "https://www.npmjs.com/package/iter8or" + }, { "title": "Русская версия", "id": "ru", diff --git a/src/core/DigitsIterable.js b/src/core/DigitsIterable.js index 2cc7246..9b7cabe 100644 --- a/src/core/DigitsIterable.js +++ b/src/core/DigitsIterable.js @@ -1,26 +1,31 @@ export class DigitsIterable { constructor(number) { - this.number = number; + if (typeof number === 'bigint' || number > Number.MAX_SAFE_INTEGER) { + this.number = BigInt(number); + } else { + this.number = number; + } } [Symbol.iterator]() { let number = this.number; return { next() { - while (number > 0) { - const digit = number % 10; - number = Math.floor(number / 10); - - return { - value: digit, - done: false, - }; + if (typeof number === 'bigint') { + if (number > 0n) { + const digit = number % 10n; + number = number / 10n; + return { value: Number(digit), done: false }; + } + } else { + if (number > 0) { + const digit = number % 10; + number = Math.floor(number / 10); + return { value: digit, done: false }; + } } - return { - done: true, - value: undefined, - }; + return { done: true, value: undefined }; }, }; } diff --git a/src/core/Iter8or.js b/src/core/Iter8or.js index 49651c2..8727a6b 100644 --- a/src/core/Iter8or.js +++ b/src/core/Iter8or.js @@ -23,22 +23,8 @@ import { toString, } from '../collectors/index.js'; -/** - * @class Iter8or - * @classdesc Iter8or — это универсальный класс, который предоставляет различные методы для работы с итераторами, как синхронными, так и асинхронными. - * Этот класс позволяет выполнять такие операции, как фильтрация, маппинг, объединение, сворачивание и другие, используя различные итераторы. - */ - export default class Iter8or { constructor(iterable, options = {}) { - /** - * Создает экземпляр Iter8or. - * @param {Iterable|AsyncIterable|number|Object} iterable - Итерабельный объект, который будет использоваться в качестве исходного набора данных. - * @param {Object} [options={}] - Объект с дополнительными опциями. - * @param {boolean} [options.digits=false] - Если true, создается итератор по цифрам числа. - * @param {boolean} [options.async=false] - Если true, создается асинхронный итератор. - * @throws {TypeError} Если `iterable` — это null, undefined, boolean или function. - */ if (iterable === null || iterable === undefined) { throw new TypeError('Cannot create an iterable from null or undefined.'); } @@ -54,7 +40,7 @@ export default class Iter8or { this.options = options; if (!iterable[Symbol.iterator] && !iterable[Symbol.asyncIterator]) { - this.createIterable(iterable, options); + this.#createIterable(iterable, options); } else { if (this.options.async && !iterable[Symbol.asyncIterator]) { this.iterable = convertToAsyncIterator(iterable); @@ -64,14 +50,6 @@ export default class Iter8or { } } - /** - * Synchronous iterator method. - * @method - * @memberof Iter8or - * @instance - * @returns {Iterator} The default iterator for the iterable. - * @throws {TypeError} Throws if the iterable is asynchronous. - */ [Symbol.iterator]() { if (this.options.async) { throw new TypeError( @@ -82,11 +60,6 @@ export default class Iter8or { } } - /** - * Asynchronous iterator method. - * @returns {AsyncIterator} The default async iterator for the iterable. - * @throws {TypeError} Throws if the iterable is synchronous. - */ [Symbol.asyncIterator]() { if (!this.options.async) { throw new TypeError( @@ -97,18 +70,14 @@ export default class Iter8or { return this.iterable[Symbol.asyncIterator](); } - /** - * Создает итератор на основе предоставленного типа данных. - * Если передан `number`, создается итератор, который проходит от 0 до указанного числа включительно. - * Если опция `digits` установлена в `true`, создается итератор, который разбивает число на отдельные разряды. - * Если передан объект, создается итератор, который проходит по ключам и значениям объекта. - * - * @private - * @memberof Iter8or - * @param {number|Object} iterable - Данные для преобразования в итератор. - * @throws {TypeError} Выбрасывает исключение, если тип данных не поддерживается. - */ - createIterable(iterable) { + next() { + if (!this.iterator) { + this.iterator = this[Symbol.iterator](); + } + return this.iterator.next(); + } + + #createIterable(iterable) { if (typeof iterable === 'number') { if (this.options.digits) { this.iterable = new DigitsIterable(iterable); @@ -122,34 +91,10 @@ export default class Iter8or { } } - /** - * Применяет функцию ко всем элементам итератора и возвращает новый Iter8or. - * @method map - * @memberof Iter8or - * @instance - * @param {Function} fn - Функция, применяемая к каждому элементу итератора. - * @returns {Iter8or} Новый экземпляр Iter8or с мапированными значениями. - * @example - * const iter = new Iter8or([1, 2, 3]); - * const mapped = iter.map(x => x * 2); - * console.log([...mapped]); // [2, 4, 6] - */ map(fn) { return new Iter8or(createMapIterator(this.iterable, fn), this.options); } - /** - * Filters the iterable using the provided predicate function. - * @method - * @memberof Iter8or - * @instance - * @param {Function} predicate - The filtering function. - * @returns {Iter8or} A new instance with the filtered iterable. - * @example - * const iter = new Iter8or([1, 2, 3, 4]); - * const filtered = iter.filter(x => x % 2 === 0); - * console.log([...filtered]); // [2, 4] - */ filter(predicate) { return new Iter8or( createFilterIterator(this.iterable, predicate), @@ -157,295 +102,83 @@ export default class Iter8or { ); } - /** - * Drops the first `n` elements from the iterable. - * @method - * @memberof Iter8or - * @instance - * @param {number} n - The number of elements to drop. - * @returns {Iter8or} A new instance with the dropped iterable. - * @example - * const iter = new Iter8or([1, 2, 3, 4]); - * const dropped = iter.drop(2); - * console.log([...dropped]); // [3, 4] - */ drop(n) { return new Iter8or(createDropIterator(this.iterable, n), this.options); } - /** - * Maps and flattens the iterable using the provided function. - * @method - * @memberof Iter8or - * @instance - * @param {Function} fn - The function to map and flatten the iterable. - * @returns {Iter8or} A new instance with the flattened iterable. - * @example - * const iter = new Iter8or([1, 2, 3]); - * const flatMapped = iter.flatMap(x => [x, x * 2]); - * console.log([...flatMapped]); // [1, 2, 2, 4, 3, 6] - */ flatMap(fn) { return new Iter8or(createFlatMapIterator(this.iterable, fn), this.options); } - /** - * Flattens the iterable up to the specified depth. - * @method - * @memberof Iter8or - * @instance - * @param {number} depth - The depth to flatten the iterable. - * @returns {Iter8or} A new instance with the flattened iterable. - * @example - * const iter = new Iter8or([1, [2, [3, 4]]]); - * const flattened = iter.flat(2); - * console.log([...flattened]); // [1, 2, 3, 4] - */ flat(depth) { return new Iter8or(createFlatIterator(this.iterable, depth), this.options); } - /** - * Reverses the order of the elements in the iterable. - * @method - * @memberof Iter8or - * @instance - * @returns {Iter8or} A new instance with the reversed iterable. - * @example - * const iter = new Iter8or([1, 2, 3]); - * const reversed = iter.reverse(); - * console.log([...reversed]); // [3, 2, 1] - */ reverse() { return new Iter8or(createReverseIterator(this.iterable), this.options); } - /** - * Takes the first `n` elements from the iterable. - * @method - * @memberof Iter8or - * @instance - * @param {number} n - The number of elements to take. - * @returns {Iter8or} A new instance with the taken iterable. - * @example - * const iter = new Iter8or([1, 2, 3, 4]); - * const taken = iter.take(2); - * console.log([...taken]); // [1, 2] - */ take(n) { return new Iter8or(createTakeIterator(this.iterable, n), this.options); } - /** - * Concatenates the current iterable with other Iter8or instances. - * @method - * @memberof Iter8or - * @instance - * @param {...Iter8or} iterators - The iterators to concatenate. - * @returns {Iter8or} A new instance with the concatenated iterable. - * @throws {Error} Throws if any of the provided iterators are not instances of Iter8or. - * @example - * const iter1 = new Iter8or([1, 2]); - * const iter2 = new Iter8or([3, 4]); - * const concatenated = iter1.concat(iter2); - * console.log([...concatenated]); // [1, 2, 3, 4] - */ concat(...iterators) { if (!iterators.some((iterator) => iterator instanceof Iter8or)) { throw new Error('All concatted iterators must be an Iter8or.'); } return new Iter8or( - createConcatIterator(this.iterable, iterators), + createConcatIterator( + this.iterable, + ...iterators.map((iterator) => iterator.iterable) + ), this.options ); } - /** - * Calculates the average of the elements in the iterable. - * @method - * @memberof Iter8or - * @instance - * @param {Function} [fn] - Optional mapping function to apply before averaging. - * @returns {number} The average value. - * @example - * const iter = new Iter8or([1, 2, 3]); - * const average = iter.avg(); - * console.log(average); // 2 - */ avg(fn) { return avg(this.iterable, fn); } - /** - * Finds the maximum value in the iterable. - * @method - * @memberof Iter8or - * @instance - * @param {Function} [fn] - Optional mapping function to apply before finding the maximum. - * @returns {number} The maximum value. - * @example - * const iter = new Iter8or([1, 2, 3]); - * const maximum = iter.max(); - * console.log(maximum); // 3 - */ max(fn) { return max(this.iterable, fn); } - /** - * Finds the minimum value in the iterable. - * @method - * @memberof Iter8or - * @instance - * @param {Function} [fn] - Optional mapping function to apply before finding the minimum. - * @returns {number} The minimum value. - * @example - * const iter = new Iter8or([1, 2, 3]); - * const minimum = iter.min(); - * console.log(minimum); // 1 - */ min(fn) { return min(this.iterable, fn); } - /** - * Reduces the iterable using the provided reducer function. - * @method - * @memberof Iter8or - * @instance - * @param {Function} reducer - The reducer function. - * @param {*} [initialValue] - The initial value for the reduction. - * @returns {*} The reduced value. - * @example - * const iter = new Iter8or([1, 2, 3]); - * const sum = iter.reduce((acc, x) => acc + x, 0); - * console.log(sum); // 6 - */ reduce(reducer, initialValue) { return reduce(this.iterable, reducer, initialValue); } - /** - * Sums the elements in the iterable. - * @method - * @memberof Iter8or - * @instance - * @param {Function} [fn] - Optional mapping function to apply before summing. - * @returns {number} The sum of the elements. - * @example - * const iter = new Iter8or([1, 2, 3]); - * const totalSum = iter.sum(); - * console.log(totalSum); // 6 - */ sum(fn) { return sum(this.iterable, fn); } - /** - * Группирует элементы итератора по ключам, определенным функцией группировщиком. - * @method groupBy - * @memberof Iter8or - * @instance - * @param {Function} grouper - Функция, которая принимает элемент и возвращает ключ группы. - * @returns {Object} Объект, где ключи — это значения, возвращенные функцией группировщиком, а значения — массивы элементов, соответствующие этим ключам. - * @example - * const iter = new Iter8or(['apple', 'banana', 'cherry']); - * const grouped = iter.groupBy(word => word.length); - * console.log(grouped); // { 5: ['apple'], 6: ['banana', 'cherry'] } - */ groupBy(grouper) { return groupBy(this.iterable, grouper); } - /** - * Разделяет элементы итератора на две группы на основе предикатной функции. - * @method partition - * @memberof Iter8or - * @instance - * @param {Function} predicate - Функция, которая возвращает true или false для каждого элемента. - * @returns {Array} Массив из двух массивов: [массив элементов, для которых предикат вернул true, массив элементов, для которых предикат вернул false]. - * @example - * const iter = new Iter8or([1, 2, 3, 4, 5]); - * const [evens, odds] = iter.partition(x => x % 2 === 0); - * console.log(evens); // [2, 4] - * console.log(odds); // [1, 3, 5] - */ partition(predicate) { return partition(this.iterable, predicate); } - /** - * Собирает элементы итератора в массив. - * @method toArray - * @memberof Iter8or - * @instance - * @returns {Array} Массив, содержащий все элементы итератора. - * @example - * const iter = new Iter8or(new Set([1, 2, 3])); - * const array = iter.toArray(); - * console.log(array); // [1, 2, 3] - */ + toArray() { return toArray(this.iterable); } - /** - * Собирает элементы итератора в Map. - * Элементы должны быть парами [ключ, значение]. - * @method toMap - * @memberof Iter8or - * @instance - * @returns {Map} Map, содержащий все пары [ключ, значение] из итератора. - * @example - * const iter = new Iter8or([['a', 1], ['b', 2], ['c', 3]]); - * const map = iter.toMap(); - * console.log(map); // Map { 'a' => 1, 'b' => 2, 'c' => 3 } - */ toMap() { return toMap(this.iterable); } - /** - * Собирает элементы итератора в объект. - * Элементы должны быть парами [ключ, значение]. - * @method toObject - * @memberof Iter8or - * @instance - * @returns {Object} Объект, содержащий все пары [ключ, значение] из итератора. - * @example - * const iter = new Iter8or([['a', 1], ['b', 2], ['c', 3]]); - * const obj = iter.toObject(); - * console.log(obj); // { a: 1, b: 2, c: 3 } - */ toObject() { return toObject(this.iterable); } - /** - * Собирает элементы итератора в Set. - * @method toSet - * @memberof Iter8or - * @instance - * @returns {Set} Set, содержащий все уникальные элементы итератора. - * @example - * const iter = new Iter8or([1, 2, 2, 3, 4]); - * const set = iter.toSet(); - * console.log(set); // Set { 1, 2, 3, 4 } - */ toSet() { return toSet(this.iterable); } - /** - * Конкатенирует элементы итератора в строку. - * @method toString - * @memberof Iter8or - * @instance - * @returns {string} Строка, содержащая все элементы итератора, конкатенированные вместе. - * @example - * const iter = new Iter8or(['H', 'e', 'l', 'l', 'o']); - * const str = iter.toString(); - * console.log(str); // 'Hello' - */ toString() { return toString(this.iterable); } diff --git a/src/core/RangeIterable.js b/src/core/RangeIterable.js index 198ce99..534b216 100644 --- a/src/core/RangeIterable.js +++ b/src/core/RangeIterable.js @@ -1,20 +1,37 @@ export class RangeIterable { constructor(number) { + const MAX_LIMIT = 1e7; + if ( + typeof number !== 'number' || + number > MAX_LIMIT || + number < -MAX_LIMIT + ) { + throw new RangeError( + 'Number exceeds MAX_LIMIT. You can create a range up to 1e7 or down to -1e7.' + ); + } this.number = number; } [Symbol.iterator]() { - let i = 1; + let i = this.number > 0 ? 1 : -1; const number = this.number; return { next() { - if (i <= number) { + if (number > 0 && i <= number) { return { done: false, value: i++, }; } + + if (number < 0 && i >= number) { + return { + done: false, + value: i--, + }; + } return { done: true, value: undefined, diff --git a/src/core/__tests__/DigitsIterable.spec.js b/src/core/__tests__/DigitsIterable.spec.js new file mode 100644 index 0000000..23bbc12 --- /dev/null +++ b/src/core/__tests__/DigitsIterable.spec.js @@ -0,0 +1,50 @@ +import { DigitsIterable } from '../DigitsIterable.js'; + +describe('DigitsIterable', () => { + it('should correctly iterate over the digits of a positive number', () => { + const digits = new DigitsIterable(12345); + const result = [...digits]; + expect(result).toEqual([5, 4, 3, 2, 1]); + }); + + it('should return an empty array for the number 0', () => { + const digits = new DigitsIterable(0); + const result = [...digits]; + expect(result).toEqual([]); + }); + + it('should correctly iterate over a single digit number', () => { + const digits = new DigitsIterable(7); + const result = [...digits]; + expect(result).toEqual([7]); + }); + + it('should correctly handle large numbers', () => { + const digits = new DigitsIterable(9876543210); + const result = [...digits]; + expect(result).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + }); + + it('should correctly handle the number 1', () => { + const digits = new DigitsIterable(1); + const result = [...digits]; + expect(result).toEqual([1]); + }); + + it('should handle consecutive iterations correctly', () => { + const digits = new DigitsIterable(456); + const firstIteration = [...digits]; + const secondIteration = [...digits]; + + expect(firstIteration).toEqual([6, 5, 4]); + expect(secondIteration).toEqual([6, 5, 4]); + }); + + it('should correctly iterate over the digits of a very large number', () => { + const digits = new DigitsIterable(98765432109876543210n); + const result = [...digits]; + expect(result).toEqual([ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, + ]); + }); +}); diff --git a/src/core/__tests__/Iter8or.spec.js b/src/core/__tests__/Iter8or.spec.js new file mode 100644 index 0000000..f614891 --- /dev/null +++ b/src/core/__tests__/Iter8or.spec.js @@ -0,0 +1,121 @@ +import Iter8or from '../Iter8or.js'; + +describe('Iter8or class', () => { + it('should map values correctly', () => { + const iter = new Iter8or([1, 2, 3]); + const mapped = iter.map((x) => x * 2); + expect([...mapped]).toEqual([2, 4, 6]); + }); + + it('should filter values correctly', () => { + const iter = new Iter8or([1, 2, 3, 4]); + const filtered = iter.filter((x) => x % 2 === 0); + expect([...filtered]).toEqual([2, 4]); + }); + + it('should drop values correctly', () => { + const iter = new Iter8or([1, 2, 3, 4]); + const dropped = iter.drop(2); + expect([...dropped]).toEqual([3, 4]); + }); + + it('should flatMap values correctly', () => { + const iter = new Iter8or([1, 2, 3]); + const flatMapped = iter.flatMap((x) => [x, x * 2]); + expect([...flatMapped]).toEqual([1, 2, 2, 4, 3, 6]); + }); + + it('should flatten values correctly', () => { + const iter = new Iter8or([1, [2, [3, 4]]]); + const flattened = iter.flat(2); + expect([...flattened]).toEqual([1, 2, 3, 4]); + }); + + it('should reverse values correctly', () => { + const iter = new Iter8or([1, 2, 3]); + const reversed = iter.reverse(); + expect([...reversed]).toEqual([3, 2, 1]); + }); + + it('should take values correctly', () => { + const iter = new Iter8or([1, 2, 3, 4]); + const taken = iter.take(2); + expect([...taken]).toEqual([1, 2]); + }); + + it('should concatenate iterators correctly', () => { + const iter1 = new Iter8or([1, 2]); + const iter2 = new Iter8or([3, 4]); + const concatenated = iter1.concat(iter2); + expect([...concatenated]).toEqual([1, 2, 3, 4]); + }); + + it('should calculate the average correctly', () => { + const iter = new Iter8or([1, 2, 3]); + const average = iter.avg(); + expect(average).toBe(2); + }); + + it('should calculate the sum correctly', () => { + const iter = new Iter8or([1, 2, 3]); + const totalSum = iter.sum(); + expect(totalSum).toBe(6); + }); + + it('should group by key correctly', () => { + const iter = new Iter8or(['apple', 'banana', 'cherry']); + const grouped = iter.groupBy((word) => word.length); + expect(grouped).toEqual({ 5: ['apple'], 6: ['banana', 'cherry'] }); + }); + + it('should partition values correctly', () => { + const iter = new Iter8or([1, 2, 3, 4, 5]); + const [evens, odds] = iter.partition((x) => x % 2 === 0); + expect(evens).toEqual([2, 4]); + expect(odds).toEqual([1, 3, 5]); + }); + + it('should collect values into an array correctly', () => { + const iter = new Iter8or(new Set([1, 2, 3])); + const array = iter.toArray(); + expect(array).toEqual([1, 2, 3]); + }); + + it('should collect key-value pairs into a Map correctly', () => { + const iter = new Iter8or([ + ['a', 1], + ['b', 2], + ['c', 3], + ]); + const map = iter.toMap(); + expect(map).toEqual( + new Map([ + ['a', 1], + ['b', 2], + ['c', 3], + ]) + ); + }); + + it('should collect key-value pairs into an object correctly', () => { + const iter = new Iter8or([ + ['a', 1], + ['b', 2], + ['c', 3], + ]); + const obj = iter.toObject(); + expect(obj).toEqual({ a: 1, b: 2, c: 3 }); + }); + + it('should collect values into a Set correctly', () => { + const iter = new Iter8or([1, 2, 2, 3, 4]); + const set = iter.toSet(); + expect(set).toEqual(new Set([1, 2, 3, 4])); + }); + + it('should concatenate values into a string correctly', () => { + const iter = new Iter8or(['H', 'e', 'l', 'l', 'o']); + const str = iter.toString(); + expect(str).toBe('Hello'); + }); +}); diff --git a/src/core/__tests__/ObjectIterable.spec.js b/src/core/__tests__/ObjectIterable.spec.js new file mode 100644 index 0000000..3e5da24 --- /dev/null +++ b/src/core/__tests__/ObjectIterable.spec.js @@ -0,0 +1,93 @@ +import { ObjectIterable } from '../ObjectIterable.js'; + +describe('ObjectIterable', () => { + it('should iterate over object properties synchronously', () => { + const obj = { a: 1, b: 2, c: 3 }; + const iterable = new ObjectIterable(obj); + const result = [...iterable]; + expect(result).toEqual([ + ['a', 1], + ['b', 2], + ['c', 3], + ]); + }); + + it('should handle empty object correctly', () => { + const obj = {}; + const iterable = new ObjectIterable(obj); + const result = [...iterable]; + expect(result).toEqual([]); + }); + + it('should iterate over object properties asynchronously', async () => { + const obj = { + a: 1, + b: async () => 2, + c: new Promise((resolve) => setTimeout(() => resolve(3), 10)), + }; + const iterable = new ObjectIterable(obj); + const result = []; + for await (const [key, value] of iterable) { + result.push([key, value]); + } + expect(result).toEqual([ + ['a', 1], + ['b', 2], + ['c', 3], + ]); + }); + + it('should handle mixed synchronous and asynchronous values', async () => { + const obj = { + a: 1, + b: Promise.resolve(2), + c: () => Promise.resolve(3), + }; + const iterable = new ObjectIterable(obj); + const result = []; + for await (const [key, value] of iterable) { + result.push([key, value]); + } + expect(result).toEqual([ + ['a', 1], + ['b', 2], + ['c', 3], + ]); + }); + + it('should handle functions that return non-promise values', async () => { + const obj = { + a: 1, + b: () => 2, + c: () => 3, + }; + const iterable = new ObjectIterable(obj); + const result = []; + for await (const [key, value] of iterable) { + result.push([key, value]); + } + expect(result).toEqual([ + ['a', 1], + ['b', 2], + ['c', 3], + ]); + }); + + it('should handle async functions that return promises', async () => { + const obj = { + a: 1, + b: async () => Promise.resolve(2), + c: async () => Promise.resolve(3), + }; + const iterable = new ObjectIterable(obj); + const result = []; + for await (const [key, value] of iterable) { + result.push([key, value]); + } + expect(result).toEqual([ + ['a', 1], + ['b', 2], + ['c', 3], + ]); + }); +}); diff --git a/src/core/__tests__/RangeIterable.spec.js b/src/core/__tests__/RangeIterable.spec.js new file mode 100644 index 0000000..a03e242 --- /dev/null +++ b/src/core/__tests__/RangeIterable.spec.js @@ -0,0 +1,53 @@ +import { RangeIterable } from '../RangeIterable.js'; + +describe('RangeIterable', () => { + it('should iterate from 1 to the given positive number', () => { + const range = new RangeIterable(5); + const result = [...range]; + expect(result).toEqual([1, 2, 3, 4, 5]); + }); + + it('should iterate from -1 to the given negative number', () => { + const range = new RangeIterable(-5); + const result = [...range]; + expect(result).toEqual([-1, -2, -3, -4, -5]); + }); + + it('should throw an error if number exceeds the positive limit', () => { + expect(() => new RangeIterable(1e7 + 1)).toThrow(RangeError); + }); + + it('should throw an error if number exceeds the negative limit', () => { + expect(() => new RangeIterable(-1e7 - 1)).toThrow(RangeError); + }); + + it('should correctly handle the maximum positive range', () => { + const range = new RangeIterable(1e7); + const iterator = range[Symbol.iterator](); + + // Проверка первого и последнего элементов, пропуская всё остальное + expect(iterator.next().value).toBe(1); + + let lastValue; + for (let i = 0; i < 1e7 - 1; i++) { + lastValue = iterator.next().value; + } + + expect(lastValue).toBe(1e7); + }); + + it('should correctly handle the maximum negative range', () => { + const range = new RangeIterable(-1e7); + const iterator = range[Symbol.iterator](); + + // Проверка первого и последнего элементов, пропуская всё остальное + expect(iterator.next().value).toBe(-1); + + let lastValue; + for (let i = 0; i < 1e7 - 1; i++) { + lastValue = iterator.next().value; + } + + expect(lastValue).toBe(-1e7); + }); +});