<commit_before>'use strict';

/**
 * Module dependencies
 */

// Public node modules.
const _ = require('lodash');

// Utils
const { models: { getValuePrimaryKey } } = require('strapi-utils');

module.exports = {
  getModel: function (model, plugin) {
    return _.get(strapi.plugins, [plugin, 'models', model]) || _.get(strapi, ['models', model]) || undefined;
  },

  update: async function (params) {
    const virtualFields = [];
    const response = await this
      .findOne({
        [this.primaryKey]: params[this.primaryKey] || params.id
      })
      .populate(this.associations.map(x => x.alias).join(' '))
      .lean();

    // Only update fields which are on this document.
    const values = params.parseRelationships === false ? params.values : Object.keys(JSON.parse(JSON.stringify(params.values))).reduce((acc, current) => {
      const association = this.associations.find(x => x.alias === current);
      const details = this._attributes[current];

      if (_.get(this._attributes, `${current}.isVirtual`) !== true && _.isUndefined(association)) {
        acc[current] = params.values[current];
      } else {
        switch (association.nature) {
          case 'oneWay':
            acc[current] = _.get(params.values[current], this.primaryKey, params.values[current]) || null;

            break;
          case 'oneToOne':
            if (response[current] !== params.values[current]) {
              const value = _.isNull(params.values[current]) ? response[current] : params.values;
              const recordId = _.isNull(params.values[current]) ? getValuePrimaryKey(value, this.primaryKey) : value[current];

              const model = module.exports.getModel(details.model || details.collection, details.plugin);

              // Remove relation in the user side.
              virtualFields.push(
                model
                  .findOne({ [model.primaryKey]: value[current] })
                  .populate(details.via)
                  .then(record => {
                    if (record && _.isObject(record[details.via])) {
                      return module.exports.update.call(this, {
                        id: getValuePrimaryKey(record[details.via], model.primaryKey),
                        values: {
                          [current]: null
                        },
                        parseRelationships: false
                      });
                    }

                    return Promise.resolve();
                  })
                  .then(() => {
                    return module.exports.update.call(model, {
                      id: getValuePrimaryKey(response[current] || {}, this.primaryKey) || value[current],
                      values: {
                        [details.via]: null
                      },
                      parseRelationships: false
                    });
                  })
                  .then(() => {
                    if (!_.isNull(params.values[current])) {
                      return module.exports.update.call(model, {
                        id: recordId,
                        values: {
                          [details.via]: getValuePrimaryKey(params, this.primaryKey) || null
                        },
                        parseRelationships: false
                      });
                    }

                    return Promise.resolve();
                  })
              );

              acc[current] = _.isNull(params.values[current]) ? null : value[current];
            }

            break;
          case 'oneToMany':
          case 'manyToOne':
          case 'manyToMany':
            if (association.nature === 'manyToMany' && details.dominant === true) {
              acc[current] = params.values[current];
            } else if (response[current] && _.isArray(response[current]) && current !== 'id') {
              // Records to add in the relation.
              const toAdd = _.differenceWith(params.values[current], response[current], (a, b) =>
                a[this.primaryKey].toString() === b[this.primaryKey].toString()
              );

              // Records to remove in the relation.
              const toRemove = _.differenceWith(response[current], params.values[current], (a, b) =>
                a[this.primaryKey].toString() === b[this.primaryKey].toString()
              )
                .filter(x => toAdd.find(y => x.id === y.id) === undefined);

              const model = module.exports.getModel(details.model || details.collection, details.plugin);

              // Push the work into the flow process.
              toAdd.forEach(value => {
                value = _.isString(value) ? { [this.primaryKey]: value } : value;

                if (association.nature === 'manyToMany' && !_.isArray(params.values[this.primaryKey] || params[this.primaryKey])) {
                  value[details.via] = (value[details.via] || [])
                    .concat([(params.values[this.primaryKey] || params[this.primaryKey])])
                    .filter(x => {
                      return x !== null && x !== undefined;
                    });
                } else {
                  value[details.via] = getValuePrimaryKey(params, this.primaryKey);
                }

                virtualFields.push(
                  model.findOne({
                    [model.primaryKey]: getValuePrimaryKey(value, model.primaryKey)
                  })
                    .populate([details.via])
                    .lean()
                    .then(entry => {
                      value[details.via] = entry[details.via].map(entry => _.get(entry, '_id', entry)).concat(value[details.via]);

                      return module.exports.addRelation.call(model, {
                        id: getValuePrimaryKey(value, this.primaryKey),
                        values: _.pick(value, [this.primaryKey, details.via]),
                        foreignKey: current
                      });
                    })
                );
              });

              toRemove.forEach(value => {
                value = _.isString(value) ? { [this.primaryKey]: value } : value;

                if (association.nature === 'manyToMany' && !_.isArray(params.values[this.primaryKey] || params[this.primaryKey])) {
                  value[details.via] = value[details.via].filter(x => _.toString(x) !== _.toString(params.values[this.primaryKey] || params[this.primaryKey]));
                } else {
                  value[details.via] = null;
                }

                virtualFields.push(
                  module.exports.removeRelation.call(model, {
                    id: getValuePrimaryKey(value, this.primaryKey),
                    values: _.pick(value, [this.primaryKey, details.via]),
                    foreignKey: current
                  })
                );
              });
            } else if (_.get(this._attributes, `${current}.isVirtual`) !== true) {
              acc[current] = params.values[current];
            }

            break;
          case 'manyMorphToMany':
          case 'manyMorphToOne':
            // Update the relational array.
            acc[current] = params.values[current].map(obj => {
              const globalId = obj.source && obj.source !== 'content-manager' ?
                strapi.plugins[obj.source].models[_.toLower(obj.ref)].globalId:
                strapi.models[_.toLower(obj.ref)].globalId;

              // Define the object stored in database.
              // The shape is this object is defined by the strapi-mongoose connector.
              return {
                ref: obj.refId,
                kind: globalId,
                [association.filter]: obj.field
              };
            });
            break;
          case 'oneToManyMorph':
          case 'manyToManyMorph': {
            const transformToArrayID = (array) => {
              if (_.isArray(array)) {
                return array.map(value => {
                  if (_.isPlainObject(value)) {
                    return getValuePrimaryKey(value, this.primaryKey);
                  }

                  return value;
                });
              }

              if (association.type === 'model' || (association.type === 'collection' && _.isObject(array))) {
                return _.isEmpty(array) ? [] : transformToArrayID([array]);
              }

              return [];
            };

            // Compare array of ID to find deleted files.
            const currentValue = transformToArrayID(response[current]).map(id => id.toString());
            const storedValue = transformToArrayID(params.values[current]).map(id => id.toString());

            const toAdd = _.difference(storedValue, currentValue);
            const toRemove = _.difference(currentValue, storedValue);

            const model = module.exports.getModel(details.model || details.collection, details.plugin);

            // Remove relations in the other side.
            toAdd.forEach(id => {
              virtualFields.push(
                module.exports.addRelationMorph.call(model, {
                  id,
                  alias: association.via,
                  ref: this.globalId,
                  refId: response._id,
                  field: association.alias
                })
              );
            });

            // Remove relations in the other side.
            toRemove.forEach(id => {
              virtualFields.push(
                module.exports.removeRelationMorph.call(model, {
                  id,
                  alias: association.via,
                  ref: this.globalId,
                  refId: response._id,
                  field: association.alias
                })
              );
            });
            break;
          }
          case 'oneMorphToOne':
          case 'oneMorphToMany':
            break;
          default:
        }
      }

      return acc;
    }, {});

    virtualFields.push(
      this
        .update({
          [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
        }, values, {
          strict: false
        })
    );

    // Update virtuals fields.
    await Promise.all(virtualFields);

    return await this
      .findOne({
        [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
      })
      .populate(this.associations.map(x => x.alias).join(' '));
  },

  addRelation: async function (params) {
    return module.exports.update.call(this, params);
  },

  removeRelation: async function (params) {
    return module.exports.update.call(this, params);
  },

  addRelationMorph: async function (params) {
    /*
      TODO:
      Test this part because it has been coded during the development of the upload feature.
      However the upload doesn't need this method. It only uses the `removeRelationMorph`.
    */

    let entry = (
      await this
        .findOne({
          [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
        })
    );

    if (entry) {
      entry = entry.toJSON();
    }

    const value = [];

    // Retrieve association.
    const association = this.associations.find(association => association.alias === params.alias);

    if (!association) {
      throw Error(`Impossible to create relationship with ${params.ref} (${params.refId})`);
    }

    // Resolve if the association is already existing.
    const isExisting = value.find(obj => {
      if (obj.kind === params.ref && obj.ref.toString() === params.refId.toString() && obj.field === params.field) {
        return true;
      }

      return false;
    });

    // Avoid duplicate.
    if (isExisting) {
      return Promise.resolve();
    }

    // Push new relation to the association array.
    value.push({
      ref: params.ref,
      refId: params.refId,
      kind: params.ref,
      field: params.field
    });

    entry[params.alias] = value;

    return module.exports.update.call(this, {
      id: params.id,
      values: entry
    });
  },

  removeRelationMorph: async function (params) {
    const entry = await this.findOne({
      [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
    });

    // Filter the association array and remove the association.
    entry[params.alias] = entry[params.alias].filter(obj => {
      if (obj.kind === params.ref && obj.ref.toString() === params.refId.toString() && obj.field === params.field) {
        return false;
      }

      return true;
    });

    return module.exports.update.call(this, {
      id: params.id,
      values: entry
    });
  }
};
<commit_msg>Fix oneToMany relationships<commit_after>'use strict';

/**
 * Module dependencies
 */

// Public node modules.
const _ = require('lodash');

// Utils
const { models: { getValuePrimaryKey } } = require('strapi-utils');

module.exports = {
  getModel: function (model, plugin) {
    return _.get(strapi.plugins, [plugin, 'models', model]) || _.get(strapi, ['models', model]) || undefined;
  },

  update: async function (params) {
    const virtualFields = [];
    const response = await this
      .findOne({
        [this.primaryKey]: params[this.primaryKey] || params.id
      })
      .populate(this.associations.map(x => x.alias).join(' '))
      .lean();

    // Only update fields which are on this document.
    const values = params.parseRelationships === false ? params.values : Object.keys(JSON.parse(JSON.stringify(params.values))).reduce((acc, current) => {
      const association = this.associations.find(x => x.alias === current);
      const details = this._attributes[current];

      if (_.get(this._attributes, `${current}.isVirtual`) !== true && _.isUndefined(association)) {
        acc[current] = params.values[current];
      } else {
        switch (association.nature) {
          case 'oneWay':
            acc[current] = _.get(params.values[current], this.primaryKey, params.values[current]) || null;

            break;
          case 'oneToOne':
            if (response[current] !== params.values[current]) {
              const value = _.isNull(params.values[current]) ? response[current] : params.values;
              const recordId = _.isNull(params.values[current]) ? getValuePrimaryKey(value, this.primaryKey) : value[current];

              const model = module.exports.getModel(details.model || details.collection, details.plugin);

              // Remove relation in the user side.
              virtualFields.push(
                model
                  .findOne({ [model.primaryKey]: value[current] })
                  .populate(details.via)
                  .then(record => {
                    if (record && _.isObject(record[details.via])) {
                      return module.exports.update.call(this, {
                        id: getValuePrimaryKey(record[details.via], model.primaryKey),
                        values: {
                          [current]: null
                        },
                        parseRelationships: false
                      });
                    }

                    return Promise.resolve();
                  })
                  .then(() => {
                    return module.exports.update.call(model, {
                      id: getValuePrimaryKey(response[current] || {}, this.primaryKey) || value[current],
                      values: {
                        [details.via]: null
                      },
                      parseRelationships: false
                    });
                  })
                  .then(() => {
                    if (!_.isNull(params.values[current])) {
                      return module.exports.update.call(model, {
                        id: recordId,
                        values: {
                          [details.via]: getValuePrimaryKey(params, this.primaryKey) || null
                        },
                        parseRelationships: false
                      });
                    }

                    return Promise.resolve();
                  })
              );

              acc[current] = _.isNull(params.values[current]) ? null : value[current];
            }

            break;
          case 'oneToMany':
          case 'manyToOne':
          case 'manyToMany':
            if (association.nature === 'manyToMany' && details.dominant === true) {
              acc[current] = params.values[current];
            } else if (response[current] && _.isArray(response[current]) && current !== 'id') {
              // Records to add in the relation.
              const toAdd = _.differenceWith(params.values[current], response[current], (a, b) =>
                a[this.primaryKey].toString() === b[this.primaryKey].toString()
              );

              // Records to remove in the relation.
              const toRemove = _.differenceWith(response[current], params.values[current], (a, b) =>
                a[this.primaryKey].toString() === b[this.primaryKey].toString()
              )
                .filter(x => toAdd.find(y => x.id === y.id) === undefined);

              const model = module.exports.getModel(details.model || details.collection, details.plugin);

              // Push the work into the flow process.
              toAdd.forEach(value => {
                value = _.isString(value) ? { [this.primaryKey]: value } : value;

                if (association.nature === 'manyToMany' && !_.isArray(params.values[this.primaryKey] || params[this.primaryKey])) {
                  value[details.via] = (value[details.via] || [])
                    .concat([(params.values[this.primaryKey] || params[this.primaryKey])])
                    .filter(x => {
                      return x !== null && x !== undefined;
                    });
                } else {
                  value[details.via] = getValuePrimaryKey(params, this.primaryKey);
                }

                virtualFields.push(
                  model.findOne({
                    [model.primaryKey]: getValuePrimaryKey(value, model.primaryKey)
                  })
                    .populate([details.via])
                    .lean()
                    .then(entry => {
                      if (_.isArray(entry[details.via])) {
                        value[details.via] = entry[details.via].map(entry => _.get(entry, '_id', entry)).concat(value[details.via]);
                      }

                      return module.exports.addRelation.call(model, {
                        id: getValuePrimaryKey(value, this.primaryKey),
                        values: _.pick(value, [this.primaryKey, details.via]),
                        foreignKey: current
                      });
                    })
                );
              });

              toRemove.forEach(value => {
                value = _.isString(value) ? { [this.primaryKey]: value } : value;

                if (association.nature === 'manyToMany' && !_.isArray(params.values[this.primaryKey] || params[this.primaryKey])) {
                  value[details.via] = value[details.via].filter(x => _.toString(x) !== _.toString(params.values[this.primaryKey] || params[this.primaryKey]));
                } else {
                  value[details.via] = null;
                }

                virtualFields.push(
                  module.exports.removeRelation.call(model, {
                    id: getValuePrimaryKey(value, this.primaryKey),
                    values: _.pick(value, [this.primaryKey, details.via]),
                    foreignKey: current
                  })
                );
              });
            } else if (_.get(this._attributes, `${current}.isVirtual`) !== true) {
              acc[current] = params.values[current];
            }

            break;
          case 'manyMorphToMany':
          case 'manyMorphToOne':
            // Update the relational array.
            acc[current] = params.values[current].map(obj => {
              const globalId = obj.source && obj.source !== 'content-manager' ?
                strapi.plugins[obj.source].models[_.toLower(obj.ref)].globalId:
                strapi.models[_.toLower(obj.ref)].globalId;

              // Define the object stored in database.
              // The shape is this object is defined by the strapi-mongoose connector.
              return {
                ref: obj.refId,
                kind: globalId,
                [association.filter]: obj.field
              };
            });
            break;
          case 'oneToManyMorph':
          case 'manyToManyMorph': {
            const transformToArrayID = (array) => {
              if (_.isArray(array)) {
                return array.map(value => {
                  if (_.isPlainObject(value)) {
                    return getValuePrimaryKey(value, this.primaryKey);
                  }

                  return value;
                });
              }

              if (association.type === 'model' || (association.type === 'collection' && _.isObject(array))) {
                return _.isEmpty(array) ? [] : transformToArrayID([array]);
              }

              return [];
            };

            // Compare array of ID to find deleted files.
            const currentValue = transformToArrayID(response[current]).map(id => id.toString());
            const storedValue = transformToArrayID(params.values[current]).map(id => id.toString());

            const toAdd = _.difference(storedValue, currentValue);
            const toRemove = _.difference(currentValue, storedValue);

            const model = module.exports.getModel(details.model || details.collection, details.plugin);

            // Remove relations in the other side.
            toAdd.forEach(id => {
              virtualFields.push(
                module.exports.addRelationMorph.call(model, {
                  id,
                  alias: association.via,
                  ref: this.globalId,
                  refId: response._id,
                  field: association.alias
                })
              );
            });

            // Remove relations in the other side.
            toRemove.forEach(id => {
              virtualFields.push(
                module.exports.removeRelationMorph.call(model, {
                  id,
                  alias: association.via,
                  ref: this.globalId,
                  refId: response._id,
                  field: association.alias
                })
              );
            });
            break;
          }
          case 'oneMorphToOne':
          case 'oneMorphToMany':
            break;
          default:
        }
      }

      return acc;
    }, {});

    virtualFields.push(
      this
        .update({
          [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
        }, values, {
          strict: false
        })
    );

    // Update virtuals fields.
    await Promise.all(virtualFields);

    return await this
      .findOne({
        [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
      })
      .populate(this.associations.map(x => x.alias).join(' '));
  },

  addRelation: async function (params) {
    return module.exports.update.call(this, params);
  },

  removeRelation: async function (params) {
    return module.exports.update.call(this, params);
  },

  addRelationMorph: async function (params) {
    /*
      TODO:
      Test this part because it has been coded during the development of the upload feature.
      However the upload doesn't need this method. It only uses the `removeRelationMorph`.
    */

    let entry = (
      await this
        .findOne({
          [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
        })
    );

    if (entry) {
      entry = entry.toJSON();
    }

    const value = [];

    // Retrieve association.
    const association = this.associations.find(association => association.alias === params.alias);

    if (!association) {
      throw Error(`Impossible to create relationship with ${params.ref} (${params.refId})`);
    }

    // Resolve if the association is already existing.
    const isExisting = value.find(obj => {
      if (obj.kind === params.ref && obj.ref.toString() === params.refId.toString() && obj.field === params.field) {
        return true;
      }

      return false;
    });

    // Avoid duplicate.
    if (isExisting) {
      return Promise.resolve();
    }

    // Push new relation to the association array.
    value.push({
      ref: params.ref,
      refId: params.refId,
      kind: params.ref,
      field: params.field
    });

    entry[params.alias] = value;

    return module.exports.update.call(this, {
      id: params.id,
      values: entry
    });
  },

  removeRelationMorph: async function (params) {
    const entry = await this.findOne({
      [this.primaryKey]: getValuePrimaryKey(params, this.primaryKey)
    });

    // Filter the association array and remove the association.
    entry[params.alias] = entry[params.alias].filter(obj => {
      if (obj.kind === params.ref && obj.ref.toString() === params.refId.toString() && obj.field === params.field) {
        return false;
      }

      return true;
    });

    return module.exports.update.call(this, {
      id: params.id,
      values: entry
    });
  }
};
<|endoftext|>


<commit_before>import React from 'react';

const getUniqueId = ((id) => () => 'Tab__' + ++id)(0);

export default class Tab {
  constructor(props) {
    this.props = Object.freeze(props);
    this.key = props.key || getUniqueId();
  }

  get file() {
    return this.props.getFile();
  }

  get component() {
    return this.props.component || this.file.component;
  }

  get isSelected() {
    return !!this.props.isSelected;
  }

  get label() {
    const { file } = this;
    if (file.is('markdown') && this.props.component) {
      return file.header;
    }
    return file.plane + file.ext;
  }

  is(tab) {
    if (!tab.file || !this.file) {
      return false;
    }
    return (tab.key === this.key) ||
      tab.file.key === this.file.key && tab.component === this.component;
  };

  select(isSelected) {
    const props = Object.assign({}, this.props, {
      key: this.key,
      isSelected,
    });
    return new Tab(props);
  }

  renderContent(props) {
    if (!this.file) {
      return null;
    }
    return (
      <this.component
        file={this.file}
        tab={this}
        {...props}
      />
    );
  }
}
<commit_msg>Not found tab<commit_after>import React from 'react';

import { SourceFile } from '../File/';

const getUniqueId = ((id) => () => 'Tab__' + ++id)(0);

export default class Tab {
  constructor(props) {
    this.props = Object.freeze(props);
    this.key = props.key || getUniqueId();
  }

  get file() {
    return this.props.getFile() || new SourceFile({
      name: 'Not Found',
      type: 'text/plane',
      text: 'File Not Found :-/',
    });
  }

  get component() {
    return this.props.component || this.file.component;
  }

  get isSelected() {
    return !!this.props.isSelected;
  }

  get label() {
    const { file } = this;
    if (file.is('markdown') && this.props.component) {
      return file.header;
    }
    return file.plane + file.ext;
  }

  is(tab) {
    if (!tab.file || !this.file) {
      return false;
    }
    return (tab.key === this.key) ||
      tab.file.key === this.file.key && tab.component === this.component;
  };

  select(isSelected) {
    const props = Object.assign({}, this.props, {
      key: this.key,
      isSelected,
    });
    return new Tab(props);
  }

  renderContent(props) {
    if (!this.file) {
      return null;
    }
    return (
      <this.component
        file={this.file}
        tab={this}
        {...props}
      />
    );
  }
}
<|endoftext|>


<commit_before>'use strict';

function Hooks(config, sessionId) {}

/**
 * Method to generate a unique digest which can identify a window, change the node.digest if you want.
 * @Params: $platform the name of the platform "iOS/android/web"
 * @Params: $source the raw json source of the current page
 * @Params: $node the node which needs to settle digest
 * @Returns: true to indicate the action has been handled and the default logic will not execute
 * to indicate the action has been handled
 * */
Hooks.prototype.checkDigest = function(platform, source, node, crawler) {
  return false;
};

/**
 * Method to sort a list of actions which will be later bind to a crawling node object, return the list of actions.
 * @Params: actions the array of actions which can be further sorted.
 * @Params: crawler the crawler instance which contains the context information as well as crawler config.
 * @Returns: actions the sorted actions which should be bind to the crawling node.
 * */
Hooks.prototype.sortActionPriority = function(actions, crawler) {
  return actions;
};

/**
 * Method to perform action for the current platform.
 * @Params: actions the actions which belongs to current active node, user can determine the priority of action execution
 * @Params: crawler the crawler instance which contains the context information as well as crawler config
 * @Returns: true to indicate the action has been handled and the default logic will not execute
 * */
Hooks.prototype.performAction = function(actions, crawler) {
  return null;
};

/**
 * Method to intercept the crawling process after an specific action has been performed
 * @Params: action the action which belongs to current active node, and has just been performed
 * @Params: crawler the crawler instance which contains the context information as well as crawler config
 * @Params: resolve during the calling of this function, the overall crawling process is pending until the resolve is finally called
 * */
Hooks.prototype.afterActionPerformed = function(action, crawler, resolve) {
  resolve();
};

/**
 * Method to analysis and insert tab nodes for a master-detail view structure.
 * @Params: sourceArray the array of elements which belongs to the candidate tab node.
 * @Params: crawler the crawler instance which contains the context information as well as crawler config
 * @Returns: true to indicate the action has been handled and the default logic will not execute
 * */
Hooks.prototype.insertTabNode = function (sourceArray, crawler) {
  return false;
};

exports.Hooks = Hooks;<commit_msg>update hook interface<commit_after>'use strict';

function Hooks(config, sessionId) {}

/**
 * Method to generate a unique digest which can identify a window, change the node.digest if you want.
 * @Params: $platform the name of the platform "iOS/android/web"
 * @Params: $source the raw json source of the current page
 * @Params: $node the node which needs to settle digest
 * @Returns: true to indicate the action has been handled and the default logic will not execute
 * to indicate the action has been handled
 * */
Hooks.prototype.checkDigest = function(platform, source, node, crawler) {
  return false;
};

/**
 * Method to sort a list of actions which will be later bind to a crawling node object, return the list of actions.
 * @Params: actions the array of actions which can be further sorted.
 * @Params: crawler the crawler instance which contains the context information as well as crawler config.
 * @Returns: actions the sorted actions which should be bind to the crawling node.
 * */
Hooks.prototype.sortActionPriority = function(actions, crawler) {
  return actions;
};

/**
 * Method to perform action for the current platform.
 * @Params: action the action which belongs to current active node
 * @Params: crawler the crawler instance which contains the context information as well as crawler config
 * @Returns: a Promise to indicate the action has been handled and the default logic will not execute
 * */
Hooks.prototype.performAction = function(action, crawler) {
  return null;
};

/**
 * Method to intercept the crawling process after an specific action has been performed
 * @Params: action the action which belongs to current active node, and has just been performed
 * @Params: crawler the crawler instance which contains the context information as well as crawler config
 * @Params: resolve during the calling of this function, the overall crawling process is pending until the resolve is finally called
 * */
Hooks.prototype.afterActionPerformed = function(action, crawler, resolve) {
  resolve();
};

/**
 * Method to analysis and insert tab nodes for a master-detail view structure.
 * @Params: sourceArray the array of elements which belongs to the candidate tab node.
 * @Params: crawler the crawler instance which contains the context information as well as crawler config
 * @Returns: true to indicate the action has been handled and the default logic will not execute
 * */
Hooks.prototype.insertTabNode = function (sourceArray, crawler) {
  return false;
};

exports.Hooks = Hooks;<|endoftext|>


<commit_before>const path = require('path')
const spawn = require('child_process').spawn
const EventEmitter = require('events')

class CLIInstance extends EventEmitter {
  constructor(cmd, args) {
    super()
    this._finished = false
    this._error = null
    this._entries = []
    this._stdout = ''
    this._stderr = ''

    try {
      this._childProcess = spawn(cmd, args)
      this._listenToProcessEvents()
    } catch (err) {
      this._emitDone(err)
    }
  }

  _emitDone(err) {
    this._finished = true
    if (err) {
      this._error = err
      this.emit('done', {failed: true, err})
    } else {
      this.emit('done', {failed: true, entries: this._entries})
    }
  }

  _processChunk(chunk) {
    this._stdout += chunk

    const lines = chunk.toString().split('\n')
    lines.forEach(line => {
      if (!line) {
        return
      }

      try {
        const message = JSON.parse(line)
        if (message.type === 'entryFinished') {
          this._entries.push(message.data)
        } else if (message.type === 'entryErrored') {
          this._entries.push(Object.assign({failed: true}, message.data))
        }
      } catch (err) {
        this.emit('error', err)
      }
    })
  }

  _listenToProcessEvents() {
    this._childProcess.stdout.on('data', chunk => this._processChunk(chunk))
    this._childProcess.stderr.on('data', s => this._stderr += s)
    this._childProcess.on('error', err => this._emitDone(err))
    this._childProcess.on('exit', code => {
      if (code === 0) {
        this._emitDone()
      } else {
        const err = new Error(`Process exited with exit code ${code}`)
        err.stdout = this._stdout
        err.stderr = this._stderr
        this._emitDone(err)
      }
    })
  }

  _entriesToPromise(options = {}) {
    const failedEntry = this._entries.find(entry => entry.failed)
    if (options.failLoudly && failedEntry) {
      const error = new Error(failedEntry.message)
      error.originalStack = error.stack
      return Promise.reject(error)
    }

    return Promise.resolve(this._entries)
  }

  waitForExit(options) {
    if (this._finished) {
      return this._error ?
        Promise.reject(this._error) :
        this._entriesToPromise(options)
    }

    return new Promise(resolve => this.once('done', resolve))
      .then(() => this.waitForExit(options))
  }
}

class CLIClient {
  constructor(options) {
    this._options = Object.assign({
      executablePath: path.join(__dirname, '../bin/index.js'),
    }, options)
  }

  run(config) {
    const args = [
      '-c',
      typeof config === 'string' ? config : JSON.stringify(config),
      '--reporter=json',
    ]

    return new CLIInstance(this._options.executablePath, args)
  }
}

module.exports = CLIClient
<commit_msg>chore: run prettier<commit_after>const path = require('path')
const spawn = require('child_process').spawn
const EventEmitter = require('events')

class CLIInstance extends EventEmitter {
  constructor(cmd, args) {
    super()
    this._finished = false
    this._error = null
    this._entries = []
    this._stdout = ''
    this._stderr = ''

    try {
      this._childProcess = spawn(cmd, args)
      this._listenToProcessEvents()
    } catch (err) {
      this._emitDone(err)
    }
  }

  _emitDone(err) {
    this._finished = true
    if (err) {
      this._error = err
      this.emit('done', {failed: true, err})
    } else {
      this.emit('done', {failed: true, entries: this._entries})
    }
  }

  _processChunk(chunk) {
    this._stdout += chunk

    const lines = chunk.toString().split('\n')
    lines.forEach(line => {
      if (!line) {
        return
      }

      try {
        const message = JSON.parse(line)
        if (message.type === 'entryFinished') {
          this._entries.push(message.data)
        } else if (message.type === 'entryErrored') {
          this._entries.push(Object.assign({failed: true}, message.data))
        }
      } catch (err) {
        this.emit('error', err)
      }
    })
  }

  _listenToProcessEvents() {
    this._childProcess.stdout.on('data', chunk => this._processChunk(chunk))
    this._childProcess.stderr.on('data', s => (this._stderr += s))
    this._childProcess.on('error', err => this._emitDone(err))
    this._childProcess.on('exit', code => {
      if (code === 0) {
        this._emitDone()
      } else {
        const err = new Error(`Process exited with exit code ${code}`)
        err.stdout = this._stdout
        err.stderr = this._stderr
        this._emitDone(err)
      }
    })
  }

  _entriesToPromise(options = {}) {
    const failedEntry = this._entries.find(entry => entry.failed)
    if (options.failLoudly && failedEntry) {
      const error = new Error(failedEntry.message)
      error.originalStack = error.stack
      return Promise.reject(error)
    }

    return Promise.resolve(this._entries)
  }

  waitForExit(options) {
    if (this._finished) {
      return this._error ? Promise.reject(this._error) : this._entriesToPromise(options)
    }

    return new Promise(resolve => this.once('done', resolve)).then(() => this.waitForExit(options))
  }
}

class CLIClient {
  constructor(options) {
    this._options = Object.assign(
      {
        executablePath: path.join(__dirname, '../bin/index.js'),
      },
      options,
    )
  }

  run(config) {
    const args = [
      '-c',
      typeof config === 'string' ? config : JSON.stringify(config),
      '--reporter=json',
    ]

    return new CLIInstance(this._options.executablePath, args)
  }
}

module.exports = CLIClient
<|endoftext|>


<commit_before>$.ajaxSetup({
    cache: false,
    contentType: 'text/plain',
    dataType: 'json',
    headers: { 'X-WebKDC-Request' : 'OK' },
    type: 'POST',
});

KDC = {};

KDC.urlBase = '/kdc/v1/';
KDC.realm = 'ATHENA.MIT.EDU'; // XXX

KDC.toBase64 = function(str) {
    return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Latin1.parse(str));
};

KDC.fromBase64 = function(str) {
    return CryptoJS.enc.Latin1.stringify(CryptoJS.enc.Base64.parse(str));
};

KDC.asReq = function(username, success, error) {
    var asReq = {};
    asReq.pvno = krb.pvno;
    asReq.msgType = krb.KRB_MT_AS_REQ;
    // asReq.padata = []; // Omit if empty.

    // FIXME: This is obnoxious. Also constants.
    asReq.reqBody = {};
    asReq.reqBody.kdcOptions = [];
    for(var i = 0; i < 32; i++)
        asReq.reqBody.kdcOptions.push(0);
    asReq.reqBody.kdcOptions[krb.KDCOptions.forwardable] = 1;
    asReq.reqBody.kdcOptions[krb.KDCOptions.proxiable] = 1;
    asReq.reqBody.kdcOptions[krb.KDCOptions.renewable_ok] = 1;

    asReq.reqBody.principalName = {};
    asReq.reqBody.principalName.nameType = krb.KRB_NT_PRINCIPAL;
    asReq.reqBody.principalName.nameString = [ username ];

    asReq.reqBody.realm = KDC.realm;

    asReq.reqBody.sname = {};
    asReq.reqBody.sname.nameType = krb.KRB_NT_SRV_INST;
    asReq.reqBody.sname.nameString = [ 'krbtgt', KDC.realm ];

    var now = new Date();
    now.setUTCMilliseconds(0);
    var later = new Date(now.getUTCFullYear(),
                         now.getUTCMonth(),
                         now.getUTCDay() + 1,
                         now.getUTCHours(),
                         now.getUTCMinutes(),
                         now.getUTCSeconds());
    asReq.reqBody.from = now;
    asReq.reqBody.till = later;
    // FIXME: Cryptographically secure nonce.
    asReq.reqBody.nonce = Math.floor(Math.random() * (1<<32));
    asReq.reqBody.etype = [18, 17, 16, 23, 1, 3, 2];

    $.ajax(KDC.urlBase + 'AS_REQ', {
        data: KDC.toBase64(krb.AS_REQ.encodeDER(asReq)),
        error: function(xhr, status, error) {
            var msg = status || 'unknown error';
            if(error)
                msg += ': ' + error;
            error(msg);
        },
        success: function(data, status, xhr) {
            switch(data.status) {
                case 'ERROR':
                    error(data.msg);
                    break;
                case 'TIMEOUT':
                    error('KDC connection timed out');
                    break;
                case 'OK':
                    var der = KDC.fromBase64(data.reply);
                    success(krb.AS_REP_OR_ERROR.decodeDER(der)[1]);
                    break;
            }
        },
    });
};
<commit_msg>Some TODOs<commit_after>$.ajaxSetup({
    cache: false,
    contentType: 'text/plain',
    dataType: 'json',
    headers: { 'X-WebKDC-Request' : 'OK' },
    type: 'POST',
});

KDC = {};

KDC.urlBase = '/kdc/v1/';
KDC.realm = 'ATHENA.MIT.EDU'; // XXX

KDC.toBase64 = function(str) {
    return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Latin1.parse(str));
};

KDC.fromBase64 = function(str) {
    return CryptoJS.enc.Latin1.stringify(CryptoJS.enc.Base64.parse(str));
};

KDC.asReq = function(username, success, error) {
    var asReq = {};
    asReq.pvno = krb.pvno;
    asReq.msgType = krb.KRB_MT_AS_REQ;
    // TODO: Implement pre-authentication and everything.
    // asReq.padata = []; // Omit if empty.

    // FIXME: This is obnoxious. Also constants.
    asReq.reqBody = {};
    asReq.reqBody.kdcOptions = [];
    for(var i = 0; i < 32; i++)
        asReq.reqBody.kdcOptions.push(0);
    // TODO: Pick a reasonable set of keys. These are just taken from
    // a wireshark trace.
    asReq.reqBody.kdcOptions[krb.KDCOptions.forwardable] = 1;
    asReq.reqBody.kdcOptions[krb.KDCOptions.proxiable] = 1;
    asReq.reqBody.kdcOptions[krb.KDCOptions.renewable_ok] = 1;

    asReq.reqBody.principalName = {};
    asReq.reqBody.principalName.nameType = krb.KRB_NT_PRINCIPAL;
    asReq.reqBody.principalName.nameString = [ username ];

    asReq.reqBody.realm = KDC.realm;

    asReq.reqBody.sname = {};
    asReq.reqBody.sname.nameType = krb.KRB_NT_SRV_INST;
    asReq.reqBody.sname.nameString = [ 'krbtgt', KDC.realm ];

    var now = new Date();
    now.setUTCMilliseconds(0);
    var later = new Date(now.getUTCFullYear(),
                         now.getUTCMonth(),
                         now.getUTCDay() + 1,
                         now.getUTCHours(),
                         now.getUTCMinutes(),
                         now.getUTCSeconds());
    asReq.reqBody.from = now;
    asReq.reqBody.till = later;
    // FIXME: Cryptographically secure nonce.
    asReq.reqBody.nonce = Math.floor(Math.random() * (1<<32));
    asReq.reqBody.etype = [18, 17, 16, 23, 1, 3, 2];

    $.ajax(KDC.urlBase + 'AS_REQ', {
        data: KDC.toBase64(krb.AS_REQ.encodeDER(asReq)),
        error: function(xhr, status, error) {
            var msg = status || 'unknown error';
            if(error)
                msg += ': ' + error;
            error(msg);
        },
        success: function(data, status, xhr) {
            switch(data.status) {
                case 'ERROR':
                    error(data.msg);
                    break;
                case 'TIMEOUT':
                    error('KDC connection timed out');
                    break;
                case 'OK':
                    var der = KDC.fromBase64(data.reply);
                    success(krb.AS_REP_OR_ERROR.decodeDER(der)[1]);
                    break;
            }
        },
    });
};
<|endoftext|>


<commit_before>var yaml = require('js-yaml')
var yarn = require('@yarnpkg/lockfile')

function splitNameAndVersion(nameAndVersion) {
  var nameAndVersionWithoutLeadingAt = nameAndVersion.replace(/^@/, '')

  var sep = nameAndVersionWithoutLeadingAt.split('@');
  var name = sep[0];
  var version = sep.slice(1, sep.length).join('@');

  if (/^@/.test(nameAndVersion)) {
    name = '@' + name;
  }

  return [name, version];
}

function getNameFromVersionString(nameAndVersion) {
  return splitNameAndVersion(nameAndVersion)[0];
}
exports.getNameFromVersionString = getNameFromVersionString;

function getVersionFromVersionString(nameAndVersion) {
  return splitNameAndVersion(nameAndVersion)[1];
}
exports.getVersionFromVersionString = getVersionFromVersionString;

function isYarnLockFileV2(manifest) {
  return manifest.match(/__metadata:/)
}

exports.parseDependencies = function(manifest) {
  var deps = []

  if (isYarnLockFileV2(manifest)) {
    var dependencies = yaml.load(manifest, {
      json: true,
      schema: yaml.FAILSAFE_SCHEMA,
    });
    Object.keys(dependencies).forEach((dep) => {
      if (dep === '__metadata') { return; }
      deps.push({
        name: getNameFromVersionString(dep),
        version: dependencies[dep].version,
        requirement: getVersionFromVersionString(dep), // TODO: test what happens with tweetnacl double requirement here?
        type: 'runtime'
      })
    })
  } else {
    var dependencies = yarn.parse(manifest).object
    Object.keys(dependencies).forEach((dep) => {
      deps.push({
        name: getNameFromVersionString(dep),
        version: dependencies[dep].version,
        requirement: getVersionFromVersionString(dep),
        type: 'runtime'
      })
    })
  }

  return deps;
}

<commit_msg>Cleanup<commit_after>var yaml = require('js-yaml')
var yarn = require('@yarnpkg/lockfile')

function splitNameAndVersion(nameAndVersion) {
  var nameAndVersionWithoutLeadingAt = nameAndVersion.replace(/^@/, '')

  var sep = nameAndVersionWithoutLeadingAt.split('@');
  var name = sep[0];
  var version = sep.slice(1, sep.length).join('@');

  if (/^@/.test(nameAndVersion)) {
    name = '@' + name;
  }

  return [name, version];
}

function getNameFromVersionString(nameAndVersion) {
  return splitNameAndVersion(nameAndVersion)[0];
}
exports.getNameFromVersionString = getNameFromVersionString;

function getVersionFromVersionString(nameAndVersion) {
  return splitNameAndVersion(nameAndVersion)[1];
}
exports.getVersionFromVersionString = getVersionFromVersionString;

function isYarnLockFileV2(manifest) {
  return manifest.match(/__metadata:/)
}

exports.parseDependencies = function(manifest) {
  if (isYarnLockFileV2(manifest)) {
    var dependencies = yaml.load(manifest, {
      onWarning: function(err) { console.log('YAML parsing error: ', err) },
      json: true, // JSON compat mode will overwrite dupe entries instead of throw an error.
      schema: yaml.FAILSAFE_SCHEMA,
    });
  } else {
    var dependencies = yarn.parse(manifest).object
  }

  var deps = []
  Object.keys(dependencies).forEach((dep) => {
    if (dep === '__metadata') { return; }
    deps.push({
      name: getNameFromVersionString(dep),
      version: dependencies[dep].version,
      requirement: getVersionFromVersionString(dep), // TODO: parse  tweetnacl double requirement example properly here, and use comparator intersection set (https://classic.yarnpkg.com/lang/en/docs/dependency-versions/#toc-intersections)
      type: 'runtime'
    })
  })

  return deps;
}

<|endoftext|>


<commit_before>export { AsyncWorker_Resulter as Resulter };

//import { PromiseResolveReject } from "./AsyncWorker_PromiseResolveReject.js";
//import { processingId_PromiseResolveRejectArray_Map }
//  from "./AsyncWorker_PromiseResolveReject.js";

/**
 * An async generator as the consumer of the processingId's PromiseResolveRejectArray.
 *
 * @member {PromiseResolveReject[]} PromiseResolveRejectArray
 *   All promises waiting for WorkerBody's result of the processing.
 */
class AsyncWorker_Resulter {

  /** */
  constructor( processingId, processingId_PromiseResolveRejectArray_Map ) {
    this.processingId = processingId;
    this.PromiseResolveRejectArray = new Array();
    this.processingId_PromiseResolveRejectArray_Map
      = processingId_PromiseResolveRejectArray_Map;
  }

  /**
   * Note: This .next() should be called until promise resolved to { done: true }.
   *       Otherwise, this resulter will not be removed from
   *       processingId_PromiseResolveRejectArray_Map. This will result in memory
   *       not been released.
   *
   * @return {Promise}
   *   Return a promise resolved to { done, value } which represents the WorkerBody's
   * result of the processing.
   */
  next() {
    let resulter
      = this.processingId_PromiseResolveRejectArray_Map.map.get( this.processingId );
    if ( !resulter ) {
      throw Error( `AsyncWorker.Resulter.next(): `
        + `processingId=${processingId}. `
        + `AsyncWorker.Resulter not found in `
        + `processingId_PromiseResolveRejectArray_Map.`
      );
      return { done: true }; // No pending promise for the processing. (should not happen)
    }

    if ( resulter != this ) {
      throw Error( `AsyncWorker.Resulter.next(): `
        + `processingId=${processingId}. `
        + `AsyncWorker.Resulter in `
        + `processingId_PromiseResolveRejectArray_Map should be this.`
      );
      return { done: true };
    }

    // PromiseResolveRejectArray should never be empty. It should has at least
    // one promise for this resulter to yield/return.
    //
    if ( this.PromiseResolveRejectArray.length < 1 ) {
      throw Error( `AsyncWorker.Resulter.next(): `
        + `processingId=${processingId}. `
        + `PromiseResolveRejectArray should never be empty.`
      );
      return { done: true };
    }

    // 0. Always yield the first promise.
    let thePromiseResolveReject;
    do {
      thePromiseResolveReject = this.PromiseResolveRejectArray[ 0 ];

      // 1. Prepare the next promise.

      // 1.1 The first pending promise will be yielded again and again
      //     (until it has been fulfilled and handled by here).
      if ( thePromiseResolveReject.pending )
        break;

      // 1.2 Otherwise, the promise has been fulfilled. Remove it so that it will not
      //     be yielded again in the future.
      this.PromiseResolveRejectArray.shift();

//!!! ...unfinished... (2022/09/14)
    } while ( thePromiseResolveReject.hasBeenReturned_byResulter );

        // 2. Handle final promise.
        //
        // If the promise is done (so it is also not pending), it means no more result
        // will be received from the WorkerBody. So remove the entire result queue
        // (i.e. PromiseResolveRejectArray) of the processing.
        if ( thePromiseResolveReject.final ) {
          this.map.delete( this.processingId );
        }
      }

    // 3. Yield/Return the promise which will resolve to { done, value } or reject.
    thePromiseResolveReject.hasBeenReturned_byResulter = true;
    return thePromiseResolveReject.promiseToYieldReturn;
  }

  /**
   * Automatically .next() until done.
   *
   * @return {Promise}
   *   Return a promise resolved to the final value (i.e. the Xxx of the
   * { done: true, value: Xxx }) of this resulter.
   */
  async untilDone() {
    let resulterNext;
    do {
      resulterNext = await this.next();
    } while ( !resulterNext.done );
    return resulterNext.value;
  }

}

<commit_msg>hasBeenReturned_byResulter<commit_after>export { AsyncWorker_Resulter as Resulter };

//import { PromiseResolveReject } from "./AsyncWorker_PromiseResolveReject.js";
//import { processingId_PromiseResolveRejectArray_Map }
//  from "./AsyncWorker_PromiseResolveReject.js";

/**
 * An async generator as the consumer of the processingId's PromiseResolveRejectArray.
 *
 * @member {PromiseResolveReject[]} PromiseResolveRejectArray
 *   All promises waiting for WorkerBody's result of the processing.
 */
class AsyncWorker_Resulter {

  /** */
  constructor( processingId, processingId_PromiseResolveRejectArray_Map ) {
    this.processingId = processingId;
    this.PromiseResolveRejectArray = new Array();
    this.processingId_PromiseResolveRejectArray_Map
      = processingId_PromiseResolveRejectArray_Map;
  }

  /**
   * Note: This .next() should be called until promise resolved to { done: true }.
   *       Otherwise, this resulter will not be removed from
   *       processingId_PromiseResolveRejectArray_Map. This will result in memory
   *       not been released.
   *
   * @return {Promise}
   *   Return a promise resolved to { done, value } which represents the WorkerBody's
   * result of the processing.
   */
  next() {
    let resulter
      = this.processingId_PromiseResolveRejectArray_Map.map.get( this.processingId );
    if ( !resulter ) {
      throw Error( `AsyncWorker.Resulter.next(): `
        + `processingId=${processingId}. `
        + `AsyncWorker.Resulter not found in `
        + `processingId_PromiseResolveRejectArray_Map.`
      );
      return { done: true }; // No pending promise for the processing. (should not happen)
    }

    if ( resulter != this ) {
      throw Error( `AsyncWorker.Resulter.next(): `
        + `processingId=${processingId}. `
        + `AsyncWorker.Resulter in `
        + `processingId_PromiseResolveRejectArray_Map should be this.`
      );
      return { done: true };
    }

    let thePromiseResolveReject;
    do {

      // PromiseResolveRejectArray should never be empty. It should has at least
      // one promise for this resulter to yield/return.
      //
      if ( this.PromiseResolveRejectArray.length < 1 ) {
        throw Error( `AsyncWorker.Resulter.next(): `
          + `processingId=${processingId}. `
          + `PromiseResolveRejectArray should never be empty.`
        );
        return { done: true };
      }

      // 1. Always yield the first promise.
      thePromiseResolveReject = this.PromiseResolveRejectArray[ 0 ];

      // 2. Prepare the next promise.

      // 2.1 The first pending promise will be yielded again and again
      //     (until it has been fulfilled and handled by here).
      if ( thePromiseResolveReject.pending )
        break;

      // 2.2 Otherwise, the promise has been fulfilled. Remove it so that it will not
      //     be yielded again in the future.
      this.PromiseResolveRejectArray.shift();

//!!! ...unfinished... (2022/09/14)
    // If the fulfilled promise has been returned by this resulter.next() before
    // (i.e. It has been returned when it was still pending), then try next.
    // Otherwise, it will be returned duplicatedly.
    //
    } while ( thePromiseResolveReject.hasBeenReturned_byResulter );

        // 3. Handle final promise.
        //
        // If the promise is done (so it is also not pending), it means no more result
        // will be received from the WorkerBody. So remove the entire result queue
        // (i.e. PromiseResolveRejectArray) of the processing.
        if ( thePromiseResolveReject.final ) {
          this.map.delete( this.processingId );
        }
      }

    // 3. Yield/Return the promise which will resolve to { done, value } or reject.
    thePromiseResolveReject.hasBeenReturned_byResulter = true;
    return thePromiseResolveReject.promiseToYieldReturn;
  }

  /**
   * Automatically .next() until done.
   *
   * @return {Promise}
   *   Return a promise resolved to the final value (i.e. the Xxx of the
   * { done: true, value: Xxx }) of this resulter.
   */
  async untilDone() {
    let resulterNext;
    do {
      resulterNext = await this.next();
    } while ( !resulterNext.done );
    return resulterNext.value;
  }

}

<|endoftext|>


<commit_before>module.exports.init = function (sf, logger) {
    /**
     * Registering handler for failed asserts. The handler is doing logging and is throwing an error.
     * @param explanation {String} - failing reason message.
     */
    let __failWasAlreadyGenerated = false;
    let __assetsCounter = 0;
    let beginWasCalled = false;

    if(typeof global.timeoutUntilBegin === "undefined"){
        global.timeoutUntilBegin = setTimeout(function () {
            if (!beginWasCalled) {
                sf.assert.begin("asset.begin was not called, the exit time for the test is automatically set to 2 seconds. asset.callback can increase this time");
            }
        }, 1000);
    }else{
        console.trace("DoubleCheck was evaluated atleast 2 times! Maybe it was required from disk instead of bundles.");
    }

    /**
     * Registering assert for printing a message and asynchronously printing all logs from logger.dumpWhys.
     * @param message {String} - message to be recorded
     * @param cleanFunctions {Function} - cleaning function
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('forceFailedTest', function (message, err) {
        console.error("Test should fail because:", message, err);
        __failWasAlreadyGenerated = true;
    });

    /**
     * Registering assert for printing a message and asynchronously printing all logs from logger.dumpWhys.
     * @param message {String} - message to be recorded
     * @param cleanFunctions {Function} - cleaning function
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('begin', function (message, cleanFunctions, timeout) {
        //logger.recordAssert(message);
        beginWasCalled = true;
        console.log(message);
        sf.assert.end(cleanFunctions, timeout, true);
    });

    function recordFail(...args) { //record fail only once
        if (!__failWasAlreadyGenerated) {
            __failWasAlreadyGenerated = true;
            logger.recordAssert(...args);
        }
    }

    function delayEnd(message, milliseconds) {
        if (!beginWasCalled) {
            clearTimeout(timeoutUntilBegin);
            sf.assert.begin(message, null, milliseconds);
        } else {
            //console.log('Begin was called before the delay could be applied')
        }
    }

    /**
     * Registering assert for evaluating a value to null. If check fails, the assertFail is invoked.
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.exceptions.register('assertFail', function (explanation) {
        const message = "Assert or invariant has failed " + (explanation ? explanation : "");
        const err = new Error(message);
        err.isFailedAssert = true;
        recordFail('[Fail] ' + message, err, true);
        throw err;
    });

    /**
     * Registering assert for equality. If check fails, the assertFail is invoked.
     * @param v1 {String|Number|Object} - first value
     * @param v1 {String|Number|Object} - second value
     * @param explanation {String} - failing reason message in case the assert fails.
     */
    sf.assert.addCheck('equal', function (v1, v2, explanation) {
        if (v1 !== v2) {
            if (!explanation) {
                explanation = "Assertion failed: [" + v1 + " !== " + v2 + "]";
            }
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for inequality. If check fails, the assertFail is invoked.
     * @param v1 {String|Number|Object} - first value
     * @param v1 {String|Number|Object} - second value
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('notEqual', function (v1, v2, explanation) {
        if (v1 === v2) {
            if (!explanation) {
                explanation = " [" + v1 + " == " + v2 + "]";
            }
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating an expression to true. If check fails, the assertFail is invoked.
     * @param b {Boolean} - result of an expression
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('true', function (b, explanation) {
        if (!b) {
            if (!explanation) {
                explanation = " expression is false but is expected to be true";
            }
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating an expression to false. If check fails, the assertFail is invoked.
     * @param b {Boolean} - result of an expression
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('false', function (b, explanation) {
        if (b) {
            if (!explanation) {
                explanation = " expression is true but is expected to be false";
            }
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating a value to null. If check fails, the assertFail is invoked.
     * @param b {Boolean} - result of an expression
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('isNull', function (v1, explanation) {
        if (v1 !== null) {
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating a value to be not null. If check fails, the assertFail is invoked.
     * @param b {Boolean} - result of an expression
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('notNull', function (v1, explanation) {
        if (v1 === null && typeof v1 === "object") {
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Checks if all properties of the second object are own properties of the first object.
     * @param firstObj {Object} - first object
     * @param secondObj{Object} - second object
     * @returns {boolean} - returns true, if the check has passed or false otherwise.
     */
    function objectHasFields(firstObj, secondObj) {
        for (let field in secondObj) {
            if (firstObj.hasOwnProperty(field)) {
                if (firstObj[field] !== secondObj[field]) {
                    return false;
                }
            } else {
                return false;
            }
        }
        return true;
    }

    function objectsAreEqual(firstObj, secondObj) {
        let areEqual = true;
        if (firstObj !== secondObj) {
            if (typeof firstObj !== typeof secondObj) {
                areEqual = false;
            } else if (Array.isArray(firstObj) && Array.isArray(secondObj)) {
                firstObj.sort();
                secondObj.sort();
                if (firstObj.length !== secondObj.length) {
                    areEqual = false;
                } else {
                    for (let i = 0; i < firstObj.length; ++i) {
                        if (!objectsAreEqual(firstObj[i], secondObj[i])) {
                            areEqual = false;
                            break;
                        }
                    }
                }
            } else if ((typeof firstObj === 'function' && typeof secondObj === 'function') ||
                (firstObj instanceof Date && secondObj instanceof Date) ||
                (firstObj instanceof RegExp && secondObj instanceof RegExp) ||
                (firstObj instanceof String && secondObj instanceof String) ||
                (firstObj instanceof Number && secondObj instanceof Number)) {
                areEqual = firstObj.toString() === secondObj.toString();
            } else if (typeof firstObj === 'object' && typeof secondObj === 'object') {
                areEqual = objectHasFields(firstObj, secondObj);
                // isNaN(undefined) returns true
            } else if (isNaN(firstObj) && isNaN(secondObj) && typeof firstObj === 'number' && typeof secondObj === 'number') {
                areEqual = true;
            } else {
                areEqual = false;
            }
        }

        return areEqual;
    }

    /**
     * Registering assert for evaluating if all properties of the second object are own properties of the first object.
     * If check fails, the assertFail is invoked.
     * @param firstObj {Object} - first object
     * @param secondObj{Object} - second object
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck("objectHasFields", function (firstObj, secondObj, explanation) {
        if (!objectHasFields(firstObj, secondObj)) {
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating if all element from the second array are present in the first array.
     * Deep comparison between the elements of the array is used.
     * If check fails, the assertFail is invoked.
     * @param firstArray {Array}- first array
     * @param secondArray {Array} - second array
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck("arraysMatch", function (firstArray, secondArray, explanation) {
        if (firstArray.length !== secondArray.length) {
            sf.exceptions.assertFail(explanation);
        } else {
            const result = objectsAreEqual(firstArray, secondArray);
            // const arraysDontMatch = secondArray.every(element => firstArray.indexOf(element) !== -1);
            // let arraysDontMatch = secondArray.some(function (expectedElement) {
            //     let found = firstArray.some(function(resultElement){
            //         return objectHasFields(resultElement,expectedElement);
            //     });
            //     return found === false;
            // });

            if (!result) {
                sf.exceptions.assertFail(explanation);
            }
        }
    });

    // added mainly for test purposes, better test frameworks like mocha could be much better

    /**
     * Registering assert for checking if a function is failing.
     * If the function is throwing an exception, the test is passed or failed otherwise.
     * @param testName {String} - test name or description
     * @param func {Function} - function to be invoked
     */
    sf.assert.addCheck('fail', function (testName, func) {
        try {
            func();
            recordFail("[Fail] " + testName);
        } catch (err) {
            logger.recordAssert("[Pass] " + testName);
        }
    });

    /**
     * Registering assert for checking if a function is executed with no exceptions.
     * If the function is not throwing any exception, the test is passed or failed otherwise.
     * @param testName {String} - test name or description
     * @param func {Function} - function to be invoked
     */
    sf.assert.addCheck('pass', function (testName, func) {
        try {
            func();
            logger.recordAssert("[Pass] " + testName);
        } catch (err) {
            recordFail("[Fail] " + testName, err.stack);
        }
    });

    /**
     * Alias for the pass assert.
     */
    sf.assert.alias('test', 'pass');

    /**
     * Registering assert for checking if a callback function is executed before timeout is reached without any exceptions.
     * If the function is throwing any exception or the timeout is reached, the test is failed or passed otherwise.
     * @param testName {String} - test name or description
     * @param func {Function} - function to be invoked
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('callback', function (testName, func, timeout) {

        if (!func || typeof func != "function") {
            throw new Error("Wrong usage of assert.callback!");
        }

        if (!timeout) {
            timeout = 500;
        }

        __assetsCounter++;

        let passed = false;

        function callback() {
            __assetsCounter--;
            if (!passed) {
                passed = true;
                logger.recordAssert("[Pass] " + testName);
                successTest();
            } else {
                recordFail("[Fail (multiple calls)] " + testName);
            }
        }

        setTimeout(successTest, timeout);
        delayEnd(testName, timeout + 100);

        try {
            func(callback);
        } catch (err) {
            recordFail("[Fail] " + testName, err, true);
        }

        function successTest(force) {
            if (!passed) {
                logger.recordAssert("[Fail Timeout] " + testName);
            }
        }

    });

    /**
     * Registering assert for checking if an array of callback functions are executed in a waterfall manner,
     * before timeout is reached without any exceptions.
     * If any of the functions is throwing any exception or the timeout is reached, the test is failed or passed otherwise.
     * @param testName {String} - test name or description
     * @param func {Function} - function to be invoked
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('steps', function (testName, arr, timeout) {
        if (!timeout) {
            timeout = 500;
        }

        let currentStep = 0;
        let passed = false;

        function next() {
            if (currentStep === arr.length) {
                passed = true;
                logger.recordAssert("[Pass] " + testName);
                return;
            }

            const func = arr[currentStep];
            currentStep++;
            try {
                func(next);
            } catch (err) {
                recordFail("[Fail] " + testName + " [at step " + currentStep + "]", err);
            }
        }

        function successTest(force) {
            if (!passed) {
                recordFail("[Fail Timeout] " + testName + " [at step " + currentStep + "]");
            }
        }

        setTimeout(successTest, timeout);
        // delayEnd(testName, timeout + 100);
        next();
    });

    /**
     * Alias for the steps assert.
     */
    sf.assert.alias('waterfall', 'steps');

    let cleaningArray = [];
    /**
     * Registering a cleaning function
     * @param func {Function} - function to be invoked
     */
    sf.assert.addCheck('addCleaningFunction', function (func) {
        cleaningArray.push(func);
    });

    /**
     * Registering a cleaning function
     * @param func {Function} - function to be invoked
     */
    sf.assert.addCheck('disableCleanings', function (func) {
        cleaningArray = [];
    });

    sf.assert.addCheck('hashesAreEqual', function (hash1, hash2) {
        if (!Array.isArray(hash1) && !Array.isArray(hash2)) {
            return hash1 === hash2;
        }

        if (!Array.isArray(hash1) || !Array.isArray(hash2)) {
            return false;
        }

        hash1.sort();
        hash2.sort();
        for (let i in hash1) {
            if (hash1[i] !== hash2[i]) {
                return false;
            }
        }

        return true;
    });

    /**
     * Registering assert for asynchronously printing all execution summary from logger.dumpWhys.
     * @param message {String} - message to be recorded
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('end', function (cleaningFunction, timeout, silence) {
        if (!timeout) {
            timeout = 1000;
        }

        function handler() {
            if (logger.dumpWhys) {
                logger.dumpWhys().forEach(function (c) {
                    const executionSummary = c.getExecutionSummary();
                    console.log(JSON.stringify(executionSummary, null, 4));
                });
            }

            if (!silence) {
                console.log("Forcing exit after", timeout, "ms");
            }

            setTimeout(function () {
                if (__failWasAlreadyGenerated || __assetsCounter != 0) {
                    process.exit(1);
                } else {
                    process.exit(0);
                }
            }, 1000);

            cleaningArray.map(function (func) {
                func();
            });
            if (cleaningFunction) {
                cleaningFunction();
            }
        }

        setTimeout(handler, timeout);
    });

};<commit_msg>small tweak to eliminate potential not necessary timeout until the test end<commit_after>module.exports.init = function (sf, logger) {
    /**
     * Registering handler for failed asserts. The handler is doing logging and is throwing an error.
     * @param explanation {String} - failing reason message.
     */
    let __failWasAlreadyGenerated = false;
    let __assetsCounter = 0;
    let beginWasCalled = false;

    if(typeof global.timeoutUntilBegin === "undefined"){
        global.timeoutUntilBegin = setTimeout(function () {
            if (!beginWasCalled) {
                sf.assert.begin("asset.begin was not called, the exit time for the test is automatically set to 2 seconds. asset.callback can increase this time");
            }
        }, 1000);
    }else{
        console.trace("DoubleCheck was evaluated atleast 2 times! Maybe it was required from disk instead of bundles.");
    }

    /**
     * Registering assert for printing a message and asynchronously printing all logs from logger.dumpWhys.
     * @param message {String} - message to be recorded
     * @param cleanFunctions {Function} - cleaning function
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('forceFailedTest', function (message, err) {
        console.error("Test should fail because:", message, err);
        __failWasAlreadyGenerated = true;
    });

    /**
     * Registering assert for printing a message and asynchronously printing all logs from logger.dumpWhys.
     * @param message {String} - message to be recorded
     * @param cleanFunctions {Function} - cleaning function
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('begin', function (message, cleanFunctions, timeout) {
        //logger.recordAssert(message);
        beginWasCalled = true;
        console.log(message);
        sf.assert.end(cleanFunctions, timeout, true);
    });

    function recordFail(...args) { //record fail only once
        if (!__failWasAlreadyGenerated) {
            __failWasAlreadyGenerated = true;
            logger.recordAssert(...args);
        }
    }

    function delayEnd(message, milliseconds) {
        if (!beginWasCalled) {
            clearTimeout(timeoutUntilBegin);
            sf.assert.begin(message, null, milliseconds);
        } else {
            //console.log('Begin was called before the delay could be applied')
        }
    }

    /**
     * Registering assert for evaluating a value to null. If check fails, the assertFail is invoked.
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.exceptions.register('assertFail', function (explanation) {
        const message = "Assert or invariant has failed " + (explanation ? explanation : "");
        const err = new Error(message);
        err.isFailedAssert = true;
        recordFail('[Fail] ' + message, err, true);
        throw err;
    });

    /**
     * Registering assert for equality. If check fails, the assertFail is invoked.
     * @param v1 {String|Number|Object} - first value
     * @param v1 {String|Number|Object} - second value
     * @param explanation {String} - failing reason message in case the assert fails.
     */
    sf.assert.addCheck('equal', function (v1, v2, explanation) {
        if (v1 !== v2) {
            if (!explanation) {
                explanation = "Assertion failed: [" + v1 + " !== " + v2 + "]";
            }
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for inequality. If check fails, the assertFail is invoked.
     * @param v1 {String|Number|Object} - first value
     * @param v1 {String|Number|Object} - second value
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('notEqual', function (v1, v2, explanation) {
        if (v1 === v2) {
            if (!explanation) {
                explanation = " [" + v1 + " == " + v2 + "]";
            }
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating an expression to true. If check fails, the assertFail is invoked.
     * @param b {Boolean} - result of an expression
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('true', function (b, explanation) {
        if (!b) {
            if (!explanation) {
                explanation = " expression is false but is expected to be true";
            }
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating an expression to false. If check fails, the assertFail is invoked.
     * @param b {Boolean} - result of an expression
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('false', function (b, explanation) {
        if (b) {
            if (!explanation) {
                explanation = " expression is true but is expected to be false";
            }
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating a value to null. If check fails, the assertFail is invoked.
     * @param b {Boolean} - result of an expression
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('isNull', function (v1, explanation) {
        if (v1 !== null) {
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating a value to be not null. If check fails, the assertFail is invoked.
     * @param b {Boolean} - result of an expression
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck('notNull', function (v1, explanation) {
        if (v1 === null && typeof v1 === "object") {
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Checks if all properties of the second object are own properties of the first object.
     * @param firstObj {Object} - first object
     * @param secondObj{Object} - second object
     * @returns {boolean} - returns true, if the check has passed or false otherwise.
     */
    function objectHasFields(firstObj, secondObj) {
        for (let field in secondObj) {
            if (firstObj.hasOwnProperty(field)) {
                if (firstObj[field] !== secondObj[field]) {
                    return false;
                }
            } else {
                return false;
            }
        }
        return true;
    }

    function objectsAreEqual(firstObj, secondObj) {
        let areEqual = true;
        if (firstObj !== secondObj) {
            if (typeof firstObj !== typeof secondObj) {
                areEqual = false;
            } else if (Array.isArray(firstObj) && Array.isArray(secondObj)) {
                firstObj.sort();
                secondObj.sort();
                if (firstObj.length !== secondObj.length) {
                    areEqual = false;
                } else {
                    for (let i = 0; i < firstObj.length; ++i) {
                        if (!objectsAreEqual(firstObj[i], secondObj[i])) {
                            areEqual = false;
                            break;
                        }
                    }
                }
            } else if ((typeof firstObj === 'function' && typeof secondObj === 'function') ||
                (firstObj instanceof Date && secondObj instanceof Date) ||
                (firstObj instanceof RegExp && secondObj instanceof RegExp) ||
                (firstObj instanceof String && secondObj instanceof String) ||
                (firstObj instanceof Number && secondObj instanceof Number)) {
                areEqual = firstObj.toString() === secondObj.toString();
            } else if (typeof firstObj === 'object' && typeof secondObj === 'object') {
                areEqual = objectHasFields(firstObj, secondObj);
                // isNaN(undefined) returns true
            } else if (isNaN(firstObj) && isNaN(secondObj) && typeof firstObj === 'number' && typeof secondObj === 'number') {
                areEqual = true;
            } else {
                areEqual = false;
            }
        }

        return areEqual;
    }

    /**
     * Registering assert for evaluating if all properties of the second object are own properties of the first object.
     * If check fails, the assertFail is invoked.
     * @param firstObj {Object} - first object
     * @param secondObj{Object} - second object
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck("objectHasFields", function (firstObj, secondObj, explanation) {
        if (!objectHasFields(firstObj, secondObj)) {
            sf.exceptions.assertFail(explanation);
        }
    });

    /**
     * Registering assert for evaluating if all element from the second array are present in the first array.
     * Deep comparison between the elements of the array is used.
     * If check fails, the assertFail is invoked.
     * @param firstArray {Array}- first array
     * @param secondArray {Array} - second array
     * @param explanation {String} - failing reason message in case the assert fails
     */
    sf.assert.addCheck("arraysMatch", function (firstArray, secondArray, explanation) {
        if (firstArray.length !== secondArray.length) {
            sf.exceptions.assertFail(explanation);
        } else {
            const result = objectsAreEqual(firstArray, secondArray);
            // const arraysDontMatch = secondArray.every(element => firstArray.indexOf(element) !== -1);
            // let arraysDontMatch = secondArray.some(function (expectedElement) {
            //     let found = firstArray.some(function(resultElement){
            //         return objectHasFields(resultElement,expectedElement);
            //     });
            //     return found === false;
            // });

            if (!result) {
                sf.exceptions.assertFail(explanation);
            }
        }
    });

    // added mainly for test purposes, better test frameworks like mocha could be much better

    /**
     * Registering assert for checking if a function is failing.
     * If the function is throwing an exception, the test is passed or failed otherwise.
     * @param testName {String} - test name or description
     * @param func {Function} - function to be invoked
     */
    sf.assert.addCheck('fail', function (testName, func) {
        try {
            func();
            recordFail("[Fail] " + testName);
        } catch (err) {
            logger.recordAssert("[Pass] " + testName);
        }
    });

    /**
     * Registering assert for checking if a function is executed with no exceptions.
     * If the function is not throwing any exception, the test is passed or failed otherwise.
     * @param testName {String} - test name or description
     * @param func {Function} - function to be invoked
     */
    sf.assert.addCheck('pass', function (testName, func) {
        try {
            func();
            logger.recordAssert("[Pass] " + testName);
        } catch (err) {
            recordFail("[Fail] " + testName, err.stack);
        }
    });

    /**
     * Alias for the pass assert.
     */
    sf.assert.alias('test', 'pass');

    /**
     * Registering assert for checking if a callback function is executed before timeout is reached without any exceptions.
     * If the function is throwing any exception or the timeout is reached, the test is failed or passed otherwise.
     * @param testName {String} - test name or description
     * @param func {Function} - function to be invoked
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('callback', function (testName, func, timeout) {

        if (!func || typeof func != "function") {
            throw new Error("Wrong usage of assert.callback!");
        }

        if (!timeout) {
            timeout = 500;
        }

        __assetsCounter++;

        let passed = false;

        function callback() {
            __assetsCounter--;
            if (!passed) {
                passed = true;
                logger.recordAssert("[Pass] " + testName);
                successTest();
            } else {
                recordFail("[Fail (multiple calls)] " + testName);
            }

            if(__assetsCounter === 0){
                //small tweak to eliminate potential not necessary timeout until the test end
                sf.assert.end(undefined, 0, true);
            }
        }

        setTimeout(successTest, timeout);
        delayEnd(testName, timeout + 100);

        try {
            func(callback);
        } catch (err) {
            recordFail("[Fail] " + testName, err, true);
        }

        function successTest(force) {
            if (!passed) {
                logger.recordAssert("[Fail Timeout] " + testName);
            }
        }

    });

    /**
     * Registering assert for checking if an array of callback functions are executed in a waterfall manner,
     * before timeout is reached without any exceptions.
     * If any of the functions is throwing any exception or the timeout is reached, the test is failed or passed otherwise.
     * @param testName {String} - test name or description
     * @param func {Function} - function to be invoked
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('steps', function (testName, arr, timeout) {
        if (!timeout) {
            timeout = 500;
        }

        let currentStep = 0;
        let passed = false;

        function next() {
            if (currentStep === arr.length) {
                passed = true;
                logger.recordAssert("[Pass] " + testName);
                return;
            }

            const func = arr[currentStep];
            currentStep++;
            try {
                func(next);
            } catch (err) {
                recordFail("[Fail] " + testName + " [at step " + currentStep + "]", err);
            }
        }

        function successTest(force) {
            if (!passed) {
                recordFail("[Fail Timeout] " + testName + " [at step " + currentStep + "]");
            }
        }

        setTimeout(successTest, timeout);
        // delayEnd(testName, timeout + 100);
        next();
    });

    /**
     * Alias for the steps assert.
     */
    sf.assert.alias('waterfall', 'steps');

    let cleaningArray = [];
    /**
     * Registering a cleaning function
     * @param func {Function} - function to be invoked
     */
    sf.assert.addCheck('addCleaningFunction', function (func) {
        cleaningArray.push(func);
    });

    /**
     * Registering a cleaning function
     * @param func {Function} - function to be invoked
     */
    sf.assert.addCheck('disableCleanings', function (func) {
        cleaningArray = [];
    });

    sf.assert.addCheck('hashesAreEqual', function (hash1, hash2) {
        if (!Array.isArray(hash1) && !Array.isArray(hash2)) {
            return hash1 === hash2;
        }

        if (!Array.isArray(hash1) || !Array.isArray(hash2)) {
            return false;
        }

        hash1.sort();
        hash2.sort();
        for (let i in hash1) {
            if (hash1[i] !== hash2[i]) {
                return false;
            }
        }

        return true;
    });

    /**
     * Registering assert for asynchronously printing all execution summary from logger.dumpWhys.
     * @param message {String} - message to be recorded
     * @param timeout {Number} - number of milliseconds for the timeout check. Default to 500ms.
     */
    sf.assert.addCheck('end', function (cleaningFunction, timeout, silence) {
        if (!timeout) {
            timeout = 1000;
        }

        function handler() {
            if (logger.dumpWhys) {
                logger.dumpWhys().forEach(function (c) {
                    const executionSummary = c.getExecutionSummary();
                    console.log(JSON.stringify(executionSummary, null, 4));
                });
            }

            if (!silence) {
                console.log("Forcing exit after", timeout, "ms");
            }

            setTimeout(function () {
                if (__failWasAlreadyGenerated || __assetsCounter != 0) {
                    process.exit(1);
                } else {
                    process.exit(0);
                }
            }, 1000);

            cleaningArray.map(function (func) {
                func();
            });
            if (cleaningFunction) {
                cleaningFunction();
            }
        }

        setTimeout(handler, timeout);
    });

};<|endoftext|>


<commit_before>var ReflectionObject = function (object) {

    if (this === window) {
        return new ReflectionObject(object);
    }

    if (typeof object !== 'object') {
        throw new TypeError('You must pass an object as argument');
    }

    this.object = object;

};
ReflectionObject.prototype = Object.create(Object.prototype, {

    /**
     * Return the list of properties set in the object
     *
     * @return {Array}
     */

    getProperties: {
        value: function () {

            var properties = [],
                refl;

            for (var prop in this.object) {
                if (this.object.hasOwnProperty(prop)) {
                    refl = new ReflectionProperty(
                        prop,
                        this.object[prop]
                    );
                    refl.setObject(this.object);
                    properties.push(refl);
                }
            }

            return properties;

        }
    },

    /**
     * Return the reflection object for the specified property
     *
     * @param {String} property
     *
     * @return {ReflectionProperty}
     */

    getProperty: {
        value: function (property) {

            if (!this.hasProperty(property)) {
                throw new ReferenceError('Unknown property');
            }

            var refl = new ReflectionProperty(property, this.object[property]);

            refl.setObject(this.object);

            return refl;

        }
    },

    /**
     * Check if the object has the property
     *
     * @param {String} property
     *
     * @return {Boolean}
     */

    hasProperty: {
        value: function (property) {

            if (this.object.hasOwnProperty(property)) {
                return true;
            }

            return false;

        }
    },

    /**
     * Return the reflection object for the specified function
     *
     * @param {String} method
     *
     * @return {ReflectionMethod}
     */

    getMethod: {
        value: function (method) {

            if (!this.hasMethod(method)) {
                throw new ReferenceError('Unknown method');
            }

            var refl = new ReflectionMethod(method, this.object[method]);

            refl.setObject(this.object);

            return refl;

        }
    },

    /**
     * Check if the object has the specified method
     *
     * @param {String} method
     *
     * @return {Boolean}
     */

    hasMethod: {
        value: function (method) {

            if (typeof this.object[method] === 'function' && !this.hasProperty(method)) {
                return true;
            }

            return false;

        }
    },

    /**
     * Return the value for the specified constant
     *
     * @param {String} constant
     *
     * @return {ReflectionConstant}
     */

    getConstant: {
        value: function (constant) {}
    },

    /**
     * Check if the specified constant exist
     *
     * @param {String} constant
     *
     * @return {Boolean}
     */

    hasConstant: {
        value: function (constant) {}
    },

    /**
     * Prevent the object from being changed, or new properties from being set
     *
     * @return {ReflectionObject}
     */

    freeze: {
        value: function () {}
    },

    /**
     * Check if the object is frozen
     *
     * @return {Boolean}
     */

    isFrozen: {
        value: function () {}
    },

    /**
     * Prevent new properties from being set on the object
     *
     * @return {ReflectionObject}
     */

    seal: {
        value: function () {}
    },

    /**
     * Check if the object is sealed
     *
     * @return {Boolean}
     */

    isSealed: {
        value: function () {}
    }

});
<commit_msg>object constant reflection added<commit_after>var ReflectionObject = function (object) {

    if (this === window) {
        return new ReflectionObject(object);
    }

    if (typeof object !== 'object') {
        throw new TypeError('You must pass an object as argument');
    }

    this.object = object;

};
ReflectionObject.prototype = Object.create(Object.prototype, {

    /**
     * Return the list of properties set in the object
     *
     * @return {Array}
     */

    getProperties: {
        value: function () {

            var properties = [],
                refl;

            for (var prop in this.object) {
                if (this.object.hasOwnProperty(prop)) {
                    refl = new ReflectionProperty(
                        prop,
                        this.object[prop]
                    );
                    refl.setObject(this.object);
                    properties.push(refl);
                }
            }

            return properties;

        }
    },

    /**
     * Return the reflection object for the specified property
     *
     * @param {String} property
     *
     * @return {ReflectionProperty}
     */

    getProperty: {
        value: function (property) {

            if (!this.hasProperty(property)) {
                throw new ReferenceError('Unknown property');
            }

            var refl = new ReflectionProperty(property, this.object[property]);

            refl.setObject(this.object);

            return refl;

        }
    },

    /**
     * Check if the object has the property
     *
     * @param {String} property
     *
     * @return {Boolean}
     */

    hasProperty: {
        value: function (property) {

            if (this.object.hasOwnProperty(property)) {
                return true;
            }

            return false;

        }
    },

    /**
     * Return the reflection object for the specified function
     *
     * @param {String} method
     *
     * @return {ReflectionMethod}
     */

    getMethod: {
        value: function (method) {

            if (!this.hasMethod(method)) {
                throw new ReferenceError('Unknown method');
            }

            var refl = new ReflectionMethod(method, this.object[method]);

            refl.setObject(this.object);

            return refl;

        }
    },

    /**
     * Check if the object has the specified method
     *
     * @param {String} method
     *
     * @return {Boolean}
     */

    hasMethod: {
        value: function (method) {

            if (typeof this.object[method] === 'function' && !this.hasProperty(method)) {
                return true;
            }

            return false;

        }
    },

    /**
     * Return the value for the specified constant
     *
     * @param {String} constant
     *
     * @return {ReflectionConstant}
     */

    getConstant: {
        value: function (constant) {

            if (!this.hasConstant(constant)) {
                throw new ReferenceError('Unknown constant');
            }

            var refl = new ReflectionConstant(constant, this.object[constant]);

            refl.setObject(this.object);

            return refl;
        }
    },

    /**
     * Check if the specified constant exist
     *
     * @param {String} constant
     *
     * @return {Boolean}
     */

    hasConstant: {
        value: function (constant) {

            if (
                this.object[constant] &&
                !this.hasProperty(constant) &&
                !this.hasMethod(constant)
            ) {
                return true;
            }

            return false;

        }
    },

    /**
     * Prevent the object from being changed, or new properties from being set
     *
     * @return {ReflectionObject}
     */

    freeze: {
        value: function () {}
    },

    /**
     * Check if the object is frozen
     *
     * @return {Boolean}
     */

    isFrozen: {
        value: function () {}
    },

    /**
     * Prevent new properties from being set on the object
     *
     * @return {ReflectionObject}
     */

    seal: {
        value: function () {}
    },

    /**
     * Check if the object is sealed
     *
     * @return {Boolean}
     */

    isSealed: {
        value: function () {}
    }

});
<|endoftext|>