Browse Source

refactor: simplifies spy api

e22m4u 1 week ago
parent
commit
8519028e5b
9 changed files with 137 additions and 835 deletions
  1. 33 259
      README.md
  2. 0 38
      dist/cjs/index.cjs
  3. 26 38
      src/create-spies-group.d.ts
  4. 16 1
      src/create-spies-group.js
  5. 1 1
      src/create-spies-group.spec.js
  6. 27 123
      src/create-spy.d.ts
  7. 8 89
      src/create-spy.js
  8. 8 286
      src/create-spy.spec.js
  9. 18 0
      src/types.d.ts

+ 33 - 259
README.md

@@ -8,7 +8,7 @@
 - [Использование](#использование)
 - [Использование](#использование)
   - [Отслеживание вызова функции](#отслеживание-вызова-функции)
   - [Отслеживание вызова функции](#отслеживание-вызова-функции)
   - [Отслеживание вызова метода](#отслеживание-вызова-метода)
   - [Отслеживание вызова метода](#отслеживание-вызова-метода)
-  - [Управление группой шпионов (SpiesGroup)](#управление-группой-шпионов-spiesgroup)
+  - [Управление группой шпионов](#управление-группой-шпионов)
 - [API](#api)
 - [API](#api)
   - [createSpy(target, [methodNameOrImpl], [customImplForMethod])](#createspytarget-methodnameorimpl-customimplformethod)
   - [createSpy(target, [methodNameOrImpl], [customImplForMethod])](#createspytarget-methodnameorimpl-customimplformethod)
   - [Свойства и методы шпиона](#свойства-и-методы-шпиона)
   - [Свойства и методы шпиона](#свойства-и-методы-шпиона)
@@ -16,11 +16,6 @@
     - [spy.calls](#spycalls)
     - [spy.calls](#spycalls)
     - [spy.isCalled](#spyiscalled)
     - [spy.isCalled](#spyiscalled)
     - [spy.callCount](#spycallcount)
     - [spy.callCount](#spycallcount)
-    - [spy.getCall(n)](#spygetcalln)
-    - [spy.calledWith(...expectedArgs)](#spycalledwithexpectedargs)
-    - [spy.nthCalledWith(n, ...expectedArgs)](#spynthcalledwithn-expectedargs)
-    - [spy.nthCallReturned(n, expectedReturnValue)](#spynthcallreturnedn-expectedreturnvalue)
-    - [spy.nthCallThrew(n, [expectedError])](#spynthcallthrewn-expectederror)
     - [spy.restore()](#spyrestore)
     - [spy.restore()](#spyrestore)
   - [createSpiesGroup()](#createspiesgroup)
   - [createSpiesGroup()](#createspiesgroup)
   - [Методы SpiesGroup](#методы-spiesgroup)
   - [Методы SpiesGroup](#методы-spiesgroup)
@@ -66,35 +61,20 @@ greetSpy('JavaScript');
 console.log(greetSpy.isCalled);  // true
 console.log(greetSpy.isCalled);  // true
 console.log(greetSpy.callCount); // 2
 console.log(greetSpy.callCount); // 2
 
 
-// аргументы вызова
-console.log(greetSpy.getCall(0).args); // ['World']
-console.log(greetSpy.getCall(1).args); // ['JavaScript']
-
-// возвращаемое значение
-console.log(greetSpy.getCall(0).returnValue); // 'Hello, World!'
-console.log(greetSpy.getCall(1).returnValue); // 'Hello, JavaScript!'
-
-// тест аргументов
-console.log(greetSpy.calledWith('World'));      // true
-console.log(greetSpy.calledWith('JavaScript')); // true
-console.log(greetSpy.calledWith('FooBar'));     // false
-
-// тест аргументов определенного вызова
-console.log(greetSpy.nthCalledWith(0, 'World'));      // true
-console.log(greetSpy.nthCalledWith(1, 'JavaScript')); // true
-console.log(greetSpy.nthCalledWith(1, 'FooBar'));     // false
-
-// тест возвращаемого значения
-console.log(greetSpy.nthCallReturned(0, 'Hello, World'));      // true
-console.log(greetSpy.nthCallReturned(1, 'Hello, JavaScript')); // true
-
-try {
-  greetSpy.getCall(5); // Попытка получить несуществующий вызов
-} catch (e) {
-  // Ожидаемая ошибка, например:
-  // "Invalid call index 5. Spy has 2 call(s)."
-  console.error(e.message);
-}
+console.log(greetSpy.calls[0].args[0]); // "World"
+console.log(greetSpy.calls[0].returnValue); // "Hello, World!"
+
+console.log(greetSpy.calls[1].args[0]); // "JavaScript"
+console.log(greetSpy.calls[1].returnValue); // "Hello, JavaScript!"
+
+console.log(greetSpy.calls.length); // 2
+console.log(greetSpy.calls[0]);
+// {
+//   args: ['World'],
+//   thisArg: undefined,
+//   returnValue: 'Hello, World!',
+//   error: undefined
+// }
 ```
 ```
 
 
 ### Отслеживание вызова метода:
 ### Отслеживание вызова метода:
@@ -121,38 +101,29 @@ console.log(calculator.value); // 3
 console.log(addSpy.isCalled);  // true
 console.log(addSpy.isCalled);  // true
 console.log(addSpy.callCount); // 2
 console.log(addSpy.callCount); // 2
 
 
-// аргументы вызова
-console.log(addSpy.getCall(0).args); // [5, 3]
-console.log(addSpy.getCall(1).args); // [2, 1]
-
-// возвращаемое значение
-console.log(addSpy.getCall(0).returnValue); // 8
-console.log(addSpy.getCall(1).returnValue); // 3
-
-// контекст вызова
-console.log(addSpy.getCall(0).thisArg === calculator); // true
-console.log(addSpy.getCall(1).thisArg === calculator); // true
+console.log(addSpy.calls[0].args[0]); // 5
+console.log(addSpy.calls[0].args[1]); // 3
+console.log(addSpy.calls[0].returnValue); // 8
 
 
-// тест аргументов
-console.log(addSpy.calledWith(5, 3));  // true
-console.log(addSpy.calledWith(2, 1));  // true
-console.log(addSpy.calledWith('foo')); // false
+console.log(addSpy.calls[1].args[0]); // 2
+console.log(addSpy.calls[1].args[1]); // 1
+console.log(addSpy.calls[1].returnValue); // 3
 
 
-// тест аргументов определенного вызова
-console.log(addSpy.nthCalledWith(0, 5, 3));  // true
-console.log(addSpy.nthCalledWith(1, 2, 1));  // true
-console.log(addSpy.nthCalledWith(1, 'foo')); // false
-
-// тест возвращаемого значения
-console.log(addSpy.nthCallReturned(0, 8)); // true
-console.log(addSpy.nthCallReturned(1, 3)); // true
+console.log(addSpy.calls.length); // 2
+console.log(addSpy.calls[0]);
+// {
+//   args: [5, 3],
+//   thisArg: calculator, // ссылка на объект calculator
+//   returnValue: 8,
+//   error: undefined
+// }
 
 
 // восстановление оригинального метода
 // восстановление оригинального метода
 addSpy.restore();
 addSpy.restore();
 // calculator.add теперь снова оригинальный метод
 // calculator.add теперь снова оригинальный метод
 ```
 ```
 
 
-### Управление группой шпионов (SpiesGroup)
+### Управление группой шпионов
 
 
 Иногда бывает удобно управлять несколькими шпионами одновременно,
 Иногда бывает удобно управлять несколькими шпионами одновременно,
 например, восстановить их все разом. Для этого используется `SpiesGroup`.
 например, восстановить их все разом. Для этого используется `SpiesGroup`.
@@ -193,7 +164,7 @@ const loggerSpy = group.on(standaloneLogger);
 const data = service.fetchData(1);
 const data = service.fetchData(1);
 service.processItem(data);
 service.processItem(data);
 
 
-// но для одиночной функции, мы должны вызывать
+// но для одиночной функции, требуется вызывать
 // созданного шпиона loggerSpy, а не оригинал
 // созданного шпиона loggerSpy, а не оригинал
 loggerSpy('All done!');
 loggerSpy('All done!');
 
 
@@ -204,7 +175,7 @@ console.log(loggerSpy.callCount);      // 1
 // восстановление всех шпионов в группе:
 // восстановление всех шпионов в группе:
 //   - оригинальные методы service.fetchData
 //   - оригинальные методы service.fetchData
 //     и service.processItem будут восстановлены
 //     и service.processItem будут восстановлены
-//   - история вызовов (callCount, isCalled, getCall и т.д.)
+//   - история вызовов (callCount, isCalled, calls и т.д.)
 //     для fetchDataSpy, processItemSpy и loggerSpy
 //     для fetchDataSpy, processItemSpy и loggerSpy
 //     будет сброшена
 //     будет сброшена
 //   - внутренний список шпионов в группе будет очищен
 //   - внутренний список шпионов в группе будет очищен
@@ -321,204 +292,6 @@ spy();
 console.log(spy.callCount); // 2
 console.log(spy.callCount); // 2
 ```
 ```
 
 
-#### spy.getCall(n)
-
-Аргументы:
-- `n`: Число, индекс вызова (начиная с 0).
-
-Возвращает: Объект `CallInfo` со свойствами:
-
-- `args`: Массив аргументов, с которыми был совершен вызов.
-- `thisArg`: Контекст `this` вызова.
-- `returnValue`: Значение, возвращенное функцией (или `undefined`,
-  если функция бросила ошибку).
-- `error`: Ошибка, выброшенная функцией (или `undefined`, если ошибки
-  не было).
-
-Выбрасывает:
-
-- `RangeError`: Если `n` не является допустимым индексом вызова.
-
-Пример:
-
-```js
-const spy = createSpy((a, b) => a + b);
-spy.call({ id: 1 }, 10, 20); // 0-й вызов
-
-const firstCall = spy.getCall(0);
-console.log(firstCall.args); // [10, 20]
-
-try {
-  spy.getCall(1); // Попытка получить несуществующий вызов
-} catch (e) {
-  // Ожидаемая ошибка, например:
-  // "Invalid call index 1. Spy has 1 call(s)."
-  console.error(e.message);
-}
-```
-
-#### spy.calledWith(...expectedArgs)
-
-Аргументы:
-
-- `...expectedArgs`: Аргументы, с которыми, как ожидается, был вызван
-    шпион.
-
-Возвращает: `boolean`
-- `true`, если шпион был хотя бы раз вызван с точно таким же набором
-  аргументов (сравнение с использованием `Object.is`).
-- `false` в противном случае.
-
-Пример:
-
-```js
-const spy = createSpy();
-spy(1, 'a', true);
-spy(2, 'b');
-
-console.log(spy.calledWith(1, 'a', true)); // true
-console.log(spy.calledWith(1, 'a'));       // false
-console.log(spy.calledWith(2, 'c'));       // false
-```
-
-#### spy.nthCalledWith(n, ...expectedArgs)
-
-Аргументы:
-
-- `n`: Число, индекс вызова (начиная с 0).
-- `...expectedArgs`: Аргументы, с которыми, как ожидается, был
-  совершен n-ый вызов.
-
-Возвращает: `boolean`
-
-- `true`, если n-ый вызов шпиона был совершен с точно таким же набором
-  аргументов.
-- `false` в противном случае.
-
-Выбрасывает:
-
-- `RangeError`: Если `n` не является допустимым индексом вызова
-  (унаследовано от `getCall`).
-
-Пример:
-
-```js
-const spy = createSpy();
-spy('first call');
-spy('second call', 123);
-
-console.log(spy.nthCalledWith(0, 'first call'));       // true
-console.log(spy.nthCalledWith(1, 'second call', 123)); // true
-console.log(spy.nthCalledWith(0, 'another'));          // false
-
-try {
-  spy.nthCalledWith(2, 'anything'); // Несуществующий вызов
-} catch (e) {
-  // Ожидаемая ошибка, например:
-  // "Invalid call index 2. Spy has 2 call(s)."
-  console.error(e.message);
-}
-```
-
-#### spy.nthCallReturned(n, expectedReturnValue)
-
-Аргументы:
-
-- `n`: Число, индекс вызова (начиная с 0).
-- `expectedReturnValue`: Ожидаемое возвращаемое значение для n-го
-  вызова.
-
-Возвращает: `boolean`
-- `true`, если n-ый вызов шпиона вернул `expectedReturnValue` (сравнение
-  с помощью `Object.is`).
-- `false`, если значение не совпало или вызов выбросил ошибку.
-
-Выбрасывает:
-
-- `RangeError`: Если `n` не является допустимым индексом вызова
-  (унаследовано от `getCall`).
-
-Пример:
-
-```js
-const spy = createSpy(val => {
-  if (val === 0) throw new Error('zero');
-  return val * 10;
-});
-spy(5); // 0-й вызов, возвращает 50
-try { spy(0); } catch(e) {} // 1-й вызов, бросает ошибку
-spy(2); // 2-й вызов, возвращает 20
-
-console.log(spy.nthCallReturned(0, 50)); // true
-console.log(spy.nthCallReturned(1, 10)); // false (вызов 1 бросил ошибку)
-console.log(spy.nthCallReturned(2, 20)); // true
-
-try {
-  spy.nthCallReturned(3, 30); // Несуществующий вызов
-} catch (e) {
-  // Ожидаемая ошибка, например:
-  // "Invalid call index 3. Spy has 3 call(s)."
-  console.error(e.message);
-}
-```
-
-#### spy.nthCallThrew(n, [expectedError])
-
-Аргументы:
-
-- `n`: Число, индекс вызова (начиная с 0).
-- `expectedError` (необязательно): Матчер для ошибки. Возможные
-  варианты:
-  - `undefined`: Проверяет, что n-ый вызов просто выбросил любую
-    ошибку.
-  - Строка: Проверяет, что сообщение выброшенной ошибки
-    (`error.message`) совпадает со строкой.
-  - Конструктор ошибки (например, `Error`, `TypeError`): Проверяет,
-    что выброшенная ошибка является экземпляром (`instanceof`) этого
-    конструктора.
-  - Экземпляр ошибки: Проверяет, что имя (`error.name`) и сообщение
-    (`error.message`) выброшенной ошибки совпадают с полями
-    `expectedError`.
-  - Любое другое значение: Проверяется прямое совпадение выброшенной
-    ошибки с `expectedError` через `Object.is`.
-
-Возвращает: `boolean`
-
-- `true`, если n-ый вызов шпиона выбросил ошибку
-  или ошибка соответствует `expectedError`.
-- `false`, если вызов не выбросил ошибку (или выбросил не ту ошибку).
-
-Выбрасывает:
-- `RangeError`: Если `n` не является допустимым индексом вызова
-  (унаследовано от `getCall`).
-
-Пример:
-
-```js
-const mightThrow = (val) => {
-  if (val === 0) throw new TypeError('Zero is not allowed');
-  if (val < 0) throw new Error('Negative value');
-  return val;
-};
-const spy = createSpy(mightThrow);
-
-try { spy(0); } catch (e) {}  // 0-й вызов
-try { spy(-5); } catch (e) {} // 1-й вызов
-spy(10);                      // 2-й вызов
-
-console.log(spy.nthCallThrew(0));                        // true
-console.log(spy.nthCallThrew(0, 'Zero is not allowed')); // true
-console.log(spy.nthCallThrew(2));                        // false (2-й вызов не бросил ошибку)
-
-try {
-  spy.nthCallThrew(3); // Несуществующий вызов
-} catch (e) {
-  // Ожидаемая ошибка, например:
-  // "Invalid call index 3. Spy has 3 call(s)."
-  console.error(e.message);
-}
-```
-
 #### spy.restore()
 #### spy.restore()
 
 
 Описание:
 Описание:
@@ -570,6 +343,7 @@ console.log(fnSpy.callCount); // 0 (история сброшена)
 
 
 ```js
 ```js
 import {createSpiesGroup} from '@e22m4u/js-spy';
 import {createSpiesGroup} from '@e22m4u/js-spy';
+
 const group = createSpiesGroup();
 const group = createSpiesGroup();
 ```
 ```
 
 

+ 0 - 38
dist/cjs/index.cjs

@@ -140,44 +140,6 @@ function createSpy(target, methodNameOrImpl, customImplForMethod) {
     enumerable: true,
     enumerable: true,
     configurable: false
     configurable: false
   });
   });
-  spy.getCall = (n) => {
-    if (typeof n !== "number" || n < 0 || n >= callLog.calls.length) {
-      throw new RangeError(
-        `Invalid call index ${n}. Spy has ${callLog.calls.length} call(s).`
-      );
-    }
-    return callLog.calls[n];
-  };
-  spy.calledWith = (...expectedArgs) => {
-    return callLog.calls.some(
-      (call) => call.args.length === expectedArgs.length && call.args.every((arg, i) => Object.is(arg, expectedArgs[i]))
-    );
-  };
-  spy.nthCalledWith = (n, ...expectedArgs) => {
-    const call = spy.getCall(n);
-    return call.args.length === expectedArgs.length && call.args.every((arg, i) => Object.is(arg, expectedArgs[i]));
-  };
-  spy.nthCallReturned = (n, expectedReturnValue) => {
-    const call = spy.getCall(n);
-    if (call.error) return false;
-    return Object.is(call.returnValue, expectedReturnValue);
-  };
-  spy.nthCallThrew = (n, expectedError) => {
-    const call = spy.getCall(n);
-    if (call.error === void 0) return false;
-    if (expectedError === void 0) return true;
-    if (call.error === expectedError) return true;
-    if (typeof expectedError === "string") {
-      return call.error && typeof call.error.message === "string" && call.error.message === expectedError;
-    }
-    if (typeof expectedError === "function" && call.error instanceof expectedError) {
-      return true;
-    }
-    if (expectedError instanceof Error && call.error instanceof Error) {
-      return call.error.name === expectedError.name && call.error.message === expectedError.message;
-    }
-    return Object.is(call.error, expectedError);
-  };
   spy.restore = () => {
   spy.restore = () => {
     if (isMethodSpy && objToSpyOn) {
     if (isMethodSpy && objToSpyOn) {
       if (originalFn !== void 0) {
       if (originalFn !== void 0) {

+ 26 - 38
src/create-spies-group.d.ts

@@ -1,6 +1,5 @@
 import {Spy} from './create-spy.js';
 import {Spy} from './create-spy.js';
-import {MethodKey} from './create-spy.js';
-import {AnyCallable} from './create-spy.js';
+import {MethodKey, AnyCallable} from './types.js';
 
 
 /**
 /**
  * Представляет группу шпионов, позволяющую
  * Представляет группу шпионов, позволяющую
@@ -8,61 +7,50 @@ import {AnyCallable} from './create-spy.js';
  */
  */
 export interface SpiesGroup {
 export interface SpiesGroup {
   /**
   /**
-   * Внутренний массив, хранящий все шпионы, созданные в этой группе.
-   * Обычно не предназначен для прямого манипулирования.
-   * @readonly
+   * Внутренний массив, хранящий все шпионы,
+   * созданные в этой группе. Не предназначен
+   * для прямого доступа.
    */
    */
-  readonly spies: Spy<any>[]; // массив шпионов любого типа
+  readonly spies: Spy<any>[];
 
 
   /**
   /**
-   * Создает шпиона для отдельной функции и добавляет его в группу.
+   * Создает шпиона для отдельной функции
+   * и добавляет его в группу.
    *
    *
-   * @template TFunc                Тип функции, для которой создается шпион.
-   * @param    targetFn             Функция, для которой создается шпион.
-   * @param    customImplementation Необязательно. Функция для замены поведения
-   *                                оригинальной функции. Должна иметь ту же сигнатуру, что и `targetFn`.
-   * @returns                       Созданная функция-шпион.
+   * @param target
+   * @param customImplementation
    */
    */
   on<TFunc extends AnyCallable>(
   on<TFunc extends AnyCallable>(
-    targetFn: TFunc,
-    customImplementation?: TFunc
+    target: TFunc,
+    customImplementation?: TFunc,
   ): Spy<TFunc>;
   ): Spy<TFunc>;
 
 
   /**
   /**
-   * Создает шпиона для метода объекта, добавляет его в группу
-   * и заменяет оригинальный метод объекта шпионом.
+   * Создает шпиона для метода объекта, добавляет
+   * его в группу и заменяет оригинальный метод
+   * объекта шпионом.
    *
    *
-   * @template TObj                 Тип объекта.
-   * @template K                    Ключ метода, для которого создается шпион.
-   * @param    targetObject         Объект, метод которого отслеживается.
-   * @param    methodName           Имя отслеживаемого метода.
-   * @param    customImplementation Необязательно. Функция для замены поведения
-   *                                оригинального метода. Должна иметь ту же сигнатуру, что и оригинальный
-   *                                метод `TObj[K]`.
-   * @returns                       Созданная функция-шпион для метода.
+   * @param target
+   * @param methodNameOrImpl
+   * @param customImplForMethod
    */
    */
-  on<
-    TObj extends object,
-    K extends MethodKey<TObj>
-  >(
-    targetObject: TObj,
+  on<TObj extends object, K extends MethodKey<TObj>>(
+    target: TObj,
     methodName: K,
     methodName: K,
-    customImplementation?: TObj[K]
+    customImplForMethod?: TObj[K],
   ): Spy<Extract<TObj[K], AnyCallable>>;
   ): Spy<Extract<TObj[K], AnyCallable>>;
 
 
   /**
   /**
-   * Восстановление всех оригинальных методов объектов, для которых
-   * были созданы шпионы в этой группе, и сброс истории вызовов
-   * для всех шпионов в группе. Очищает внутренний список шпионов.
-   *
-   * @returns `this` (экземпляр `SpiesGroup`) для возможной цепочки вызовов.
+   * Восстановление всех оригинальных методов объектов,
+   * для которых были созданы шпионы в этой группе,
+   * и сброс истории вызовов для всех шпионов в группе.
+   * Очищает внутренний список шпионов.
    */
    */
   restore(): this;
   restore(): this;
 }
 }
 
 
 /**
 /**
- * Фабричная функция для создания нового экземпляра `SpiesGroup`.
- *
- * @returns Новый экземпляр `SpiesGroup`.
+ * Фабричная функция для создания
+ * нового экземпляра `SpiesGroup`.
  */
  */
 export function createSpiesGroup(): SpiesGroup;
 export function createSpiesGroup(): SpiesGroup;

+ 16 - 1
src/create-spies-group.js

@@ -1,7 +1,8 @@
 import {createSpy} from './create-spy.js';
 import {createSpy} from './create-spy.js';
 
 
 /**
 /**
- * Группа позволяет создавать шпионов и управлять ими как одним.
+ * Группа позволяет создавать шпионов
+ * и управлять ими как одним.
  *
  *
  * @constructor
  * @constructor
  */
  */
@@ -9,6 +10,14 @@ export function SpiesGroup() {
   this.spies = [];
   this.spies = [];
 }
 }
 
 
+/**
+ * Создает шпиона для отдельной функции
+ * или метода объекта и добавляет его в группу.
+ *
+ * @param target
+ * @param methodNameOrImpl
+ * @param customImplForMethod
+ */
 SpiesGroup.prototype.on = function (
 SpiesGroup.prototype.on = function (
   target,
   target,
   methodNameOrImpl,
   methodNameOrImpl,
@@ -19,6 +28,12 @@ SpiesGroup.prototype.on = function (
   return spy;
   return spy;
 };
 };
 
 
+/**
+ * Восстановление всех оригинальных методов объектов,
+ * для которых были созданы шпионы в этой группе,
+ * и сброс истории вызовов для всех шпионов в группе.
+ * Очищает внутренний список шпионов.
+ */
 SpiesGroup.prototype.restore = function () {
 SpiesGroup.prototype.restore = function () {
   this.spies.forEach(spy => spy.restore());
   this.spies.forEach(spy => spy.restore());
   this.spies = [];
   this.spies = [];

+ 1 - 1
src/create-spies-group.spec.js

@@ -34,7 +34,7 @@ describe('SpiesGroup', function () {
         const fnSpyWithImpl = group.on(targetFn, customImpl);
         const fnSpyWithImpl = group.on(targetFn, customImpl);
         // вызов для проверки кастомной реализации
         // вызов для проверки кастомной реализации
         fnSpyWithImpl();
         fnSpyWithImpl();
-        expect(fnSpyWithImpl.getCall(0).returnValue).to.equal(customImpl());
+        expect(fnSpyWithImpl.calls[0].returnValue).to.equal(customImpl());
         // шпион для метода объекта
         // шпион для метода объекта
         const obj = {method: () => 'original method'};
         const obj = {method: () => 'original method'};
         const methodSpy = group.on(obj, 'method');
         const methodSpy = group.on(obj, 'method');

+ 27 - 123
src/create-spy.d.ts

@@ -1,26 +1,11 @@
-/**
- * Тип любой функции.
- */
-type AnyCallable = (...args: any[]) => any;
-
-/**
- * Конструктор любой ошибки.
- */
-type AnyErrorCtor = (new (...args: any[]) => Error);
-
-/**
- * Ключ любого метода в объекте.
- * Должен быть ключом `TObj`, значение которого является функцией.
- */
-type MethodKey<TObj extends object> = {
-  [P in keyof TObj]: TObj[P] extends AnyCallable ? P : never
-}[keyof TObj];
+import {AnyCallable, MethodKey} from './types.js';
 
 
 /**
 /**
- * Информация о единичном вызове отслеживаемой функции.
+ * Информация о единичном вызове
+ * отслеживаемой функции.
  *
  *
- * @template Args Кортеж, представляющий типы аргументов вызова.
- * @template Return Тип возвращаемого значения отслеживаемой функции.
+ * @template Args
+ * @template Return
  */
  */
 export interface CallInfo<Args extends any[] = any[], Return = any> {
 export interface CallInfo<Args extends any[] = any[], Return = any> {
   /**
   /**
@@ -29,19 +14,19 @@ export interface CallInfo<Args extends any[] = any[], Return = any> {
   readonly args: Args;
   readonly args: Args;
 
 
   /**
   /**
-   * Контекст `this`, с которым был вызван шпион.
+   * Контекст this, с которым был вызван шпион.
    */
    */
   readonly thisArg: any;
   readonly thisArg: any;
 
 
   /**
   /**
    * Значение, возвращенное шпионом.
    * Значение, возвращенное шпионом.
-   * (`undefined`, если шпион выбросил ошибку)
+   * (undefined, если шпион выбросил ошибку)
    */
    */
   readonly returnValue: Return | undefined;
   readonly returnValue: Return | undefined;
 
 
   /**
   /**
    * Ошибка, выброшенная шпионом.
    * Ошибка, выброшенная шпионом.
-   * (`undefined`, если шпион не выбросил ошибку)
+   * (undefined, если шпион не выбросил ошибку)
    */
    */
   readonly error: unknown | undefined;
   readonly error: unknown | undefined;
 }
 }
@@ -51,7 +36,7 @@ export interface CallInfo<Args extends any[] = any[], Return = any> {
  * Это вызываемая функция, которая также имеет свойства
  * Это вызываемая функция, которая также имеет свойства
  * и методы для инспекции вызовов.
  * и методы для инспекции вызовов.
  *
  *
- * @template TFunc Тип отслеживаемой функции.
+ * @template TFunc
  */
  */
 export interface Spy<TFunc extends AnyCallable = AnyCallable> {
 export interface Spy<TFunc extends AnyCallable = AnyCallable> {
   /**
   /**
@@ -61,95 +46,30 @@ export interface Spy<TFunc extends AnyCallable = AnyCallable> {
 
 
   /**
   /**
    * Вызовы шпиона.
    * Вызовы шпиона.
-   * 
+   *
    * @readonly
    * @readonly
    */
    */
   readonly calls: CallInfo[];
   readonly calls: CallInfo[];
 
 
   /**
   /**
    * Количество вызовов шпиона.
    * Количество вызовов шпиона.
-   * 
+   *
    * @readonly
    * @readonly
    */
    */
   readonly callCount: number;
   readonly callCount: number;
 
 
   /**
   /**
-   * Булево значение, указывающее, был ли шпион вызван хотя бы один раз.
+   * Булево значение, указывающее,
+   * был ли шпион вызван хотя бы один раз.
    *
    *
    * @readonly
    * @readonly
    */
    */
   readonly isCalled: boolean;
   readonly isCalled: boolean;
 
 
   /**
   /**
-   * Получает детали n-го вызова шпиона.
-   * Если вызов с указанным индексом `n` не существует, выбрасывает `RangeError`.
-   *
-   * @param   n           Индекс вызова (начиная с нуля).
-   * @returns             Объект `CallInfo` для n-го вызова.
-   * @throws `RangeError` если индекс `n` невалиден.
-   */
-  getCall(n: number): CallInfo<Parameters<TFunc>, ReturnType<TFunc>>;
-
-  /**
-   * Проверяет, был ли шпион когда-либо вызван с предоставленными аргументами.
-   * Использует `Object.is` для сравнения аргументов.
-   *
-   * @param expectedArgs Ожидаемые аргументы для проверки.
-   * @returns            `true`, если шпион был вызван с совпадающими
-   *                     аргументами, иначе `false`.
-   */
-  calledWith(...expectedArgs: Parameters<TFunc>): boolean;
-
-  /**
-   * Проверяет, был ли n-ый вызов шпиона совершен с предоставленными аргументами.
-   * Использует `Object.is` для сравнения аргументов.
-   * Если вызов с указанным индексом `n` не существует, выбрасывает `RangeError`.
-   *
-   * @param   n            Индекс вызова (начиная с нуля).
-   * @param   expectedArgs Ожидаемые аргументы для проверки.
-   * @returns              `true`, если n-ый вызов имел совпадающие
-   *                       аргументы, иначе `false`.
-   * @throws  `RangeError` если индекс `n` невалиден (унаследовано от `getCall`).
-   */
-  nthCalledWith(n: number, ...expectedArgs: Parameters<TFunc>): boolean;
-
-  /**
-   * Проверяет, вернул ли n-ый вызов шпиона ожидаемое значение.
-   * Использует `Object.is` для сравнения значений.
-   * Если вызов с указанным индексом `n` не существует, выбрасывает `RangeError`.
-   *
-   * @param   n                   Индекс вызова (начиная с нуля).
-   * @param   expectedReturnValue Ожидаемое возвращаемое значение.
-   * @returns                     `true`, если n-ый вызов вернул ожидаемое значение,
-   *                              иначе `false` (включая случаи, когда он выбросил ошибку).
-   * @throws  `RangeError`        если индекс `n` невалиден (унаследовано от `getCall`).
-   */
-  nthCallReturned(n: number, expectedReturnValue: ReturnType<TFunc>): boolean;
-
-  /**
-   * Проверяет, выбросил ли n-ый вызов шпиона ошибку.
-   * Если вызов с указанным индексом `n` не существует, выбрасывает `RangeError`.
-   *
-   * @param n             Индекс вызова (начиная с нуля).
-   * @param expectedError Необязательно.
-   *                      Если предоставлено, проверяет соответствие выброшенной ошибки:
-   *                        - `string`: совпадение по сообщению ошибки.
-   *                        - Конструктор `Error`: совпадение через `instanceof`.
-   *                        - Экземпляр `Error`: совпадение по имени и сообщению ошибки.
-   *                        - Прямое сравнение объектов с использованием `Object.is`.
-   * @returns             `true`, если n-ый вызов выбросил совпадающую ошибку
-   *                      (или любую ошибку, если матчер не предоставлен),
-   *                      иначе `false` (если вызов не бросил ошибку).
-   * @throws `RangeError` если индекс `n` невалиден (унаследовано от `getCall`).
-   */
-  nthCallThrew(
-    n: number,
-    expectedError?: string | AnyErrorCtor | Error
-  ): boolean;
-
-  /**
-   * Восстанавливает оригинальный метод, если шпион был создан
-   * для метода объекта. Ничего не делает, если шпион был создан
+   * Восстанавливает оригинальный метод,
+   * если шпион был создан для метода объекта.
+   * Ничего не делает, если шпион был создан
    * для отдельной функции.
    * для отдельной функции.
    */
    */
   restore(): void;
   restore(): void;
@@ -157,47 +77,31 @@ export interface Spy<TFunc extends AnyCallable = AnyCallable> {
 
 
 /**
 /**
  * Создает шпиона.
  * Создает шпиона.
- *
- * @template TFunc Тип функции-заглушки.
- * @returns        Функция-шпион.
  */
  */
 export function createSpy(): Spy<(...args: any[]) => void>;
 export function createSpy(): Spy<(...args: any[]) => void>;
 
 
 /**
 /**
  * Создает шпиона для отдельной функции.
  * Создает шпиона для отдельной функции.
  *
  *
- * @template TFunc                Тип функции, для которой создается шпион.
- * @param    targetFn             Функция, для которой создается шпион.
- * @param    customImplementation Необязательно. Функция для замены поведения
- *                                оригинальной функции. Должна иметь ту же
- *                                сигнатуру, что и `targetFn`.
- * @returns                       Функция-шпион.
+ * @param targetFn
+ * @param customImpl
  */
  */
 export function createSpy<TFunc extends AnyCallable>(
 export function createSpy<TFunc extends AnyCallable>(
   targetFn: TFunc,
   targetFn: TFunc,
-  customImplementation?: TFunc
+  customImpl?: TFunc,
 ): Spy<TFunc>;
 ): Spy<TFunc>;
 
 
 /**
 /**
- * Создание шпиона для метода объекта. Оригинальный метод объекта будет заменен
- * шпионом. Используйте `spy.restore()` для восстановления оригинального метода.
+ * Создание шпиона для метода объекта. Оригинальный метод
+ * объекта будет заменен шпионом. Используйте `spy.restore()`
+ * для восстановления оригинального метода.
  *
  *
- * @template TObj Тип объекта.
- * @template K                    Ключ метода, для которого создается шпион.
- *                                Должен быть ключом `TObj`, значение которого
- *                                является функцией.
- * @param    targetObject         Объект, метод которого отслеживается.
- * @param    methodName           Имя отслеживаемого метода.
- * @param    customImplementation Необязательно. Функция для замены поведения
- *                                оригинального метода. Должна иметь ту же сигнатуру,
- *                                что и оригинальный метод `TObj[K]`.
- * @returns                       Функция-шпион для метода.
+ * @param targetObject
+ * @param methodName
+ * @param customImpl
  */
  */
-export function createSpy<
-  TObj extends object,
-  K extends MethodKey<TObj>
->(
+export function createSpy<TObj extends object, K extends MethodKey<TObj>>(
   targetObject: TObj,
   targetObject: TObj,
   methodName: K,
   methodName: K,
-  customImplementation?: TObj[K]
+  customImpl?: TObj[K],
 ): Spy<Extract<TObj[K], AnyCallable>>;
 ): Spy<Extract<TObj[K], AnyCallable>>;

+ 8 - 89
src/create-spy.js

@@ -1,7 +1,10 @@
 /**
 /**
  * Вспомогательная функция для разбора аргументов createSpy.
  * Вспомогательная функция для разбора аргументов createSpy.
  *
  *
- * @private
+ * @param {Function|object} target
+ * @param {Function|string|undefined} methodNameOrImpl
+ * @param {Function|undefined} customImplForMethod
+ * @returns {object}
  */
  */
 function _parseSpyArgs(
 function _parseSpyArgs(
   target,
   target,
@@ -140,11 +143,10 @@ function _parseSpyArgs(
  * Шпионить за методом объекта:
  * Шпионить за методом объекта:
  * createSpy(targetObject, methodName, [customImplementation])
  * createSpy(targetObject, methodName, [customImplementation])
  *
  *
- * @param target - Функция для шпионажа или объект, на методе которого ставится шпион.
- * @param methodNameOrImpl - Имя метода (строка) если target - объект,
- *                           или кастомная реализация (функция) если target - функция.
- * @param customImplForMethod - Кастомная реализация (функция) если target - объект и указан methodName.
- * @returns {(function(...[*]): (*|undefined))|*} Шпион-функция.
+ * @param {Function|object} target
+ * @param {Function|string|undefined} methodNameOrImpl
+ * @param {Function|undefined} customImplForMethod
+ * @returns {Function}
  */
  */
 export function createSpy(target, methodNameOrImpl, customImplForMethod) {
 export function createSpy(target, methodNameOrImpl, customImplForMethod) {
   // если аргументы не передавались,
   // если аргументы не передавались,
@@ -231,89 +233,6 @@ export function createSpy(target, methodNameOrImpl, customImplForMethod) {
     enumerable: true,
     enumerable: true,
     configurable: false,
     configurable: false,
   });
   });
-  // определение метода `getCall` для получения
-  // информации о конкретном вызове по его индексу
-  spy.getCall = n => {
-    // проверка корректности индекса вызова,
-    // выбрасывание ошибки при выходе за границы
-    if (typeof n !== 'number' || n < 0 || n >= callLog.calls.length) {
-      throw new RangeError(
-        `Invalid call index ${n}. Spy has ${callLog.calls.length} call(s).`,
-      );
-    }
-    return callLog.calls[n];
-  };
-  // определение метода `calledWith` для проверки,
-  // был ли шпион вызван с определенным набором аргументов
-  spy.calledWith = (...expectedArgs) => {
-    return callLog.calls.some(
-      call =>
-        call.args.length === expectedArgs.length &&
-        call.args.every((arg, i) => Object.is(arg, expectedArgs[i])),
-    );
-  };
-  // определение метода `nthCalledWith` для проверки
-  // аргументов n-го вызова шпиона
-  spy.nthCalledWith = (n, ...expectedArgs) => {
-    // getCall(n) выбросит ошибку, если индекс n невалиден
-    const call = spy.getCall(n);
-    return (
-      call.args.length === expectedArgs.length &&
-      call.args.every((arg, i) => Object.is(arg, expectedArgs[i]))
-    );
-  };
-  // определение метода `nthCallReturned` для проверки
-  // значения, возвращенного n-ым вызовом шпиона
-  spy.nthCallReturned = (n, expectedReturnValue) => {
-    // getCall(n) выбросит ошибку, если индекс n невалиден
-    const call = spy.getCall(n);
-    // возврат false, если вызов завершился ошибкой
-    if (call.error) return false;
-    return Object.is(call.returnValue, expectedReturnValue);
-  };
-  // определение метода `nthCallThrew` для проверки,
-  // выбросил ли n-ый вызов шпиона ошибку
-  spy.nthCallThrew = (n, expectedError) => {
-    // getCall(n) выбросит ошибку, если индекс n невалиден
-    const call = spy.getCall(n);
-    // возврат false, если вызов не выбросил ошибку
-    if (call.error === undefined) return false;
-    // если тип ожидаемой ошибки не указан,
-    // любая ошибка считается совпадением
-    if (expectedError === undefined) return true;
-    // проверка строгого равенства
-    // ожидаемой ошибки с выброшенной
-    if (call.error === expectedError) return true;
-    // проверка совпадения ошибки по сообщению,
-    // если ожидаемая ошибка - строка
-    if (typeof expectedError === 'string') {
-      // убедимся, что call.error существует и имеет свойство message
-      return (
-        call.error &&
-        typeof call.error.message === 'string' &&
-        call.error.message === expectedError
-      );
-    }
-    // проверка совпадения ошибки по типу (конструктору),
-    // если ожидаемая ошибка - функция-конструктор
-    if (
-      typeof expectedError === 'function' &&
-      call.error instanceof expectedError
-    ) {
-      return true;
-    }
-    // проверка совпадения ошибки по имени и сообщению,
-    // если ожидаемая ошибка - экземпляр Error
-    if (expectedError instanceof Error && call.error instanceof Error) {
-      return (
-        call.error.name === expectedError.name &&
-        call.error.message === expectedError.message
-      );
-    }
-    // прямое сравнение объектов ошибок
-    // как крайний случай
-    return Object.is(call.error, expectedError);
-  };
   // определение метода `restore` для восстановления
   // определение метода `restore` для восстановления
   // оригинального метода объекта и сброса истории вызовов
   // оригинального метода объекта и сброса истории вызовов
   spy.restore = () => {
   spy.restore = () => {

+ 8 - 286
src/create-spy.spec.js

@@ -112,9 +112,9 @@ describe('createSpy', function () {
       expect(spy.callCount).to.equal(2);
       expect(spy.callCount).to.equal(2);
 
 
       // проверка аргументов первого вызова
       // проверка аргументов первого вызова
-      expect(spy.getCall(0).args).to.deep.equal([1, 2]);
+      expect(spy.calls[0].args).to.deep.equal([1, 2]);
       // проверка аргументов второго вызова
       // проверка аргументов второго вызова
-      expect(spy.getCall(1).args).to.deep.equal([3, 4]);
+      expect(spy.calls[1].args).to.deep.equal([3, 4]);
     });
     });
 
 
     it('should call the original function and return its value by default', function () {
     it('should call the original function and return its value by default', function () {
@@ -163,7 +163,7 @@ describe('createSpy', function () {
       expect(result).to.equal(10);
       expect(result).to.equal(10);
       // проверка сохраненного контекста
       // проверка сохраненного контекста
       // в информации о вызове
       // в информации о вызове
-      expect(spy.getCall(0).thisArg).to.equal(contextObj);
+      expect(spy.calls[0].thisArg).to.equal(contextObj);
     });
     });
 
 
     it('should preserve `this` context for the custom implementation', function () {
     it('should preserve `this` context for the custom implementation', function () {
@@ -182,7 +182,7 @@ describe('createSpy', function () {
       // пользовательской реализации
       // пользовательской реализации
       expect(result).to.equal(20);
       expect(result).to.equal(20);
       // проверка сохраненного контекста
       // проверка сохраненного контекста
-      expect(spy.getCall(0).thisArg).to.equal(contextObj);
+      expect(spy.calls[0].thisArg).to.equal(contextObj);
     });
     });
 
 
     it('restore() on a function spy should reset its history and not throw', function () {
     it('restore() on a function spy should reset its history and not throw', function () {
@@ -192,16 +192,12 @@ describe('createSpy', function () {
       fnSpy('call standalone');
       fnSpy('call standalone');
       expect(fnSpy.isCalled).to.be.true;
       expect(fnSpy.isCalled).to.be.true;
       expect(fnSpy.callCount).to.equal(1);
       expect(fnSpy.callCount).to.equal(1);
-      expect(fnSpy.getCall(0).args).to.deep.equal(['call standalone']);
+      expect(fnSpy.calls[0].args).to.deep.equal(['call standalone']);
       // проверка, что вызов restore не вызывает ошибок
       // проверка, что вызов restore не вызывает ошибок
       expect(() => fnSpy.restore()).to.not.throw();
       expect(() => fnSpy.restore()).to.not.throw();
       // проверки сброса истории
       // проверки сброса истории
       expect(fnSpy.callCount).to.equal(0);
       expect(fnSpy.callCount).to.equal(0);
       expect(fnSpy.isCalled).to.be.false;
       expect(fnSpy.isCalled).to.be.false;
-      expect(() => fnSpy.getCall(0)).to.throw(
-        RangeError,
-        'Invalid call index 0. Spy has 0 call(s).',
-      );
     });
     });
   });
   });
 
 
@@ -247,9 +243,9 @@ describe('createSpy', function () {
       // проверка счетчика вызовов
       // проверка счетчика вызовов
       expect(spy.callCount).to.equal(1);
       expect(spy.callCount).to.equal(1);
       // проверка сохраненного контекста
       // проверка сохраненного контекста
-      expect(spy.getCall(0).thisArg).to.equal(obj);
+      expect(spy.calls[0].thisArg).to.equal(obj);
       // проверка сохраненных аргументов
       // проверка сохраненных аргументов
-      expect(spy.getCall(0).args).to.deep.equal(['arg1']);
+      expect(spy.calls[0].args).to.deep.equal(['arg1']);
     });
     });
 
 
     it('should use custom implementation with object context if provided', function () {
     it('should use custom implementation with object context if provided', function () {
@@ -268,7 +264,7 @@ describe('createSpy', function () {
       // проверка счетчика вызовов
       // проверка счетчика вызовов
       expect(spy.callCount).to.equal(1);
       expect(spy.callCount).to.equal(1);
       // проверка сохраненного контекста
       // проверка сохраненного контекста
-      expect(spy.getCall(0).thisArg).to.equal(obj);
+      expect(spy.calls[0].thisArg).to.equal(obj);
     });
     });
 
 
     it('restore() should put the original method back and reset spy history', function () {
     it('restore() should put the original method back and reset spy history', function () {
@@ -291,10 +287,6 @@ describe('createSpy', function () {
       // проверки сброса истории
       // проверки сброса истории
       expect(spy.callCount).to.equal(0);
       expect(spy.callCount).to.equal(0);
       expect(spy.isCalled).to.be.false;
       expect(spy.isCalled).to.be.false;
-      expect(() => spy.getCall(0)).to.throw(
-        RangeError,
-        'Invalid call index 0. Spy has 0 call(s).',
-      );
     });
     });
 
 
     // Этот тест стал частью теста для standalone функции, но если хочешь оставить его здесь для ясности
     // Этот тест стал частью теста для standalone функции, но если хочешь оставить его здесь для ясности
@@ -368,275 +360,5 @@ describe('createSpy', function () {
         expect(spy.callCount).to.equal(2);
         expect(spy.callCount).to.equal(2);
       });
       });
     });
     });
-
-    describe('.getCall(n)', function () {
-      it('should throw RangeError for out-of-bounds index', function () {
-        // проверка для отрицательного индекса
-        expect(() => spy.getCall(-1)).to.throw(
-          RangeError,
-          /Invalid call index -1/,
-        );
-        // проверка для индекса, равного количеству вызовов (когда вызовов нет)
-        expect(() => spy.getCall(0)).to.throw(
-          RangeError,
-          /Invalid call index 0. Spy has 0 call\(s\)\./,
-        );
-        spy(1, 1);
-        // проверка для индекса, равного количеству вызовов (когда есть один вызов)
-        expect(() => spy.getCall(1)).to.throw(
-          RangeError,
-          /Invalid call index 1. Spy has 1 call\(s\)\./,
-        );
-        // проверка для индекса, большего количества вызовов
-        expect(() => spy.getCall(10)).to.throw(
-          RangeError,
-          /Invalid call index 10. Spy has 1 call\(s\)\./,
-        );
-      });
-
-      it('should throw RangeError if index is not a number', function () {
-        // проверка для нечислового индекса
-        expect(() => spy.getCall('a')).to.throw(
-          RangeError,
-          /Invalid call index a/,
-        );
-        expect(() => spy.getCall(null)).to.throw(
-          RangeError,
-          /Invalid call index null/,
-        );
-        expect(() => spy.getCall(undefined)).to.throw(
-          RangeError,
-          /Invalid call index undefined/,
-        );
-      });
-
-      it('should return call details for a valid index', function () {
-        // вызов шпиона с определенными аргументами
-        // и контекстом
-        const context = {id: 1};
-        spy.call(context, 10, 20);
-        // получение информации о первом вызове (индекс 0)
-        const callInfo = spy.getCall(0);
-        // проверка наличия информации о вызове
-        expect(callInfo).to.exist;
-        // проверка аргументов вызова
-        expect(callInfo.args).to.deep.equal([10, 20]);
-        // проверка контекста вызова
-        expect(callInfo.thisArg).to.equal(context);
-        // проверка возвращенного значения
-        expect(callInfo.returnValue).to.equal(30);
-        // проверка отсутствия ошибки
-        expect(callInfo.error).to.be.undefined;
-      });
-
-      it('should record error if thrown', function () {
-        // попытка вызова, который приведет к ошибке
-        try {
-          spy(0, 1);
-        } catch (e) {
-          // ошибка ожидаема
-        }
-        // получение информации о вызове,
-        // завершившемся ошибкой
-        const callInfo = spy.getCall(0);
-        // проверка наличия ошибки в информации о вызове
-        expect(callInfo.error).to.be.instanceOf(Error);
-        // проверка сообщения ошибки
-        expect(callInfo.error.message).to.equal('zero error');
-        // проверка отсутствия возвращенного значения
-        expect(callInfo.returnValue).to.be.undefined;
-      });
-    });
-
-    describe('.calledWith(...args)', function () {
-      it('should return true if called with matching arguments (Object.is comparison)', function () {
-        // вызов шпиона с разными наборами аргументов
-        spy(1, 2);
-        spy('a', 'b');
-        const objArg = {};
-        spy(objArg, null, undefined);
-        // проверка совпадения аргументов
-        expect(spy.calledWith(1, 2)).to.be.true;
-        expect(spy.calledWith('a', 'b')).to.be.true;
-        expect(spy.calledWith(objArg, null, undefined)).to.be.true;
-      });
-
-      it('should return false if not called with matching arguments', function () {
-        // вызов шпиона
-        spy(1, 2);
-        // проверка с несовпадающими аргументами
-        expect(spy.calledWith(1, 3)).to.be.false;
-        expect(spy.calledWith(1)).to.be.false;
-        expect(spy.calledWith()).to.be.false;
-        expect(spy.calledWith(1, 2, 3)).to.be.false;
-      });
-
-      it('should return false if never called', function () {
-        // проверка для шпиона,
-        // который не был вызван
-        expect(spy.calledWith(1, 2)).to.be.false;
-      });
-    });
-
-    describe('.nthCalledWith(n, ...args)', function () {
-      it('should return true if nth call had matching arguments', function () {
-        // серия вызовов шпиона
-        spy(1, 2); // 0-й вызов
-        spy('x', 'y'); // 1-й вызов
-        // проверка аргументов конкретных вызовов
-        expect(spy.nthCalledWith(0, 1, 2)).to.be.true;
-        expect(spy.nthCalledWith(1, 'x', 'y')).to.be.true;
-      });
-
-      it('should return false if nth call had different arguments', function () {
-        // вызов шпиона
-        spy(1, 2);
-        // проверки для несовпадающих аргументов
-        expect(spy.nthCalledWith(0, 1, 3)).to.be.false;
-        expect(spy.nthCalledWith(0)).to.be.false;
-      });
-
-      it('should throw RangeError if call index is out of bounds', function () {
-        spy(1, 2);
-        // проверка для несуществующего вызова
-        expect(() => spy.nthCalledWith(1, 1, 2)).to.throw(
-          RangeError,
-          /Invalid call index 1/,
-        );
-        expect(() => spy.nthCalledWith(-1, 1, 2)).to.throw(
-          RangeError,
-          /Invalid call index -1/,
-        );
-      });
-    });
-
-    describe('.nthCallReturned(n, returnValue)', function () {
-      it('should return true if nth call returned the expected value (Object.is comparison)', function () {
-        // серия вызовов
-        spy(1, 2); // возвращает 3
-        spy(5, 5); // возвращает 10
-        const objRet = {};
-        const fnWithObjRet = () => objRet;
-        const spy2 = createSpy(fnWithObjRet);
-        spy2();
-
-        // проверка возвращаемых значений
-        expect(spy.nthCallReturned(0, 3)).to.be.true;
-        expect(spy.nthCallReturned(1, 10)).to.be.true;
-        expect(spy2.nthCallReturned(0, objRet)).to.be.true;
-      });
-
-      it('should return false if nth call returned different value or threw', function () {
-        // вызов, который вернет значение
-        spy(1, 2); // возвращает 3 (0-й вызов)
-        // вызов, который вызовет ошибку
-        try {
-          spy(0, 1); // 1-й вызов
-        } catch (e) {
-          // бросает ошибку
-        }
-        // проверки для различных сценариев
-        expect(spy.nthCallReturned(0, 4)).to.be.false;
-        expect(spy.nthCallReturned(1, undefined)).to.be.false;
-      });
-
-      it('should throw RangeError if call index is out of bounds', function () {
-        spy(1, 2);
-        // проверка для несуществующего вызова
-        expect(() => spy.nthCallReturned(1, 3)).to.throw(
-          RangeError,
-          /Invalid call index 1/,
-        );
-      });
-    });
-
-    describe('.nthCallThrew(n, errorMatcher)', function () {
-      it('should return true if nth call threw any error (no matcher)', function () {
-        // вызов, приводящий к ошибке
-        try {
-          spy(0, 1);
-        } catch (e) {
-          // бросает ошибку
-        }
-        // проверка факта ошибки
-        expect(spy.nthCallThrew(0)).to.be.true;
-      });
-
-      it('should return false if nth call did not throw', function () {
-        // успешный вызов
-        spy(1, 2);
-        // проверки
-        expect(spy.nthCallThrew(0)).to.be.false;
-      });
-
-      it('should throw RangeError if call index is out of bounds', function () {
-        spy(1, 2);
-        // проверка для несуществующего вызова
-        expect(() => spy.nthCallThrew(1)).to.throw(
-          RangeError,
-          /Invalid call index 1/,
-        );
-      });
-
-      it('should match error by message (string)', function () {
-        // вызов, приводящий к ошибке
-        try {
-          spy(0, 1);
-        } catch (e) {
-          // бросает ошибку
-        }
-        // проверка по сообщению ошибки
-        expect(spy.nthCallThrew(0, 'zero error')).to.be.true;
-        expect(spy.nthCallThrew(0, 'other error')).to.be.false;
-      });
-
-      it('should match error by type (constructor)', function () {
-        // вызов, приводящий к ошибке
-        try {
-          spy(0, 1);
-        } catch (e) {
-          // бросает ошибку
-        }
-        // проверка по типу ошибки
-        expect(spy.nthCallThrew(0, Error)).to.be.true;
-        expect(spy.nthCallThrew(0, TypeError)).to.be.false;
-      });
-
-      it('should match error by instance (name and message)', function () {
-        // вызов, приводящий к ошибке
-        try {
-          spy(0, 1);
-        } catch (e) {
-          // бросает ошибку
-        }
-        // создание экземпляра ошибки для сравнения
-        const expectedError = new Error('zero error');
-        const wrongError = new Error('another error');
-        const differentType = new TypeError('zero error');
-        // проверки
-        expect(spy.nthCallThrew(0, expectedError)).to.be.true;
-        expect(spy.nthCallThrew(0, wrongError)).to.be.false;
-        expect(spy.nthCallThrew(0, differentType)).to.be.false;
-      });
-
-      it('should match error by Object.is for direct error object comparison', function () {
-        // создание специфической ошибки
-        const specificError = new RangeError('specific');
-        const fnThrowsSpecific = () => {
-          throw specificError;
-        };
-        const specificSpy = createSpy(fnThrowsSpecific);
-        try {
-          specificSpy();
-        } catch (e) {
-          // бросает ошибку
-        }
-        // проверка по прямому совпадению объекта ошибки
-        expect(specificSpy.nthCallThrew(0, specificError)).to.be.true;
-        // новый экземпляр не тот же объект, но совпадет по name и message
-        expect(specificSpy.nthCallThrew(0, new RangeError('specific'))).to.be
-          .true;
-      });
-    });
   });
   });
 });
 });

+ 18 - 0
src/types.d.ts

@@ -0,0 +1,18 @@
+/**
+ * Тип любой функции.
+ */
+export type AnyCallable = (...args: any[]) => any;
+
+/**
+ * Конструктор любой ошибки.
+ */
+export type AnyErrorCtor = (new (...args: any[]) => Error);
+
+/**
+ * Ключ любого метода в объекте.
+ * Должен быть ключом `TObj`, значение
+ * которого является функцией.
+ */
+export type MethodKey<TObj extends object> = {
+  [P in keyof TObj]: TObj[P] extends AnyCallable ? P : never
+}[keyof TObj];