Browse Source

chore: moves inspection feature to `inspect` method

e22m4u 3 months ago
parent
commit
352bd3ac92
6 changed files with 212 additions and 235 deletions
  1. 17 8
      README.md
  2. 22 22
      dist/cjs/index.cjs
  3. 2 2
      package.json
  4. 17 9
      src/create-debugger.d.ts
  5. 41 26
      src/create-debugger.js
  6. 113 168
      src/create-debugger.spec.js

+ 17 - 8
README.md

@@ -47,13 +47,13 @@ debug('Got values %l.', ['foo', 10, true]);
 // Got values "foo", 10, true.
 // Got values "foo", 10, true.
 ```
 ```
 
 
-Дамп значений.
+Дамп значений (метод `debug.inspect`).
 
 
 ```js
 ```js
 import {createDebugger} from '@e22m4u/js-debug';
 import {createDebugger} from '@e22m4u/js-debug';
 
 
 const debug = createDebugger();
 const debug = createDebugger();
-debug({
+debug.inspect({
   email: 'john.doe@example.com',
   email: 'john.doe@example.com',
   phone: {
   phone: {
     mobile: '+1-555-123-4567',
     mobile: '+1-555-123-4567',
@@ -75,11 +75,11 @@ debug({
 import {createDebugger} from '@e22m4u/js-debug';
 import {createDebugger} from '@e22m4u/js-debug';
 
 
 const debug = createDebugger();
 const debug = createDebugger();
-debug({
+debug.inspect('Order details:', {
   orderId: 988,
   orderId: 988,
   date: '2023-10-27',
   date: '2023-10-27',
   totalAmount: 120.50,
   totalAmount: 120.50,
-}, 'Order details:');
+});
 
 
 // Order details:
 // Order details:
 // {
 // {
@@ -94,9 +94,15 @@ debug({
 ```js
 ```js
 import {createDebugger} from '@e22m4u/js-debug';
 import {createDebugger} from '@e22m4u/js-debug';
 
 
-const debug1 = createDebugger('myApp');
-const debug2 = createDebugger('myApp', 'myService');
-const debug3 = createDebugger('myApp:myService');
+// вызов createDebugger() без аргументов создает
+// отладчик с пустым пространством имен
+const debug1 = createDebugger();
+
+// пространство имен можно передать в первом
+// аргументе фабрики, как это показано ниже
+const debug2 = createDebugger('myApp');
+const debug3 = createDebugger('myApp', 'myService');
+const debug4 = createDebugger('myApp:myService');
 debug1('Hello world');
 debug1('Hello world');
 debug2('Hello world');
 debug2('Hello world');
 debug3('Hello world');
 debug3('Hello world');
@@ -186,12 +192,15 @@ import {createDebugger} from '@e22m4u/js-debug';
 const debug1 = createDebugger().withOffset(1);
 const debug1 = createDebugger().withOffset(1);
 const debug2 = createDebugger().withOffset(2);
 const debug2 = createDebugger().withOffset(2);
 const debug3 = createDebugger().withOffset(3);
 const debug3 = createDebugger().withOffset(3);
+const debug4 = createDebugger('myApp').withOffset(1);
 debug1('Hello world');
 debug1('Hello world');
 debug2('Hello world');
 debug2('Hello world');
 debug3('Hello world');
 debug3('Hello world');
+debug4('Hello world');
 // Hello world
 // Hello world
 //    Hello world
 //    Hello world
 //        Hello world
 //        Hello world
+// myApp       Hello world
 ```
 ```
 
 
 Комбинирование методов.
 Комбинирование методов.
@@ -212,7 +221,7 @@ const contact = {
 
 
 debug('Iterating over %v participants.', 10);
 debug('Iterating over %v participants.', 10);
 debugWo1('Looking for contacts of %v participant.', 1);
 debugWo1('Looking for contacts of %v participant.', 1);
-debugWo1(contact, 'Participant contacts found:');
+debugWo1.inspect('Participant contacts found:', contact);
 
 
 // myApp:myService:o3pk Iterating over 10 participants.
 // myApp:myService:o3pk Iterating over 10 participants.
 // myApp:myService:o3pk   Looking for contacts of 1 participant.
 // myApp:myService:o3pk   Looking for contacts of 1 participant.

+ 22 - 22
dist/cjs/index.cjs

@@ -259,29 +259,11 @@ function createDebugger(namespaceOrOptions = void 0, ...namespaceSegments) {
   function debugFn(messageOrData, ...args) {
   function debugFn(messageOrData, ...args) {
     if (!isDebuggerEnabled()) return;
     if (!isDebuggerEnabled()) return;
     const prefix = getPrefix();
     const prefix = getPrefix();
-    if (typeof messageOrData === "string") {
-      const multiString2 = (0, import_js_format2.format)(messageOrData, ...args);
-      const rows2 = multiString2.split("\n");
-      rows2.forEach((message) => {
-        prefix ? console.log(`${prefix} ${message}`) : console.log(message);
-      });
-      return;
-    }
-    const multiString = createColorizedDump(messageOrData);
+    const multiString = (0, import_js_format2.format)(messageOrData, ...args);
     const rows = multiString.split("\n");
     const rows = multiString.split("\n");
-    if (args.length) {
-      args.forEach((message) => {
-        prefix ? console.log(`${prefix} ${message}`) : console.log(message);
-      });
-      rows.forEach((message) => {
-        message = `${state.offsetStep}${message}`;
-        prefix ? console.log(`${prefix} ${message}`) : console.log(message);
-      });
-    } else {
-      rows.forEach((message) => {
-        prefix ? console.log(`${prefix} ${message}`) : console.log(message);
-      });
-    }
+    rows.forEach((message) => {
+      prefix ? console.log(`${prefix} ${message}`) : console.log(message);
+    });
   }
   }
   __name(debugFn, "debugFn");
   __name(debugFn, "debugFn");
   debugFn.withNs = function(namespace, ...args) {
   debugFn.withNs = function(namespace, ...args) {
@@ -323,6 +305,24 @@ function createDebugger(namespaceOrOptions = void 0, ...namespaceSegments) {
     stateCopy.envNsSegments = [];
     stateCopy.envNsSegments = [];
     return createDebugger(stateCopy);
     return createDebugger(stateCopy);
   };
   };
+  debugFn.inspect = function(valueOrDesc, ...args) {
+    if (!isDebuggerEnabled()) return;
+    const prefix = getPrefix();
+    let multiString = "";
+    if (typeof valueOrDesc === "string" && args.length) {
+      multiString += `${valueOrDesc}
+`;
+      const multilineDump = args.map((v) => createColorizedDump(v)).join("\n");
+      const dumpRows = multilineDump.split("\n");
+      multiString += dumpRows.map((v) => `${state.offsetStep}${v}`).join("\n");
+    } else {
+      multiString += [valueOrDesc, ...args].map((v) => createColorizedDump(v)).join("\n");
+    }
+    const rows = multiString.split("\n");
+    rows.forEach((message) => {
+      prefix ? console.log(`${prefix} ${message}`) : console.log(message);
+    });
+  };
   return debugFn;
   return debugFn;
 }
 }
 __name(createDebugger, "createDebugger");
 __name(createDebugger, "createDebugger");

+ 2 - 2
package.json

@@ -32,8 +32,8 @@
     "lint": "tsc && eslint ./src",
     "lint": "tsc && eslint ./src",
     "lint:fix": "tsc && eslint ./src --fix",
     "lint:fix": "tsc && eslint ./src --fix",
     "format": "prettier --write \"./src/**/*.js\"",
     "format": "prettier --write \"./src/**/*.js\"",
-    "test": "npm run lint && c8 --reporter=text-summary mocha",
-    "test:coverage": "npm run lint && c8 --reporter=text mocha",
+    "test": "npm run lint && c8 --reporter=text-summary mocha --bail",
+    "test:coverage": "npm run lint && c8 --reporter=text mocha --bail",
     "build:cjs": "rimraf ./dist/cjs && node --no-warnings=ExperimentalWarning build-cjs.js",
     "build:cjs": "rimraf ./dist/cjs && node --no-warnings=ExperimentalWarning build-cjs.js",
     "prepare": "husky"
     "prepare": "husky"
   },
   },

+ 17 - 9
src/create-debugger.d.ts

@@ -4,17 +4,16 @@
  */
  */
 export interface Debugger {
 export interface Debugger {
   /**
   /**
-   * Выводит отладочное сообщение или дамп данных.
+   * Вывод отладочного сообщения.
    *
    *
    * @param messageOrData Строка сообщения (с опциональными
    * @param messageOrData Строка сообщения (с опциональными
-   *   спецификаторами формата) или данные для дампа.
-   * @param args Дополнительные аргументы для форматирования сообщения
-   *   или дополнительные данные для вывода после дампа.
+   *   спецификаторами формата).
+   * @param args Дополнительные аргументы для интерполяции.
    */
    */
-  (messageOrData: unknown, ...args: any[]): void;
+  (message: unknown, ...args: any[]): void;
 
 
   /**
   /**
-   * Создает новый экземпляр отладчика с добавленным сегментом
+   * Создание нового экземпляра отладчика с добавленным сегментом
    * пространства имен.
    * пространства имен.
    *
    *
    * @param namespace Сегмент пространства имен для добавления.
    * @param namespace Сегмент пространства имен для добавления.
@@ -27,7 +26,7 @@ export interface Debugger {
   withNs(namespace: string, ...args: string[]): Debugger;
   withNs(namespace: string, ...args: string[]): Debugger;
 
 
   /**
   /**
-   * Создает новый экземпляр отладчика со статическим случайным хэшем,
+   * Создание нового экземпляра отладчика со статическим случайным хэшем,
    * добавляемым к префиксу.
    * добавляемым к префиксу.
    *
    *
    * @param hashLength Желаемая длина шестнадцатеричного хэша
    * @param hashLength Желаемая длина шестнадцатеричного хэша
@@ -39,7 +38,7 @@ export interface Debugger {
   withHash(hashLength?: number): Debugger;
   withHash(hashLength?: number): Debugger;
 
 
   /**
   /**
-   * Создает новый экземпляр отладчика с отступом для его сообщений.
+   * Создание нового экземпляра отладчика с отступом для его сообщений.
    *
    *
    * @param offsetSize Количество шагов отступа (положительное
    * @param offsetSize Количество шагов отступа (положительное
    *   целое число).
    *   целое число).
@@ -50,12 +49,21 @@ export interface Debugger {
   withOffset(offsetSize: number): Debugger;
   withOffset(offsetSize: number): Debugger;
 
 
   /**
   /**
-   * Создает новый экземпляр отладчика без пространства имен
+   * Создание нового экземпляра отладчика без пространства имен
    * из переменной окружения DEBUGGER_NAMESPACE.
    * из переменной окружения DEBUGGER_NAMESPACE.
    *
    *
    * @returns Новый экземпляр Debugger.
    * @returns Новый экземпляр Debugger.
    */
    */
   withoutEnvNs(): Debugger;
   withoutEnvNs(): Debugger;
+
+  /**
+   * Вывод дампа первого аргумента. Если передано два аргумента,
+   * то первый будет являться описанием для второго.
+   * 
+   * @param dataOrDescription Данные отладки или описание для второго аргумента.
+   * @param args Данные отладки (при наличии описания).
+   */
+  inspect(dataOrDescription: unknown, ...args: any[]): void;
 }
 }
 
 
 /**
 /**

+ 41 - 26
src/create-debugger.js

@@ -229,33 +229,14 @@ export function createDebugger(
   function debugFn(messageOrData, ...args) {
   function debugFn(messageOrData, ...args) {
     if (!isDebuggerEnabled()) return;
     if (!isDebuggerEnabled()) return;
     const prefix = getPrefix();
     const prefix = getPrefix();
-    if (typeof messageOrData === 'string') {
-      const multiString = format(messageOrData, ...args);
-      const rows = multiString.split('\n');
-      rows.forEach(message => {
-        prefix ? console.log(`${prefix} ${message}`) : console.log(message);
-      });
-      return;
-    }
-    const multiString = createColorizedDump(messageOrData);
+    const multiString = format(messageOrData, ...args);
     const rows = multiString.split('\n');
     const rows = multiString.split('\n');
-    // если дамп объекта имеет заголовочные сообщения передаваемые
-    // в аргументах данной функции, то после вывода этих сообщений
-    // к дампу добавляется один шаг смещения, чтобы визуально связать
-    // дамп с заголовочными сообщениями
-    if (args.length) {
-      args.forEach(message => {
-        prefix ? console.log(`${prefix} ${message}`) : console.log(message);
-      });
-      rows.forEach(message => {
-        message = `${state.offsetStep}${message}`;
-        prefix ? console.log(`${prefix} ${message}`) : console.log(message);
-      });
-    } else {
-      rows.forEach(message => {
-        prefix ? console.log(`${prefix} ${message}`) : console.log(message);
-      });
-    }
+    rows.forEach(message => {
+      // (!) вывод сообщения отладки
+      // для сообщения может быть определен префикс,
+      // содержащий пространства имен и хэш операции
+      prefix ? console.log(`${prefix} ${message}`) : console.log(message);
+    });
   }
   }
   // создание новой функции логирования
   // создание новой функции логирования
   // с дополнительным пространством имен
   // с дополнительным пространством имен
@@ -305,5 +286,39 @@ export function createDebugger(
     stateCopy.envNsSegments = [];
     stateCopy.envNsSegments = [];
     return createDebugger(stateCopy);
     return createDebugger(stateCopy);
   };
   };
+  // определение метода inspect, где значение первого аргумента будет
+  // использовано для дампа, а если передано два аргумента, то первый
+  // будет являться описанием для второго
+  debugFn.inspect = function (valueOrDesc, ...args) {
+    if (!isDebuggerEnabled()) return;
+    const prefix = getPrefix();
+    let multiString = '';
+    // если первый аргумент является строкой, при условии наличия
+    // других аргументов, то первое значение используется
+    // как заголовок
+    if (typeof valueOrDesc === 'string' && args.length) {
+      multiString += `${valueOrDesc}\n`;
+      // к дампу добавляется один шаг смещения,
+      // чтобы визуально связать дамп с заголовком
+      const multilineDump = args.map(v => createColorizedDump(v)).join('\n');
+      const dumpRows = multilineDump.split('\n');
+      multiString += dumpRows.map(v => `${state.offsetStep}${v}`).join('\n');
+    }
+    // если первый аргумент не является строкой,
+    // то все аргументы будут использованы
+    // как значения дампа
+    else {
+      multiString += [valueOrDesc, ...args]
+        .map(v => createColorizedDump(v))
+        .join('\n');
+    }
+    const rows = multiString.split('\n');
+    rows.forEach(message => {
+      // (!) вывод сообщения отладки
+      // для сообщения может быть определен префикс,
+      // содержащий пространства имен и хэш операции
+      prefix ? console.log(`${prefix} ${message}`) : console.log(message);
+    });
+  };
   return debugFn;
   return debugFn;
 }
 }

+ 113 - 168
src/create-debugger.spec.js

@@ -1,5 +1,4 @@
 import {expect} from 'chai';
 import {expect} from 'chai';
-import {inspect} from 'util';
 import {createSpy} from '@e22m4u/js-spy';
 import {createSpy} from '@e22m4u/js-spy';
 import {createDebugger} from './create-debugger.js';
 import {createDebugger} from './create-debugger.js';
 import {DEFAULT_OFFSET_STEP_SPACES} from './create-debugger.js';
 import {DEFAULT_OFFSET_STEP_SPACES} from './create-debugger.js';
@@ -104,56 +103,6 @@ describe('createDebugger', function () {
         'format list: "a", 1, true',
         'format list: "a", 1, true',
       );
       );
     });
     });
-
-    it('should output object inspection', function () {
-      process.env.DEBUG = 'obj';
-      const debug = createDebugger('obj');
-      const data = {a: 1, b: {c: 'deep'}};
-      debug(data);
-      const expectedInspect = inspect(data, {
-        colors: true,
-        depth: null,
-        compact: false,
-      });
-      const expectedLines = expectedInspect.split('\n');
-      expect(consoleLogSpy.callCount).to.equal(expectedLines.length);
-      expectedLines.forEach((line, index) => {
-        expect(stripAnsi(consoleLogSpy.getCall(index).args[0])).to.contain(
-          'obj ',
-        );
-        expect(stripAnsi(consoleLogSpy.getCall(index).args[0])).to.have.string(
-          stripAnsi(line),
-        );
-      });
-    });
-
-    it('should output object inspection with a description', function () {
-      process.env.DEBUG = 'objdesc';
-      const debug = createDebugger('objdesc');
-      const data = {email: 'test@example.com'};
-      const description = 'User data:';
-      debug(data, description);
-      const expectedInspect = inspect(data, {
-        colors: true,
-        depth: null,
-        compact: false,
-      });
-      const expectedLines = expectedInspect.split('\n');
-      const totalExpectedCalls = 1 + expectedLines.length;
-      expect(consoleLogSpy.callCount).to.equal(totalExpectedCalls);
-      expect(stripAnsi(consoleLogSpy.getCall(0).args[0])).to.equal(
-        `objdesc ${description}`,
-      );
-      expectedLines.forEach((line, index) => {
-        const callIndex = index + 1;
-        expect(stripAnsi(consoleLogSpy.getCall(callIndex).args[0])).to.contain(
-          'objdesc ',
-        );
-        expect(
-          stripAnsi(consoleLogSpy.getCall(callIndex).args[0]),
-        ).to.have.string(stripAnsi(line));
-      });
-    });
   });
   });
 
 
   describe('namespaces', function () {
   describe('namespaces', function () {
@@ -309,41 +258,6 @@ describe('createDebugger', function () {
       );
       );
     });
     });
 
 
-    it('should add extra offset to dump if it has heading messages', function () {
-      process.env.DEBUG = 'app:service';
-      const debug = createDebugger('app', 'service');
-      const dummyData = {foo: 'bar', baz: 'qux'};
-      debug(dummyData, 'Data:');
-      expect(consoleLogSpy.callCount).to.be.eq(5);
-      expect(stripAnsi(consoleLogSpy.getCall(0).args[0])).to.be.eq(
-        'app:service Data:',
-      );
-      expect(stripAnsi(consoleLogSpy.getCall(1).args[0])).to.match(
-        new RegExp(
-          '^app:service\\s{' + (DEFAULT_OFFSET_STEP_SPACES + 1) + '}\\{',
-        ),
-      );
-      expect(stripAnsi(consoleLogSpy.getCall(2).args[0])).to.match(
-        new RegExp(
-          '^app:service\\s{' +
-            (DEFAULT_OFFSET_STEP_SPACES + 1) +
-            "}  foo: 'bar',",
-        ),
-      );
-      expect(stripAnsi(consoleLogSpy.getCall(3).args[0])).to.match(
-        new RegExp(
-          '^app:service\\s{' +
-            (DEFAULT_OFFSET_STEP_SPACES + 1) +
-            "}  baz: 'qux'",
-        ),
-      );
-      expect(stripAnsi(consoleLogSpy.getCall(4).args[0])).to.match(
-        new RegExp(
-          '^app:service\\s{' + (DEFAULT_OFFSET_STEP_SPACES + 1) + '}\\}',
-        ),
-      );
-    });
-
     it('should throw error if createDebugger is called with invalid subsequent segment type', function () {
     it('should throw error if createDebugger is called with invalid subsequent segment type', function () {
       expect(() => createDebugger('app', 'valid', 123)).to.throw(
       expect(() => createDebugger('app', 'valid', 123)).to.throw(
         /Namespace segment must be a non-empty String/,
         /Namespace segment must be a non-empty String/,
@@ -589,29 +503,6 @@ describe('createDebugger', function () {
       );
       );
     });
     });
 
 
-    it('should apply offset to all lines of object inspection', function () {
-      process.env.DEBUG = 'offsetobj';
-      const debug = createDebugger('offsetobj').withOffset(1);
-      const data = {a: 1, b: 2};
-      debug(data);
-      const expectedInspect = inspect(data, {
-        colors: true,
-        depth: null,
-        compact: false,
-      });
-      const expectedLines = expectedInspect.split('\n');
-      expect(consoleLogSpy.callCount).to.equal(expectedLines.length);
-      expectedLines.forEach((line, index) => {
-        // предполагая, что offsetStep = '   '
-        expect(stripAnsi(consoleLogSpy.getCall(index).args[0])).to.match(
-          new RegExp('^offsetobj\\s{' + (DEFAULT_OFFSET_STEP_SPACES + 1) + '}'),
-        );
-        expect(stripAnsi(consoleLogSpy.getCall(index).args[0])).to.contain(
-          stripAnsi(line),
-        );
-      });
-    });
-
     it('should throw error if withOffset is called with invalid size', function () {
     it('should throw error if withOffset is called with invalid size', function () {
       const debug = createDebugger('app');
       const debug = createDebugger('app');
       expect(() => debug.withOffset(0)).to.throw(/must be a positive Number/);
       expect(() => debug.withOffset(0)).to.throw(/must be a positive Number/);
@@ -638,27 +529,6 @@ describe('createDebugger', function () {
         ),
         ),
       );
       );
     });
     });
-
-    it('should add extra offset to dump if it has heading messages', function () {
-      process.env.DEBUG = '*';
-      const debug = createDebugger();
-      const dummyData = {foo: 'bar', baz: 'qux'};
-      debug(dummyData, 'Data:');
-      expect(consoleLogSpy.callCount).to.be.eq(5);
-      expect(stripAnsi(consoleLogSpy.getCall(0).args[0])).to.be.eq('Data:');
-      expect(stripAnsi(consoleLogSpy.getCall(1).args[0])).to.match(
-        new RegExp('^\\s{' + DEFAULT_OFFSET_STEP_SPACES + '}\\{'),
-      );
-      expect(stripAnsi(consoleLogSpy.getCall(2).args[0])).to.match(
-        new RegExp('^\\s{' + DEFAULT_OFFSET_STEP_SPACES + "}  foo: 'bar',"),
-      );
-      expect(stripAnsi(consoleLogSpy.getCall(3).args[0])).to.match(
-        new RegExp('^\\s{' + DEFAULT_OFFSET_STEP_SPACES + "}  baz: 'qux'"),
-      );
-      expect(stripAnsi(consoleLogSpy.getCall(4).args[0])).to.match(
-        new RegExp('^\\s{' + DEFAULT_OFFSET_STEP_SPACES + '}\\}'),
-      );
-    });
   });
   });
 
 
   describe('combine', function () {
   describe('combine', function () {
@@ -679,44 +549,6 @@ describe('createDebugger', function () {
         ),
         ),
       );
       );
     });
     });
-
-    it('should combine features and output object correctly', function () {
-      process.env.DEBUG = 'app:svc';
-      const debug = createDebugger('app')
-        .withNs('svc')
-        .withHash(3)
-        .withOffset(1);
-      const data = {id: 123};
-      const description = 'Data:';
-      debug(data, description);
-      const expectedInspect = inspect(data, {
-        colors: true,
-        depth: null,
-        compact: false,
-      });
-      const expectedLines = expectedInspect.split('\n');
-      const totalExpectedCalls = 1 + expectedLines.length;
-      expect(consoleLogSpy.callCount).to.equal(totalExpectedCalls);
-      // предполагая, что offsetStep = '   '
-      expect(stripAnsi(consoleLogSpy.getCall(0).args[0])).to.match(
-        new RegExp(
-          '^app:svc:[a-f0-9]{3}\\s{' +
-            (DEFAULT_OFFSET_STEP_SPACES + 1) +
-            '}Data:$',
-        ),
-      );
-      expectedLines.forEach((line, index) => {
-        const callIndex = index + 1;
-        const logLine = stripAnsi(consoleLogSpy.getCall(callIndex).args[0]);
-        // предполагая, что offsetStep = '   '
-        expect(logLine).to.match(
-          new RegExp(
-            '^app:svc:[a-f0-9]{3}\\s{' + (DEFAULT_OFFSET_STEP_SPACES + 1) + '}',
-          ),
-        );
-        expect(logLine).to.contain(stripAnsi(line));
-      });
-    });
   });
   });
 
 
   describe('creation error', function () {
   describe('creation error', function () {
@@ -732,4 +564,117 @@ describe('createDebugger', function () {
       );
       );
     });
     });
   });
   });
+
+  describe('inspect method', function () {
+    it('should not output if debugger is disabled', function () {
+      process.env.DEBUG = 'other';
+      const debug = createDebugger('app');
+      debug.inspect({a: 1});
+      expect(consoleLogSpy.called).to.be.false;
+    });
+
+    it('should output a colorized dump of a single argument', function () {
+      process.env.DEBUG = 'app';
+      const debug = createDebugger('app');
+      const data = {user: {id: 1, name: 'test'}};
+      debug.inspect(data);
+      // createColorizedDump выводит многострочный результат,
+      // поэтому проверяем несколько вызовов console.log
+      expect(consoleLogSpy.callCount).to.be.above(1);
+      const firstLine = stripAnsi(consoleLogSpy.getCall(0).args[0]);
+      const secondLine = stripAnsi(consoleLogSpy.getCall(1).args[0]);
+      expect(firstLine).to.equal('app {');
+      // проверяем вторую строку с отступом,
+      // который добавляет createColorizedDump
+      expect(secondLine).to.match(/^app\s{3}user: \{$/);
+    });
+
+    it('should use the first argument as a description for the next ones', function () {
+      process.env.DEBUG = 'app';
+      const debug = createDebugger('app');
+      const data = {id: 123};
+      debug.inspect('User data:', data);
+      expect(consoleLogSpy.callCount).to.be.above(1);
+      const descriptionLine = stripAnsi(consoleLogSpy.getCall(0).args[0]);
+      const dumpLine = stripAnsi(consoleLogSpy.getCall(1).args[0]);
+      expect(descriptionLine).to.equal('app User data:');
+      // проверка, что к дампу добавлен дополнительный отступ
+      const expectedInternalOffset = ' '.repeat(DEFAULT_OFFSET_STEP_SPACES);
+      expect(dumpLine).to.equal(`app ${expectedInternalOffset}{`);
+    });
+
+    describe('combine', function () {
+      it('should include namespace from withNs() in inspect output', function () {
+        process.env.DEBUG = 'app:svc';
+        const debug = createDebugger('app').withNs('svc');
+        debug.inspect('Data:', {a: 1});
+        const descriptionLine = stripAnsi(consoleLogSpy.getCall(0).args[0]);
+        expect(descriptionLine).to.equal('app:svc Data:');
+      });
+
+      it('should include hash from withHash() in inspect output', function () {
+        process.env.DEBUG = 'app';
+        const debug = createDebugger('app').withHash(6);
+        debug.inspect({a: 1});
+        const firstLine = stripAnsi(consoleLogSpy.getCall(0).args[0]);
+        expect(firstLine).to.match(/^app:[a-f0-9]{6} \{$/);
+      });
+
+      it('should apply offset from withOffset() to inspect output', function () {
+        process.env.DEBUG = 'app';
+        const debug = createDebugger('app').withOffset(2);
+        debug.inspect('My Data:', {a: 1});
+        const offsetSpaces = ' '.repeat(DEFAULT_OFFSET_STEP_SPACES * 2);
+        const internalOffset = ' '.repeat(DEFAULT_OFFSET_STEP_SPACES);
+        const descriptionLine = stripAnsi(consoleLogSpy.getCall(0).args[0]);
+        const dumpLine = stripAnsi(consoleLogSpy.getCall(1).args[0]);
+        expect(descriptionLine).to.equal(`app${offsetSpaces} My Data:`);
+        // проверка применения отступа к дампу (префикс + внутренний отступ)
+        expect(dumpLine).to.equal(`app${offsetSpaces} ${internalOffset}{`);
+      });
+
+      it('should respect withoutEnvNs() in inspect output', function () {
+        process.env.DEBUG = '*';
+        process.env.DEBUGGER_NAMESPACE = 'myApp';
+        const debugWithEnv = createDebugger('api');
+        const debugWithoutEnv = debugWithEnv.withoutEnvNs();
+        // вызов inspect на исходном отладчике
+        debugWithEnv.inspect('With env');
+        expect(stripAnsi(consoleLogSpy.getCall(0).args[0])).to.equal(
+          "myApp:api 'With env'",
+        );
+        // вызов на новом отладчике
+        debugWithoutEnv.inspect('Without env');
+        expect(stripAnsi(consoleLogSpy.getCall(1).args[0])).to.equal(
+          "api 'Without env'",
+        );
+      });
+
+      it('should combine all modifiers for inspect output', function () {
+        process.env.DEBUG = '*';
+        process.env.DEBUGGER_NAMESPACE = 'myApp';
+        const debug = createDebugger('api')
+          .withNs('users')
+          .withHash(4)
+          .withOffset(1)
+          .withoutEnvNs();
+        debug.inspect('User List:', [{id: 1}, {id: 2}]);
+        const offsetSpaces = ' '.repeat(DEFAULT_OFFSET_STEP_SPACES * 1);
+        const internalOffset = ' '.repeat(DEFAULT_OFFSET_STEP_SPACES);
+        const descriptionLine = stripAnsi(consoleLogSpy.getCall(0).args[0]);
+        const dumpLine = stripAnsi(consoleLogSpy.getCall(1).args[0]);
+        // проверка отсутствия DEBUGGER_NAMESPACE
+        expect(descriptionLine).to.not.contain('myApp');
+        // проверка всей строки с помощью регулярного выражения
+        expect(descriptionLine).to.match(
+          new RegExp(`^api:users:[a-f0-9]{4}${offsetSpaces} User List:$`),
+        );
+        expect(dumpLine).to.match(
+          new RegExp(
+            `^api:users:[a-f0-9]{4}${offsetSpaces} ${internalOffset}\\[$`,
+          ),
+        );
+      });
+    });
+  });
 });
 });