4 Коммиты d1c59574b0 ... 04c0424309

Автор SHA1 Сообщение Дата
  e22m4u 04c0424309 chore: bumps version to 0.3.5 1 день назад
  e22m4u cd26f3cfc8 chore: updates dependencies 1 день назад
  e22m4u d624aff4e7 refactor: reformat code 1 день назад
  e22m4u 2ea733526b refactor: renames SpiesGroup to Sandbox 1 день назад

+ 43 - 44
README.md

@@ -20,10 +20,10 @@
     - [spy.called](#spycalled)
     - [spy.callCount](#spycallcount)
     - [spy.restore()](#spyrestore)
-  - [Функция `createSpiesGroup`](#функция-createspiesgroup)
-  - [Методы группы шпионов](#методы-группы-шпионов)
-    - [group.on(...)](#groupon)
-    - [group.restore()](#grouprestore)
+  - [Функция `createSandbox`](#функция-createsandbox)
+  - [Методы песочницы](#методы-песочницы)
+    - [sandbox.on(...)](#sandboxon)
+    - [sandbox.restore()](#sandboxrestore)
 - [Тесты](#тесты)
 - [Лицензия](#лицензия)
 
@@ -38,13 +38,13 @@ npm install @e22m4u/js-spy
 *ESM*
 
 ```js
-import {createSpy, createSpiesGroup} from '@e22m4u/js-spy';
+import {createSpy, createSandbox} from '@e22m4u/js-spy';
 ```
 
 *CommonJS*
 
 ```js
-const {createSpy, createSpiesGroup} = require('@e22m4u/js-spy');
+const {createSpy, createSandbox} = require('@e22m4u/js-spy');
 ```
 
 ## Использование
@@ -167,7 +167,7 @@ addSpy.restore();
 например, восстановить их все разом.
 
 ```js
-import {createSpiesGroup} from '@e22m4u/js-spy';
+import {createSandbox} from '@e22m4u/js-spy';
 
 // объект с методами для отслеживания
 const service = {
@@ -186,16 +186,16 @@ function standaloneLogger(message) {
   console.log(`LOG: ${message}`);
 }
 
-// создание группы
-const group = createSpiesGroup();
+// создание песочницы
+const sandbox = createSandbox();
 
-// добавление шпионов в группу:
-//   метод group.on() работает аналогично createSpy(),
-//   но добавляет шпиона в группу и возвращает созданного
+// добавление шпионов в песочницу:
+//   метод sandbox.on() работает аналогично createSpy(),
+//   но добавляет шпиона в песочницу и возвращает созданного
 //   шпиона
-const fetchDataSpy = group.on(service, 'fetchData');
-const processItemSpy = group.on(service, 'processItem');
-const loggerSpy = group.on(standaloneLogger);
+const fetchDataSpy = sandbox.on(service, 'fetchData');
+const processItemSpy = sandbox.on(service, 'processItem');
+const loggerSpy = sandbox.on(standaloneLogger);
 
 // так как методы заменяются шпионами прямо на объекте,
 // допустимо вызывать непосредственно их
@@ -210,14 +210,14 @@ console.log(fetchDataSpy.callCount);   // 1
 console.log(processItemSpy.callCount); // 1
 console.log(loggerSpy.callCount);      // 1
 
-// восстановление всех шпионов в группе:
+// восстановление всех шпионов песочницы:
 //   - оригинальные методы service.fetchData
 //     и service.processItem будут восстановлены
 //   - история вызовов (callCount, called, calls и т.д.)
 //     для fetchDataSpy, processItemSpy и loggerSpy
 //     будет сброшена
-//   - внутренний список шпионов в группе будет очищен
-group.restore();
+//   - внутренний список шпионов песочницы будет очищен
+sandbox.restore();
 
 console.log(service.fetchData === fetchDataSpy);
 // false (оригинальный метод восстановлен)
@@ -345,7 +345,7 @@ expect(spy).on.nth(10).be.called.with('result');
 
 ### Свойства и методы шпиона
 
-Каждая функция-шпион, возвращаемая `createSpy` (или `group.on`), обладает
+Каждая функция-шпион, возвращаемая `createSpy` (или `sandbox.on`), обладает
 следующими свойствами и методами:
 
 #### spy(...args)
@@ -458,31 +458,31 @@ fnSpy.restore();
 console.log(fnSpy.callCount); // 0 (история сброшена)
 ```
 
-### Функция `createSpiesGroup`
+### Функция `createSandbox`
 
-Фабричная функция для создания экземпляра `SpiesGroup` (группа шпионов).
+Фабричная функция для создания экземпляра песочницы.
 
 ```js
-import {createSpiesGroup} from '@e22m4u/js-spy';
+import {createSandbox} from '@e22m4u/js-spy';
 
-const group = createSpiesGroup();
+const sandbox = createSandbox();
 ```
 
-### Методы группы шпионов
+### Методы песочницы
 
-Экземпляр `SpiesGroup` имеет следующие методы:
+Экземпляр `Sandbox` имеет следующие методы:
 
-#### group.on(...)
+#### sandbox.on(...)
 
-Создает шпиона и добавляет его в группу.
+Создает шпиона и добавляет его в песочницу.
 
 Сигнатуры вызова:
 
 1.  Отслеживание отдельной функции:  
-    `group.on(targetFn, [customImpl])`
+    `sandbox.on(targetFn, [customImpl])`
 
 2.  Отслеживание метода объекта:  
-    `group.on(targetObject, methodName, [customImpl])`
+    `sandbox.on(targetObject, methodName, [customImpl])`
 
 Возвращает:
 
@@ -491,34 +491,33 @@ const group = createSpiesGroup();
 Пример:
 
 ```js
-const group = createSpiesGroup();
+const sandbox = createSandbox();
 const obj = {greet: () => 'Hello'};
 
-const greetSpy = group.on(obj, 'greet');
-// obj.greet теперь шпион, и greetSpy добавлен в группу
+const greetSpy = sandbox.on(obj, 'greet');
+// obj.greet теперь шпион, и greetSpy добавлен в песочницу
 obj.greet();
 console.log(greetSpy.called); // true
 ```
 
-#### group.restore()
+#### sandbox.restore()
 
-Вызывает метод `restore()` для каждого шпиона, содержащегося в группе.
+Вызывает метод `restore()` для каждого шпиона, содержащегося в песочнице.
 Это означает, что:
 
 - Все оригинальные методы объектов, для которых были созданы шпионы
-  в этой группе, будут восстановлены.
-- История вызовов всех шпионов в группе будет сброшена.
-- Внутренний список шпионов в самой группе будет очищен, делая группу
-  готовой к повторному использованию (если необходимо).
+  в данной песочнице, будут восстановлены.
+- История вызовов всех шпионов в песочнице будет сброшена.
+- Внутренний список шпионов в песочнице будет очищен.
 
 Возвращает:
 
-- `this` (экземпляр `SpiesGroup`) для возможной цепочки вызовов.
+- `this` для возможной цепочки вызовов.
 
 Пример:
 
 ```js
-const group = createSpiesGroup();
+const sandbox = createSandbox();
 
 // объект с методом для отслеживания
 const service = {
@@ -529,8 +528,8 @@ const service = {
 function utilFn() { /* ... */ }
 
 // создание шпионов
-const processSpy = group.on(service, 'process');
-const utilSpy = group.on(utilFn);
+const processSpy = sandbox.on(service, 'process');
+const utilSpy = sandbox.on(utilFn);
 
 // вызов отслеживаемого метода
 // и шпиона одиночной функции
@@ -543,12 +542,12 @@ console.log(utilSpy.callCount);    // 1
 
 // восстановление шпионов
 // и сброс истории
-group.restore();
+sandbox.restore();
 
 // service.process теперь оригинальный метод
 console.log(processSpy.callCount); // 0
 console.log(utilSpy.callCount);    // 0
-console.log(group.spies.length);   // 0
+console.log(sandbox.spies.length);   // 0
 ```
 
 ## Тесты

+ 19 - 21
dist/cjs/index.cjs

@@ -21,9 +21,9 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
 // src/index.js
 var index_exports = {};
 __export(index_exports, {
-  SpiesGroup: () => SpiesGroup,
+  Sandbox: () => Sandbox,
   chaiSpies: () => chaiSpies,
-  createSpiesGroup: () => createSpiesGroup,
+  createSandbox: () => createSandbox,
   createSpy: () => createSpy
 });
 module.exports = __toCommonJS(index_exports);
@@ -135,20 +135,20 @@ function chaiSpies(chai, _) {
     return n + "th";
   }
   __name(generateOrdinalNumber, "generateOrdinalNumber");
-  function assertWith() {
+  function assertWith(...expArgs) {
     new Assertion(this._obj).to.be.spy;
-    const expArgs = [].slice.call(arguments, 0), spy = this._obj, calls = spy.calls, always = _.flag(this, "spy always"), nthCall = _.flag(this, "spy nth call with");
+    const spy = this._obj, calls = spy.calls, always = _.flag(this, "spy always"), nthCall = _.flag(this, "spy nth call with");
     if (always) {
       const passed = numberOfCallsWith(spy, expArgs);
       this.assert(
-        arguments.length ? calls.length && passed === calls.length : calls.length === 0,
+        expArgs.length ? calls.length && passed === calls.length : calls.length === 0,
         "expected " + this._obj + " to have been always called with #{exp} but got " + passed + " out of " + calls.length,
         "expected " + this._obj + " to have not always been called with #{exp}",
         expArgs
       );
     } else if (nthCall) {
       const ordinalNumber = generateOrdinalNumber(nthCall), actArgs = calls[nthCall - 1];
-      new Assertion(this._obj).to.be.have.been.called.min(nthCall);
+      new Assertion(this._obj).to.have.been.called.min(nthCall);
       this.assert(
         nthCallWith(spy, nthCall - 1, expArgs),
         "expected " + this._obj + " to have been called at the " + ordinalNumber + " time with #{exp} but got #{act}",
@@ -179,9 +179,8 @@ function chaiSpies(chai, _) {
       _.flag(this, "spy always", true);
     }
   });
-  Assertion.addMethod("exactly", function() {
+  Assertion.addMethod("exactly", function(...args) {
     new Assertion(this._obj).to.be.spy;
-    const args = [].slice.call(arguments, 0);
     this.assert(
       this._obj.calls.length === args[0],
       "expected " + this._obj + " to have been called #{exp} times but got #{act}",
@@ -414,8 +413,8 @@ function createSpy(target = void 0, methodNameOrImpl = void 0, customImplForMeth
 }
 __name(createSpy, "createSpy");
 
-// src/create-spies-group.js
-var _SpiesGroup = class _SpiesGroup {
+// src/create-sandbox.js
+var _Sandbox = class _Sandbox {
   /**
    * Constructor.
    */
@@ -424,7 +423,7 @@ var _SpiesGroup = class _SpiesGroup {
   }
   /**
    * Создает шпиона для отдельной функции
-   * или метода объекта и добавляет его в группу.
+   * или метода объекта и добавляет его в песочницу.
    *
    * @param {Function|object} [target]
    * @param {Function|string} [methodNameOrImpl]
@@ -438,9 +437,8 @@ var _SpiesGroup = class _SpiesGroup {
   }
   /**
    * Восстановление всех оригинальных методов объектов,
-   * для которых были созданы шпионы в этой группе,
-   * и сброс истории вызовов для всех шпионов в группе.
-   * Очищает внутренний список шпионов.
+   * для которых были созданы шпионы в данной песочнице,
+   * и сброс истории вызовов.
    *
    * @returns {this}
    */
@@ -450,16 +448,16 @@ var _SpiesGroup = class _SpiesGroup {
     return this;
   }
 };
-__name(_SpiesGroup, "SpiesGroup");
-var SpiesGroup = _SpiesGroup;
-function createSpiesGroup() {
-  return new SpiesGroup();
+__name(_Sandbox, "Sandbox");
+var Sandbox = _Sandbox;
+function createSandbox() {
+  return new Sandbox();
 }
-__name(createSpiesGroup, "createSpiesGroup");
+__name(createSandbox, "createSandbox");
 // Annotate the CommonJS export names for ESM import in node:
 0 && (module.exports = {
-  SpiesGroup,
+  Sandbox,
   chaiSpies,
-  createSpiesGroup,
+  createSandbox,
   createSpy
 });

+ 1 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "@e22m4u/js-spy",
-  "version": "0.3.4",
+  "version": "0.3.5",
   "description": "Утилита слежения за вызовом функций и методов для JavaScript",
   "author": "Mikhail Evstropov <e22m4u@yandex.ru>",
   "license": "MIT",

+ 5 - 8
src/chai/chai-spies.js

@@ -126,10 +126,9 @@ export function chaiSpies(chai, _) {
     return n + 'th';
   }
 
-  function assertWith() {
+  function assertWith(...expArgs) {
     new Assertion(this._obj).to.be.spy;
-    const expArgs = [].slice.call(arguments, 0)
-      , spy = this._obj
+    const spy = this._obj
       , calls = spy.calls
       , always = _.flag(this, 'spy always')
       , nthCall = _.flag(this, 'spy nth call with');
@@ -137,7 +136,7 @@ export function chaiSpies(chai, _) {
     if (always) {
       const passed = numberOfCallsWith(spy, expArgs);
       this.assert(
-          arguments.length
+          expArgs.length
             ? calls.length && passed === calls.length
             : calls.length === 0
         , 'expected ' + this._obj + ' to have been always called with #{exp} but got ' + passed + ' out of ' + calls.length
@@ -147,7 +146,7 @@ export function chaiSpies(chai, _) {
     } else if (nthCall) {
       const ordinalNumber = generateOrdinalNumber(nthCall),
           actArgs = calls[nthCall - 1];
-      new Assertion(this._obj).to.be.have.been.called.min(nthCall);
+      new Assertion(this._obj).to.have.been.called.min(nthCall);
       this.assert(
           nthCallWith(spy, nthCall - 1, expArgs)
         , 'expected ' + this._obj + ' to have been called at the ' + ordinalNumber + ' time with #{exp} but got #{act}'
@@ -180,9 +179,8 @@ export function chaiSpies(chai, _) {
     }
   });
 
-  Assertion.addMethod('exactly', function () {
+  Assertion.addMethod('exactly', function (...args) {
     new Assertion(this._obj).to.be.spy;
-    const args = [].slice.call(arguments, 0);
     this.assert(
         this._obj.calls.length === args[0]
       , 'expected ' + this._obj + ' to have been called #{exp} times but got #{act}'
@@ -196,7 +194,6 @@ export function chaiSpies(chai, _) {
     return function (n) {
       if (this._obj.__isSpy) {
         new Assertion(this._obj).to.be.spy;
-
         this.assert(
             this._obj.calls.length > n
           , 'expected ' + this._obj + ' to have been called more than #{exp} times but got #{act}'

+ 44 - 0
src/create-sandbox.d.ts

@@ -0,0 +1,44 @@
+import {Spy} from './create-spy.js';
+import {MethodKey, AnyCallable} from './types.js';
+
+/**
+ * Песочница позволяет создавать шпионов
+ * и управлять ими как единым целым.
+ */
+export declare class Sandbox {
+  /**
+   * Внутренний массив, хранящий все шпионы.
+   */
+  spies: Spy<any>[];
+
+  /**
+   * Создает шпиона для отдельной функции
+   * и добавляет его в песочницу.
+   */
+  on<TFunc extends AnyCallable>(
+    target: TFunc,
+    customImpl?: TFunc,
+  ): Spy<TFunc>;
+
+  /**
+   * Создает шпиона для метода объекта, добавляет
+   * его в песочницу и заменяет оригинальный метод.
+   */
+  on<TObj extends object, K extends MethodKey<TObj>>(
+    target: TObj,
+    methodName: K,
+    customImpl?: AnyCallable,
+  ): Spy<Extract<TObj[K], AnyCallable>>;
+
+  /**
+   * Восстановление всех оригинальных методов объектов,
+   * для которых были созданы шпионы в данной песочнице,
+   * и сброс истории вызовов.
+   */
+  restore(): this;
+}
+
+/**
+ * Создание песочницы.
+ */
+export function createSandbox(): Sandbox;

+ 10 - 11
src/create-spies-group.js → src/create-sandbox.js

@@ -1,10 +1,10 @@
 import {createSpy} from './create-spy.js';
 
 /**
- * Группа позволяет создавать шпионов
- * и управлять ими как одним.
+ * Песочница позволяет создавать шпионов
+ * и управлять ими как единым целым.
  */
-export class SpiesGroup {
+export class Sandbox {
   /**
    * Constructor.
    */
@@ -14,7 +14,7 @@ export class SpiesGroup {
 
   /**
    * Создает шпиона для отдельной функции
-   * или метода объекта и добавляет его в группу.
+   * или метода объекта и добавляет его в песочницу.
    *
    * @param {Function|object} [target]
    * @param {Function|string} [methodNameOrImpl]
@@ -29,9 +29,8 @@ export class SpiesGroup {
 
   /**
    * Восстановление всех оригинальных методов объектов,
-   * для которых были созданы шпионы в этой группе,
-   * и сброс истории вызовов для всех шпионов в группе.
-   * Очищает внутренний список шпионов.
+   * для которых были созданы шпионы в данной песочнице,
+   * и сброс истории вызовов.
    *
    * @returns {this}
    */
@@ -43,10 +42,10 @@ export class SpiesGroup {
 }
 
 /**
- * Создание группы шпионов.
+ * Создание песочницы.
  *
- * @returns {SpiesGroup}
+ * @returns {Sandbox}
  */
-export function createSpiesGroup() {
-  return new SpiesGroup();
+export function createSandbox() {
+  return new Sandbox();
 }

+ 128 - 0
src/create-sandbox.spec.js

@@ -0,0 +1,128 @@
+import {expect} from 'chai';
+import {Sandbox, createSandbox} from './create-sandbox.js';
+
+describe('Sandbox', function () {
+  describe('createSandbox factory', function () {
+    it('should return an instance of Sandbox', function () {
+      const sandbox = createSandbox();
+      expect(sandbox).to.be.instanceOf(Sandbox);
+    });
+
+    it('should initialize with an empty spies array', function () {
+      const sandbox = createSandbox();
+      expect(sandbox.spies).to.be.an('array').that.is.empty;
+    });
+  });
+
+  describe('Sandbox instance', function () {
+    describe('.on(target, methodNameOrImpl, customImplForMethod)', function () {
+      it('should create a spy for a standalone function', function () {
+        const sandbox = createSandbox();
+        const targetFn = () => {};
+        const fnSpy = sandbox.on(targetFn);
+        expect(fnSpy).to.be.a('function');
+        expect(fnSpy.callCount).to.equal(0);
+        expect(fnSpy).to.have.property('calls');
+      });
+
+      it('should create a spy for a standalone function with a custom implementation', function () {
+        const sandbox = createSandbox();
+        const targetFn = () => {};
+        const customImpl = () => 'custom result';
+        const fnSpy = sandbox.on(targetFn, customImpl);
+        fnSpy();
+        expect(fnSpy.calls[0].returnValue).to.equal('custom result');
+      });
+
+      it('should create a spy for an object method and replace the original method', function () {
+        const sandbox = createSandbox();
+        const obj = {method: () => 'original method'};
+        const methodSpy = sandbox.on(obj, 'method');
+        expect(obj.method).to.equal(methodSpy);
+        expect(methodSpy).to.be.a('function');
+      });
+
+      it('should create a spy for an object method with a custom implementation', function () {
+        const sandbox = createSandbox();
+        const obj = {method: () => 'original method'};
+        const customImpl = () => 'custom method';
+        sandbox.on(obj, 'method', customImpl);
+        expect(obj.method()).to.equal('custom method');
+      });
+
+      it('should add the created spy to the internal spies array', function () {
+        const sandbox = createSandbox();
+        const targetFn1 = () => {};
+        const targetFn2 = () => {};
+        const spy1 = sandbox.on(targetFn1);
+        expect(sandbox.spies).to.have.lengthOf(1);
+        expect(sandbox.spies[0]).to.equal(spy1);
+        const spy2 = sandbox.on(targetFn2);
+        expect(sandbox.spies).to.have.lengthOf(2);
+        expect(sandbox.spies[1]).to.equal(spy2);
+      });
+
+      it('should return the created spy instance', function () {
+        const sandbox = createSandbox();
+        const targetFn = () => {};
+        const returnedSpy = sandbox.on(targetFn);
+        expect(returnedSpy).to.be.a('function');
+        expect(sandbox.spies[0]).to.equal(returnedSpy);
+      });
+    });
+
+    describe('.restore()', function () {
+      it('should restore original methods on objects', function () {
+        const sandbox = createSandbox();
+        const originalMethod = () => 'original';
+        const obj = {method: originalMethod};
+        sandbox.on(obj, 'method');
+        expect(obj.method).to.not.equal(originalMethod);
+        sandbox.restore();
+        expect(obj.method).to.equal(originalMethod);
+        expect(obj.method()).to.equal('original');
+      });
+
+      it('should reset call history for all spies', function () {
+        const sandbox = createSandbox();
+        const fn = () => {};
+        const spy = sandbox.on(fn);
+        spy();
+        expect(spy.called).to.be.true;
+        expect(spy.callCount).to.equal(1);
+        sandbox.restore();
+        expect(spy.called).to.be.false;
+        expect(spy.callCount).to.equal(0);
+      });
+
+      it('should clear the internal spies array', function () {
+        const sandbox = createSandbox();
+        sandbox.on(() => {});
+        sandbox.on({m: () => {}}, 'm');
+        expect(sandbox.spies).to.have.lengthOf(2);
+        sandbox.restore();
+        expect(sandbox.spies).to.be.an('array').that.is.empty;
+      });
+
+      it('should return the Sandbox instance for chaining', function () {
+        const sandbox = createSandbox();
+        const returnedValue = sandbox.restore();
+        expect(returnedValue).to.equal(sandbox);
+      });
+
+      it('should be idempotent (calling restore multiple times does not throw)', function () {
+        const sandbox = createSandbox();
+        const obj = {method: () => {}};
+        sandbox.on(obj, 'method');
+        sandbox.restore();
+        expect(() => sandbox.restore()).to.not.throw();
+      });
+
+      it('should handle an empty spies array gracefully', function () {
+        const sandbox = createSandbox();
+        expect(() => sandbox.restore()).to.not.throw();
+        expect(sandbox.spies).to.be.an('array').that.is.empty;
+      });
+    });
+  });
+});

+ 0 - 56
src/create-spies-group.d.ts

@@ -1,56 +0,0 @@
-import {Spy} from './create-spy.js';
-import {MethodKey, AnyCallable} from './types.js';
-
-/**
- * Представляет группу шпионов, позволяющую
- * управлять ими коллективно.
- */
-export declare class SpiesGroup {
-  /**
-   * Внутренний массив, хранящий все шпионы,
-   * созданные в этой группе. Не предназначен
-   * для прямого доступа.
-   */
-  spies: Spy<any>[];
-
-  /**
-   * Создает шпиона для отдельной функции
-   * и добавляет его в группу.
-   *
-   * @param target
-   * @param customImpl
-   */
-  on<TFunc extends AnyCallable>(
-    target: TFunc,
-    customImpl?: TFunc,
-  ): Spy<TFunc>;
-
-  /**
-   * Создает шпиона для метода объекта, добавляет
-   * его в группу и заменяет оригинальный метод
-   * объекта шпионом.
-   *
-   * @param target
-   * @param methodName
-   * @param customImpl
-   */
-  on<TObj extends object, K extends MethodKey<TObj>>(
-    target: TObj,
-    methodName: K,
-    customImpl?: AnyCallable,
-  ): Spy<Extract<TObj[K], AnyCallable>>;
-
-  /**
-   * Восстановление всех оригинальных методов объектов,
-   * для которых были созданы шпионы в этой группе,
-   * и сброс истории вызовов для всех шпионов в группе.
-   * Очищает внутренний список шпионов.
-   */
-  restore(): this;
-}
-
-/**
- * Фабричная функция для создания
- * нового экземпляра `SpiesGroup`.
- */
-export function createSpiesGroup(): SpiesGroup;

+ 0 - 159
src/create-spies-group.spec.js

@@ -1,159 +0,0 @@
-import {expect} from 'chai';
-import {SpiesGroup, createSpiesGroup} from './create-spies-group.js';
-
-describe('SpiesGroup', function () {
-  describe('createSpiesGroup factory', function () {
-    it('should return an instance of SpiesGroup', function () {
-      const group = createSpiesGroup();
-      expect(group).to.be.instanceOf(SpiesGroup);
-    });
-
-    it('should initialize with an empty spies array', function () {
-      const group = createSpiesGroup();
-      expect(group.spies).to.be.an('array').that.is.empty;
-    });
-  });
-
-  describe('SpiesGroup instance', function () {
-    let group;
-
-    beforeEach(function () {
-      group = createSpiesGroup();
-    });
-
-    describe('.on(target, methodNameOrImpl, customImplForMethod)', function () {
-      it('should create a spy using createSpy with the given arguments', function () {
-        const targetFn = () => {};
-        const customImpl = () => {};
-        // шпион для standalone функции
-        const fnSpy = group.on(targetFn);
-        expect(fnSpy).to.be.a('function');
-        // проверка, что это действительно шпион
-        expect(fnSpy.callCount).to.equal(0);
-        // шпион для standalone функции с кастомной реализацией
-        const fnSpyWithImpl = group.on(targetFn, customImpl);
-        // вызов для проверки кастомной реализации
-        fnSpyWithImpl();
-        expect(fnSpyWithImpl.calls[0].returnValue).to.equal(customImpl());
-        // шпион для метода объекта
-        const obj = {method: () => 'original method'};
-        const methodSpy = group.on(obj, 'method');
-        // проверка замены метода
-        expect(obj.method).to.equal(methodSpy);
-        // шпион для метода объекта с кастомной реализацией
-        const objWithCustom = {method: () => 'original method 2'};
-        const customMethodImpl = () => 'custom method';
-        group.on(objWithCustom, 'method', customMethodImpl);
-        // проверка вызова кастомной реализации
-        expect(objWithCustom.method()).to.equal('custom method');
-      });
-
-      it('should add the created spy to the internal spies array', function () {
-        const targetFn1 = () => {};
-        const targetFn2 = () => {};
-        const spy1 = group.on(targetFn1);
-        expect(group.spies).to.have.lengthOf(1);
-        expect(group.spies[0]).to.equal(spy1);
-        const spy2 = group.on(targetFn2);
-        expect(group.spies).to.have.lengthOf(2);
-        expect(group.spies[1]).to.equal(spy2);
-      });
-
-      it('should return the created spy instance', function () {
-        const targetFn = () => {};
-        const returnedSpy = group.on(targetFn);
-        expect(returnedSpy).to.be.a('function');
-        // проверка, что это тот же шпион, что и в массиве
-        expect(group.spies[0]).to.equal(returnedSpy);
-      });
-    });
-
-    describe('.restore()', function () {
-      let obj1, originalMethod1;
-      let obj2, originalMethod2;
-      let standaloneFn1, standaloneFn2;
-      let spyObj1, spyObj2, spyFn1, spyFn2;
-
-      beforeEach(function () {
-        originalMethod1 = function () {
-          return 'original1';
-        };
-        obj1 = {method: originalMethod1};
-
-        originalMethod2 = function () {
-          return 'original2';
-        };
-        obj2 = {method: originalMethod2};
-
-        standaloneFn1 = function () {
-          return 'standalone1';
-        };
-        standaloneFn2 = function () {
-          return 'standalone2';
-        };
-
-        spyObj1 = group.on(obj1, 'method');
-        spyFn1 = group.on(standaloneFn1);
-        spyObj2 = group.on(obj2, 'method');
-        spyFn2 = group.on(standaloneFn2);
-
-        // вызов всех шпионов для наполнения истории
-        obj1.method(); // spyObj1
-        spyFn1();
-        obj2.method(); // spyObj2
-        spyFn2();
-
-        expect(spyObj1.callCount).to.equal(1);
-        expect(spyFn1.callCount).to.equal(1);
-        expect(spyObj2.callCount).to.equal(1);
-        expect(spyFn2.callCount).to.equal(1);
-      });
-
-      it('should call restore() on all spies in the group', function () {
-        group.restore();
-        // проверка восстановления методов объектов
-        expect(obj1.method).to.equal(originalMethod1);
-        expect(obj1.method()).to.equal('original1');
-        expect(obj2.method).to.equal(originalMethod2);
-        expect(obj2.method()).to.equal('original2');
-        // проверка сброса истории
-        expect(spyObj1.callCount).to.equal(0);
-        expect(spyObj1.called).to.be.false;
-        expect(spyFn1.callCount).to.equal(0);
-        expect(spyFn1.called).to.be.false;
-        expect(spyObj2.callCount).to.equal(0);
-        expect(spyObj2.called).to.be.false;
-        expect(spyFn2.callCount).to.equal(0);
-        expect(spyFn2.called).to.be.false;
-      });
-
-      it('should clear the internal spies array', function () {
-        expect(group.spies).to.have.lengthOf(4);
-        group.restore();
-        expect(group.spies).to.be.an('array').that.is.empty;
-      });
-
-      it('should return the SpiesGroup instance for chaining (if other methods were added)', function () {
-        const returnedValue = group.restore();
-        expect(returnedValue).to.equal(group);
-      });
-
-      it('should be idempotent - calling restore multiple times should not error', function () {
-        // первый вызов restore
-        group.restore();
-        // второй вызов restore
-        expect(() => group.restore()).to.not.throw();
-        // проверки состояний после второго вызова (должно быть таким же)
-        expect(obj1.method).to.equal(originalMethod1);
-        expect(spyObj1.callCount).to.equal(0);
-        expect(group.spies).to.be.an('array').that.is.empty;
-      });
-
-      it('should handle an empty spies array gracefully', function () {
-        const emptyGroup = createSpiesGroup();
-        expect(() => emptyGroup.restore()).to.not.throw();
-        expect(emptyGroup.spies).to.be.an('array').that.is.empty;
-      });
-    });
-  });
-});

+ 24 - 141
src/create-spy.spec.js

@@ -13,8 +13,6 @@ describe('createSpy', function () {
     });
 
     it('should throw when trying to spy on null', function () {
-      // проверка генерации ошибки при попытке
-      // шпионить за значением null
       expect(() => createSpy(null)).to.throw(
         TypeError,
         'Attempted to spy on null.',
@@ -22,15 +20,11 @@ describe('createSpy', function () {
     });
 
     it('should throw if target is not a function and no method name is given', function () {
-      // проверка генерации ошибки для не-функции
-      // без указания имени метода
-      // @ts-ignore
       expect(() => createSpy({})).to.throw(
         TypeError,
         'Attempted to spy on a non-function value. To spy on an object method, ' +
           'you must provide the method name as the second argument.',
       );
-      // @ts-ignore
       expect(() => createSpy(123)).to.throw(
         TypeError,
         'Attempted to spy on a non-function value. To spy on an object method, ' +
@@ -39,10 +33,7 @@ describe('createSpy', function () {
     });
 
     it('should throw if custom implementation for a function spy is not a function', function () {
-      // проверка генерации ошибки, если кастомная
-      // реализация для шпиона функции не является функцией
       const targetFn = () => {};
-      // @ts-ignore
       expect(() => createSpy(targetFn, 'not a function')).to.throw(
         TypeError,
         'When spying on a function, the second argument (custom implementation) must be a function if provided.',
@@ -50,10 +41,7 @@ describe('createSpy', function () {
     });
 
     it('should throw if trying to spy on a non-existent method', function () {
-      // проверка генерации ошибки при попытке
-      // шпионить за несуществующим методом объекта
       const obj = {};
-      // @ts-ignore
       expect(() => createSpy(obj, 'nonExistentMethod')).to.throw(
         TypeError,
         'Attempted to spy on a non-existent property: "nonExistentMethod"',
@@ -61,10 +49,7 @@ describe('createSpy', function () {
     });
 
     it('should throw if trying to spy on a non-function property of an object', function () {
-      // проверка генерации ошибки при попытке
-      // шпионить за свойством объекта, не являющимся функцией
       const obj = {prop: 123};
-      // @ts-ignore
       expect(() => createSpy(obj, 'prop')).to.throw(
         TypeError,
         'Attempted to spy on "prop" which is not a function. It is a "number".',
@@ -72,10 +57,7 @@ describe('createSpy', function () {
     });
 
     it('should throw if custom implementation for a method spy is not a function', function () {
-      // проверка генерации ошибки, если кастомная реализация
-      // для шпиона метода не является функцией
       const obj = {method: () => {}};
-      // @ts-ignore
       expect(() => createSpy(obj, 'method', 'not a function')).to.throw(
         TypeError,
         'When spying on a method, the third argument (custom implementation) must be a function if provided.',
@@ -85,110 +67,65 @@ describe('createSpy', function () {
 
   describe('when spying on a standalone function', function () {
     it('should return a function that is the spy', function () {
-      // создание шпиона для пустой функции
       const targetFn = () => {};
       const spy = createSpy(targetFn);
-      // проверка того, что шпион
-      // является функцией
       expect(spy).to.be.a('function');
     });
 
     it('should not be called initially', function () {
-      // создание шпиона
       const spy = createSpy(function () {});
-      // первоначальное состояние свойства called
       expect(spy.called).to.be.false;
-      // первоначальное значение счетчика вызовов
       expect(spy.callCount).to.be.eq(0);
     });
 
     it('should track calls and arguments', function () {
-      // создание шпиона для функции,
-      // возвращающей сумму аргументов
       const sum = (a, b) => a + b;
       const spy = createSpy(sum);
-      // первый вызов шпиона
       spy(1, 2);
-      // второй вызов шпиона
       spy(3, 4);
-
-      // состояние свойства called
-      // после вызовов
       expect(spy.called).to.be.true;
-      // значение счетчика вызовов
       expect(spy.callCount).to.be.eq(2);
-
-      // проверка аргументов первого вызова
       expect(spy.calls[0].args).to.deep.equal([1, 2]);
-      // проверка аргументов второго вызова
       expect(spy.calls[1].args).to.deep.equal([3, 4]);
     });
 
     it('should call the original function and return its value by default', function () {
-      // создание функции, возвращающей
-      // определенное значение
       const originalFn = () => 'original value';
       const spy = createSpy(originalFn);
-      // вызов шпиона и сохранение результата
       const result = spy();
-
-      // проверка возвращенного значения
       expect(result).to.be.eq('original value');
-      // проверка того, что шпион был вызван
       expect(spy.called).to.be.true;
     });
 
     it('should use the custom implementation if provided', function () {
-      // создание оригинальной функции и
-      // пользовательской реализации
       const originalFn = () => 'original';
       const customImpl = () => 'custom';
       const spy = createSpy(originalFn, customImpl);
-      // вызов шпиона и сохранение результата
       const result = spy();
-
-      // проверка того, что возвращено значение
-      // из пользовательской реализации
       expect(result).to.be.eq('custom');
-      // проверка свойства called
       expect(spy.called).to.be.true;
     });
 
     it('should preserve `this` context for the original function', function () {
-      // определение объекта с методом, который
-      // будет использоваться как target функция
       const contextObj = {val: 10};
       function originalFn() {
         return this.val;
       }
       const spy = createSpy(originalFn);
-      // вызов шпиона с привязкой контекста
       const result = spy.call(contextObj);
-
-      // проверка возвращенного значения, которое
-      // зависит от контекста this
       expect(result).to.be.eq(10);
-      // проверка сохраненного контекста
-      // в информации о вызове
       expect(spy.calls[0].thisArg).to.be.eq(contextObj);
     });
 
     it('should preserve `this` context for the custom implementation', function () {
-      // определение объекта и пользовательской реализации,
-      // использующей контекст this
       const contextObj = {val: 20};
       const originalFn = () => {};
       function customImpl() {
         return this.val;
       }
       const spy = createSpy(originalFn, customImpl);
-      // вызов шпиона с привязкой контекста
       const result = spy.call(contextObj);
-
-      // проверка возвращенного значения из
-      // пользовательской реализации
       expect(result).to.be.eq(20);
-      // проверка сохраненного контекста
       expect(spy.calls[0].thisArg).to.be.eq(contextObj);
     });
 
@@ -196,15 +133,10 @@ describe('createSpy', function () {
       it('should reset its history and not throw', function () {
         const standaloneFn = () => 'standalone result';
         const fnSpy = createSpy(standaloneFn);
-        // вызов шпиона, чтобы у него была история
-        // @ts-ignore
         fnSpy('call standalone');
         expect(fnSpy.called).to.be.true;
         expect(fnSpy.callCount).to.be.eq(1);
-        expect(fnSpy.calls[0].args).to.deep.equal(['call standalone']);
-        // проверка, что вызов restore не вызывает ошибок
         expect(() => fnSpy.restore()).to.not.throw();
-        // проверки сброса истории
         expect(fnSpy.callCount).to.be.eq(0);
         expect(fnSpy.called).to.be.false;
       });
@@ -212,90 +144,61 @@ describe('createSpy', function () {
   });
 
   describe('when spying on an object method', function () {
-    // определение объекта и его метода
-    // для каждого теста в этом блоке
-    let obj;
-    let originalMethodImpl; // для проверки восстановления
-
-    beforeEach(function () {
-      // это функция, которая будет телом
-      // beforeEach, поэтому комментарии здесь уместны
-      // инициализация объекта с методом
-      // перед каждым тестом
-      originalMethodImpl = function (val) {
-        return `original: ${this.name} ${val}`;
-      };
-      obj = {
-        name: 'TestObj',
-        method: originalMethodImpl,
-      };
-    });
-
     it('should replace the original method with the spy', function () {
-      // создание шпиона для метода объекта
+      const obj = {method: () => {}};
       const spy = createSpy(obj, 'method');
-      // проверка того, что свойство объекта
-      // теперь является шпионом
       expect(obj.method).to.be.eq(spy);
-      // проверка того, что шпион является функцией
       expect(obj.method).to.be.a('function');
     });
 
     it('should call the original method with object context and return its value', function () {
-      // создание шпиона для метода
+      const originalMethodImpl = function (val) {
+        return `original: ${this.name} ${val}`;
+      };
+      const obj = {
+        name: 'TestObj',
+        method: originalMethodImpl,
+      };
       const spy = createSpy(obj, 'method');
-      // вызов подмененного метода
       const result = obj.method('arg1');
-
-      // проверка возвращенного значения
-      // от оригинального метода
       expect(result).to.be.eq('original: TestObj arg1');
-      // проверка счетчика вызовов
       expect(spy.callCount).to.be.eq(1);
-      // проверка сохраненного контекста
       expect(spy.calls[0].thisArg).to.be.eq(obj);
-      // проверка сохраненных аргументов
       expect(spy.calls[0].args).to.deep.equal(['arg1']);
     });
 
     it('should use custom implementation with object context if provided', function () {
-      // определение пользовательской реализации
+      const obj = {
+        name: 'TestObj',
+        method: () => {},
+      };
       const customImpl = function (val) {
         return `custom: ${this.name} ${val}`;
       };
-      // создание шпиона с пользовательской реализацией
       const spy = createSpy(obj, 'method', customImpl);
-      // вызов подмененного метода
       const result = obj.method('argCustom');
-
-      // проверка возвращенного значения
-      // от пользовательской реализации
       expect(result).to.be.eq('custom: TestObj argCustom');
-      // проверка счетчика вызовов
       expect(spy.callCount).to.be.eq(1);
-      // проверка сохраненного контекста
       expect(spy.calls[0].thisArg).to.be.eq(obj);
     });
 
     describe('.restore()', function () {
       it('should put the original method back and reset spy history', function () {
-        // создание шпиона для метода
+        const originalMethodImpl = function (val) {
+          return `original: ${this.name} ${val}`;
+        };
+        const obj = {
+          name: 'TestObj',
+          method: originalMethodImpl,
+        };
         const spy = createSpy(obj, 'method');
-        // вызов шпиона, чтобы у него была история
         obj.method('call before restore');
         expect(spy.called).to.be.true;
-        expect(spy.callCount).to.be.eq(1);
         expect(obj.method).to.be.eq(spy);
-        // вызов метода restore на шпионе
         spy.restore();
-        // проверка, что оригинальный метод восстановлен
         expect(obj.method).to.be.eq(originalMethodImpl);
-        // вызов восстановленного метода
-        // для проверки его работоспособности
         const result = obj.method('call after restore');
-        // проверка результата вызова оригинального метода
         expect(result).to.be.eq('original: TestObj call after restore');
-        // проверки сброса истории
         expect(spy.callCount).to.be.eq(0);
         expect(spy.called).to.be.false;
       });
@@ -306,7 +209,6 @@ describe('createSpy', function () {
             return 'own';
           },
         };
-        expect(Object.prototype.hasOwnProperty.call(obj, 'fn')).to.be.true;
         const spy = createSpy(obj, 'fn');
         expect(obj.fn).to.be.eq(spy);
         spy.restore();
@@ -323,7 +225,6 @@ describe('createSpy', function () {
         const instance = new Base();
         expect(Object.prototype.hasOwnProperty.call(instance, 'method')).to.be
           .false;
-        expect(instance.method()).to.be.eq('prototype');
         const spy = createSpy(instance, 'method');
         expect(Object.prototype.hasOwnProperty.call(instance, 'method')).to.be
           .true;
@@ -337,29 +238,15 @@ describe('createSpy', function () {
   });
 
   describe('spy properties and methods', function () {
-    // объявление шпиона для использования в тестах
-    let spy;
-    // определение функции, которая
-    // будет объектом шпионажа
-    const targetFn = (a, b) => {
-      if (a === 0) throw new Error('zero error');
-      return a + b;
-    };
-
-    beforeEach(function () {
-      // это функция, которая будет телом
-      // beforeEach, поэтому комментарии здесь уместны
-      // создание нового шпиона перед
-      // каждым тестом в этом блоке
-      spy = createSpy(targetFn);
-    });
-
     it('should have the __isSpy property with true value', function () {
+      const spy = createSpy(() => {});
       expect(spy['__isSpy']).to.be.true;
     });
 
     describe('.calls', function () {
       it('should return an array of CallInfo', function () {
+        const targetFn = (a, b) => a + b;
+        const spy = createSpy(targetFn);
         expect(spy.calls).to.be.eql([]);
         spy(1, 2);
         expect(spy.calls[0]).to.be.eql({
@@ -381,21 +268,17 @@ describe('createSpy', function () {
 
     describe('.callCount and .called', function () {
       it('should have callCount = 0 and called = false initially', function () {
-        // начальное состояние счетчика вызовов
+        const spy = createSpy(() => {});
         expect(spy.callCount).to.be.eq(0);
-        // начальное состояние флага called
         expect(spy.called).to.be.false;
       });
 
       it('should update after calls', function () {
-        // первый вызов шпиона
+        const spy = createSpy(() => {});
         spy(1, 1);
-        // состояние после первого вызова
         expect(spy.callCount).to.be.eq(1);
         expect(spy.called).to.be.true;
-        // второй вызов шпиона
         spy(2, 2);
-        // состояние после второго вызова
         expect(spy.callCount).to.be.eq(2);
       });
     });

+ 1 - 1
src/index.d.ts

@@ -1,3 +1,3 @@
 export * from './chai/index.js';
 export * from './create-spy.js';
-export * from './create-spies-group.js';
+export * from './create-sandbox.js';

+ 1 - 1
src/index.js

@@ -1,3 +1,3 @@
 export * from './chai/index.js';
 export * from './create-spy.js';
-export * from './create-spies-group.js';
+export * from './create-sandbox.js';