%PDF- %PDF-
Mini Shell

Mini Shell

Direktori : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/lib/internal/test_runner/mock/
Upload File :
Create Path :
Current File : /home/vacivi36/vittasync.vacivitta.com.br/vittasync/node/lib/internal/test_runner/mock/mock.js

'use strict';
const {
  ArrayPrototypePush,
  ArrayPrototypeSlice,
  Error,
  FunctionPrototypeCall,
  ObjectDefineProperty,
  ObjectGetOwnPropertyDescriptor,
  ObjectGetPrototypeOf,
  Proxy,
  ReflectApply,
  ReflectConstruct,
  ReflectGet,
  SafeMap,
} = primordials;
const {
  codes: {
    ERR_INVALID_ARG_TYPE,
    ERR_INVALID_ARG_VALUE,
  },
} = require('internal/errors');
const { kEmptyObject } = require('internal/util');
const {
  validateBoolean,
  validateFunction,
  validateInteger,
  validateObject,
} = require('internal/validators');
const { MockTimers } = require('internal/test_runner/mock/mock_timers');

function kDefaultFunction() {}

class MockFunctionContext {
  #calls;
  #mocks;
  #implementation;
  #restore;
  #times;

  constructor(implementation, restore, times) {
    this.#calls = [];
    this.#mocks = new SafeMap();
    this.#implementation = implementation;
    this.#restore = restore;
    this.#times = times;
  }

  /**
   * Gets an array of recorded calls made to the mock function.
   * @returns {Array} An array of recorded calls.
   */
  get calls() {
    return ArrayPrototypeSlice(this.#calls, 0);
  }

  /**
   * Retrieves the number of times the mock function has been called.
   * @returns {number} The call count.
   */
  callCount() {
    return this.#calls.length;
  }

  /**
   * Sets a new implementation for the mock function.
   * @param {Function} implementation - The new implementation for the mock function.
   */
  mockImplementation(implementation) {
    validateFunction(implementation, 'implementation');
    this.#implementation = implementation;
  }

  /**
   * Replaces the implementation of the function only once.
   * @param {Function} implementation - The substitute function.
   * @param {number} [onCall] - The call index to be replaced.
   */
  mockImplementationOnce(implementation, onCall) {
    validateFunction(implementation, 'implementation');
    const nextCall = this.#calls.length;
    const call = onCall ?? nextCall;
    validateInteger(call, 'onCall', nextCall);
    this.#mocks.set(call, implementation);
  }

  /**
   * Restores the original function that was mocked.
   */
  restore() {
    const { descriptor, object, original, methodName } = this.#restore;

    if (typeof methodName === 'string') {
      // This is an object method spy.
      ObjectDefineProperty(object, methodName, descriptor);
    } else {
      // This is a bare function spy. There isn't much to do here but make
      // the mock call the original function.
      this.#implementation = original;
    }
  }

  /**
   * Resets the recorded calls to the mock function
   */
  resetCalls() {
    this.#calls = [];
  }

  /**
   * Tracks a call made to the mock function.
   * @param {object} call - The call details.
   */
  trackCall(call) {
    ArrayPrototypePush(this.#calls, call);
  }

  /**
   * Gets the next implementation to use for the mock function.
   * @returns {Function} The next implementation.
   */
  nextImpl() {
    const nextCall = this.#calls.length;
    const mock = this.#mocks.get(nextCall);
    const impl = mock ?? this.#implementation;

    if (nextCall + 1 === this.#times) {
      this.restore();
    }

    this.#mocks.delete(nextCall);
    return impl;
  }
}

const { nextImpl, restore, trackCall } = MockFunctionContext.prototype;
delete MockFunctionContext.prototype.trackCall;
delete MockFunctionContext.prototype.nextImpl;

class MockTracker {
  #mocks = [];
  #timers;

  /**
   * Returns the mock timers of this MockTracker instance.
   * @returns {MockTimers} The mock timers instance.
   */
  get timers() {
    this.#timers ??= new MockTimers();
    return this.#timers;
  }

  /**
   * Creates a mock function tracker.
   * @param {Function} [original] - The original function to be tracked.
   * @param {Function} [implementation] - An optional replacement function for the original one.
   * @param {object} [options] - Additional tracking options.
   * @param {number} [options.times=Infinity] - The maximum number of times the mock function can be called.
   * @returns {ProxyConstructor} The mock function tracker.
   */
  fn(
    original = function() {},
    implementation = original,
    options = kEmptyObject,
  ) {
    if (original !== null && typeof original === 'object') {
      options = original;
      original = function() {};
      implementation = original;
    } else if (implementation !== null && typeof implementation === 'object') {
      options = implementation;
      implementation = original;
    }

    validateFunction(original, 'original');
    validateFunction(implementation, 'implementation');
    validateObject(options, 'options');
    const { times = Infinity } = options;
    validateTimes(times, 'options.times');
    const ctx = new MockFunctionContext(implementation, { __proto__: null, original }, times);
    return this.#setupMock(ctx, original);
  }

  /**
   * Creates a method tracker for a specified object or function.
   * @param {(object | Function)} objectOrFunction - The object or function containing the method to be tracked.
   * @param {string} methodName - The name of the method to be tracked.
   * @param {Function} [implementation] - An optional replacement function for the original method.
   * @param {object} [options] - Additional tracking options.
   * @param {boolean} [options.getter=false] - Indicates whether this is a getter method.
   * @param {boolean} [options.setter=false] - Indicates whether this is a setter method.
   * @param {number} [options.times=Infinity] - The maximum number of times the mock method can be called.
   * @returns {ProxyConstructor} The mock method tracker.
   */
  method(
    objectOrFunction,
    methodName,
    implementation = kDefaultFunction,
    options = kEmptyObject,
  ) {
    validateStringOrSymbol(methodName, 'methodName');
    if (typeof objectOrFunction !== 'function') {
      validateObject(objectOrFunction, 'object');
    }

    if (implementation !== null && typeof implementation === 'object') {
      options = implementation;
      implementation = kDefaultFunction;
    }

    validateFunction(implementation, 'implementation');
    validateObject(options, 'options');

    const {
      getter = false,
      setter = false,
      times = Infinity,
    } = options;

    validateBoolean(getter, 'options.getter');
    validateBoolean(setter, 'options.setter');
    validateTimes(times, 'options.times');

    if (setter && getter) {
      throw new ERR_INVALID_ARG_VALUE(
        'options.setter', setter, "cannot be used with 'options.getter'",
      );
    }
    const descriptor = findMethodOnPrototypeChain(objectOrFunction, methodName);

    let original;

    if (getter) {
      original = descriptor?.get;
    } else if (setter) {
      original = descriptor?.set;
    } else {
      original = descriptor?.value;
    }

    if (typeof original !== 'function') {
      throw new ERR_INVALID_ARG_VALUE(
        'methodName', original, 'must be a method',
      );
    }

    const restore = { __proto__: null, descriptor, object: objectOrFunction, methodName };
    const impl = implementation === kDefaultFunction ?
      original : implementation;
    const ctx = new MockFunctionContext(impl, restore, times);
    const mock = this.#setupMock(ctx, original);
    const mockDescriptor = {
      __proto__: null,
      configurable: descriptor.configurable,
      enumerable: descriptor.enumerable,
    };

    if (getter) {
      mockDescriptor.get = mock;
      mockDescriptor.set = descriptor.set;
    } else if (setter) {
      mockDescriptor.get = descriptor.get;
      mockDescriptor.set = mock;
    } else {
      mockDescriptor.writable = descriptor.writable;
      mockDescriptor.value = mock;
    }

    ObjectDefineProperty(objectOrFunction, methodName, mockDescriptor);

    return mock;
  }

  /**
   * Mocks a getter method of an object.
   * This is a syntax sugar for the MockTracker.method with options.getter set to true
   * @param {object} object - The target object.
   * @param {string} methodName - The name of the getter method to be mocked.
   * @param {Function} [implementation] - An optional replacement function for the targeted method.
   * @param {object} [options] - Additional tracking options.
   * @param {boolean} [options.getter=true] - Indicates whether this is a getter method.
   * @param {boolean} [options.setter=false] - Indicates whether this is a setter method.
   * @param {number} [options.times=Infinity] - The maximum number of times the mock method can be called.
   * @returns {ProxyConstructor} The mock method tracker.
   */
  getter(
    object,
    methodName,
    implementation = kDefaultFunction,
    options = kEmptyObject,
  ) {
    if (implementation !== null && typeof implementation === 'object') {
      options = implementation;
      implementation = kDefaultFunction;
    } else {
      validateObject(options, 'options');
    }

    const { getter = true } = options;

    if (getter === false) {
      throw new ERR_INVALID_ARG_VALUE(
        'options.getter', getter, 'cannot be false',
      );
    }

    return this.method(object, methodName, implementation, {
      __proto__: null,
      ...options,
      getter,
    });
  }

  /**
   * Mocks a setter method of an object.
   * This function is a syntax sugar for MockTracker.method with options.setter set to true.
   * @param {object} object - The target object.
   * @param {string} methodName  - The setter method to be mocked.
   * @param {Function} [implementation] - An optional replacement function for the targeted method.
   * @param {object} [options] - Additional tracking options.
   * @param {boolean} [options.getter=false] - Indicates whether this is a getter method.
   * @param {boolean} [options.setter=true] - Indicates whether this is a setter method.
   * @param {number} [options.times=Infinity] - The maximum number of times the mock method can be called.
   * @returns {ProxyConstructor} The mock method tracker.
   */
  setter(
    object,
    methodName,
    implementation = kDefaultFunction,
    options = kEmptyObject,
  ) {
    if (implementation !== null && typeof implementation === 'object') {
      options = implementation;
      implementation = kDefaultFunction;
    } else {
      validateObject(options, 'options');
    }

    const { setter = true } = options;

    if (setter === false) {
      throw new ERR_INVALID_ARG_VALUE(
        'options.setter', setter, 'cannot be false',
      );
    }

    return this.method(object, methodName, implementation, {
      __proto__: null,
      ...options,
      setter,
    });
  }

  /**
   * Resets the mock tracker, restoring all mocks and clearing timers.
   */
  reset() {
    this.restoreAll();
    this.#timers?.reset();
    this.#mocks = [];
  }

  /**
   * Restore all mocks created by this MockTracker instance.
   */
  restoreAll() {
    for (let i = 0; i < this.#mocks.length; i++) {
      FunctionPrototypeCall(restore, this.#mocks[i]);
    }
  }

  #setupMock(ctx, fnToMatch) {
    const mock = new Proxy(fnToMatch, {
      __proto__: null,
      apply(_fn, thisArg, argList) {
        const fn = FunctionPrototypeCall(nextImpl, ctx);
        let result;
        let error;

        try {
          result = ReflectApply(fn, thisArg, argList);
        } catch (err) {
          error = err;
          throw err;
        } finally {
          FunctionPrototypeCall(trackCall, ctx, {
            __proto__: null,
            arguments: argList,
            error,
            result,
            // eslint-disable-next-line no-restricted-syntax
            stack: new Error(),
            target: undefined,
            this: thisArg,
          });
        }

        return result;
      },
      construct(target, argList, newTarget) {
        const realTarget = FunctionPrototypeCall(nextImpl, ctx);
        let result;
        let error;

        try {
          result = ReflectConstruct(realTarget, argList, newTarget);
        } catch (err) {
          error = err;
          throw err;
        } finally {
          FunctionPrototypeCall(trackCall, ctx, {
            __proto__: null,
            arguments: argList,
            error,
            result,
            // eslint-disable-next-line no-restricted-syntax
            stack: new Error(),
            target,
            this: result,
          });
        }

        return result;
      },
      get(target, property, receiver) {
        if (property === 'mock') {
          return ctx;
        }

        return ReflectGet(target, property, receiver);
      },
    });

    ArrayPrototypePush(this.#mocks, ctx);
    return mock;
  }
}

function validateStringOrSymbol(value, name) {
  if (typeof value !== 'string' && typeof value !== 'symbol') {
    throw new ERR_INVALID_ARG_TYPE(name, ['string', 'symbol'], value);
  }
}

function validateTimes(value, name) {
  if (value === Infinity) {
    return;
  }

  validateInteger(value, name, 1);
}

function findMethodOnPrototypeChain(instance, methodName) {
  let host = instance;
  let descriptor;

  while (host !== null) {
    descriptor = ObjectGetOwnPropertyDescriptor(host, methodName);

    if (descriptor) {
      break;
    }

    host = ObjectGetPrototypeOf(host);
  }

  return descriptor;
}

module.exports = { MockTracker };

Zerion Mini Shell 1.0